@zoralabs/coins 2.1.2 → 2.2.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 (97) hide show
  1. package/.turbo/turbo-build$colon$js.log +152 -0
  2. package/CHANGELOG.md +54 -0
  3. package/abis/BaseCoin.json +26 -0
  4. package/abis/BaseTest.json +2 -7
  5. package/abis/CoinConstants.json +0 -104
  6. package/abis/ContentCoin.json +26 -0
  7. package/abis/CreatorCoin.json +30 -4
  8. package/abis/FeeEstimatorHook.json +0 -5
  9. package/abis/ICoin.json +26 -0
  10. package/abis/ICoinV3.json +26 -0
  11. package/abis/ICreatorCoin.json +39 -0
  12. package/abis/IERC721.json +36 -36
  13. package/abis/IHasCoinType.json +15 -0
  14. package/abis/IHasTotalSupplyForPositions.json +15 -0
  15. package/abis/IZoraFactory.json +52 -0
  16. package/abis/IZoraHookRegistry.json +188 -0
  17. package/abis/VmContractHelper227.json +233 -0
  18. package/abis/ZoraFactoryImpl.json +32 -6
  19. package/abis/ZoraHookRegistry.json +375 -0
  20. package/abis/{CreatorCoinHook.json → ZoraV4CoinHook.json} +1 -1
  21. package/addresses/8453.json +2 -1
  22. package/dist/index.cjs +72 -10
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.js +72 -10
  25. package/dist/index.js.map +1 -1
  26. package/dist/wagmiGenerated.d.ts +90 -10
  27. package/dist/wagmiGenerated.d.ts.map +1 -1
  28. package/foundry.toml +4 -1
  29. package/package/wagmiGenerated.ts +72 -10
  30. package/package.json +6 -4
  31. package/script/PrintRegisterUpgradePath.s.sol +0 -7
  32. package/script/TestBackingCoinSwap.s.sol +0 -1
  33. package/script/TestV4Swap.s.sol +0 -1
  34. package/script/UpgradeFactoryImpl.s.sol +1 -1
  35. package/src/BaseCoin.sol +15 -12
  36. package/src/ContentCoin.sol +10 -0
  37. package/src/CreatorCoin.sol +28 -7
  38. package/src/ZoraFactoryImpl.sol +62 -23
  39. package/src/deployment/CoinsDeployerBase.sol +24 -58
  40. package/src/hook-registry/ZoraHookRegistry.sol +93 -0
  41. package/src/hooks/{BaseZoraV4CoinHook.sol → ZoraV4CoinHook.sol} +13 -8
  42. package/src/interfaces/ICoin.sol +19 -1
  43. package/src/interfaces/ICreatorCoin.sol +4 -0
  44. package/src/interfaces/IZoraFactory.sol +32 -10
  45. package/src/interfaces/IZoraHookRegistry.sol +47 -0
  46. package/src/libs/CoinConstants.sol +0 -32
  47. package/src/libs/CoinRewardsV4.sol +53 -15
  48. package/src/libs/CreatorCoinConstants.sol +0 -1
  49. package/src/libs/HooksDeployment.sol +13 -65
  50. package/src/libs/MarketConstants.sol +11 -3
  51. package/src/libs/V4Liquidity.sol +30 -0
  52. package/src/version/ContractVersionBase.sol +1 -1
  53. package/test/CoinUniV4.t.sol +33 -30
  54. package/test/ContentCoinRewards.t.sol +320 -0
  55. package/test/CreatorCoin.t.sol +1 -1
  56. package/test/CreatorCoinRewards.t.sol +375 -0
  57. package/test/DeploymentHooks.t.sol +10 -10
  58. package/test/Factory.t.sol +24 -7
  59. package/test/HooksDeployment.t.sol +4 -4
  60. package/test/LiquidityMigration.t.sol +4 -9
  61. package/test/Upgrades.t.sol +44 -48
  62. package/test/ZoraHookRegistry.t.sol +266 -0
  63. package/test/utils/BaseTest.sol +25 -42
  64. package/test/utils/FeeEstimatorHook.sol +4 -6
  65. package/test/utils/RewardTestHelpers.sol +106 -0
  66. package/.turbo/turbo-build.log +0 -199
  67. package/abis/AutoSwapperTest.json +0 -618
  68. package/abis/BadImpl.json +0 -15
  69. package/abis/BaseZoraV4CoinHook.json +0 -1664
  70. package/abis/CoinTest.json +0 -819
  71. package/abis/CoinUniV4Test.json +0 -1128
  72. package/abis/ContentCoinHook.json +0 -1733
  73. package/abis/CreatorCoinTest.json +0 -887
  74. package/abis/Deploy.json +0 -9
  75. package/abis/DeployHooks.json +0 -9
  76. package/abis/DeployScript.json +0 -35
  77. package/abis/DeployedCoinVersionLookupTest.json +0 -740
  78. package/abis/DifferentNamespaceVersionLookup.json +0 -39
  79. package/abis/FactoryTest.json +0 -748
  80. package/abis/FakeHookNoInterface.json +0 -21
  81. package/abis/GenerateDeterministicParams.json +0 -9
  82. package/abis/HooksDeploymentTest.json +0 -645
  83. package/abis/HooksTest.json +0 -709
  84. package/abis/InvalidLiquidityMigrationReceiver.json +0 -21
  85. package/abis/LiquidityMigrationReceiver.json +0 -103
  86. package/abis/LiquidityMigrationTest.json +0 -889
  87. package/abis/MockBadFactory.json +0 -15
  88. package/abis/MultiOwnableTest.json +0 -766
  89. package/abis/PrintUpgradeCommand.json +0 -9
  90. package/abis/TestDeployedCoinVersionLookupImplementation.json +0 -39
  91. package/abis/TestV4Swap.json +0 -9
  92. package/abis/UpgradeFactoryImpl.json +0 -9
  93. package/abis/UpgradeHooks.json +0 -35
  94. package/abis/UpgradesTest.json +0 -723
  95. package/src/hooks/ContentCoinHook.sol +0 -27
  96. package/src/hooks/CreatorCoinHook.sol +0 -27
  97. package/src/libs/CreatorCoinRewards.sol +0 -34
@@ -0,0 +1,375 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import "./utils/BaseTest.sol";
5
+ import {console} from "forge-std/console.sol";
6
+
7
+ import {ICreatorCoin} from "../src/interfaces/ICreatorCoin.sol";
8
+ import {ICreatorCoinHook} from "../src/interfaces/ICreatorCoinHook.sol";
9
+ import {IHasRewardsRecipients} from "../src/interfaces/IHasRewardsRecipients.sol";
10
+ import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
11
+ import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
12
+ import {FeeEstimatorHook} from "./utils/FeeEstimatorHook.sol";
13
+ import {RewardTestHelpers, RewardBalances} from "./utils/RewardTestHelpers.sol";
14
+ import {CoinConstants} from "../src/libs/CoinConstants.sol";
15
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
16
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
17
+
18
+ contract CreatorCoinRewardsTest is BaseTest {
19
+ CreatorCoin internal creatorCoin;
20
+
21
+ address internal platformReferrer;
22
+ address internal tradeReferrer;
23
+
24
+ function setUp() public override {
25
+ super.setUpWithBlockNumber(30267794);
26
+
27
+ deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
28
+
29
+ // Set up referrer addresses for all tests
30
+ platformReferrer = makeAddr("platformReferrer");
31
+ tradeReferrer = makeAddr("tradeReferrer");
32
+ }
33
+
34
+ function _getMultiCurvePoolConfig() internal view returns (bytes memory) {
35
+ int24[] memory tickLower = new int24[](1);
36
+ int24[] memory tickUpper = new int24[](1);
37
+ uint16[] memory numDiscoveryPositions = new uint16[](1);
38
+ uint256[] memory maxDiscoverySupplyShare = new uint256[](1);
39
+
40
+ tickLower[0] = -138_000;
41
+ tickUpper[0] = 81_000;
42
+ numDiscoveryPositions[0] = 11;
43
+ maxDiscoverySupplyShare[0] = 0.25e18;
44
+
45
+ return
46
+ abi.encode(
47
+ CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION,
48
+ address(zoraToken),
49
+ tickLower,
50
+ tickUpper,
51
+ numDiscoveryPositions,
52
+ maxDiscoverySupplyShare
53
+ );
54
+ }
55
+
56
+ function _deployCreatorCoin(bool hasPlatformReferrer) internal {
57
+ bytes memory poolConfig = _getMultiCurvePoolConfig();
58
+
59
+ // Generate unique salt based on referrer addresses and block timestamp
60
+ bytes32 uniqueSalt = keccak256(abi.encodePacked(platformReferrer, block.timestamp, gasleft()));
61
+
62
+ vm.prank(users.creator);
63
+ address creatorCoinAddress = factory.deployCreatorCoin(
64
+ users.creator,
65
+ _getDefaultOwners(),
66
+ "https://test.com",
67
+ "Testcoin",
68
+ "TEST",
69
+ poolConfig,
70
+ hasPlatformReferrer ? platformReferrer : address(0),
71
+ uniqueSalt // unique salt to prevent collisions
72
+ );
73
+
74
+ creatorCoin = CreatorCoin(creatorCoinAddress);
75
+ vm.label(address(creatorCoin), "TEST_CREATOR_COIN");
76
+ }
77
+
78
+ /// @dev Estimates the fees from a swap, by deploying a test hook that doesn't distribute the fees
79
+ /// and then reverting the state after the swap
80
+ function _estimateLpFees(bytes memory commands, bytes[] memory inputs) internal returns (FeeEstimatorHook.FeeEstimatorState memory feeState) {
81
+ uint256 snapshot = vm.snapshotState();
82
+ _deployFeeEstimatorHook(address(hook));
83
+
84
+ // Execute the swap
85
+ uint256 deadline = block.timestamp + 20;
86
+ router.execute(commands, inputs, deadline);
87
+
88
+ feeState = FeeEstimatorHook(payable(address(hook))).getFeeState();
89
+
90
+ vm.revertToState(snapshot);
91
+ }
92
+
93
+ // Generic function to record token balances for all reward recipients
94
+ function _recordBalances(IERC20 token) internal view returns (RewardBalances memory balances) {
95
+ balances.creator = token.balanceOf(users.creator);
96
+ balances.platformReferrer = token.balanceOf(platformReferrer);
97
+ balances.tradeReferrer = token.balanceOf(tradeReferrer);
98
+ balances.protocol = token.balanceOf(creatorCoin.protocolRewardRecipient());
99
+ balances.doppler = token.balanceOf(creatorCoin.dopplerFeeRecipient());
100
+ }
101
+
102
+ // Helper function to record initial ZORA token balances for all reward recipients
103
+ function _recordZoraBalances() internal view returns (RewardBalances memory balances) {
104
+ return _recordBalances(zoraToken);
105
+ }
106
+
107
+ // Helper function to record initial creator coin balances for all reward recipients
108
+ function _recordCreatorCoinBalances() internal view returns (RewardBalances memory balances) {
109
+ return _recordBalances(IERC20(address(creatorCoin)));
110
+ }
111
+
112
+ // Legacy function for backward compatibility
113
+ function _recordInitialBalances() internal view returns (RewardBalances memory balances) {
114
+ return _recordZoraBalances();
115
+ }
116
+
117
+ // Helper function to calculate ZORA token reward deltas after trade
118
+ function _calculateZoraRewardDeltas(RewardBalances memory initialBalances) internal view returns (RewardBalances memory deltas) {
119
+ return
120
+ RewardTestHelpers.calculateTokenRewardDeltas(
121
+ initialBalances,
122
+ zoraToken,
123
+ users.creator,
124
+ platformReferrer,
125
+ tradeReferrer,
126
+ creatorCoin.protocolRewardRecipient(),
127
+ creatorCoin.dopplerFeeRecipient()
128
+ );
129
+ }
130
+
131
+ // Helper function to calculate creator coin reward deltas after trade
132
+ function _calculateCreatorCoinRewardDeltas(RewardBalances memory initialBalances) internal view returns (RewardBalances memory deltas) {
133
+ deltas.creator = creatorCoin.balanceOf(users.creator) - initialBalances.creator;
134
+ // creatorReferrer is now unified with platformReferrer
135
+ deltas.platformReferrer = creatorCoin.balanceOf(platformReferrer) - initialBalances.platformReferrer;
136
+ deltas.tradeReferrer = creatorCoin.balanceOf(tradeReferrer) - initialBalances.tradeReferrer;
137
+ deltas.protocol = creatorCoin.balanceOf(creatorCoin.protocolRewardRecipient()) - initialBalances.protocol;
138
+ deltas.doppler = creatorCoin.balanceOf(creatorCoin.dopplerFeeRecipient()) - initialBalances.doppler;
139
+ }
140
+
141
+ // Legacy function for backward compatibility
142
+ function _calculateRewardDeltas(RewardBalances memory initialBalances) internal view returns (RewardBalances memory deltas) {
143
+ return _calculateZoraRewardDeltas(initialBalances);
144
+ }
145
+
146
+ // Helper function to perform trades with optional trade referrer and return fee estimation
147
+ function _buyCreatorCoin(uint128 amountIn, bool hasTradeReferrer) internal returns (uint256 feeCurrency) {
148
+ deal(address(zoraToken), users.buyer, amountIn);
149
+
150
+ vm.warp(block.timestamp + 1 days);
151
+
152
+ vm.startPrank(users.buyer);
153
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), uint128(amountIn), uint48(block.timestamp + 1 days));
154
+
155
+ // Build hook data with trade referrer if provided
156
+ bytes memory hookData = hasTradeReferrer ? abi.encode(tradeReferrer) : bytes("");
157
+
158
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
159
+ address(zoraToken),
160
+ uint128(amountIn),
161
+ address(creatorCoin),
162
+ 0,
163
+ creatorCoin.getPoolKey(),
164
+ hookData
165
+ );
166
+
167
+ // Estimate the total fees (3%) before executing
168
+ FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
169
+
170
+ feeCurrency = feeState.afterSwapCurrencyAmount;
171
+
172
+ router.execute(commands, inputs, block.timestamp + 1 days);
173
+ vm.stopPrank();
174
+ }
175
+
176
+ // Helper function to sell creator coin for ZORA tokens
177
+ function _sellCreatorCoin(uint128 amountIn, bool hasTradeReferrer) internal returns (uint256 feeCurrency) {
178
+ vm.startPrank(users.buyer);
179
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(creatorCoin), uint128(amountIn), uint48(block.timestamp + 1 days));
180
+
181
+ // Build hook data with trade referrer if provided
182
+ bytes memory hookData = hasTradeReferrer ? abi.encode(tradeReferrer) : bytes("");
183
+
184
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
185
+ address(creatorCoin),
186
+ uint128(amountIn),
187
+ address(zoraToken),
188
+ 0,
189
+ creatorCoin.getPoolKey(),
190
+ hookData
191
+ );
192
+
193
+ // Estimate the fees before executing
194
+ FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
195
+
196
+ feeCurrency = feeState.afterSwapCurrencyAmount;
197
+
198
+ router.execute(commands, inputs, block.timestamp + 1 days);
199
+ vm.stopPrank();
200
+ }
201
+
202
+ /// @notice Test exact reward distribution percentages with platform referrer only
203
+ function test_rewards_platform_referrer_only() public {
204
+ // Deploy CreatorCoin with platform referrer
205
+ _deployCreatorCoin(true);
206
+
207
+ uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
208
+
209
+ // Record initial balances
210
+ RewardBalances memory initialBalances = _recordInitialBalances();
211
+
212
+ // Perform trade
213
+ uint256 zoraFees = _buyCreatorCoin(tradeAmount, false);
214
+
215
+ // Calculate reward deltas
216
+ RewardBalances memory rewards = _calculateRewardDeltas(initialBalances);
217
+
218
+ // Calculate market rewards from total fees
219
+ // Use estimated market rewards (already accounts for LP deduction and slippage)
220
+ RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(zoraFees, true, false);
221
+ RewardTestHelpers.assertRewardsApproxEqRel(rewards, expected);
222
+ }
223
+
224
+ /// @notice Test exact reward distribution percentages with trade referrer only
225
+ function test_rewards_trade_referrer_only() public {
226
+ // Deploy CreatorCoin with no platform referrer
227
+ _deployCreatorCoin(false);
228
+
229
+ uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
230
+
231
+ // Record initial balances
232
+ RewardBalances memory initialBalances = _recordInitialBalances();
233
+
234
+ // Perform trade with trade referrer
235
+ uint256 zoraFees = _buyCreatorCoin(tradeAmount, true);
236
+
237
+ // Calculate reward deltas
238
+ RewardBalances memory rewards = _calculateRewardDeltas(initialBalances);
239
+
240
+ // Calculate market rewards from total fees
241
+ // Use estimated market rewards (already accounts for LP deduction and slippage)
242
+ RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(zoraFees, false, true);
243
+ RewardTestHelpers.assertRewardsApproxEqRel(rewards, expected);
244
+ }
245
+
246
+ /// @notice Test exact reward distribution percentages with both platform and trade referrers
247
+ function test_rewards_both_referrers() public {
248
+ // Deploy CreatorCoin with platform referrer
249
+ _deployCreatorCoin(true);
250
+
251
+ uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
252
+
253
+ // Record initial balances
254
+ RewardBalances memory initialBalances = _recordInitialBalances();
255
+
256
+ // Perform trade with both referrers
257
+ uint256 zoraFees = _buyCreatorCoin(tradeAmount, true);
258
+
259
+ // Calculate reward deltas
260
+ RewardBalances memory rewards = _calculateRewardDeltas(initialBalances);
261
+
262
+ // Calculate market rewards from total fees
263
+ // Use estimated market rewards (already accounts for LP deduction and slippage)
264
+ RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(zoraFees, true, true);
265
+ RewardTestHelpers.assertRewardsApproxEqRel(rewards, expected);
266
+ }
267
+
268
+ /// @notice Test exact reward distribution percentages with no referrers (baseline case)
269
+ function test_rewards_no_referrers() public {
270
+ // Deploy CreatorCoin with no platform referrer
271
+ _deployCreatorCoin(false);
272
+
273
+ uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
274
+
275
+ // Record initial balances
276
+ RewardBalances memory initialBalances = _recordInitialBalances();
277
+
278
+ // Perform trade with no trade referrer
279
+ uint256 zoraFees = _buyCreatorCoin(tradeAmount, false);
280
+
281
+ // Calculate reward deltas
282
+ RewardBalances memory rewards = _calculateRewardDeltas(initialBalances);
283
+
284
+ // Calculate market rewards from total fees
285
+ // Use estimated market rewards (already accounts for LP deduction and slippage)
286
+ RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(zoraFees, false, false);
287
+ RewardTestHelpers.assertRewardsApproxEqRel(rewards, expected);
288
+ }
289
+
290
+ /// @notice Test buy-then-sell creator coin with both referrers set
291
+ function test_buy_then_sell_both_referrers() public {
292
+ uint128 buyAmount = 100 ether; // Fixed amount
293
+
294
+ // Deploy CreatorCoin with platform referrer
295
+ _deployCreatorCoin(true);
296
+
297
+ // Step 1: Buy creator coin (ZORA -> Creator Coin)
298
+ _buyCreatorCoin(buyAmount, true);
299
+
300
+ // Get buyer's creator coin balance after purchase
301
+ uint256 creatorCoinBalance = creatorCoin.balanceOf(users.buyer);
302
+ require(creatorCoinBalance > 0, "Buyer must have creator coin balance to sell");
303
+
304
+ // Record initial balances for both ZORA and creator coin
305
+ RewardBalances memory initialZoraBalances = _recordZoraBalances();
306
+
307
+ // Step 2: Sell creator coin (Creator Coin -> ZORA)
308
+ uint128 sellAmount = uint128(creatorCoinBalance);
309
+ uint256 sellZoraFees = _sellCreatorCoin(sellAmount, true);
310
+
311
+ // Calculate final reward deltas for both currencies
312
+ RewardBalances memory finalZoraRewards = _calculateZoraRewardDeltas(initialZoraBalances);
313
+
314
+ // Calculate total market rewards from both trades
315
+ RewardBalances memory expectedTotalRewards = RewardTestHelpers.calculateExpectedRewards(sellZoraFees, true, true);
316
+
317
+ // Validate ZORA token distributions (where all final rewards end up)
318
+ RewardTestHelpers.assertRewardsApproxEqRel(finalZoraRewards, expectedTotalRewards);
319
+ }
320
+
321
+ /// @notice Test that fee estimation matches actual total reward distribution
322
+ function test_estimateAfterSwapCurrencyAmount() public {
323
+ // Deploy CreatorCoin with platform referrer
324
+ _deployCreatorCoin(true);
325
+
326
+ uint128 tradeAmount = 1 ether; // Much smaller amount for testing
327
+
328
+ // Build swap command but don't execute yet
329
+ deal(address(zoraToken), users.buyer, tradeAmount);
330
+ vm.startPrank(users.buyer);
331
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), tradeAmount, uint48(block.timestamp + 1 days));
332
+
333
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
334
+ address(zoraToken),
335
+ tradeAmount,
336
+ address(creatorCoin),
337
+ 0,
338
+ creatorCoin.getPoolKey(),
339
+ bytes("") // No trade referrer
340
+ );
341
+
342
+ // Estimate fees using FeeEstimatorHook
343
+ FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
344
+
345
+ // Record initial balances
346
+ RewardBalances memory initialBalances = _recordInitialBalances();
347
+
348
+ // Execute actual swap
349
+ router.execute(commands, inputs, block.timestamp + 20);
350
+ vm.stopPrank();
351
+
352
+ // Calculate total actual rewards distributed
353
+ RewardBalances memory finalRewards = _calculateZoraRewardDeltas(initialBalances);
354
+ uint256 totalActualRewards = finalRewards.creator +
355
+ finalRewards.platformReferrer +
356
+ finalRewards.tradeReferrer +
357
+ finalRewards.protocol +
358
+ finalRewards.doppler;
359
+
360
+ // Verify that total actual rewards match the estimated afterSwapCurrencyAmount
361
+ assertApproxEqRel(totalActualRewards, feeState.afterSwapCurrencyAmount, 0.25e18, "Total rewards should match estimated afterSwapCurrencyAmount");
362
+ }
363
+
364
+ function test_isLegacyCreatorCoinCategorization() public {
365
+ vm.createSelectFork("base", 31872861);
366
+
367
+ // Use the same creator coin from the upgrades test
368
+ address creatorCoinAddress = 0x2F03aB8fD97F5874bc3274C296Bb954Ae92EdA34;
369
+
370
+ // Test that the legacy creator coin is correctly categorized as a creator coin
371
+ bool isLegacy = CoinRewardsV4.isLegacyCreatorCoin(IHasRewardsRecipients(creatorCoinAddress));
372
+
373
+ assertTrue(isLegacy, "Legacy creator coin should be categorized as legacy creator coin");
374
+ }
375
+ }
@@ -21,18 +21,18 @@ contract FakeHookNoInterface {
21
21
  }
22
22
  }
23
23
 
24
- contract HooksTest is BaseTest {
24
+ contract DeploymentsHooksTest is BaseTest {
25
25
  address constant zora = 0x1111111111166b7FE7bd91427724B487980aFc69;
26
- BuySupplyWithSwapRouterHook hook;
26
+ BuySupplyWithSwapRouterHook buySupplyWithSwapRouterHook;
27
27
 
28
28
  function _generateDefaultPoolConfig(address currency) internal pure returns (bytes memory) {
29
29
  return _generatePoolConfig(currency);
30
30
  }
31
31
 
32
32
  function setUp() public override {
33
- super.setUpWithBlockNumber(29585474);
33
+ super.setUpWithBlockNumber(30267794);
34
34
 
35
- hook = new BuySupplyWithSwapRouterHook(factory, address(swapRouter), address(V4_POOL_MANAGER));
35
+ buySupplyWithSwapRouterHook = new BuySupplyWithSwapRouterHook(factory, address(swapRouter), address(V4_POOL_MANAGER));
36
36
  }
37
37
 
38
38
  function _deployWithHook(address _hook, bytes memory hookData, address currency) internal returns (address, bytes memory) {
@@ -46,7 +46,7 @@ contract HooksTest is BaseTest {
46
46
  "TEST",
47
47
  poolConfig,
48
48
  users.platformReferrer,
49
- _hook,
49
+ address(_hook),
50
50
  hookData
51
51
  );
52
52
  }
@@ -77,7 +77,7 @@ contract HooksTest is BaseTest {
77
77
  users.creator,
78
78
  ISwapRouter.ExactInputParams({
79
79
  path: abi.encodePacked(address(weth), poolFee, USDC_ADDRESS, poolFee, zora),
80
- recipient: address(hook),
80
+ recipient: address(buySupplyWithSwapRouterHook),
81
81
  amountIn: initialOrderSize,
82
82
  amountOutMinimum: 0
83
83
  })
@@ -94,7 +94,7 @@ contract HooksTest is BaseTest {
94
94
  "TEST",
95
95
  poolConfig,
96
96
  users.platformReferrer,
97
- address(hook),
97
+ address(buySupplyWithSwapRouterHook),
98
98
  hookData
99
99
  );
100
100
 
@@ -121,7 +121,7 @@ contract HooksTest is BaseTest {
121
121
  ISwapRouter.exactOutputSingle.selector,
122
122
  ISwapRouter.ExactOutputParams({
123
123
  path: abi.encodePacked(address(weth), poolFee, USDC_ADDRESS, poolFee, zora),
124
- recipient: address(hook),
124
+ recipient: address(buySupplyWithSwapRouterHook),
125
125
  amountOut: 0.0001 ether,
126
126
  amountInMaximum: 0
127
127
  })
@@ -138,7 +138,7 @@ contract HooksTest is BaseTest {
138
138
  "TEST",
139
139
  _generateDefaultPoolConfig(zora),
140
140
  users.platformReferrer,
141
- address(hook),
141
+ address(buySupplyWithSwapRouterHook),
142
142
  hookData
143
143
  );
144
144
  }
@@ -170,7 +170,7 @@ contract HooksTest is BaseTest {
170
170
  "TEST",
171
171
  _generateDefaultPoolConfig(zora),
172
172
  users.platformReferrer,
173
- address(hook),
173
+ address(buySupplyWithSwapRouterHook),
174
174
  hookData
175
175
  );
176
176
  }
@@ -13,7 +13,7 @@ contract FactoryTest is BaseTest {
13
13
 
14
14
  function test_factory_constructor_and_proxy_setup() public {
15
15
  // Impl constructor test
16
- ZoraFactoryImpl impl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook));
16
+ ZoraFactoryImpl impl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
17
17
  assertEq(ZoraFactoryImpl(address(factory)).owner(), users.factoryOwner);
18
18
  assertEq(ZoraFactoryImpl(address(factory)).coinV4Impl(), address(coinV4Impl));
19
19
 
@@ -38,9 +38,7 @@ contract FactoryTest is BaseTest {
38
38
 
39
39
  assertEq(ZoraFactoryImpl(address(factory)).pendingOwner(), address(0));
40
40
 
41
- address newFactoryImpl = address(
42
- new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook))
43
- );
41
+ address newFactoryImpl = address(new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry)));
44
42
 
45
43
  // Upgrade to current / new impl
46
44
  vm.prank(users.factoryOwner);
@@ -66,7 +64,7 @@ contract FactoryTest is BaseTest {
66
64
  }
67
65
 
68
66
  function test_upgrade() public {
69
- ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook));
67
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
70
68
 
71
69
  vm.prank(users.factoryOwner);
72
70
  ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
@@ -82,12 +80,12 @@ contract FactoryTest is BaseTest {
82
80
  address newImpl = address(this);
83
81
 
84
82
  vm.prank(users.factoryOwner);
85
- vm.expectRevert(abi.encodeWithSelector(ERC1967Utils.ERC1967InvalidImplementation.selector, address(newImpl)));
83
+ vm.expectRevert();
86
84
  ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
87
85
  }
88
86
 
89
87
  function test_revert_invalid_owner() public {
90
- ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook));
88
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
91
89
 
92
90
  vm.prank(users.creator);
93
91
  vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, users.creator));
@@ -163,6 +161,25 @@ contract FactoryTest is BaseTest {
163
161
  vm.expectRevert(abi.encodeWithSelector(IZoraFactory.UpgradeToMismatchedContractName.selector, "ZoraCoinFactory", "BadFactory"));
164
162
  ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(badImpl), "");
165
163
  }
164
+
165
+ function test_upgrade_auto_registers_hooks() public {
166
+ address[] memory registeredHooks;
167
+
168
+ registeredHooks = zoraHookRegistry.getHookAddresses();
169
+ assertEq(registeredHooks.length, 0);
170
+
171
+ _deployHooks(); // Deploys new content and creator coin hook addresses
172
+
173
+ // Deploy new factory impl with new content and creator coin hook addresses
174
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
175
+
176
+ vm.prank(users.factoryOwner);
177
+ ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
178
+
179
+ registeredHooks = zoraHookRegistry.getHookAddresses();
180
+ assertEq(registeredHooks.length, 1);
181
+ assertTrue(zoraHookRegistry.isRegisteredHook(address(hook)));
182
+ }
166
183
  }
167
184
 
168
185
  // Mock contracts for testing
@@ -40,14 +40,14 @@ contract HooksDeploymentTest is Test, ContractAddresses {
40
40
  vm.createSelectFork("base", 31653138);
41
41
 
42
42
  address[] memory trustedMessageSenders = new address[](0);
43
- (, bytes32 salt) = HooksDeployment.mineForContentCoinSalt(
43
+ (, bytes32 salt) = HooksDeployment.mineForCoinSalt(
44
44
  address(this),
45
45
  V4_POOL_MANAGER,
46
46
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
47
47
  trustedMessageSenders,
48
48
  hookUpgradeGate
49
49
  );
50
- IHooks hook = HooksDeployment.deployContentCoinHook(
50
+ IHooks hook = HooksDeployment.deployZoraV4CoinHook(
51
51
  V4_POOL_MANAGER,
52
52
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
53
53
  trustedMessageSenders,
@@ -68,7 +68,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
68
68
  vm.createSelectFork("base", 31653138);
69
69
 
70
70
  address[] memory trustedMessageSenders = new address[](0);
71
- (, bytes32 salt) = HooksDeployment.mineForCreatorCoinSalt(
71
+ (, bytes32 salt) = HooksDeployment.mineForCoinSalt(
72
72
  address(this),
73
73
  V4_POOL_MANAGER,
74
74
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
@@ -77,7 +77,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
77
77
  );
78
78
 
79
79
  IHooks hook = HooksDeployment.deployHookWithSalt(
80
- HooksDeployment.creatorCoinHookCreationCode(V4_POOL_MANAGER, 0x777777751622c0d3258f214F9DF38E35BF45baF3, trustedMessageSenders, hookUpgradeGate),
80
+ HooksDeployment.makeHookCreationCode(V4_POOL_MANAGER, 0x777777751622c0d3258f214F9DF38E35BF45baF3, trustedMessageSenders, hookUpgradeGate),
81
81
  salt
82
82
  );
83
83
 
@@ -77,7 +77,7 @@ contract LiquidityMigrationTest is BaseTest {
77
77
  address[] memory trustedMessageSenders = new address[](1);
78
78
  trustedMessageSenders[0] = UNIVERSAL_ROUTER;
79
79
 
80
- address originalHook = address(contentCoinHook);
80
+ address originalHook = address(hook);
81
81
 
82
82
  address newHook = address(new LiquidityMigrationReceiver());
83
83
 
@@ -109,7 +109,7 @@ contract LiquidityMigrationTest is BaseTest {
109
109
  assertEq(coinV4.balanceOf(address(originalHook)), originalHookCoinBalanceBefore, "original coin balance");
110
110
 
111
111
  // validate that the existing hook has no liquidity for its positions
112
- LpPosition[] memory positions = contentCoinHook.getPoolCoin(poolKey).positions;
112
+ LpPosition[] memory positions = hook.getPoolCoin(poolKey).positions;
113
113
 
114
114
  for (uint256 i = 0; i < positions.length; i++) {
115
115
  uint128 liquidity = V4Liquidity.getLiquidity(poolManager, address(originalHook), poolKey, positions[i].tickLower, positions[i].tickUpper);
@@ -185,8 +185,6 @@ contract LiquidityMigrationTest is BaseTest {
185
185
  address currency = address(mockERC20A);
186
186
  _deployV4Coin(currency);
187
187
 
188
- address originalHook = address(contentCoinHook);
189
-
190
188
  address invalidNewHook = address(new InvalidLiquidityMigrationReceiver());
191
189
 
192
190
  PoolKey memory poolKey = coinV4.getPoolKey();
@@ -203,14 +201,11 @@ contract LiquidityMigrationTest is BaseTest {
203
201
  address currency = address(mockERC20A);
204
202
  _deployV4Coin(currency);
205
203
 
206
- address originalHook = address(contentCoinHook);
204
+ address originalHook = address(hook);
207
205
 
208
206
  address newHook = address(new LiquidityMigrationReceiver());
209
207
 
210
- PoolKey memory poolKey = coinV4.getPoolKey();
211
-
212
208
  // Note: NOT registering the upgrade path
213
-
214
209
  // expect the migration to revert with UpgradePathNotRegistered error
215
210
  vm.prank(users.creator);
216
211
  vm.expectRevert(abi.encodeWithSelector(IUpgradeableV4Hook.UpgradePathNotRegistered.selector, originalHook, newHook));
@@ -221,7 +216,7 @@ contract LiquidityMigrationTest is BaseTest {
221
216
  address currency = address(mockERC20A);
222
217
  _deployV4Coin(currency);
223
218
 
224
- address originalHook = address(contentCoinHook);
219
+ address originalHook = address(hook);
225
220
  address newHook = address(new LiquidityMigrationReceiver());
226
221
  PoolKey memory poolKey = coinV4.getPoolKey();
227
222