@zoralabs/coins 2.2.1 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$js.log +99 -99
- package/CHANGELOG.md +44 -5
- package/README.md +4 -0
- package/abis/BaseCoin.json +0 -5
- package/abis/ContentCoin.json +0 -5
- package/abis/CreatorCoin.json +0 -5
- package/abis/FeeEstimatorHook.json +94 -1
- package/abis/IUpgradeableDestinationV4HookWithUpdateableFee.json +95 -0
- package/abis/IZoraFactory.json +69 -0
- package/abis/ZoraFactoryImpl.json +69 -0
- package/abis/ZoraV4CoinHook.json +94 -1
- package/addresses/8453.json +6 -6
- package/audits/report-cantinacode-zora-0827.pdf +3498 -4
- package/dist/index.cjs +21 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +21 -3
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +54 -12
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/foundry.toml +3 -3
- package/package/wagmiGenerated.ts +21 -3
- package/package.json +1 -1
- package/script/TestBackingCoinSwap.s.sol +0 -2
- package/script/TestV4Swap.s.sol +0 -2
- package/src/BaseCoin.sol +4 -12
- package/src/ContentCoin.sol +3 -4
- package/src/CreatorCoin.sol +8 -10
- package/src/ZoraFactoryImpl.sol +115 -83
- package/src/hook-registry/ZoraHookRegistry.sol +4 -0
- package/src/hooks/ZoraV4CoinHook.sol +66 -9
- package/src/interfaces/IUpgradeableV4Hook.sol +18 -0
- package/src/interfaces/IZoraFactory.sol +21 -2
- package/src/libs/CoinConstants.sol +51 -8
- package/src/libs/CoinDopplerMultiCurve.sol +11 -11
- package/src/libs/CoinRewardsV4.sol +26 -33
- package/src/libs/CoinSetup.sol +2 -9
- package/src/libs/DopplerMath.sol +2 -2
- package/src/libs/V4Liquidity.sol +79 -15
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/Coin.t.sol +5 -5
- package/test/CoinRewardsV4.t.sol +33 -0
- package/test/CoinUniV4.t.sol +2 -3
- package/test/ContentCoinRewards.t.sol +43 -0
- package/test/CreatorCoin.t.sol +53 -29
- package/test/DeploymentHooks.t.sol +54 -2
- package/test/LiquidityMigration.t.sol +145 -7
- package/test/V4Liquidity.t.sol +178 -0
- package/test/utils/BaseTest.sol +0 -1
- package/test/utils/RewardTestHelpers.sol +4 -4
- package/abis/CoinConstants.json +0 -54
- package/abis/CoinRewardsV4.json +0 -67
- package/src/libs/CreatorCoinConstants.sol +0 -15
- package/src/libs/MarketConstants.sol +0 -23
- /package/abis/{VmContractHelper227.json → VmContractHelper226.json} +0 -0
|
@@ -17,7 +17,13 @@ import {ICoin} from "../src/interfaces/ICoin.sol";
|
|
|
17
17
|
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
18
18
|
import {CoinCommon} from "../src/libs/CoinCommon.sol";
|
|
19
19
|
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
20
|
+
import {MultiOwnable} from "../src/utils/MultiOwnable.sol";
|
|
20
21
|
import {IHooksUpgradeGate} from "../src/interfaces/IHooksUpgradeGate.sol";
|
|
22
|
+
import {BaseCoin} from "../src/BaseCoin.sol";
|
|
23
|
+
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
24
|
+
import {SwapParams} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
25
|
+
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
26
|
+
import {BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
21
27
|
|
|
22
28
|
contract LiquidityMigrationReceiver is IUpgradeableDestinationV4Hook, IERC165 {
|
|
23
29
|
function initializeFromMigration(
|
|
@@ -45,6 +51,8 @@ contract InvalidLiquidityMigrationReceiver is IERC165 {
|
|
|
45
51
|
contract LiquidityMigrationTest is BaseTest {
|
|
46
52
|
MockERC20 internal mockERC20A;
|
|
47
53
|
|
|
54
|
+
address constant coinVersionLookup = 0x777777751622c0d3258f214F9DF38E35BF45baF3;
|
|
55
|
+
|
|
48
56
|
function setUp() public override {
|
|
49
57
|
super.setUpWithBlockNumber(30267794);
|
|
50
58
|
|
|
@@ -125,31 +133,59 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
125
133
|
assertEq(newPoolKey.tickSpacing, poolKey.tickSpacing, "poolkey tickSpacing");
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
function
|
|
136
|
+
function test_migrateLiquidity_revertsSwapsOnOldPoolKey() public {
|
|
129
137
|
address currency = address(mockERC20A);
|
|
130
138
|
mockERC20A.mint(address(poolManager), 1_000_000_000 ether);
|
|
131
139
|
_deployV4Coin(currency);
|
|
132
140
|
|
|
133
141
|
address trader = makeAddr("trader");
|
|
134
|
-
|
|
135
142
|
mockERC20A.mint(trader, 10 ether);
|
|
136
143
|
|
|
137
|
-
// do some swaps
|
|
144
|
+
// do some swaps before migration
|
|
138
145
|
_swapSomeCurrencyForCoin(coinV4, currency, 1 ether, trader);
|
|
139
146
|
_swapSomeCoinForCurrency(coinV4, currency, uint128(coinV4.balanceOf(trader)), trader);
|
|
140
147
|
|
|
141
|
-
address newHook = address(new LiquidityMigrationReceiver());
|
|
142
|
-
|
|
143
148
|
PoolKey memory poolKey = coinV4.getPoolKey();
|
|
149
|
+
bytes32 poolKeyHash = CoinCommon.hashPoolKey(poolKey);
|
|
150
|
+
|
|
151
|
+
// Verify the mapping exists before migration
|
|
152
|
+
IZoraV4CoinHook.PoolCoin memory poolCoinBefore = hook.getPoolCoin(poolKey);
|
|
153
|
+
assertEq(poolCoinBefore.coin, address(coinV4), "pool coin should exist before migration");
|
|
154
|
+
|
|
155
|
+
IZoraV4CoinHook.PoolCoin memory poolCoinByHashBefore = hook.getPoolCoinByHash(poolKeyHash);
|
|
156
|
+
assertEq(poolCoinByHashBefore.coin, address(coinV4), "pool coin by hash should exist before migration");
|
|
144
157
|
|
|
158
|
+
address newHook = address(new LiquidityMigrationReceiver());
|
|
145
159
|
registerUpgradePath(address(poolKey.hooks), address(newHook));
|
|
146
160
|
|
|
147
161
|
// migrate the liquidity
|
|
148
162
|
vm.prank(users.creator);
|
|
149
163
|
coinV4.migrateLiquidity(address(newHook), "");
|
|
150
164
|
|
|
151
|
-
//
|
|
152
|
-
|
|
165
|
+
// Verify that the old pool key mapping has been deleted
|
|
166
|
+
IZoraV4CoinHook.PoolCoin memory poolCoinAfter = hook.getPoolCoin(poolKey);
|
|
167
|
+
assertEq(poolCoinAfter.coin, address(0), "old pool key should have no associated coin after migration");
|
|
168
|
+
assertEq(poolCoinAfter.positions.length, 0, "old pool coin positions should be empty after migration");
|
|
169
|
+
|
|
170
|
+
IZoraV4CoinHook.PoolCoin memory poolCoinByHashAfter = hook.getPoolCoinByHash(poolKeyHash);
|
|
171
|
+
assertEq(poolCoinByHashAfter.coin, address(0), "old pool coin by hash should have no associated coin after migration");
|
|
172
|
+
assertEq(poolCoinByHashAfter.positions.length, 0, "old pool coin by hash positions should be empty after migration");
|
|
173
|
+
|
|
174
|
+
// Verify that hook operations revert with NoCoinForHook on the old poolkey
|
|
175
|
+
vm.expectRevert(abi.encodeWithSelector(IZoraV4CoinHook.NoCoinForHook.selector, poolKey));
|
|
176
|
+
|
|
177
|
+
SwapParams memory mockSwapParams = SwapParams({zeroForOne: true, amountSpecified: 1 ether, sqrtPriceLimitX96: 0});
|
|
178
|
+
|
|
179
|
+
BalanceDelta mockDelta = BalanceDeltaLibrary.ZERO_DELTA;
|
|
180
|
+
|
|
181
|
+
// Call afterSwap directly - this should revert with NoCoinForHook
|
|
182
|
+
vm.prank(address(poolManager));
|
|
183
|
+
IHooks(address(hook)).afterSwap(trader, poolKey, mockSwapParams, mockDelta, "");
|
|
184
|
+
|
|
185
|
+
// Verify the new pool key still works
|
|
186
|
+
PoolKey memory newPoolKey = coinV4.getPoolKey();
|
|
187
|
+
assertEq(address(newPoolKey.hooks), address(newHook), "coin should have updated to new hook");
|
|
188
|
+
assertTrue(address(poolKey.hooks) != address(newPoolKey.hooks), "old and new pool keys should be different");
|
|
153
189
|
}
|
|
154
190
|
|
|
155
191
|
function test_migrateLiquidity_emitsLiquidityMigrated() public {
|
|
@@ -383,4 +419,106 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
383
419
|
// Should match isRegisteredUpgradePath
|
|
384
420
|
assertEq(hookUpgradeGate.isAllowedHookUpgrade(baseImpl, upgradeImpl), hookUpgradeGate.isRegisteredUpgradePath(baseImpl, upgradeImpl));
|
|
385
421
|
}
|
|
422
|
+
|
|
423
|
+
function test_migrateLiquidity_failsWithEmptyPositionBug() public {
|
|
424
|
+
// Reproduce the bug discovered in hook version 1.1.2 where migration
|
|
425
|
+
// tries to modify liquidity positions that have zero liquidity
|
|
426
|
+
vm.createSelectFork("base", 35671635);
|
|
427
|
+
|
|
428
|
+
address contentCoin = 0x81f5F30217dA777a5d6441606AFa57E093833d7C;
|
|
429
|
+
address oldHook = 0x9ea932730A7787000042e34390B8E435dD839040; // v1.1.2 hook
|
|
430
|
+
address newHook = 0xff74Be9D3596eA7a33BB4983DD7906fB34135040; // current hook
|
|
431
|
+
address upgradeGate = 0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2; // deployed upgrade gate
|
|
432
|
+
|
|
433
|
+
BaseCoin coin = BaseCoin(contentCoin);
|
|
434
|
+
|
|
435
|
+
uint24 oldFee = coin.getPoolKey().fee;
|
|
436
|
+
|
|
437
|
+
// Register upgrade path
|
|
438
|
+
address[] memory baseImpls = new address[](1);
|
|
439
|
+
baseImpls[0] = oldHook;
|
|
440
|
+
|
|
441
|
+
vm.prank(Ownable(upgradeGate).owner());
|
|
442
|
+
IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, newHook);
|
|
443
|
+
|
|
444
|
+
// Get coin owner
|
|
445
|
+
address coinOwner = MultiOwnable(contentCoin).owners()[0];
|
|
446
|
+
|
|
447
|
+
// First, demonstrate the bug exists - this should fail
|
|
448
|
+
vm.prank(coinOwner);
|
|
449
|
+
vm.expectRevert();
|
|
450
|
+
coin.migrateLiquidity(newHook, "");
|
|
451
|
+
|
|
452
|
+
// Now fix the bug by etching fixed hook code onto the old hook address
|
|
453
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
|
|
454
|
+
|
|
455
|
+
(IHooks fixedHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
456
|
+
|
|
457
|
+
// Etch the fixed hook code onto the old hook address
|
|
458
|
+
vm.etch(oldHook, address(fixedHook).code);
|
|
459
|
+
|
|
460
|
+
// Now migration should work
|
|
461
|
+
vm.prank(coinOwner);
|
|
462
|
+
coin.migrateLiquidity(newHook, "");
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function test_migrateLiquidity_canUseNewFee() public {
|
|
466
|
+
// Reproduce the bug discovered in hook version 1.1.2 where migration
|
|
467
|
+
// tries to modify liquidity positions that have zero liquidity
|
|
468
|
+
vm.createSelectFork("base", 35754730);
|
|
469
|
+
|
|
470
|
+
// jacob creator coin
|
|
471
|
+
BaseCoin coin = BaseCoin(0x9B13358E3a023507E7046c18f508A958cDA75f54);
|
|
472
|
+
|
|
473
|
+
address upgradeGate = 0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2; // live upgrade gate
|
|
474
|
+
|
|
475
|
+
uint24 oldFee = coin.getPoolKey().fee;
|
|
476
|
+
|
|
477
|
+
assertEq(oldFee, 30000);
|
|
478
|
+
|
|
479
|
+
// Now fix the bug by etching fixed hook code onto the old hook address
|
|
480
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
|
|
481
|
+
|
|
482
|
+
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
483
|
+
|
|
484
|
+
// Register upgrade path
|
|
485
|
+
address[] memory baseImpls = new address[](1);
|
|
486
|
+
baseImpls[0] = address(coin.hooks());
|
|
487
|
+
|
|
488
|
+
vm.prank(Ownable(upgradeGate).owner());
|
|
489
|
+
IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
|
|
490
|
+
|
|
491
|
+
// Get coin owner
|
|
492
|
+
address coinOwner = MultiOwnable(address(coin)).owners()[0];
|
|
493
|
+
|
|
494
|
+
vm.prank(coinOwner);
|
|
495
|
+
coin.migrateLiquidity(address(newHook), "");
|
|
496
|
+
|
|
497
|
+
// fee should still be the same as before, because we didnt have the logic to update the fee in the old coin's hook.
|
|
498
|
+
assertEq(coin.getPoolKey().fee, oldFee);
|
|
499
|
+
|
|
500
|
+
address currencyAddress = address(coin.currency());
|
|
501
|
+
|
|
502
|
+
// now test swapping the migrated liquidity
|
|
503
|
+
address trader = makeAddr("trader");
|
|
504
|
+
deal(currencyAddress, trader, 10 ether);
|
|
505
|
+
_swapSomeCurrencyForCoin(coin, coin.currency(), 1 ether, trader);
|
|
506
|
+
|
|
507
|
+
// now migrate liquidity again, but this time to the same new hook as before
|
|
508
|
+
// since the bug has been fixed in the new hook, we should now be able to get the new fee
|
|
509
|
+
// register the upgrade path for the new hook to itself
|
|
510
|
+
baseImpls[0] = address(newHook);
|
|
511
|
+
vm.prank(Ownable(upgradeGate).owner());
|
|
512
|
+
IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
|
|
513
|
+
|
|
514
|
+
// migrate liquidity again to the same new hook as before
|
|
515
|
+
vm.prank(coinOwner);
|
|
516
|
+
coin.migrateLiquidity(address(newHook), "");
|
|
517
|
+
|
|
518
|
+
// the new fee should be the correct current fee
|
|
519
|
+
assertEq(coin.getPoolKey().fee, CoinConstants.LP_FEE_V4);
|
|
520
|
+
|
|
521
|
+
// now test swapping the migrated liquidity - it should work
|
|
522
|
+
_swapSomeCurrencyForCoin(coin, coin.currency(), 1 ether, trader);
|
|
523
|
+
}
|
|
386
524
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import "./utils/BaseTest.sol";
|
|
5
|
+
import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
|
|
6
|
+
import {CoinDopplerMultiCurve} from "../src/libs/CoinDopplerMultiCurve.sol";
|
|
7
|
+
import {V4Liquidity} from "../src/libs/V4Liquidity.sol";
|
|
8
|
+
import {LpPosition} from "../src/types/LpPosition.sol";
|
|
9
|
+
import {PoolConfiguration} from "../src/interfaces/ICoin.sol";
|
|
10
|
+
import {CoinConstants} from "../src/libs/CoinConstants.sol";
|
|
11
|
+
import {MockERC20} from "./mocks/MockERC20.sol";
|
|
12
|
+
import {ContentCoin} from "../src/ContentCoin.sol";
|
|
13
|
+
import {ZoraV4CoinHook} from "../src/hooks/ZoraV4CoinHook.sol";
|
|
14
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
15
|
+
|
|
16
|
+
contract V4LiquidityTest is BaseTest {
|
|
17
|
+
MockERC20 internal mockERC20A;
|
|
18
|
+
|
|
19
|
+
function setUp() public override {
|
|
20
|
+
super.setUp();
|
|
21
|
+
mockERC20A = new MockERC20("MockERC20A", "MCKA");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function _poolConfigWithDuplicatePositions(address currency) private pure returns (bytes memory poolConfig) {
|
|
25
|
+
// Create configuration that will produce duplicate positions
|
|
26
|
+
int24[] memory tickLower_ = new int24[](2);
|
|
27
|
+
tickLower_[0] = -54000;
|
|
28
|
+
tickLower_[1] = -54000; // Same as first curve
|
|
29
|
+
|
|
30
|
+
int24[] memory tickUpper_ = new int24[](2);
|
|
31
|
+
tickUpper_[0] = 7000;
|
|
32
|
+
tickUpper_[1] = 7000; // Same as first curve
|
|
33
|
+
|
|
34
|
+
uint16[] memory numDiscoveryPositions_ = new uint16[](2);
|
|
35
|
+
numDiscoveryPositions_[0] = 5;
|
|
36
|
+
numDiscoveryPositions_[1] = 5;
|
|
37
|
+
|
|
38
|
+
uint256[] memory maxDiscoverySupplyShare_ = new uint256[](2);
|
|
39
|
+
maxDiscoverySupplyShare_[0] = 100000000000000000; // 0.1e18
|
|
40
|
+
maxDiscoverySupplyShare_[1] = 100000000000000000; // 0.1e18
|
|
41
|
+
|
|
42
|
+
poolConfig = CoinConfigurationVersions.encodeDopplerMultiCurveUniV4(currency, tickLower_, tickUpper_, numDiscoveryPositions_, maxDiscoverySupplyShare_);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function _countDuplicatePositions(LpPosition[] memory positions) private pure returns (uint256 duplicateCount) {
|
|
46
|
+
for (uint256 i = 0; i < positions.length; i++) {
|
|
47
|
+
for (uint256 j = i + 1; j < positions.length; j++) {
|
|
48
|
+
if (positions[i].tickLower == positions[j].tickLower && positions[i].tickUpper == positions[j].tickUpper) {
|
|
49
|
+
duplicateCount++;
|
|
50
|
+
break; // Only count each unique duplicate once
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function test_calculatePositionsWithDuplicateConfigCreatesDuplicates() public view {
|
|
57
|
+
address currency = address(mockERC20A);
|
|
58
|
+
bytes memory poolConfig = _poolConfigWithDuplicatePositions(currency);
|
|
59
|
+
|
|
60
|
+
(, PoolConfiguration memory poolConfiguration) = CoinDopplerMultiCurve.setupPool(true, poolConfig);
|
|
61
|
+
|
|
62
|
+
LpPosition[] memory positions = CoinDopplerMultiCurve.calculatePositions(
|
|
63
|
+
true, // isCoinToken0
|
|
64
|
+
poolConfiguration,
|
|
65
|
+
CoinConstants.CONTENT_COIN_MARKET_SUPPLY
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
uint256 duplicateCount = _countDuplicatePositions(positions);
|
|
69
|
+
assertGt(duplicateCount, 0, "Should have duplicate positions");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function test_dedupePositionsMergesDuplicates() public view {
|
|
73
|
+
address currency = address(mockERC20A);
|
|
74
|
+
bytes memory poolConfig = _poolConfigWithDuplicatePositions(currency);
|
|
75
|
+
|
|
76
|
+
(, PoolConfiguration memory poolConfiguration) = CoinDopplerMultiCurve.setupPool(true, poolConfig);
|
|
77
|
+
|
|
78
|
+
LpPosition[] memory originalPositions = CoinDopplerMultiCurve.calculatePositions(
|
|
79
|
+
true, // isCoinToken0
|
|
80
|
+
poolConfiguration,
|
|
81
|
+
CoinConstants.CONTENT_COIN_MARKET_SUPPLY
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
uint256 originalDuplicateCount = _countDuplicatePositions(originalPositions);
|
|
85
|
+
assertGt(originalDuplicateCount, 0, "Should have duplicate positions to test deduplication");
|
|
86
|
+
|
|
87
|
+
// Deduplicate the positions
|
|
88
|
+
LpPosition[] memory dedupedPositions = V4Liquidity.dedupePositions(originalPositions);
|
|
89
|
+
|
|
90
|
+
// Verify no duplicates exist in deduped array
|
|
91
|
+
uint256 dedupedDuplicateCount = _countDuplicatePositions(dedupedPositions);
|
|
92
|
+
assertEq(dedupedDuplicateCount, 0, "Should have no duplicates after deduplication");
|
|
93
|
+
|
|
94
|
+
// Verify that array is smaller after deduplication
|
|
95
|
+
assertLt(dedupedPositions.length, originalPositions.length, "Deduped array should be smaller");
|
|
96
|
+
|
|
97
|
+
// Calculate total liquidity before and after deduplication
|
|
98
|
+
uint256 totalOriginalLiquidity = 0;
|
|
99
|
+
uint256 totalDedupedLiquidity = 0;
|
|
100
|
+
|
|
101
|
+
for (uint256 i = 0; i < originalPositions.length; i++) {
|
|
102
|
+
totalOriginalLiquidity += originalPositions[i].liquidity;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
for (uint256 i = 0; i < dedupedPositions.length; i++) {
|
|
106
|
+
totalDedupedLiquidity += dedupedPositions[i].liquidity;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
assertEq(totalOriginalLiquidity, totalDedupedLiquidity, "Total liquidity should be preserved");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function test_memoryStructModification() public pure {
|
|
113
|
+
// this test shows that we can modify a struct in memory and it will be reflected in the array
|
|
114
|
+
LpPosition[] memory positions = new LpPosition[](2);
|
|
115
|
+
positions[0] = LpPosition({tickLower: -100, tickUpper: 100, liquidity: 1000});
|
|
116
|
+
positions[1] = LpPosition({tickLower: -200, tickUpper: 200, liquidity: 2000});
|
|
117
|
+
|
|
118
|
+
LpPosition memory pos = positions[0];
|
|
119
|
+
pos.liquidity += 500;
|
|
120
|
+
pos = positions[1];
|
|
121
|
+
pos.liquidity = 3000;
|
|
122
|
+
|
|
123
|
+
// The array element should be modified
|
|
124
|
+
assertEq(positions[0].liquidity, 1500, "Array element should change when modifying copy");
|
|
125
|
+
assertEq(positions[1].liquidity, 3000, "Array element should change when modifying copy");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function test_mstoreArrayLength() public pure {
|
|
129
|
+
LpPosition[] memory positions = new LpPosition[](5);
|
|
130
|
+
positions[0] = LpPosition({tickLower: -100, tickUpper: 100, liquidity: 1000});
|
|
131
|
+
positions[1] = LpPosition({tickLower: -200, tickUpper: 200, liquidity: 2000});
|
|
132
|
+
positions[2] = LpPosition({tickLower: -300, tickUpper: 300, liquidity: 3000});
|
|
133
|
+
positions[3] = LpPosition({tickLower: -400, tickUpper: 400, liquidity: 4000});
|
|
134
|
+
positions[4] = LpPosition({tickLower: -500, tickUpper: 500, liquidity: 5000});
|
|
135
|
+
|
|
136
|
+
assertEq(positions.length, 5, "Initial length should be 5");
|
|
137
|
+
|
|
138
|
+
assembly {
|
|
139
|
+
mstore(positions, 2)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
assertEq(positions.length, 2, "Length should be 2 after mstore");
|
|
143
|
+
assertEq(positions[0].liquidity, 1000, "First element should be preserved");
|
|
144
|
+
assertEq(positions[1].liquidity, 2000, "Second element should be preserved");
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function test_deployedCoinWithDuplicateConfigHasNoDuplicatePositions() public {
|
|
148
|
+
address currency = address(mockERC20A);
|
|
149
|
+
|
|
150
|
+
address[] memory owners = new address[](1);
|
|
151
|
+
owners[0] = users.creator;
|
|
152
|
+
|
|
153
|
+
bytes memory poolConfig = _poolConfigWithDuplicatePositions(currency);
|
|
154
|
+
|
|
155
|
+
(address coinAddress, ) = factory.deploy(
|
|
156
|
+
users.creator,
|
|
157
|
+
owners,
|
|
158
|
+
"https://test.com",
|
|
159
|
+
DEFAULT_NAME,
|
|
160
|
+
DEFAULT_SYMBOL,
|
|
161
|
+
poolConfig,
|
|
162
|
+
address(0),
|
|
163
|
+
address(0),
|
|
164
|
+
bytes(""),
|
|
165
|
+
bytes32(0)
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
ContentCoin coinV4 = ContentCoin(payable(coinAddress));
|
|
169
|
+
|
|
170
|
+
// get hooks
|
|
171
|
+
PoolKey memory poolKey = coinV4.getPoolKey();
|
|
172
|
+
LpPosition[] memory positions = ZoraV4CoinHook(payable(address(coinV4.hooks()))).getPoolCoin(poolKey).positions;
|
|
173
|
+
|
|
174
|
+
// Verify no duplicate positions exist in the deployed coin (deduplication worked during deployment)
|
|
175
|
+
uint256 duplicateCount = _countDuplicatePositions(positions);
|
|
176
|
+
assertEq(duplicateCount, 0, "Should have no duplicates after deployment");
|
|
177
|
+
}
|
|
178
|
+
}
|
package/test/utils/BaseTest.sol
CHANGED
|
@@ -24,7 +24,6 @@ import {IUniswapV3Factory} from "../../src/interfaces/IUniswapV3Factory.sol";
|
|
|
24
24
|
import {IUniswapV3Pool} from "../../src/interfaces/IUniswapV3Pool.sol";
|
|
25
25
|
import {IProtocolRewards} from "../../src/interfaces/IProtocolRewards.sol";
|
|
26
26
|
import {ProtocolRewards} from "../utils/ProtocolRewards.sol";
|
|
27
|
-
import {MarketConstants} from "../../src/libs/MarketConstants.sol";
|
|
28
27
|
import {CoinConfigurationVersions} from "../../src/libs/CoinConfigurationVersions.sol";
|
|
29
28
|
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
30
29
|
import {ZoraV4CoinHook} from "../../src/hooks/ZoraV4CoinHook.sol";
|
|
@@ -29,10 +29,10 @@ library RewardTestHelpers {
|
|
|
29
29
|
bool hasPlatformReferrer,
|
|
30
30
|
bool hasTradeReferrer
|
|
31
31
|
) internal pure returns (RewardBalances memory rewards) {
|
|
32
|
-
rewards.creator = calculateReward(marketRewards,
|
|
33
|
-
rewards.platformReferrer = hasPlatformReferrer ? calculateReward(marketRewards,
|
|
34
|
-
rewards.tradeReferrer = hasTradeReferrer ? calculateReward(marketRewards,
|
|
35
|
-
rewards.doppler = calculateReward(marketRewards,
|
|
32
|
+
rewards.creator = calculateReward(marketRewards, CoinConstants.CREATOR_REWARD_BPS);
|
|
33
|
+
rewards.platformReferrer = hasPlatformReferrer ? calculateReward(marketRewards, CoinConstants.CREATE_REFERRAL_REWARD_BPS) : 0;
|
|
34
|
+
rewards.tradeReferrer = hasTradeReferrer ? calculateReward(marketRewards, CoinConstants.TRADE_REFERRAL_REWARD_BPS) : 0;
|
|
35
|
+
rewards.doppler = calculateReward(marketRewards, CoinConstants.DOPPLER_REWARD_BPS);
|
|
36
36
|
rewards.protocol = marketRewards - rewards.creator - rewards.platformReferrer - rewards.tradeReferrer - rewards.doppler;
|
|
37
37
|
}
|
|
38
38
|
|
package/abis/CoinConstants.json
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"type": "function",
|
|
4
|
-
"name": "CREATOR_LAUNCH_REWARD",
|
|
5
|
-
"inputs": [],
|
|
6
|
-
"outputs": [
|
|
7
|
-
{
|
|
8
|
-
"name": "",
|
|
9
|
-
"type": "uint256",
|
|
10
|
-
"internalType": "uint256"
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"stateMutability": "view"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"type": "function",
|
|
17
|
-
"name": "MAX_TOTAL_SUPPLY",
|
|
18
|
-
"inputs": [],
|
|
19
|
-
"outputs": [
|
|
20
|
-
{
|
|
21
|
-
"name": "",
|
|
22
|
-
"type": "uint256",
|
|
23
|
-
"internalType": "uint256"
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"stateMutability": "view"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"type": "function",
|
|
30
|
-
"name": "MIN_ORDER_SIZE",
|
|
31
|
-
"inputs": [],
|
|
32
|
-
"outputs": [
|
|
33
|
-
{
|
|
34
|
-
"name": "",
|
|
35
|
-
"type": "uint256",
|
|
36
|
-
"internalType": "uint256"
|
|
37
|
-
}
|
|
38
|
-
],
|
|
39
|
-
"stateMutability": "view"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"type": "function",
|
|
43
|
-
"name": "POOL_LAUNCH_SUPPLY",
|
|
44
|
-
"inputs": [],
|
|
45
|
-
"outputs": [
|
|
46
|
-
{
|
|
47
|
-
"name": "",
|
|
48
|
-
"type": "uint256",
|
|
49
|
-
"internalType": "uint256"
|
|
50
|
-
}
|
|
51
|
-
],
|
|
52
|
-
"stateMutability": "view"
|
|
53
|
-
}
|
|
54
|
-
]
|
package/abis/CoinRewardsV4.json
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"type": "function",
|
|
4
|
-
"name": "CREATE_REFERRAL_REWARD_BPS",
|
|
5
|
-
"inputs": [],
|
|
6
|
-
"outputs": [
|
|
7
|
-
{
|
|
8
|
-
"name": "",
|
|
9
|
-
"type": "uint256",
|
|
10
|
-
"internalType": "uint256"
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"stateMutability": "view"
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"type": "function",
|
|
17
|
-
"name": "CREATOR_REWARD_BPS",
|
|
18
|
-
"inputs": [],
|
|
19
|
-
"outputs": [
|
|
20
|
-
{
|
|
21
|
-
"name": "",
|
|
22
|
-
"type": "uint256",
|
|
23
|
-
"internalType": "uint256"
|
|
24
|
-
}
|
|
25
|
-
],
|
|
26
|
-
"stateMutability": "view"
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
"type": "function",
|
|
30
|
-
"name": "DOPPLER_REWARD_BPS",
|
|
31
|
-
"inputs": [],
|
|
32
|
-
"outputs": [
|
|
33
|
-
{
|
|
34
|
-
"name": "",
|
|
35
|
-
"type": "uint256",
|
|
36
|
-
"internalType": "uint256"
|
|
37
|
-
}
|
|
38
|
-
],
|
|
39
|
-
"stateMutability": "view"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"type": "function",
|
|
43
|
-
"name": "LP_REWARD_BPS",
|
|
44
|
-
"inputs": [],
|
|
45
|
-
"outputs": [
|
|
46
|
-
{
|
|
47
|
-
"name": "",
|
|
48
|
-
"type": "uint256",
|
|
49
|
-
"internalType": "uint256"
|
|
50
|
-
}
|
|
51
|
-
],
|
|
52
|
-
"stateMutability": "view"
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
"type": "function",
|
|
56
|
-
"name": "TRADE_REFERRAL_REWARD_BPS",
|
|
57
|
-
"inputs": [],
|
|
58
|
-
"outputs": [
|
|
59
|
-
{
|
|
60
|
-
"name": "",
|
|
61
|
-
"type": "uint256",
|
|
62
|
-
"internalType": "uint256"
|
|
63
|
-
}
|
|
64
|
-
],
|
|
65
|
-
"stateMutability": "view"
|
|
66
|
-
}
|
|
67
|
-
]
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
|
|
2
|
-
// This software is licensed under the Zora Delayed Open Source License.
|
|
3
|
-
// Under this license, you may use, copy, modify, and distribute this software for
|
|
4
|
-
// non-commercial purposes only. Commercial use and competitive products are prohibited
|
|
5
|
-
// until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
|
|
6
|
-
// at which point this software automatically becomes available under the MIT License.
|
|
7
|
-
// Full license terms available at: https://docs.zora.co/coins/license
|
|
8
|
-
pragma solidity ^0.8.23;
|
|
9
|
-
|
|
10
|
-
library CreatorCoinConstants {
|
|
11
|
-
uint256 internal constant TOTAL_SUPPLY = 1_000_000_000e18; // 1b coins
|
|
12
|
-
uint256 internal constant CREATOR_VESTING_SUPPLY = 500_000_000e18; // 500m coins
|
|
13
|
-
uint256 internal constant CREATOR_VESTING_DURATION = 5 * 365 days; // 5 years
|
|
14
|
-
address internal constant CURRENCY = 0x1111111111166b7FE7bd91427724B487980aFc69;
|
|
15
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.23;
|
|
3
|
-
|
|
4
|
-
library MarketConstants {
|
|
5
|
-
/// @dev Constant used to increase precision during calculations
|
|
6
|
-
uint256 internal constant WAD = 1e18;
|
|
7
|
-
|
|
8
|
-
/// @notice The number of coins allocated to the liquidity pool for content coins
|
|
9
|
-
/// @dev 990 million coins
|
|
10
|
-
uint256 internal constant CONTENT_COIN_MARKET_SUPPLY = 990_000_000 * WAD;
|
|
11
|
-
|
|
12
|
-
/// @notice The number of coins allocated to the liquidity pool for creator coins
|
|
13
|
-
/// @dev 500 million coins
|
|
14
|
-
uint256 internal constant CREATOR_COIN_MARKET_SUPPLY = 500_000_000 * WAD;
|
|
15
|
-
|
|
16
|
-
/// @notice The LP fee
|
|
17
|
-
/// @dev 10000 basis points = 1%
|
|
18
|
-
uint24 internal constant LP_FEE_V4 = 10_000;
|
|
19
|
-
|
|
20
|
-
/// @notice The spacing for 1% pools
|
|
21
|
-
/// @dev 200 ticks
|
|
22
|
-
int24 internal constant TICK_SPACING = 200;
|
|
23
|
-
}
|
|
File without changes
|