@zoralabs/coins 2.0.0 → 2.1.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.
Files changed (69) hide show
  1. package/.turbo/turbo-build.log +106 -110
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +30 -109
  4. package/abis/BaseCoin.json +442 -0
  5. package/abis/CoinTest.json +3 -246
  6. package/abis/FactoryTest.json +5 -137
  7. package/abis/HooksTest.json +0 -26
  8. package/abis/ICoin.json +378 -0
  9. package/abis/ICoinV3.json +378 -0
  10. package/abis/IZoraFactory.json +0 -18
  11. package/abis/LiquidityMigrationTest.json +101 -0
  12. package/abis/MockBadFactory.json +15 -0
  13. package/abis/ZoraFactoryImpl.json +1 -67
  14. package/dist/index.cjs +236 -265
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +235 -264
  17. package/dist/index.js.map +1 -1
  18. package/dist/wagmiGenerated.d.ts +389 -493
  19. package/dist/wagmiGenerated.d.ts.map +1 -1
  20. package/package/wagmiGenerated.ts +240 -269
  21. package/package.json +3 -3
  22. package/script/DeployPostDeploymentHooks.s.sol +2 -2
  23. package/script/TestBackingCoinSwap.s.sol +8 -8
  24. package/script/TestV4Swap.s.sol +8 -8
  25. package/script/UpgradeFactoryImpl.s.sol +0 -1
  26. package/src/BaseCoin.sol +109 -6
  27. package/src/ContentCoin.sol +4 -4
  28. package/src/CreatorCoin.sol +5 -5
  29. package/src/ZoraFactoryImpl.sol +10 -93
  30. package/src/deployment/CoinsDeployerBase.sol +10 -27
  31. package/src/hooks/BaseZoraV4CoinHook.sol +5 -5
  32. package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +4 -5
  33. package/src/interfaces/ICoin.sol +67 -1
  34. package/src/interfaces/ICreatorCoin.sol +2 -2
  35. package/src/interfaces/IZoraFactory.sol +0 -5
  36. package/src/libs/CoinConfigurationVersions.sol +1 -39
  37. package/src/libs/CoinRewardsV4.sol +2 -2
  38. package/src/libs/CoinSetup.sol +1 -4
  39. package/src/libs/UniV4SwapHelper.sol +1 -1
  40. package/src/libs/UniV4SwapToCurrency.sol +2 -2
  41. package/src/libs/V4Liquidity.sol +1 -1
  42. package/src/version/ContractVersionBase.sol +1 -1
  43. package/test/Coin.t.sol +112 -535
  44. package/test/CoinUniV4.t.sol +5 -5
  45. package/test/DeploymentHooks.t.sol +5 -102
  46. package/test/Factory.t.sol +23 -306
  47. package/test/LiquidityMigration.t.sol +160 -2
  48. package/test/MultiOwnable.t.sol +36 -36
  49. package/test/Upgrades.t.sol +16 -35
  50. package/test/utils/BaseTest.sol +16 -69
  51. package/test/utils/FeeEstimatorHook.sol +3 -3
  52. package/wagmi.config.ts +1 -1
  53. package/abis/BaseCoinV4.json +0 -1840
  54. package/abis/Coin.json +0 -1912
  55. package/abis/DopplerUniswapV3Test.json +0 -800
  56. package/abis/ICoinV4.json +0 -1048
  57. package/abis/Simulate.json +0 -29
  58. package/abis/UniV3BuySell.json +0 -12
  59. package/abis/UniV3Errors.json +0 -32
  60. package/script/Simulate.s.sol +0 -59
  61. package/src/BaseCoinV4.sol +0 -143
  62. package/src/Coin.sol +0 -236
  63. package/src/interfaces/ICoinV4.sol +0 -74
  64. package/src/libs/CoinDopplerUniV3.sol +0 -50
  65. package/src/libs/CoinRewards.sol +0 -201
  66. package/src/libs/CoinSetupV3.sol +0 -50
  67. package/src/libs/UniV3BuySell.sol +0 -231
  68. package/src/libs/UniV3Errors.sol +0 -11
  69. package/test/CoinDopplerUniV3.t.sol +0 -310
package/test/Coin.t.sol CHANGED
@@ -10,6 +10,7 @@ import {IHasRewardsRecipients} from "../src/interfaces/IHasRewardsRecipients.sol
10
10
  import {PoolConfiguration} from "../src/interfaces/ICoin.sol";
11
11
  import {IERC165, IERC7572, ICoin, ICoinComments, IERC20} from "../src/BaseCoin.sol";
12
12
  import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
13
+ import {BaseCoin} from "../src/BaseCoin.sol";
13
14
 
14
15
  contract CoinTest is BaseTest {
15
16
  using stdJson for string;
@@ -19,20 +20,20 @@ contract CoinTest is BaseTest {
19
20
  }
20
21
 
21
22
  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);
23
+ _deployV4Coin();
24
+ assertEq(coinV4.supportsInterface(type(IZoraFactory).interfaceId), false);
25
+ assertEq(coinV4.supportsInterface(bytes4(0x00000000)), false);
26
+ assertEq(coinV4.supportsInterface(type(IERC165).interfaceId), true);
27
+ assertEq(coinV4.supportsInterface(type(IERC7572).interfaceId), true);
28
+ assertEq(coinV4.supportsInterface(type(ICoin).interfaceId), true);
29
+ assertEq(coinV4.supportsInterface(type(ICoinComments).interfaceId), true);
30
+ assertEq(coinV4.supportsInterface(type(IERC7572).interfaceId), true);
30
31
  }
31
32
 
32
33
  function test_contract_version() public {
33
- _deployCoin();
34
+ _deployV4Coin();
34
35
  string memory package = vm.readFile("./package.json");
35
- assertEq(package.readString(".version"), coin.contractVersion());
36
+ assertEq(package.readString(".version"), coinV4.contractVersion());
36
37
  }
37
38
 
38
39
  function test_supply_constants() public {
@@ -42,36 +43,10 @@ contract CoinTest is BaseTest {
42
43
  assertEq(CoinConstants.POOL_LAUNCH_SUPPLY, 990_000_000e18);
43
44
  assertEq(CoinConstants.CREATOR_LAUNCH_REWARD, 10_000_000e18);
44
45
 
45
- _deployCoin();
46
- assertEq(coin.totalSupply(), CoinConstants.MAX_TOTAL_SUPPLY);
47
- assertEq(coin.balanceOf(coin.payoutRecipient()), CoinConstants.CREATOR_LAUNCH_REWARD);
48
- assertApproxEqAbs(coin.balanceOf(address(pool)), CoinConstants.POOL_LAUNCH_SUPPLY, 1e18);
49
- }
50
-
51
- function test_constructor_validation() public {
52
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
53
- new Coin(address(0), address(protocolRewards), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER, DOPPLER_AIRLOCK);
54
-
55
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
56
- new Coin(users.feeRecipient, address(0), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER, DOPPLER_AIRLOCK);
57
-
58
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
59
- new Coin(users.feeRecipient, address(protocolRewards), address(0), NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER, DOPPLER_AIRLOCK);
60
-
61
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
62
- new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, address(0), SWAP_ROUTER, DOPPLER_AIRLOCK);
63
-
64
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
65
- new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, address(0), DOPPLER_AIRLOCK);
66
-
67
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
68
- new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER, address(0));
69
-
70
- Coin newToken = new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER, DOPPLER_AIRLOCK);
71
- assertEq(address(newToken.protocolRewardRecipient()), users.feeRecipient);
72
- assertEq(address(newToken.protocolRewards()), address(protocolRewards));
73
- assertEq(address(newToken.WETH()), WETH_ADDRESS);
74
- assertEq(address(newToken.swapRouter()), SWAP_ROUTER);
46
+ _deployV4Coin();
47
+ assertEq(coinV4.totalSupply(), CoinConstants.MAX_TOTAL_SUPPLY);
48
+ assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.CREATOR_LAUNCH_REWARD);
49
+ assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())), CoinConstants.POOL_LAUNCH_SUPPLY, 1e18);
75
50
  }
76
51
 
77
52
  function test_initialize_validation() public {
@@ -82,16 +57,16 @@ contract CoinTest is BaseTest {
82
57
 
83
58
  vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
84
59
  (address coinAddress, ) = factory.deploy(address(0), owners, "https://init.com", "Init Token", "INIT", poolConfig_, users.platformReferrer, 0);
85
- coin = Coin(payable(coinAddress));
60
+ coinV4 = ContentCoin(payable(coinAddress));
86
61
 
87
62
  (coinAddress, ) = factory.deploy(users.creator, owners, "https://init.com", "Init Token", "INIT", poolConfig_, users.platformReferrer, 0);
88
- coin = Coin(payable(coinAddress));
63
+ coinV4 = ContentCoin(payable(coinAddress));
89
64
 
90
- assertEq(coin.payoutRecipient(), users.creator, "creator");
91
- assertEq(coin.platformReferrer(), users.platformReferrer, "platformReferrer");
92
- assertEq(coin.tokenURI(), "https://init.com");
93
- assertEq(coin.name(), "Init Token");
94
- assertEq(coin.symbol(), "INIT");
65
+ assertEq(coinV4.payoutRecipient(), users.creator, "creator");
66
+ assertEq(coinV4.platformReferrer(), users.platformReferrer, "platformReferrer");
67
+ assertEq(coinV4.tokenURI(), "https://init.com");
68
+ assertEq(coinV4.name(), "Init Token");
69
+ assertEq(coinV4.symbol(), "INIT");
95
70
  }
96
71
 
97
72
  function test_invalid_pool_config_version() public {
@@ -117,568 +92,170 @@ contract CoinTest is BaseTest {
117
92
  0
118
93
  );
119
94
 
120
- Coin coin = Coin(payable(coinAddress));
95
+ ContentCoin coin = ContentCoin(payable(coinAddress));
121
96
 
122
97
  PoolConfiguration memory poolConfig = coin.getPoolConfiguration();
123
98
 
124
- assertEq(poolConfig.version, CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION);
125
- }
126
-
127
- function test_uniswap_v3_mint_callback_wrong_sender() public {
128
- _deployCoin();
129
- address random = makeAddr("random");
130
- vm.prank(random);
131
- vm.expectRevert(abi.encodeWithSelector(ICoin.OnlyPool.selector, random, address(pool)));
132
- coin.uniswapV3MintCallback(1 ether, 1 ether, "");
99
+ assertEq(poolConfig.version, CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION);
133
100
  }
134
101
 
135
102
  function test_erc165_interface_support() public {
136
- _deployCoin();
137
- assertEq(coin.supportsInterface(type(IERC165).interfaceId), true);
138
- assertEq(coin.supportsInterface(type(IHasRewardsRecipients).interfaceId), true);
139
- assertEq(coin.supportsInterface(type(IERC7572).interfaceId), true);
140
- }
141
-
142
- function test_buy_with_eth() public {
143
- _deployCoin();
144
- vm.deal(users.buyer, 1 ether);
145
- vm.prank(users.buyer);
146
- coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
147
-
148
- assertGt(coin.balanceOf(users.coinRecipient), 0);
149
- assertEq(users.seller.balance, 0);
150
- }
151
-
152
- function test_buy_with_eth_fuzz(uint256 ethOrderSize) public {
153
- vm.assume(ethOrderSize >= CoinConstants.MIN_ORDER_SIZE);
154
- vm.assume(ethOrderSize < 10 ether);
155
- _deployCoin();
156
-
157
- uint256 platformReferrerBalanceBeforeSale = users.platformReferrer.balance;
158
- uint256 orderReferrerBalanceBeforeSale = users.tradeReferrer.balance;
159
- uint256 tokenCreatorBalanceBeforeSale = users.creator.balance;
160
- uint256 feeRecipientBalanceBeforeSale = users.feeRecipient.balance;
161
-
162
- vm.deal(users.buyer, ethOrderSize);
163
- vm.prank(users.buyer);
164
- coin.buy{value: ethOrderSize}(users.coinRecipient, ethOrderSize, 0, 0, users.tradeReferrer);
165
-
166
- assertGt(coin.balanceOf(users.coinRecipient), 0, "coinRecipient coin balance");
167
- assertGt(protocolRewards.balanceOf(users.feeRecipient), feeRecipientBalanceBeforeSale, "feeRecipient eth balance");
168
- assertGt(protocolRewards.balanceOf(users.platformReferrer), platformReferrerBalanceBeforeSale, "platformReferrer eth balance");
169
- assertGt(protocolRewards.balanceOf(users.tradeReferrer), orderReferrerBalanceBeforeSale, "tradeReferrer eth balance");
170
- assertGt(protocolRewards.balanceOf(users.creator), tokenCreatorBalanceBeforeSale, "creator eth balance");
171
- }
172
-
173
- function test_buy_with_eth_too_small() public {
174
- _deployCoin();
175
- vm.expectRevert(abi.encodeWithSelector(ICoin.EthAmountTooSmall.selector));
176
- coin.buy{value: CoinConstants.MIN_ORDER_SIZE - 1}(users.coinRecipient, CoinConstants.MIN_ORDER_SIZE - 1, 0, 0, users.tradeReferrer);
177
- }
178
-
179
- function test_buy_with_minimum_eth() public {
180
- _deployCoin();
181
- uint256 minEth = CoinConstants.MIN_ORDER_SIZE;
182
- vm.deal(users.buyer, minEth);
183
- vm.prank(users.buyer);
184
- coin.buy{value: minEth}(users.coinRecipient, minEth, 0, 0, users.tradeReferrer);
185
-
186
- assertGt(coin.balanceOf(users.coinRecipient), 0, "coinRecipient coin balance");
187
- }
188
-
189
- function test_revert_buy_zero_address_recipient_legacy() public {
190
- _deployCoin();
191
- vm.deal(users.buyer, 1 ether);
192
-
193
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
194
- vm.prank(users.buyer);
195
- coin.buy{value: 1 ether}(address(0), 1 ether, 0, 0, users.tradeReferrer);
196
- }
197
-
198
- function test_revert_buy_zero_address_recipient() public {
199
- _deployCoin();
200
- vm.deal(users.buyer, 1 ether);
201
-
202
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
203
- vm.prank(users.buyer);
204
- coin.buy(address(0), 1 ether, 0, 0, users.tradeReferrer);
205
- }
206
-
207
- function test_buy_with_usdc() public {
208
- _deployCoinUSDCPair();
209
-
210
- deal(address(usdc), users.buyer, 100e6);
211
-
212
- vm.prank(users.buyer);
213
- usdc.approve(address(coin), 10e6);
214
-
215
- vm.prank(users.buyer);
216
- coin.buy(users.coinRecipient, 10e6, 0, 0, users.tradeReferrer);
217
-
218
- assertGt(coin.balanceOf(users.coinRecipient), 0, "coinRecipient coin balance");
219
- }
220
-
221
- function test_buy_with_usdc_revert_no_approval() public {
222
- _deployCoinUSDCPair();
223
-
224
- deal(address(usdc), users.buyer, 100e6);
225
-
226
- vm.prank(users.buyer);
227
- vm.expectRevert("ERC20: transfer amount exceeds allowance");
228
- coin.buy(users.coinRecipient, 100e6, 0, 0, users.tradeReferrer);
229
- }
230
-
231
- function test_buy_validate_return_amounts(uint256 orderSize) public {
232
- vm.assume(orderSize >= CoinConstants.MIN_ORDER_SIZE);
233
- vm.assume(orderSize < 10 ether);
234
- _deployCoin();
235
-
236
- vm.deal(users.buyer, orderSize);
237
- vm.prank(users.buyer);
238
- (uint256 amountIn, uint256 amountOut) = coin.buy{value: orderSize}(users.coinRecipient, orderSize, 0, 0, users.tradeReferrer);
239
-
240
- assertEq(amountIn, orderSize, "amountIn");
241
- assertGe(coin.balanceOf(users.coinRecipient), amountOut, "coinRecipient coin balance");
242
- }
243
-
244
- function test_sell_for_eth_direct_and_claim_secondary() public {
245
- _deployCoin();
246
- vm.deal(users.buyer, 1 ether);
247
-
248
- vm.prank(users.buyer);
249
- weth.deposit{value: 100_000}();
250
-
251
- vm.prank(users.buyer);
252
- weth.approve(address(swapRouter), 100_000);
253
-
254
- assertEq(coin.balanceOf(users.buyer), 0, "buyer coin balance initial");
255
-
256
- // Set up the swap parameters
257
- ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
258
- tokenIn: WETH_ADDRESS,
259
- tokenOut: address(coin),
260
- fee: MarketConstants.LP_FEE,
261
- recipient: address(users.buyer),
262
- amountIn: 100_000,
263
- amountOutMinimum: 0,
264
- sqrtPriceLimitX96: 0
265
- });
266
-
267
- // Execute the swap
268
- vm.prank(users.buyer);
269
- uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
270
-
271
- assertGt(coin.balanceOf(users.buyer), 0, "buyer coin balance");
272
- assertGt(amountOut, 0, "amountOut");
273
- assertGt(users.buyer.balance, 0, "seller eth balance");
274
-
275
- // now we have unclaimed secondary rewards to claim
276
- vm.prank(users.buyer);
277
-
278
- // don't push ETH
279
- coin.claimSecondaryRewards(false);
280
- assertGt(protocolRewards.balanceOf(users.creator), 0);
281
- assertGt(protocolRewards.balanceOf(users.platformReferrer), 0);
282
- assertGt(protocolRewards.balanceOf(users.feeRecipient), 0);
283
- assertGt(dopplerFeeRecipient().balance, 0);
284
- }
285
-
286
- function test_sell_for_eth_direct_and_claim_secondary_push_eth() public {
287
- _deployCoin();
288
- vm.deal(users.buyer, 1 ether);
289
-
290
- vm.prank(users.buyer);
291
- weth.deposit{value: 100_000}();
292
-
293
- vm.prank(users.buyer);
294
- weth.approve(address(swapRouter), 100_000);
295
-
296
- // Set up the swap parameters
297
- ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
298
- tokenIn: WETH_ADDRESS,
299
- tokenOut: address(coin),
300
- fee: MarketConstants.LP_FEE,
301
- recipient: address(users.buyer),
302
- amountIn: 100_000,
303
- amountOutMinimum: 0,
304
- sqrtPriceLimitX96: 0
305
- });
306
-
307
- // Execute the swap
308
- vm.prank(users.buyer);
309
- uint256 amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
310
-
311
- assertGt(coin.balanceOf(users.buyer), 0, "buyer coin balance");
312
- assertGt(amountOut, 0, "amountOut");
313
- assertGt(users.buyer.balance, 0, "seller eth balance");
314
-
315
- // Now we have unclaimed secondary rewards to claim
316
- vm.prank(users.buyer);
317
-
318
- uint256 initialBalance = users.creator.balance;
319
-
320
- // Push ETH
321
- coin.claimSecondaryRewards(true);
322
- assertGt(users.creator.balance, initialBalance);
323
- }
324
-
325
- function test_sell_for_eth() public {
326
- _deployCoin();
327
- vm.deal(users.buyer, 1 ether);
328
- vm.prank(users.buyer);
329
- coin.buy{value: 1 ether}(users.seller, 1 ether, 0, 0, users.tradeReferrer);
330
-
331
- uint256 tokensToSell = coin.balanceOf(users.seller);
332
- vm.prank(users.seller);
333
- coin.sell(users.seller, tokensToSell, 0, 0, users.tradeReferrer);
334
-
335
- assertEq(coin.balanceOf(users.seller), 0, "seller coin balance");
336
- assertGt(users.seller.balance, 0, "seller eth balance");
337
- }
338
-
339
- function test_sell_for_eth_fuzz(uint256 ethOrderSize) public {
340
- vm.assume(ethOrderSize < 10 ether);
341
- vm.assume(ethOrderSize >= CoinConstants.MIN_ORDER_SIZE);
342
- _deployCoin();
343
- vm.deal(users.buyer, ethOrderSize);
344
- vm.prank(users.buyer);
345
- coin.buy{value: ethOrderSize}(users.seller, ethOrderSize, 0, 0, users.tradeReferrer);
346
-
347
- uint256 platformReferrerBalanceBeforeSale = users.platformReferrer.balance;
348
- uint256 orderReferrerBalanceBeforeSale = users.tradeReferrer.balance;
349
- uint256 tokenCreatorBalanceBeforeSale = users.creator.balance;
350
- uint256 feeRecipientBalanceBeforeSale = users.feeRecipient.balance;
351
-
352
- uint256 tokensToSell = coin.balanceOf(users.seller);
353
- vm.prank(users.seller);
354
- coin.sell(users.coinRecipient, tokensToSell, 0, 0, users.tradeReferrer);
355
-
356
- assertEq(coin.balanceOf(users.seller), 0, "seller coin balance");
357
- assertEq(coin.balanceOf(users.coinRecipient), 0, "coinRecipient coin balance");
358
-
359
- assertEq(users.seller.balance, 0, "seller eth balance");
360
- assertGt(protocolRewards.balanceOf(users.feeRecipient), feeRecipientBalanceBeforeSale, "feeRecipient eth balance");
361
- assertGt(protocolRewards.balanceOf(users.platformReferrer), platformReferrerBalanceBeforeSale, "platformReferrer eth balance");
362
- assertGt(protocolRewards.balanceOf(users.tradeReferrer), orderReferrerBalanceBeforeSale, "tradeReferrer eth balance");
363
- assertGt(protocolRewards.balanceOf(users.creator), tokenCreatorBalanceBeforeSale, "creator eth balance");
364
- }
365
-
366
- function test_sell_for_usdc() public {
367
- _deployCoinUSDCPair();
368
-
369
- deal(address(usdc), users.buyer, 10e6);
370
-
371
- vm.prank(users.buyer);
372
- usdc.approve(address(coin), 10e6);
373
-
374
- vm.prank(users.buyer);
375
- coin.buy(users.coinRecipient, 10e6, 0, 0, users.tradeReferrer);
376
-
377
- uint256 coinBalance = coin.balanceOf(users.coinRecipient);
378
-
379
- vm.prank(users.coinRecipient);
380
- coin.sell(users.seller, coinBalance, 0, 0, users.tradeReferrer);
381
- }
382
-
383
- function test_revert_sell_zero_address_recipient() public {
384
- _deployCoin();
385
- vm.deal(users.buyer, 1 ether);
386
- vm.prank(users.buyer);
387
- coin.buy{value: 1 ether}(users.seller, 1 ether, 0, 0, users.tradeReferrer);
388
-
389
- uint256 tokensToSell = coin.balanceOf(users.seller);
390
- vm.prank(users.seller);
391
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
392
- coin.sell(address(0), tokensToSell, 0, 0, users.tradeReferrer);
393
- }
394
-
395
- function test_revert_sell_insufficient_liquidity() public {
396
- _deployCoin();
397
- vm.deal(users.buyer, 1 ether);
398
- vm.prank(users.buyer);
399
- coin.buy{value: 1 ether}(users.seller, 1 ether, 0, 0, users.tradeReferrer);
400
-
401
- uint256 balance = coin.balanceOf(users.seller);
402
- vm.prank(users.seller);
403
- vm.expectRevert(abi.encodeWithSignature("ERC20InsufficientBalance(address,uint256,uint256)", users.seller, balance, balance + 1));
404
- coin.sell(users.coinRecipient, balance + 1, 0, 0, users.tradeReferrer);
405
- }
406
-
407
- function test_sell_partial_execution() public {
408
- _deployCoin();
409
- vm.deal(users.creator, 1 ether);
410
- vm.prank(users.creator);
411
- coin.buy{value: 0.001 ether}(users.creator, 0.001 ether, 0, 0, users.tradeReferrer);
412
-
413
- uint256 beforeBalance = coin.balanceOf(users.creator);
414
- assertGt(beforeBalance, 0, "before balance");
415
-
416
- vm.prank(users.creator);
417
- (uint256 amountSold, ) = coin.sell(users.creator, beforeBalance, 0, 0, users.tradeReferrer);
418
- assertGt(amountSold, 0, "amountSold");
419
-
420
- // these seemed to change with different configuration of the pool. uncomment when we can figure
421
- // out the values
422
- // uint256 afterBalance = coin.balanceOf(users.creator);
423
- // assertEq(afterBalance, 9994558841570544323195890, "after balance"); // 9,994,559 coins
424
-
425
- // uint256 expectedMarketReward = 5441158429455676804107; // 5,441 coins
426
-
427
- // // 9,994,559 = 11,077,349 order size - 1,088,232 true order size + 5,441 creator market reward
428
- // assertEq(afterBalance, ((beforeBalance - amountSold) + expectedMarketReward), "amountSold");
103
+ _deployV4Coin();
104
+ assertEq(coinV4.supportsInterface(type(IERC165).interfaceId), true);
105
+ assertEq(coinV4.supportsInterface(type(IHasRewardsRecipients).interfaceId), true);
106
+ assertEq(coinV4.supportsInterface(type(IERC7572).interfaceId), true);
429
107
  }
430
108
 
431
109
  function test_burn() public {
432
- _deployCoin();
110
+ _deployV4Coin();
433
111
  vm.deal(users.buyer, 1 ether);
434
112
  vm.prank(users.buyer);
435
- coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
436
-
437
- uint256 beforeBalance = coin.balanceOf(users.coinRecipient);
438
- uint256 beforeTotalSupply = coin.totalSupply();
113
+ _swapSomeCurrencyForCoin(coinV4, address(weth), 1 ether, users.coinRecipient);
439
114
 
440
- vm.prank(users.coinRecipient);
441
- coin.burn(1e18);
115
+ uint256 beforeBalance = coinV4.balanceOf(users.coinRecipient);
116
+ uint256 beforeTotalSupply = coinV4.totalSupply();
442
117
 
443
- uint256 afterBalance = coin.balanceOf(users.coinRecipient);
444
- uint256 afterTotalSupply = coin.totalSupply();
445
-
446
- assertEq(beforeBalance - afterBalance, 1e18, "coinRecipient coin balance");
447
- assertEq(beforeTotalSupply - afterTotalSupply, 1e18, "coin total supply");
448
- }
449
-
450
- function test_receive_from_weth() public {
451
- _deployCoin();
452
- uint256 orderSize = 1 ether;
453
- vm.deal(users.buyer, orderSize);
454
- vm.prank(users.buyer);
455
- coin.buy{value: orderSize}(users.coinRecipient, orderSize, 0, 0, users.tradeReferrer);
456
-
457
- vm.deal(WETH_ADDRESS, 1 ether);
458
- vm.prank(WETH_ADDRESS);
459
- (bool success, ) = address(coin).call{value: 1 ether}("");
460
- assertTrue(success);
461
- }
462
-
463
- function test_revert_receive_from_weth_wrong_sender() public {
464
- _deployCoin();
465
- address random = makeAddr("random");
466
- vm.prank(random);
467
- vm.expectRevert(abi.encodeWithSelector(ICoin.OnlyWeth.selector, random));
468
- (bool success, ) = address(coin).call{value: 1 ether}("");
469
- assertFalse(success);
470
- }
471
-
472
- function test_default_platform_referrer() public {
473
- address[] memory owners = new address[](1);
474
- owners[0] = users.creator;
475
-
476
- bytes memory poolConfig_ = _generatePoolConfig(
477
- CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
478
- address(weth),
479
- DEFAULT_DISCOVERY_TICK_LOWER,
480
- DEFAULT_DISCOVERY_TICK_UPPER,
481
- DEFAULT_NUM_DISCOVERY_POSITIONS,
482
- DEFAULT_DISCOVERY_SUPPLY_SHARE
483
- );
484
-
485
- (address newCoinAddr, ) = factory.deploy(users.creator, owners, "https://test.com", "Test Token", "TEST", poolConfig_, users.platformReferrer, 0);
486
- Coin newCoin = Coin(payable(newCoinAddr));
487
-
488
- vm.deal(users.buyer, 1 ether);
489
- vm.prank(users.buyer);
490
- newCoin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
491
-
492
- uint256 fee = _calculateExpectedFee(1 ether);
493
- TradeRewards memory expectedFees = _calculateTradeRewards(fee);
494
-
495
- assertGt(protocolRewards.balanceOf(users.feeRecipient), expectedFees.platformReferrer, "feeRecipient eth balance");
496
- }
497
-
498
- function test_default_order_referrer() public {
499
- _deployCoin();
500
- vm.deal(users.buyer, 1 ether);
501
- vm.prank(users.buyer);
502
- coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, address(0));
503
-
504
- uint256 fee = _calculateExpectedFee(1 ether);
505
- TradeRewards memory expectedFees = _calculateTradeRewards(fee);
506
-
507
- assertGt(protocolRewards.balanceOf(users.feeRecipient), expectedFees.tradeReferrer, "feeRecipient eth balance");
508
- }
509
-
510
- function test_market_slippage() public {
511
- _deployCoin();
512
- vm.deal(users.buyer, 1 ether);
513
- vm.prank(users.buyer);
514
- coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
515
-
516
- vm.deal(users.buyer, 1 ether);
517
- vm.prank(users.buyer);
518
- vm.expectRevert("Too little received"); // Uniswap V3 revert
519
- coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, type(uint256).max, 0, users.tradeReferrer);
520
-
521
- vm.prank(users.coinRecipient);
522
- vm.expectRevert("Too little received"); // Uniswap V3 revert
523
- coin.sell(users.coinRecipient, 1e18, type(uint256).max, 0, users.tradeReferrer);
524
- }
525
-
526
- function test_eth_transfer_fail() public {
527
- _deployCoin();
528
- vm.deal(users.buyer, 1 ether);
529
- vm.prank(users.buyer);
530
- (, uint256 amountOut) = coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
531
-
532
- assertEq(coin.balanceOf(users.coinRecipient), amountOut);
533
-
534
- // Recipient reverts on ETH receive
535
- address payable badRecipient = payable(makeAddr("badRecipient"));
536
- vm.etch(badRecipient, hex"fe");
118
+ uint256 burnAmount = beforeBalance / 2;
537
119
 
538
120
  vm.prank(users.coinRecipient);
539
- vm.expectRevert(abi.encodeWithSelector(Address.FailedInnerCall.selector));
540
- coin.sell(badRecipient, 1e18, 0, 0, users.tradeReferrer);
541
- }
121
+ coinV4.burn(burnAmount);
542
122
 
543
- function test_revert_receive_only_weth() public {
544
- _deployCoin();
545
- vm.deal(users.buyer, 1 ether);
546
- vm.prank(users.buyer);
547
- vm.expectRevert(abi.encodeWithSelector(ICoin.OnlyWeth.selector));
548
- (bool ignoredSuccess, ) = address(coin).call{value: 1 ether}("");
549
- (ignoredSuccess);
550
-
551
- assertEq(address(coin).balance, 0, "coin balance");
552
- }
123
+ uint256 afterBalance = coinV4.balanceOf(users.coinRecipient);
124
+ uint256 afterTotalSupply = coinV4.totalSupply();
553
125
 
554
- function test_rewards() public {
555
- _deployCoin();
556
- uint256 initialPlatformReferrerBalance = protocolRewards.balanceOf(users.platformReferrer);
557
- uint256 initialTokenCreatorBalance = protocolRewards.balanceOf(users.creator);
558
- uint256 initialOrderReferrerBalance = protocolRewards.balanceOf(users.tradeReferrer);
559
- uint256 initialFeeRecipientBalance = protocolRewards.balanceOf(users.feeRecipient);
560
- uint256 initialDopplerRecipientBalance = airlock.owner().balance;
561
-
562
- uint256 buyAmount = 1 ether;
563
- vm.deal(users.buyer, buyAmount);
564
- vm.prank(users.buyer);
565
- coin.buy{value: buyAmount}(users.coinRecipient, buyAmount, 0, 0, users.tradeReferrer);
566
-
567
- uint256 orderFee = _calculateExpectedFee(buyAmount); // 1 ETH * 1% --> 0.01 ETH
568
- TradeRewards memory orderFees = _calculateTradeRewards(orderFee);
569
-
570
- uint256 expectedLpFee = 9900000000000000; // 0.99 ETH * 1% --> ~0.00989 ETH
571
- MarketRewards memory marketRewards = _calculateMarketRewards(expectedLpFee);
572
-
573
- assertEq(
574
- marketRewards.creator + marketRewards.platformReferrer + marketRewards.protocol + marketRewards.doppler,
575
- expectedLpFee,
576
- "Secondary rewards incorrect"
577
- );
578
- assertApproxEqAbs(
579
- protocolRewards.balanceOf(users.creator),
580
- initialTokenCreatorBalance + orderFees.creator + marketRewards.creator,
581
- 0.0000000000000001 ether,
582
- "Token creator rewards incorrect"
583
- );
584
- assertApproxEqAbs(
585
- protocolRewards.balanceOf(users.platformReferrer),
586
- initialPlatformReferrerBalance + orderFees.platformReferrer + marketRewards.platformReferrer,
587
- 0.0000000000000001 ether,
588
- "Platform referrer rewards incorrect"
589
- );
590
- assertApproxEqAbs(
591
- airlock.owner().balance,
592
- initialDopplerRecipientBalance + marketRewards.doppler,
593
- 0.0000000000000001 ether,
594
- "Doppler rewards incorrect"
595
- );
596
- assertApproxEqAbs(
597
- protocolRewards.balanceOf(users.feeRecipient),
598
- initialFeeRecipientBalance + orderFees.protocol + marketRewards.protocol,
599
- 0.0000000000000001 ether,
600
- "Protocol rewards incorrect"
601
- );
602
- assertEq(protocolRewards.balanceOf(users.tradeReferrer), initialOrderReferrerBalance + orderFees.tradeReferrer, "Order referrer rewards incorrect");
126
+ assertEq(beforeBalance - afterBalance, burnAmount, "coinRecipient coin balance");
127
+ assertEq(beforeTotalSupply - afterTotalSupply, burnAmount, "coin total supply");
603
128
  }
604
129
 
605
130
  function test_contract_uri() public {
606
- _deployCoin();
607
- assertEq(coin.contractURI(), "https://test.com");
131
+ _deployV4Coin();
132
+ assertEq(coinV4.contractURI(), "https://test.com");
608
133
  }
609
134
 
610
135
  function test_set_contract_uri() public {
611
- _deployCoin();
136
+ _deployV4Coin();
612
137
  string memory newURI = "https://new.com";
613
138
 
614
139
  vm.prank(users.creator);
615
- coin.setContractURI(newURI);
616
- assertEq(coin.contractURI(), newURI);
140
+ coinV4.setContractURI(newURI);
141
+ assertEq(coinV4.contractURI(), newURI);
617
142
  }
618
143
 
619
144
  function test_set_contract_uri_reverts_if_not_owner() public {
620
- _deployCoin();
145
+ _deployV4Coin();
621
146
  string memory newURI = "https://new.com";
622
147
 
623
148
  vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
624
- coin.setContractURI(newURI);
149
+ coinV4.setContractURI(newURI);
625
150
  }
626
151
 
627
152
  function test_set_payout_recipient() public {
628
- _deployCoin();
153
+ _deployV4Coin();
629
154
  address newPayoutRecipient = makeAddr("NewPayoutRecipient");
630
155
 
631
156
  vm.prank(users.creator);
632
- coin.setPayoutRecipient(newPayoutRecipient);
633
- assertEq(coin.payoutRecipient(), newPayoutRecipient);
157
+ coinV4.setPayoutRecipient(newPayoutRecipient);
158
+ assertEq(coinV4.payoutRecipient(), newPayoutRecipient);
634
159
  }
635
160
 
636
161
  function test_revert_set_payout_recipient_address_zero() public {
637
- _deployCoin();
162
+ _deployV4Coin();
638
163
  address newPayoutRecipient = address(0);
639
164
 
640
165
  vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
641
166
  vm.prank(users.creator);
642
- coin.setPayoutRecipient(newPayoutRecipient);
167
+ coinV4.setPayoutRecipient(newPayoutRecipient);
643
168
  }
644
169
 
645
170
  function test_revert_set_payout_recipient_only_owner() public {
646
- _deployCoin();
171
+ _deployV4Coin();
647
172
  address newPayoutRecipient = makeAddr("NewPayoutRecipient");
648
173
 
649
174
  vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
650
- coin.setPayoutRecipient(newPayoutRecipient);
175
+ coinV4.setPayoutRecipient(newPayoutRecipient);
651
176
  }
652
177
 
653
178
  function test_update_metadata() public {
654
- _deployCoin();
179
+ _deployV4Coin();
655
180
  string memory newName = "NewName";
656
181
  string memory newSymbol = "NEW";
657
182
 
658
183
  vm.prank(users.creator);
659
184
  vm.expectEmit(true, true, true, true);
660
185
  emit ICoin.NameAndSymbolUpdated(users.creator, newName, newSymbol);
661
- coin.setNameAndSymbol(newName, newSymbol);
662
- assertEq(coin.name(), newName);
663
- assertEq(coin.symbol(), newSymbol);
186
+ coinV4.setNameAndSymbol(newName, newSymbol);
187
+ assertEq(coinV4.name(), newName);
188
+ assertEq(coinV4.symbol(), newSymbol);
664
189
  }
665
190
 
666
191
  function test_update_metadata_reverts_if_not_owner() public {
667
- _deployCoin();
192
+ _deployV4Coin();
668
193
  string memory newName = "NewName";
669
194
  string memory newSymbol = "NEW";
670
195
 
671
196
  vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
672
- coin.setNameAndSymbol(newName, newSymbol);
197
+ coinV4.setNameAndSymbol(newName, newSymbol);
673
198
  }
674
199
 
675
200
  function test_update_metadata_reverts_if_name_is_blank() public {
676
- _deployCoin();
201
+ _deployV4Coin();
677
202
  string memory newName = "";
678
203
  string memory newSymbol = "NEW";
679
204
 
680
205
  vm.prank(users.creator);
681
206
  vm.expectRevert(abi.encodeWithSelector(ICoin.NameIsRequired.selector));
682
- coin.setNameAndSymbol(newName, newSymbol);
207
+ coinV4.setNameAndSymbol(newName, newSymbol);
208
+ }
209
+
210
+ function test_deploy_coin_with_invalid_parameters() public {
211
+ address[] memory owners = new address[](1);
212
+ owners[0] = users.creator;
213
+
214
+ bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(weth));
215
+
216
+ // Test with empty name - should revert
217
+ vm.expectRevert(abi.encodeWithSelector(ICoin.NameIsRequired.selector));
218
+ factory.deploy(
219
+ users.creator,
220
+ owners,
221
+ "https://test.com",
222
+ "", // empty name
223
+ "TEST",
224
+ poolConfig,
225
+ users.platformReferrer,
226
+ address(0),
227
+ bytes(""),
228
+ bytes32(0)
229
+ );
230
+
231
+ // Test with zero address payout recipient - should revert
232
+ vm.expectRevert();
233
+ factory.deploy(
234
+ address(0), // zero address payout recipient
235
+ owners,
236
+ "https://test.com",
237
+ "TestCoin",
238
+ "TEST",
239
+ poolConfig,
240
+ users.platformReferrer,
241
+ address(0),
242
+ bytes(""),
243
+ bytes32(0)
244
+ );
245
+ }
246
+
247
+ function test_access_control_unauthorized_actions() public {
248
+ _deployV4Coin();
249
+
250
+ address unauthorizedUser = makeAddr("unauthorized");
251
+
252
+ // Test unauthorized access to owner-only functions
253
+ vm.prank(unauthorizedUser);
254
+ vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
255
+ coinV4.setPayoutRecipient(makeAddr("newRecipient"));
256
+
257
+ vm.prank(unauthorizedUser);
258
+ vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
259
+ coinV4.setContractURI("https://new-uri.com");
683
260
  }
684
261
  }