@zoralabs/coins 2.1.1 → 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 +60 -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 +7 -5
  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 +10 -12
  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,320 @@
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 {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
8
+ import {IHasRewardsRecipients} from "../src/interfaces/IHasRewardsRecipients.sol";
9
+ import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
10
+ import {FeeEstimatorHook} from "./utils/FeeEstimatorHook.sol";
11
+ import {RewardTestHelpers, RewardBalances} from "./utils/RewardTestHelpers.sol";
12
+ import {CoinConstants} from "../src/libs/CoinConstants.sol";
13
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
14
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
15
+
16
+ contract ContentCoinRewardsTest is BaseTest {
17
+ ContentCoin internal contentCoin;
18
+ CreatorCoin internal backingCreatorCoin;
19
+
20
+ address internal platformReferrer;
21
+ address internal tradeReferrer;
22
+
23
+ function setUp() public override {
24
+ super.setUpWithBlockNumber(30267794);
25
+
26
+ deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
27
+
28
+ backingCreatorCoin = CreatorCoin(_deployCreatorCoin());
29
+
30
+ vm.label(address(backingCreatorCoin), "BACKING_CREATOR_COIN");
31
+
32
+ // Set up referrer addresses for all tests
33
+ platformReferrer = makeAddr("platformReferrer");
34
+ tradeReferrer = makeAddr("tradeReferrer");
35
+ }
36
+
37
+ // Generic function to record token balances for all reward recipients
38
+ function _recordBalances(IERC20 token) internal view returns (RewardBalances memory balances) {
39
+ balances.creator = token.balanceOf(users.creator);
40
+ balances.platformReferrer = token.balanceOf(platformReferrer);
41
+ balances.tradeReferrer = token.balanceOf(tradeReferrer);
42
+ balances.protocol = token.balanceOf(contentCoin.protocolRewardRecipient());
43
+ balances.doppler = token.balanceOf(contentCoin.dopplerFeeRecipient());
44
+ }
45
+
46
+ // Helper function to record initial ZORA token balances for all reward recipients
47
+ function _recordZoraBalances() internal view returns (RewardBalances memory balances) {
48
+ return _recordBalances(zoraToken);
49
+ }
50
+
51
+ // Helper function to calculate ZORA token reward deltas after trade
52
+ function _calculateZoraRewardDeltas(RewardBalances memory initialBalances) internal view returns (RewardBalances memory deltas) {
53
+ deltas.creator = zoraToken.balanceOf(users.creator) - initialBalances.creator;
54
+ deltas.platformReferrer = zoraToken.balanceOf(platformReferrer) - initialBalances.platformReferrer;
55
+ deltas.tradeReferrer = zoraToken.balanceOf(tradeReferrer) - initialBalances.tradeReferrer;
56
+ deltas.protocol = zoraToken.balanceOf(contentCoin.protocolRewardRecipient()) - initialBalances.protocol;
57
+ deltas.doppler = zoraToken.balanceOf(contentCoin.dopplerFeeRecipient()) - initialBalances.doppler;
58
+ }
59
+
60
+ /// @dev Estimates the fees from a swap
61
+ function _estimateLpFees(bytes memory commands, bytes[] memory inputs) internal returns (FeeEstimatorHook.FeeEstimatorState memory feeState) {
62
+ uint256 snapshot = vm.snapshot();
63
+ _deployFeeEstimatorHook(address(hook));
64
+
65
+ // Execute the swap
66
+ uint256 deadline = block.timestamp + 20;
67
+ router.execute(commands, inputs, deadline);
68
+
69
+ feeState = FeeEstimatorHook(payable(address(hook))).getFeeState();
70
+
71
+ vm.revertToState(snapshot);
72
+ }
73
+
74
+ // Helper function to buy content coin
75
+ function _buyContentCoin(address currencyIn, uint128 amountIn, bool hasTradeReferrer) internal returns (uint256 feeCurrency) {
76
+ vm.warp(block.timestamp + 1 days);
77
+
78
+ vm.startPrank(users.buyer);
79
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), currencyIn, uint128(amountIn), uint48(block.timestamp + 1 days));
80
+
81
+ // Build hook data with trade referrer if provided
82
+ bytes memory hookData = hasTradeReferrer ? abi.encode(tradeReferrer) : bytes("");
83
+
84
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
85
+ currencyIn,
86
+ uint128(amountIn),
87
+ address(contentCoin),
88
+ 0,
89
+ contentCoin.getPoolKey(),
90
+ hookData
91
+ );
92
+
93
+ // Estimate the total fees before executing
94
+ FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
95
+ feeCurrency = feeState.afterSwapCurrencyAmount;
96
+
97
+ router.execute(commands, inputs, block.timestamp + 1 days);
98
+ vm.stopPrank();
99
+ }
100
+
101
+ // Helper function to deploy content coin backed by creator coin
102
+ function _deployContentCoin(bool hasPlatformReferrer) internal {
103
+ // Then deploy content coin backed by the creator coin
104
+ bytes memory poolConfig = _defaultPoolConfig(address(backingCreatorCoin));
105
+
106
+ // Generate unique salt
107
+ bytes32 uniqueSalt = keccak256(abi.encodePacked("content", address(backingCreatorCoin), block.timestamp, gasleft()));
108
+
109
+ vm.prank(users.creator);
110
+ (address contentCoinAddress, ) = factory.deploy(
111
+ users.creator,
112
+ _getDefaultOwners(),
113
+ "https://content.com",
114
+ "ContentCoin",
115
+ "CONTENT",
116
+ poolConfig,
117
+ hasPlatformReferrer ? platformReferrer : address(0),
118
+ address(0), // postDeployHook
119
+ bytes(""), // postDeployHookData
120
+ uniqueSalt
121
+ );
122
+
123
+ contentCoin = ContentCoin(contentCoinAddress);
124
+ vm.label(address(contentCoin), "TEST_CONTENT_COIN");
125
+ }
126
+
127
+ // Helper function to deploy creator coin (backing for content coin)
128
+ function _deployCreatorCoin() internal returns (address) {
129
+ // Use the same multi-curve config as CreatorCoinRewards.t.sol
130
+ int24[] memory tickLower = new int24[](1);
131
+ int24[] memory tickUpper = new int24[](1);
132
+ uint16[] memory numDiscoveryPositions = new uint16[](1);
133
+ uint256[] memory maxDiscoverySupplyShare = new uint256[](1);
134
+
135
+ tickLower[0] = -138_000;
136
+ tickUpper[0] = 81_000;
137
+ numDiscoveryPositions[0] = 11;
138
+ maxDiscoverySupplyShare[0] = 0.25e18;
139
+
140
+ bytes memory poolConfig = abi.encode(
141
+ CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION,
142
+ address(zoraToken),
143
+ tickLower,
144
+ tickUpper,
145
+ numDiscoveryPositions,
146
+ maxDiscoverySupplyShare
147
+ );
148
+
149
+ // Generate unique salt
150
+ bytes32 uniqueSalt = keccak256(abi.encodePacked("creator", block.timestamp, gasleft()));
151
+
152
+ vm.prank(users.creator);
153
+ address creatorCoinAddress = factory.deployCreatorCoin(
154
+ users.creator,
155
+ _getDefaultOwners(),
156
+ "https://creator.com",
157
+ "CreatorCoin",
158
+ "CREATOR",
159
+ poolConfig,
160
+ address(0),
161
+ uniqueSalt
162
+ );
163
+
164
+ return creatorCoinAddress;
165
+ }
166
+
167
+ /// @notice Test that fee estimation matches actual reward distribution
168
+ function test_estimateAfterSwapCurrencyAmount() public {
169
+ // Deploy content coin backed by creator coin
170
+ _deployContentCoin(true);
171
+
172
+ uint128 tradeAmount = 1000 ether;
173
+
174
+ // First, get trader some backing creator coins
175
+ address trader = users.buyer;
176
+ deal(address(zoraToken), trader, tradeAmount * 2);
177
+ _swapSomeCurrencyForCoin(ICoin(address(backingCreatorCoin)), address(zoraToken), tradeAmount, trader);
178
+
179
+ // Record initial balances
180
+ RewardBalances memory initialBalances = _recordZoraBalances();
181
+
182
+ // Build swap command: Creator Coin -> Content Coin
183
+ uint128 backingBalance = uint128(backingCreatorCoin.balanceOf(trader));
184
+
185
+ vm.startPrank(trader);
186
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(backingCreatorCoin), backingBalance, uint48(block.timestamp + 1 days));
187
+
188
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
189
+ address(backingCreatorCoin),
190
+ backingBalance,
191
+ address(contentCoin),
192
+ 0,
193
+ contentCoin.getPoolKey(),
194
+ bytes("") // No trade referrer
195
+ );
196
+
197
+ // Estimate fees using the same pattern as CoinUniV4.t.sol
198
+ FeeEstimatorHook.FeeEstimatorState memory feeState = _estimateLpFees(commands, inputs);
199
+
200
+ // Execute actual swap
201
+ router.execute(commands, inputs, block.timestamp + 20);
202
+ vm.stopPrank();
203
+
204
+ // Calculate actual total rewards distributed
205
+ RewardBalances memory finalRewards = _calculateZoraRewardDeltas(initialBalances);
206
+ uint256 totalActualRewards = RewardTestHelpers.getTotalRewards(finalRewards);
207
+
208
+ // Verify that total actual rewards match the estimated afterSwapCurrencyAmount
209
+ assertApproxEqRel(totalActualRewards, feeState.afterSwapCurrencyAmount, 0.25e18, "Total rewards should match estimated afterSwapCurrencyAmount");
210
+ }
211
+
212
+ /// @notice Test reward distribution with creator referrer only (no trade referrer, no platform referrer)
213
+ function test_rewards_creator_referrer_only() public {
214
+ // Deploy content coin backed by creator coin with creator referrer (inherits creator referrer)
215
+ _deployContentCoin(true);
216
+
217
+ uint128 tradeAmount = 1000 ether; // 1000 ZORA tokens
218
+
219
+ // First, trader needs to get some backing creator coins to trade for content coin
220
+ address trader = users.buyer;
221
+ deal(address(zoraToken), trader, tradeAmount * 2); // Give extra for initial swap
222
+
223
+ // Step 1: Swap ZORA for backing creator coin
224
+ _swapSomeCurrencyForCoin(ICoin(address(backingCreatorCoin)), address(zoraToken), tradeAmount, trader);
225
+
226
+ // Step 2: Record balances before content coin trade and perform the actual test trade
227
+ RewardBalances memory initialBalances = _recordZoraBalances();
228
+
229
+ // Swap backing creator coin for content coin
230
+ uint128 backingBalance = uint128(backingCreatorCoin.balanceOf(trader));
231
+ uint256 rewardsAmount = _buyContentCoin(address(backingCreatorCoin), backingBalance, false);
232
+
233
+ RewardBalances memory rewards = _calculateZoraRewardDeltas(initialBalances);
234
+
235
+ // Calculate expected rewards based on actual reward deltas (like creator coin tests do)
236
+ uint256 totalRewards = rewardsAmount;
237
+ RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(totalRewards, true, false);
238
+ RewardTestHelpers.assertRewardsApproxEqRelWithTolerance(rewards, expected, 0.25e18);
239
+ }
240
+
241
+ /// @notice Test reward distribution with trade referrer only (no creator referrer, no platform referrer)
242
+ function test_rewards_trade_referrer_only() public {
243
+ _deployContentCoin(false); // Deploy without platform referrer
244
+
245
+ uint128 tradeAmount = 1000 ether;
246
+ address trader = users.buyer;
247
+ deal(address(zoraToken), trader, tradeAmount * 2);
248
+
249
+ // Step 1: Get backing creator coins
250
+ _swapSomeCurrencyForCoin(ICoin(address(backingCreatorCoin)), address(zoraToken), tradeAmount, trader);
251
+
252
+ // Step 2: Test content coin trade
253
+ RewardBalances memory initialBalances = _recordZoraBalances();
254
+ uint128 backingBalance = uint128(backingCreatorCoin.balanceOf(trader));
255
+ uint256 rewardsAmount = _buyContentCoin(address(backingCreatorCoin), backingBalance, true);
256
+ RewardBalances memory rewards = _calculateZoraRewardDeltas(initialBalances);
257
+
258
+ // Step 3: Validate rewards
259
+ RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(rewardsAmount, false, true);
260
+ RewardTestHelpers.assertRewardsApproxEqRelWithTolerance(rewards, expected, 0.25e18);
261
+ }
262
+
263
+ /// @notice Test reward distribution with creator referrer + trade referrer (no platform referrer)
264
+ function test_rewards_platform_and_trade_referrers() public {
265
+ _deployContentCoin(true); // Deploy with platform referrer
266
+
267
+ uint128 tradeAmount = 1000 ether;
268
+ address trader = users.buyer;
269
+ deal(address(zoraToken), trader, tradeAmount * 2);
270
+
271
+ // Step 1: Get backing creator coins
272
+ _swapSomeCurrencyForCoin(ICoin(address(backingCreatorCoin)), address(zoraToken), tradeAmount, trader);
273
+
274
+ // Step 2: Test content coin trade
275
+ RewardBalances memory initialBalances = _recordZoraBalances();
276
+ uint128 backingBalance = uint128(backingCreatorCoin.balanceOf(trader));
277
+ uint256 rewardsAmount = _buyContentCoin(address(backingCreatorCoin), backingBalance, true);
278
+ RewardBalances memory rewards = _calculateZoraRewardDeltas(initialBalances);
279
+
280
+ // Step 3: Validate rewards
281
+ RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(rewardsAmount, true, true);
282
+ console.log("protocol rewards", rewards.protocol);
283
+ console.log("expected protocol rewards", expected.protocol);
284
+ RewardTestHelpers.assertRewardsApproxEqRelWithTolerance(rewards, expected, 0.25e18);
285
+ }
286
+
287
+ /// @notice Test reward distribution with no referrers (all address(0))
288
+ function test_rewards_no_referrers() public {
289
+ _deployContentCoin(false); // Deploy without platform referrer
290
+
291
+ uint128 tradeAmount = 1000 ether;
292
+ address trader = users.buyer;
293
+ deal(address(zoraToken), trader, tradeAmount * 2);
294
+
295
+ // Step 1: Get backing creator coins
296
+ _swapSomeCurrencyForCoin(ICoin(address(backingCreatorCoin)), address(zoraToken), tradeAmount, trader);
297
+
298
+ // Step 2: Test content coin trade
299
+ RewardBalances memory initialBalances = _recordZoraBalances();
300
+ uint128 backingBalance = uint128(backingCreatorCoin.balanceOf(trader));
301
+ uint256 rewardsAmount = _buyContentCoin(address(backingCreatorCoin), backingBalance, false);
302
+ RewardBalances memory rewards = _calculateZoraRewardDeltas(initialBalances);
303
+
304
+ // Step 3: Validate rewards
305
+ RewardBalances memory expected = RewardTestHelpers.calculateExpectedRewards(rewardsAmount, false, false);
306
+ RewardTestHelpers.assertRewardsApproxEqRelWithTolerance(rewards, expected, 0.25e18);
307
+ }
308
+
309
+ function test_isNotLegacyCreatorCoinCategorization() public {
310
+ vm.createSelectFork("base", 31835069);
311
+
312
+ // Use the same content coin from the upgrades test
313
+ address contentCoinAddress = 0x4E93A01c90f812284F71291a8d1415a904957156;
314
+
315
+ // Test that the content coin is NOT categorized as a legacy creator coin
316
+ bool isLegacy = CoinRewardsV4.isLegacyCreatorCoin(IHasRewardsRecipients(contentCoinAddress));
317
+
318
+ assertFalse(isLegacy, "Content coin should NOT be categorized as legacy creator coin");
319
+ }
320
+ }
@@ -69,7 +69,7 @@ contract CreatorCoinTest is BaseTest {
69
69
  assertEq(creatorCoin.totalSupply(), CreatorCoinConstants.TOTAL_SUPPLY);
70
70
 
71
71
  assertEq(creatorCoin.balanceOf(address(creatorCoin)), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
72
- assertEq(creatorCoin.balanceOf(address(creatorCoin.poolManager())), CreatorCoinConstants.MARKET_SUPPLY);
72
+ assertEq(creatorCoin.balanceOf(address(creatorCoin.poolManager())), MarketConstants.CREATOR_COIN_MARKET_SUPPLY);
73
73
  }
74
74
 
75
75
  function test_deploy_creator_coin_with_invalid_currency_reverts() public {