@zoralabs/coins 2.1.2 → 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 +152 -0
- package/CHANGELOG.md +93 -0
- package/README.md +4 -0
- package/abis/BaseCoin.json +26 -5
- package/abis/BaseTest.json +2 -7
- package/abis/ContentCoin.json +26 -5
- package/abis/CreatorCoin.json +30 -9
- package/abis/FeeEstimatorHook.json +94 -6
- package/abis/ICoin.json +26 -0
- package/abis/ICoinV3.json +26 -0
- package/abis/ICreatorCoin.json +39 -0
- package/abis/IERC721.json +36 -36
- package/abis/IHasCoinType.json +15 -0
- package/abis/IHasTotalSupplyForPositions.json +15 -0
- package/abis/{LiquidityMigrationReceiver.json → IUpgradeableDestinationV4HookWithUpdateableFee.json} +10 -18
- package/abis/IZoraFactory.json +121 -0
- package/abis/IZoraHookRegistry.json +188 -0
- package/abis/VmContractHelper226.json +233 -0
- package/abis/ZoraFactoryImpl.json +101 -6
- package/abis/ZoraHookRegistry.json +375 -0
- package/abis/{CreatorCoinHook.json → ZoraV4CoinHook.json} +95 -2
- package/addresses/8453.json +6 -5
- package/audits/report-cantinacode-zora-0827.pdf +3498 -4
- package/dist/index.cjs +93 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +93 -13
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +144 -22
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/foundry.toml +4 -1
- package/package/wagmiGenerated.ts +93 -13
- package/package.json +6 -4
- package/script/PrintRegisterUpgradePath.s.sol +0 -7
- package/script/TestBackingCoinSwap.s.sol +0 -3
- package/script/TestV4Swap.s.sol +0 -3
- package/script/UpgradeFactoryImpl.s.sol +1 -1
- package/src/BaseCoin.sol +19 -24
- package/src/ContentCoin.sol +11 -2
- package/src/CreatorCoin.sol +34 -15
- package/src/ZoraFactoryImpl.sol +163 -92
- package/src/deployment/CoinsDeployerBase.sol +24 -58
- package/src/hook-registry/ZoraHookRegistry.sol +97 -0
- package/src/hooks/{BaseZoraV4CoinHook.sol → ZoraV4CoinHook.sol} +77 -15
- package/src/interfaces/ICoin.sol +19 -1
- package/src/interfaces/ICreatorCoin.sol +4 -0
- package/src/interfaces/IUpgradeableV4Hook.sol +18 -0
- package/src/interfaces/IZoraFactory.sol +51 -10
- package/src/interfaces/IZoraHookRegistry.sol +47 -0
- package/src/libs/CoinConstants.sol +43 -32
- package/src/libs/CoinDopplerMultiCurve.sol +11 -11
- package/src/libs/CoinRewardsV4.sol +68 -37
- package/src/libs/CoinSetup.sol +2 -9
- package/src/libs/DopplerMath.sol +2 -2
- package/src/libs/HooksDeployment.sol +13 -65
- package/src/libs/V4Liquidity.sol +109 -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 +32 -30
- package/test/ContentCoinRewards.t.sol +363 -0
- package/test/CreatorCoin.t.sol +53 -29
- package/test/CreatorCoinRewards.t.sol +375 -0
- package/test/DeploymentHooks.t.sol +64 -12
- package/test/Factory.t.sol +24 -7
- package/test/HooksDeployment.t.sol +4 -4
- package/test/LiquidityMigration.t.sol +149 -16
- package/test/Upgrades.t.sol +44 -48
- package/test/V4Liquidity.t.sol +178 -0
- package/test/ZoraHookRegistry.t.sol +266 -0
- package/test/utils/BaseTest.sol +25 -43
- package/test/utils/FeeEstimatorHook.sol +4 -6
- package/test/utils/RewardTestHelpers.sol +106 -0
- package/.turbo/turbo-build.log +0 -199
- package/abis/AutoSwapperTest.json +0 -618
- package/abis/BadImpl.json +0 -15
- package/abis/BaseZoraV4CoinHook.json +0 -1664
- package/abis/CoinConstants.json +0 -158
- package/abis/CoinRewardsV4.json +0 -67
- package/abis/CoinTest.json +0 -819
- package/abis/CoinUniV4Test.json +0 -1128
- package/abis/ContentCoinHook.json +0 -1733
- package/abis/CreatorCoinTest.json +0 -887
- package/abis/Deploy.json +0 -9
- package/abis/DeployHooks.json +0 -9
- package/abis/DeployScript.json +0 -35
- package/abis/DeployedCoinVersionLookupTest.json +0 -740
- package/abis/DifferentNamespaceVersionLookup.json +0 -39
- package/abis/FactoryTest.json +0 -748
- package/abis/FakeHookNoInterface.json +0 -21
- package/abis/GenerateDeterministicParams.json +0 -9
- package/abis/HooksDeploymentTest.json +0 -645
- package/abis/HooksTest.json +0 -709
- package/abis/InvalidLiquidityMigrationReceiver.json +0 -21
- package/abis/LiquidityMigrationTest.json +0 -889
- package/abis/MockBadFactory.json +0 -15
- package/abis/MultiOwnableTest.json +0 -766
- package/abis/PrintUpgradeCommand.json +0 -9
- package/abis/TestDeployedCoinVersionLookupImplementation.json +0 -39
- package/abis/TestV4Swap.json +0 -9
- package/abis/UpgradeFactoryImpl.json +0 -9
- package/abis/UpgradeHooks.json +0 -35
- package/abis/UpgradesTest.json +0 -723
- package/src/hooks/ContentCoinHook.sol +0 -27
- package/src/hooks/CreatorCoinHook.sol +0 -27
- package/src/libs/CreatorCoinConstants.sol +0 -16
- package/src/libs/CreatorCoinRewards.sol +0 -34
- package/src/libs/MarketConstants.sol +0 -15
|
@@ -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
|
|
|
@@ -77,7 +85,7 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
77
85
|
address[] memory trustedMessageSenders = new address[](1);
|
|
78
86
|
trustedMessageSenders[0] = UNIVERSAL_ROUTER;
|
|
79
87
|
|
|
80
|
-
address originalHook = address(
|
|
88
|
+
address originalHook = address(hook);
|
|
81
89
|
|
|
82
90
|
address newHook = address(new LiquidityMigrationReceiver());
|
|
83
91
|
|
|
@@ -109,7 +117,7 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
109
117
|
assertEq(coinV4.balanceOf(address(originalHook)), originalHookCoinBalanceBefore, "original coin balance");
|
|
110
118
|
|
|
111
119
|
// validate that the existing hook has no liquidity for its positions
|
|
112
|
-
LpPosition[] memory positions =
|
|
120
|
+
LpPosition[] memory positions = hook.getPoolCoin(poolKey).positions;
|
|
113
121
|
|
|
114
122
|
for (uint256 i = 0; i < positions.length; i++) {
|
|
115
123
|
uint128 liquidity = V4Liquidity.getLiquidity(poolManager, address(originalHook), poolKey, positions[i].tickLower, positions[i].tickUpper);
|
|
@@ -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 {
|
|
@@ -185,8 +221,6 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
185
221
|
address currency = address(mockERC20A);
|
|
186
222
|
_deployV4Coin(currency);
|
|
187
223
|
|
|
188
|
-
address originalHook = address(contentCoinHook);
|
|
189
|
-
|
|
190
224
|
address invalidNewHook = address(new InvalidLiquidityMigrationReceiver());
|
|
191
225
|
|
|
192
226
|
PoolKey memory poolKey = coinV4.getPoolKey();
|
|
@@ -203,14 +237,11 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
203
237
|
address currency = address(mockERC20A);
|
|
204
238
|
_deployV4Coin(currency);
|
|
205
239
|
|
|
206
|
-
address originalHook = address(
|
|
240
|
+
address originalHook = address(hook);
|
|
207
241
|
|
|
208
242
|
address newHook = address(new LiquidityMigrationReceiver());
|
|
209
243
|
|
|
210
|
-
PoolKey memory poolKey = coinV4.getPoolKey();
|
|
211
|
-
|
|
212
244
|
// Note: NOT registering the upgrade path
|
|
213
|
-
|
|
214
245
|
// expect the migration to revert with UpgradePathNotRegistered error
|
|
215
246
|
vm.prank(users.creator);
|
|
216
247
|
vm.expectRevert(abi.encodeWithSelector(IUpgradeableV4Hook.UpgradePathNotRegistered.selector, originalHook, newHook));
|
|
@@ -221,7 +252,7 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
221
252
|
address currency = address(mockERC20A);
|
|
222
253
|
_deployV4Coin(currency);
|
|
223
254
|
|
|
224
|
-
address originalHook = address(
|
|
255
|
+
address originalHook = address(hook);
|
|
225
256
|
address newHook = address(new LiquidityMigrationReceiver());
|
|
226
257
|
PoolKey memory poolKey = coinV4.getPoolKey();
|
|
227
258
|
|
|
@@ -388,4 +419,106 @@ contract LiquidityMigrationTest is BaseTest {
|
|
|
388
419
|
// Should match isRegisteredUpgradePath
|
|
389
420
|
assertEq(hookUpgradeGate.isAllowedHookUpgrade(baseImpl, upgradeImpl), hookUpgradeGate.isRegisteredUpgradePath(baseImpl, upgradeImpl));
|
|
390
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
|
+
}
|
|
391
524
|
}
|
package/test/Upgrades.t.sol
CHANGED
|
@@ -13,7 +13,7 @@ import {IWETH} from "../src/interfaces/IWETH.sol";
|
|
|
13
13
|
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
14
14
|
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
15
15
|
import {BuySupplyWithSwapRouterHook} from "../src/hooks/deployment/BuySupplyWithSwapRouterHook.sol";
|
|
16
|
-
import {
|
|
16
|
+
import {ZoraV4CoinHook} from "../src/hooks/ZoraV4CoinHook.sol";
|
|
17
17
|
import {console} from "forge-std/console.sol";
|
|
18
18
|
import {IDeployedCoinVersionLookup} from "../src/interfaces/IDeployedCoinVersionLookup.sol";
|
|
19
19
|
import {IHooksUpgradeGate} from "../src/interfaces/IHooksUpgradeGate.sol";
|
|
@@ -43,7 +43,7 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
43
43
|
|
|
44
44
|
factoryProxy = ZoraFactoryImpl(0x777777751622c0d3258f214F9DF38E35BF45baF3);
|
|
45
45
|
|
|
46
|
-
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(
|
|
46
|
+
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
|
|
47
47
|
|
|
48
48
|
vm.prank(factoryProxy.owner());
|
|
49
49
|
factoryProxy.upgradeToAndCall(address(newImpl), "");
|
|
@@ -58,7 +58,7 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
58
58
|
|
|
59
59
|
factoryProxy = ZoraFactoryImpl(0x777777751622c0d3258f214F9DF38E35BF45baF3);
|
|
60
60
|
|
|
61
|
-
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(
|
|
61
|
+
ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
|
|
62
62
|
|
|
63
63
|
vm.prank(factoryProxy.owner());
|
|
64
64
|
factoryProxy.upgradeToAndCall(address(newImpl), "");
|
|
@@ -70,29 +70,25 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
70
70
|
factoryProxy.upgradeToAndCall(address(badImpl), "");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
// This fork test needs to be updated after hook registry + new factory is deployed
|
|
74
|
+
// function test_canUpgradeToSameContractName() public {
|
|
75
|
+
// // this test that we can upgrade to the same contract name, when we have already upgraded to a version that has a contract name
|
|
76
|
+
// vm.createSelectFork("base", 29675508);
|
|
76
77
|
|
|
77
|
-
|
|
78
|
+
// factoryProxy = ZoraFactoryImpl(0x777777751622c0d3258f214F9DF38E35BF45baF3);
|
|
78
79
|
|
|
79
|
-
|
|
80
|
+
// ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
// vm.prank(factoryProxy.owner());
|
|
83
|
+
// factoryProxy.upgradeToAndCall(address(newImpl), "");
|
|
83
84
|
|
|
84
|
-
|
|
85
|
-
factoryProxy.coinV4Impl(),
|
|
86
|
-
factoryProxy.creatorCoinImpl(),
|
|
87
|
-
address(contentCoinHook),
|
|
88
|
-
address(creatorCoinHook)
|
|
89
|
-
);
|
|
85
|
+
// ZoraFactoryImpl newImpl2 = new ZoraFactoryImpl(factoryProxy.coinV4Impl(), factoryProxy.creatorCoinImpl(), address(hook), address(zoraHookRegistry));
|
|
90
86
|
|
|
91
|
-
|
|
92
|
-
|
|
87
|
+
// vm.prank(factoryProxy.owner());
|
|
88
|
+
// factoryProxy.upgradeToAndCall(address(newImpl2), "");
|
|
93
89
|
|
|
94
|
-
|
|
95
|
-
}
|
|
90
|
+
// assertEq(factoryProxy.implementation(), address(newImpl2));
|
|
91
|
+
// }
|
|
96
92
|
|
|
97
93
|
function test_canUpgradeAndSwap() public {
|
|
98
94
|
vm.createSelectFork("base");
|
|
@@ -179,32 +175,6 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
179
175
|
vm.stopPrank();
|
|
180
176
|
}
|
|
181
177
|
|
|
182
|
-
function test_canCanFixBrokenContentCoinAndSwap() public {
|
|
183
|
-
vm.createSelectFork("base", 31835069);
|
|
184
|
-
|
|
185
|
-
address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
|
|
186
|
-
|
|
187
|
-
address contentCoin = 0x4E93A01c90f812284F71291a8d1415a904957156;
|
|
188
|
-
|
|
189
|
-
address creatorCoin = ICoin(contentCoin).currency();
|
|
190
|
-
|
|
191
|
-
uint256 amountIn = IERC20(creatorCoin).balanceOf(trader);
|
|
192
|
-
|
|
193
|
-
require(amountIn > 0, "no balance");
|
|
194
|
-
|
|
195
|
-
// this swap should revert because the content coin is broken
|
|
196
|
-
_swapSomeCurrencyForCoinAndExpectRevert(ICoin(contentCoin), creatorCoin, uint128(amountIn), trader);
|
|
197
|
-
|
|
198
|
-
bytes memory creationCode = HooksDeployment.contentCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
|
|
199
|
-
|
|
200
|
-
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
201
|
-
|
|
202
|
-
// etch new hook into the content coin, it shouldn't revert anymore when swapping
|
|
203
|
-
vm.etch(address(ICoin(contentCoin).hooks()), address(newHook).code);
|
|
204
|
-
|
|
205
|
-
_swapSomeCurrencyForCoin(ICoin(contentCoin), creatorCoin, uint128(amountIn), trader);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
178
|
function test_canUpgradeBrokenContentCoinAndSwap() public {
|
|
209
179
|
vm.createSelectFork("base", 32613149);
|
|
210
180
|
|
|
@@ -216,7 +186,7 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
216
186
|
|
|
217
187
|
uint256 amountIn = 0.000111 ether;
|
|
218
188
|
|
|
219
|
-
bytes memory creationCode = HooksDeployment.
|
|
189
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
|
|
220
190
|
|
|
221
191
|
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
222
192
|
|
|
@@ -268,7 +238,7 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
268
238
|
|
|
269
239
|
address existingHook = address(creatorCoin.hooks());
|
|
270
240
|
|
|
271
|
-
bytes memory creationCode = HooksDeployment.
|
|
241
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
|
|
272
242
|
|
|
273
243
|
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
274
244
|
|
|
@@ -319,4 +289,30 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
|
|
|
319
289
|
// now try to swap some currency for the creator coin - it should succeed
|
|
320
290
|
_swapSomeCurrencyForCoin(creatorCoin, zora, uint128(IERC20(zora).balanceOf(trader) / 2), trader);
|
|
321
291
|
}
|
|
292
|
+
|
|
293
|
+
function test_canFixBrokenContentCoinAndSwap() public {
|
|
294
|
+
vm.createSelectFork("base", 31835069);
|
|
295
|
+
|
|
296
|
+
address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
|
|
297
|
+
|
|
298
|
+
address contentCoin = 0x4E93A01c90f812284F71291a8d1415a904957156;
|
|
299
|
+
|
|
300
|
+
address creatorCoin = ICoin(contentCoin).currency();
|
|
301
|
+
|
|
302
|
+
uint256 amountIn = IERC20(creatorCoin).balanceOf(trader);
|
|
303
|
+
|
|
304
|
+
require(amountIn > 0, "no balance");
|
|
305
|
+
|
|
306
|
+
// this swap should revert because the content coin is broken
|
|
307
|
+
_swapSomeCurrencyForCoinAndExpectRevert(ICoin(contentCoin), creatorCoin, uint128(amountIn), trader);
|
|
308
|
+
|
|
309
|
+
bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
|
|
310
|
+
|
|
311
|
+
(IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
|
|
312
|
+
|
|
313
|
+
// etch new hook into the content coin, it shouldn't revert anymore when swapping
|
|
314
|
+
vm.etch(address(ICoin(contentCoin).hooks()), address(newHook).code);
|
|
315
|
+
|
|
316
|
+
_swapSomeCurrencyForCoin(ICoin(contentCoin), creatorCoin, uint128(amountIn), trader);
|
|
317
|
+
}
|
|
322
318
|
}
|
|
@@ -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
|
+
}
|