@zoralabs/coins 1.1.0 → 1.1.1

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.
Files changed (47) hide show
  1. package/.turbo/turbo-build.log +97 -95
  2. package/CHANGELOG.md +8 -0
  3. package/abis/BaseCoin.json +6 -1
  4. package/abis/BaseZoraV4CoinHook.json +119 -0
  5. package/abis/Coin.json +6 -1
  6. package/abis/CoinTest.json +14 -0
  7. package/abis/CoinV4.json +6 -1
  8. package/abis/ContentCoinHook.json +119 -0
  9. package/abis/CreatorCoin.json +6 -1
  10. package/abis/CreatorCoinHook.json +119 -0
  11. package/abis/ERC165.json +21 -0
  12. package/abis/ERC165Upgradeable.json +44 -0
  13. package/abis/FeeEstimatorHook.json +119 -0
  14. package/abis/ICoin.json +5 -0
  15. package/abis/ICoinV3.json +5 -0
  16. package/abis/ICoinV4.json +5 -0
  17. package/abis/ICreatorCoin.json +5 -0
  18. package/abis/LiquidityMigrationTest.json +7 -0
  19. package/abis/UpgradeHooks.json +35 -0
  20. package/abis/UpgradesTest.json +21 -0
  21. package/addresses/8453.json +11 -7
  22. package/dist/index.cjs +6 -3
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.js +6 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/wagmiGenerated.d.ts +15 -3
  27. package/dist/wagmiGenerated.d.ts.map +1 -1
  28. package/package/wagmiGenerated.ts +6 -3
  29. package/package.json +1 -1
  30. package/script/DeployUpgradeGate.s.sol +1 -1
  31. package/script/PrintRegisterUpgradePath.s.sol +35 -0
  32. package/script/UpgradeCoinImpl.sol +1 -1
  33. package/script/UpgradeHooks.s.sol +23 -0
  34. package/src/BaseCoin.sol +25 -8
  35. package/src/CoinV4.sol +2 -2
  36. package/src/deployment/CoinsDeployerBase.sol +29 -9
  37. package/src/hooks/BaseZoraV4CoinHook.sol +117 -3
  38. package/src/interfaces/ICoin.sol +3 -0
  39. package/src/libs/CoinRewardsV4.sol +18 -4
  40. package/src/libs/HooksDeployment.sol +13 -0
  41. package/src/libs/V4Liquidity.sol +27 -0
  42. package/src/version/ContractVersionBase.sol +1 -1
  43. package/test/Coin.t.sol +23 -0
  44. package/test/LiquidityMigration.t.sol +27 -0
  45. package/test/Upgrades.t.sol +180 -1
  46. package/test/utils/BaseTest.sol +5 -1
  47. /package/script/{DeployHooks.s.sol → DeployPostDeploymentHooks.s.sol} +0 -0
@@ -48,6 +48,9 @@ library V4Liquidity {
48
48
  error InvalidCallbackId(uint8 callbackId);
49
49
 
50
50
  /// @notice Locks the pool, and mint initial positions to the hook
51
+ /// @param poolManager The pool manager.
52
+ /// @param poolKey The pool key.
53
+ /// @param positions The positions.
51
54
  function lockAndMint(IPoolManager poolManager, PoolKey memory poolKey, LpPosition[] memory positions) internal {
52
55
  bytes memory data = abi.encode(MINT_CALLBACK_ID, abi.encode(MintCallbackData({poolKey: poolKey, positions: positions})));
53
56
 
@@ -129,11 +132,35 @@ library V4Liquidity {
129
132
  return abi.encode(result);
130
133
  }
131
134
 
135
+ function generatePositionsFromMigratedLiquidity(
136
+ uint160 sqrtPriceX96,
137
+ BurnedPosition[] calldata migratedLiquidity
138
+ ) internal pure returns (LpPosition[] memory positions) {
139
+ positions = new LpPosition[](migratedLiquidity.length);
140
+
141
+ for (uint256 i = 0; i < migratedLiquidity.length; i++) {
142
+ uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(
143
+ sqrtPriceX96,
144
+ TickMath.getSqrtPriceAtTick(migratedLiquidity[i].tickLower),
145
+ TickMath.getSqrtPriceAtTick(migratedLiquidity[i].tickUpper),
146
+ migratedLiquidity[i].amount0Received,
147
+ migratedLiquidity[i].amount1Received
148
+ );
149
+ positions[i] = LpPosition({liquidity: liquidity, tickLower: migratedLiquidity[i].tickLower, tickUpper: migratedLiquidity[i].tickUpper});
150
+ }
151
+ }
152
+
132
153
  function collectFees(IPoolManager poolManager, PoolKey memory poolKey, LpPosition[] storage positions) internal returns (int128 balance0, int128 balance1) {
133
154
  ModifyLiquidityParams memory params;
134
155
  uint256 numPositions = positions.length;
135
156
 
136
157
  for (uint256 i; i < numPositions; i++) {
158
+ // if there is no liquidity, skip
159
+ uint128 liquidity = getLiquidity(poolManager, address(this), poolKey, positions[i].tickLower, positions[i].tickUpper);
160
+ if (liquidity == 0) {
161
+ continue;
162
+ }
163
+
137
164
  params = ModifyLiquidityParams({
138
165
  tickLower: positions[i].tickLower,
139
166
  tickUpper: positions[i].tickUpper,
@@ -9,6 +9,6 @@ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersion
9
9
  contract ContractVersionBase is IVersionedContract {
10
10
  /// @notice The version of the contract
11
11
  function contractVersion() external pure override returns (string memory) {
12
- return "1.1.0";
12
+ return "1.1.1";
13
13
  }
14
14
  }
package/test/Coin.t.sol CHANGED
@@ -8,6 +8,8 @@ import {CoinConstants} from "../src/libs/CoinConstants.sol";
8
8
  import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
9
9
  import {IHasRewardsRecipients} from "../src/interfaces/IHasRewardsRecipients.sol";
10
10
  import {PoolConfiguration} from "../src/interfaces/ICoin.sol";
11
+ import {IERC165, IERC7572, ICoin, ICoinComments, IERC20} from "../src/BaseCoin.sol";
12
+ import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
11
13
 
12
14
  contract CoinTest is BaseTest {
13
15
  using stdJson for string;
@@ -16,6 +18,17 @@ contract CoinTest is BaseTest {
16
18
  super.setUp();
17
19
  }
18
20
 
21
+ function test_contract_ierc165_support() public {
22
+ _deployCoin();
23
+ assertEq(coin.supportsInterface(type(IZoraFactory).interfaceId), false);
24
+ assertEq(coin.supportsInterface(bytes4(0x00000000)), false);
25
+ assertEq(coin.supportsInterface(type(IERC165).interfaceId), true);
26
+ assertEq(coin.supportsInterface(type(IERC7572).interfaceId), true);
27
+ assertEq(coin.supportsInterface(type(ICoin).interfaceId), true);
28
+ assertEq(coin.supportsInterface(type(ICoinComments).interfaceId), true);
29
+ assertEq(coin.supportsInterface(type(IERC7572).interfaceId), true);
30
+ }
31
+
19
32
  function test_contract_version() public {
20
33
  _deployCoin();
21
34
  string memory package = vm.readFile("./package.json");
@@ -658,4 +671,14 @@ contract CoinTest is BaseTest {
658
671
  vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
659
672
  coin.setNameAndSymbol(newName, newSymbol);
660
673
  }
674
+
675
+ function test_update_metadata_reverts_if_name_is_blank() public {
676
+ _deployCoin();
677
+ string memory newName = "";
678
+ string memory newSymbol = "NEW";
679
+
680
+ vm.prank(users.creator);
681
+ vm.expectRevert(abi.encodeWithSelector(ICoin.NameIsRequired.selector));
682
+ coin.setNameAndSymbol(newName, newSymbol);
683
+ }
661
684
  }
@@ -123,6 +123,33 @@ contract LiquidityMigrationTest is BaseTest {
123
123
  assertEq(newPoolKey.tickSpacing, poolKey.tickSpacing, "poolkey tickSpacing");
124
124
  }
125
125
 
126
+ function test_migrateLiquidity_enablesSwapsOnOldPoolKey() public {
127
+ address currency = address(mockERC20A);
128
+ mockERC20A.mint(address(poolManager), 1_000_000_000 ether);
129
+ _deployV4Coin(currency);
130
+
131
+ address trader = makeAddr("trader");
132
+
133
+ mockERC20A.mint(trader, 10 ether);
134
+
135
+ // do some swaps
136
+ _swapSomeCurrencyForCoin(coinV4, currency, 1 ether, trader);
137
+ _swapSomeCoinForCurrency(coinV4, currency, uint128(coinV4.balanceOf(trader)), trader);
138
+
139
+ address newHook = address(new LiquidityMigrationReceiver());
140
+
141
+ PoolKey memory poolKey = coinV4.getPoolKey();
142
+
143
+ registerUpgradePath(address(poolKey.hooks), address(newHook));
144
+
145
+ // migrate the liquidity
146
+ vm.prank(users.creator);
147
+ coinV4.migrateLiquidity(address(newHook), "");
148
+
149
+ // now swap using the existing pool key, it should succeed
150
+ _swapSomeCurrencyForCoin(poolKey, coinV4, currency, uint128(mockERC20A.balanceOf(trader)), trader);
151
+ }
152
+
126
153
  function test_migrateLiquidity_emitsLiquidityMigrated() public {
127
154
  address currency = address(mockERC20A);
128
155
  _deployV4Coin(currency);
@@ -13,8 +13,20 @@ 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
-
16
+ import {ContentCoinHook} from "../src/hooks/ContentCoinHook.sol";
17
17
  import {console} from "forge-std/console.sol";
18
+ import {IDeployedCoinVersionLookup} from "../src/interfaces/IDeployedCoinVersionLookup.sol";
19
+ import {IHooksUpgradeGate} from "../src/interfaces/IHooksUpgradeGate.sol";
20
+ import {HooksDeployment} from "../src/libs/HooksDeployment.sol";
21
+ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
22
+ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
23
+ import {MultiOwnable} from "../src/utils/MultiOwnable.sol";
24
+ import {CoinV4} from "../src/CoinV4.sol";
25
+ import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
26
+ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
27
+ import {IZoraV4CoinHook} from "../src/interfaces/IZoraV4CoinHook.sol";
28
+ import {PoolStateReader} from "../src/libs/PoolStateReader.sol";
29
+ import {LpPosition} from "../src/types/LpPosition.sol";
18
30
 
19
31
  contract BadImpl {
20
32
  function contractName() public pure returns (string memory) {
@@ -159,4 +171,171 @@ contract UpgradesTest is BaseTest, CoinsDeployerBase {
159
171
  // do some swaps to test out
160
172
  _swapSomeCoinForCurrency(ICoinV4(coinAddress), ZORA, uint128(IERC20(coinAddress).balanceOf(trader)), trader);
161
173
  }
174
+
175
+ address coinVersionLookup = 0x777777751622c0d3258f214F9DF38E35BF45baF3;
176
+ address upgradeGate = 0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2;
177
+
178
+ function _swapSomeCurrencyForCoinAndExpectRevert(ICoinV4 _coin, address currency, uint128 amountIn, address trader) internal {
179
+ uint128 minAmountOut = uint128(0);
180
+
181
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
182
+ currency,
183
+ amountIn,
184
+ address(_coin),
185
+ minAmountOut,
186
+ _coin.getPoolKey(),
187
+ bytes("")
188
+ );
189
+
190
+ vm.startPrank(trader);
191
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currency, amountIn, uint48(block.timestamp + 1 days));
192
+
193
+ // Execute the swap
194
+ uint256 deadline = block.timestamp + 20;
195
+ vm.expectRevert();
196
+ router.execute(commands, inputs, deadline);
197
+
198
+ vm.stopPrank();
199
+ }
200
+
201
+ function test_canCanFixBrokenContentCoinAndSwap() public {
202
+ vm.createSelectFork("base", 31835069);
203
+
204
+ address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
205
+
206
+ address contentCoin = 0x4E93A01c90f812284F71291a8d1415a904957156;
207
+
208
+ address creatorCoin = ICoinV4(contentCoin).currency();
209
+
210
+ uint256 amountIn = IERC20(creatorCoin).balanceOf(trader);
211
+
212
+ require(amountIn > 0, "no balance");
213
+
214
+ // this swap should revert because the content coin is broken
215
+ _swapSomeCurrencyForCoinAndExpectRevert(ICoinV4(contentCoin), creatorCoin, uint128(amountIn), trader);
216
+
217
+ bytes memory creationCode = HooksDeployment.contentCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
218
+
219
+ (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
220
+
221
+ // etch new hook into the content coin, it shouldn't revert anymore when swapping
222
+ vm.etch(address(ICoinV4(contentCoin).hooks()), address(newHook).code);
223
+
224
+ _swapSomeCurrencyForCoin(ICoinV4(contentCoin), creatorCoin, uint128(amountIn), trader);
225
+ }
226
+
227
+ function test_canUpgradeBrokenContentCoinAndSwap() public {
228
+ vm.createSelectFork("base", 31835069);
229
+
230
+ address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
231
+
232
+ address contentCoin = 0x4E93A01c90f812284F71291a8d1415a904957156;
233
+
234
+ address creatorCoin = ICoinV4(contentCoin).currency();
235
+
236
+ address existingHook = 0xd3D133469ADC85e01A4887404D8AC12d630e9040;
237
+
238
+ uint256 amountIn = IERC20(creatorCoin).balanceOf(trader);
239
+
240
+ bytes memory creationCode = HooksDeployment.contentCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
241
+
242
+ (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
243
+
244
+ address[] memory baseImpls = new address[](1);
245
+ baseImpls[0] = existingHook;
246
+
247
+ vm.prank(Ownable(upgradeGate).owner());
248
+ IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
249
+
250
+ vm.prank(MultiOwnable(contentCoin).owners()[0]);
251
+ CoinV4(contentCoin).migrateLiquidity(address(newHook), "");
252
+
253
+ // do some swaps to test out
254
+ _swapSomeCurrencyForCoin(ICoinV4(contentCoin), creatorCoin, uint128(amountIn), trader);
255
+ }
256
+
257
+ function getPositionInfo(
258
+ PoolKey memory key,
259
+ address owner,
260
+ int24 tickLower,
261
+ int24 tickUpper
262
+ ) internal view returns (uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) {
263
+ return StateLibrary.getPositionInfo(poolManager, key.toId(), owner, tickLower, tickUpper, bytes32(0));
264
+ }
265
+
266
+ function getLiquidityForPositions(PoolKey memory key, LpPosition[] memory positions) internal view returns (uint128[] memory liquidityForPositions) {
267
+ liquidityForPositions = new uint128[](positions.length);
268
+
269
+ for (uint256 i = 0; i < positions.length; i++) {
270
+ (uint128 liquidity, , ) = getPositionInfo(key, address(key.hooks), positions[i].tickLower, positions[i].tickUpper);
271
+ liquidityForPositions[i] = liquidity;
272
+ }
273
+ }
274
+
275
+ function getLiquidityForPoolCoin(ICoinV4 coin) internal view returns (uint128[] memory liquidityForPositions) {
276
+ return getLiquidityForPositions(coin.getPoolKey(), IZoraV4CoinHook(address(coin.hooks())).getPoolCoin(coin.getPoolKey()).positions);
277
+ }
278
+
279
+ function test_canUpgradeBrokenCreatorCoinAndSwap() public {
280
+ vm.createSelectFork("base", 31872861);
281
+
282
+ address trader = 0xf69fEc6d858c77e969509843852178bd24CAd2B6;
283
+
284
+ ICoinV4 creatorCoin = ICoinV4(0x2F03aB8fD97F5874bc3274C296Bb954Ae92EdA34);
285
+
286
+ address zora = creatorCoin.currency();
287
+
288
+ address existingHook = address(creatorCoin.hooks());
289
+
290
+ bytes memory creationCode = HooksDeployment.creatorCoinCreationCode(address(poolManager), coinVersionLookup, new address[](0), upgradeGate);
291
+
292
+ (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
293
+
294
+ address[] memory baseImpls = new address[](1);
295
+ baseImpls[0] = existingHook;
296
+
297
+ vm.prank(Ownable(upgradeGate).owner());
298
+ IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
299
+
300
+ LpPosition[] memory beforePositions = IZoraV4CoinHook(address(creatorCoin.hooks())).getPoolCoin(creatorCoin.getPoolKey()).positions;
301
+ PoolKey memory beforeKey = creatorCoin.getPoolKey();
302
+
303
+ uint128[] memory beforeLiquidity = getLiquidityForPositions(beforeKey, beforePositions);
304
+ // get before price
305
+ uint160 beforePrice = PoolStateReader.getSqrtPriceX96(creatorCoin.getPoolKey(), poolManager);
306
+
307
+ vm.prank(MultiOwnable(address(creatorCoin)).owners()[0]);
308
+ CoinV4(address(creatorCoin)).migrateLiquidity(address(newHook), "");
309
+
310
+ // get liquidity of original positions after migration
311
+ uint128[] memory liquidityOfPositionsAfterMigration = getLiquidityForPositions(beforeKey, beforePositions);
312
+
313
+ // there should be no liquidity left in the original positions after migration
314
+ for (uint256 i = 0; i < liquidityOfPositionsAfterMigration.length; i++) {
315
+ assertEq(liquidityOfPositionsAfterMigration[i], 0);
316
+ }
317
+
318
+ // get liquidity of new positions after migration
319
+ PoolKey memory afterKey = creatorCoin.getPoolKey();
320
+ LpPosition[] memory afterPositions = IZoraV4CoinHook(address(afterKey.hooks)).getPoolCoin(afterKey).positions;
321
+ uint128[] memory afterLiquidity = getLiquidityForPositions(afterKey, afterPositions);
322
+
323
+ for (uint256 i = 0; i < beforeLiquidity.length; i++) {
324
+ // we added any extra liquidity to the last position, so we don't expect it to be the same
325
+ if (i != beforeLiquidity.length - 1) {
326
+ assertApproxEqAbs(beforeLiquidity[i], afterLiquidity[i], 200);
327
+ }
328
+ }
329
+
330
+ uint160 afterPrice = PoolStateReader.getSqrtPriceX96(creatorCoin.getPoolKey(), poolManager);
331
+
332
+ assertEq(beforePrice, afterPrice);
333
+
334
+ // make sure that the new hook has no balance of 0 or 1
335
+ assertApproxEqAbs(creatorCoin.getPoolKey().currency0.balanceOf(address(newHook)), 0, 10);
336
+ assertApproxEqAbs(creatorCoin.getPoolKey().currency1.balanceOf(address(newHook)), 0, 10);
337
+
338
+ // now try to swap some currency for the creator coin - it should succeed
339
+ _swapSomeCurrencyForCoin(creatorCoin, zora, uint128(IERC20(zora).balanceOf(trader) / 2), trader);
340
+ }
162
341
  }
@@ -190,6 +190,10 @@ contract BaseTest is Test, ContractAddresses {
190
190
  }
191
191
 
192
192
  function _swapSomeCurrencyForCoin(ICoinV4 _coin, address currency, uint128 amountIn, address trader) internal {
193
+ _swapSomeCurrencyForCoin(_coin.getPoolKey(), _coin, currency, amountIn, trader);
194
+ }
195
+
196
+ function _swapSomeCurrencyForCoin(PoolKey memory poolKey, ICoinV4 _coin, address currency, uint128 amountIn, address trader) internal {
193
197
  uint128 minAmountOut = uint128(0);
194
198
 
195
199
  (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
@@ -197,7 +201,7 @@ contract BaseTest is Test, ContractAddresses {
197
201
  amountIn,
198
202
  address(_coin),
199
203
  minAmountOut,
200
- _coin.getPoolKey(),
204
+ poolKey,
201
205
  bytes("")
202
206
  );
203
207