@zoralabs/coins 0.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 (116) hide show
  1. package/.turbo/turbo-build.log +76 -0
  2. package/CHANGELOG.md +14 -0
  3. package/LICENSE +21 -0
  4. package/abis/Address.json +29 -0
  5. package/abis/BaseTest.json +691 -0
  6. package/abis/Clones.json +7 -0
  7. package/abis/Coin.json +1693 -0
  8. package/abis/CoinConstants.json +93 -0
  9. package/abis/CoinTest.json +998 -0
  10. package/abis/ContextUpgradeable.json +25 -0
  11. package/abis/ContractVersionBase.json +15 -0
  12. package/abis/Deploy.json +29 -0
  13. package/abis/ECDSA.json +29 -0
  14. package/abis/EIP712.json +67 -0
  15. package/abis/EIP712Upgradeable.json +74 -0
  16. package/abis/ERC1967Proxy.json +67 -0
  17. package/abis/ERC1967Utils.json +85 -0
  18. package/abis/ERC20PermitUpgradeable.json +527 -0
  19. package/abis/ERC20Upgradeable.json +333 -0
  20. package/abis/FactoryTest.json +845 -0
  21. package/abis/IBeacon.json +15 -0
  22. package/abis/ICoin.json +541 -0
  23. package/abis/ICoinComments.json +53 -0
  24. package/abis/IERC1155Errors.json +104 -0
  25. package/abis/IERC165.json +21 -0
  26. package/abis/IERC1822Proxiable.json +15 -0
  27. package/abis/IERC20.json +221 -0
  28. package/abis/IERC20Errors.json +88 -0
  29. package/abis/IERC20Metadata.json +224 -0
  30. package/abis/IERC20Permit.json +77 -0
  31. package/abis/IERC5267.json +51 -0
  32. package/abis/IERC721.json +287 -0
  33. package/abis/IERC721Enumerable.json +343 -0
  34. package/abis/IERC721Errors.json +105 -0
  35. package/abis/IERC721Metadata.json +332 -0
  36. package/abis/IERC721Receiver.json +36 -0
  37. package/abis/IERC721TokenReceiver.json +36 -0
  38. package/abis/IERC7572.json +21 -0
  39. package/abis/IMulticall3.json +440 -0
  40. package/abis/INonfungiblePositionManager.json +366 -0
  41. package/abis/IProtocolRewards.json +348 -0
  42. package/abis/ISwapRouter.json +137 -0
  43. package/abis/IUniswapV3Pool.json +148 -0
  44. package/abis/IUniswapV3SwapCallback.json +25 -0
  45. package/abis/IVersionedContract.json +15 -0
  46. package/abis/IWETH.json +118 -0
  47. package/abis/IZoraFactory.json +138 -0
  48. package/abis/Initializable.json +25 -0
  49. package/abis/Math.json +7 -0
  50. package/abis/MockERC20.json +322 -0
  51. package/abis/MockERC721.json +350 -0
  52. package/abis/MultiOwnable.json +171 -0
  53. package/abis/MultiOwnableTest.json +796 -0
  54. package/abis/NoncesUpgradeable.json +60 -0
  55. package/abis/OwnableUpgradeable.json +99 -0
  56. package/abis/ProtocolRewards.json +494 -0
  57. package/abis/Proxy.json +6 -0
  58. package/abis/ReentrancyGuardUpgradeable.json +30 -0
  59. package/abis/SafeERC20.json +34 -0
  60. package/abis/Script.json +15 -0
  61. package/abis/ShortStrings.json +18 -0
  62. package/abis/StdAssertions.json +379 -0
  63. package/abis/StdInvariant.json +180 -0
  64. package/abis/Strings.json +18 -0
  65. package/abis/Test.json +570 -0
  66. package/abis/UUPSUpgradeable.json +130 -0
  67. package/abis/Vm.json +8627 -0
  68. package/abis/VmSafe.json +7297 -0
  69. package/abis/ZoraFactory.json +67 -0
  70. package/abis/ZoraFactoryImpl.json +422 -0
  71. package/abis/stdError.json +119 -0
  72. package/abis/stdStorageSafe.json +52 -0
  73. package/addresses/1.json +4 -0
  74. package/addresses/8453.json +9 -0
  75. package/addresses/84532.json +9 -0
  76. package/dist/index.cjs +1236 -0
  77. package/dist/index.cjs.map +1 -0
  78. package/dist/index.d.ts +2 -0
  79. package/dist/index.d.ts.map +1 -0
  80. package/dist/index.js +1208 -0
  81. package/dist/index.js.map +1 -0
  82. package/dist/wagmiGenerated.d.ts +1645 -0
  83. package/dist/wagmiGenerated.d.ts.map +1 -0
  84. package/foundry.toml +9 -0
  85. package/package/index.ts +1 -0
  86. package/package/wagmiGenerated.ts +1211 -0
  87. package/package.json +48 -0
  88. package/remappings.txt +4 -0
  89. package/script/Deploy.s.sol +14 -0
  90. package/slither.config.json +6 -0
  91. package/src/Coin.sol +579 -0
  92. package/src/ZoraFactoryImpl.sol +142 -0
  93. package/src/interfaces/ICoin.sol +194 -0
  94. package/src/interfaces/ICoinComments.sol +8 -0
  95. package/src/interfaces/IERC7572.sol +12 -0
  96. package/src/interfaces/INonfungiblePositionManager.sol +104 -0
  97. package/src/interfaces/IProtocolRewards.sol +12 -0
  98. package/src/interfaces/ISwapRouter.sol +38 -0
  99. package/src/interfaces/IUniswapV3Pool.sol +43 -0
  100. package/src/interfaces/IUniswapV3SwapCallback.sol +17 -0
  101. package/src/interfaces/IWETH.sol +16 -0
  102. package/src/interfaces/IZoraFactory.sol +56 -0
  103. package/src/proxy/ZoraFactory.sol +26 -0
  104. package/src/utils/CoinConstants.sol +67 -0
  105. package/src/utils/MultiOwnable.sol +126 -0
  106. package/src/utils/TickMath.sol +210 -0
  107. package/src/version/ContractVersionBase.sol +14 -0
  108. package/test/Coin.t.sol +443 -0
  109. package/test/Factory.t.sol +298 -0
  110. package/test/MultiOwnable.t.sol +156 -0
  111. package/test/utils/BaseTest.sol +178 -0
  112. package/test/utils/ProtocolRewards.sol +1499 -0
  113. package/tsconfig.build.json +10 -0
  114. package/tsconfig.json +9 -0
  115. package/tsup.config.ts +11 -0
  116. package/wagmi.config.ts +16 -0
@@ -0,0 +1,443 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import "./utils/BaseTest.sol";
5
+
6
+ contract CoinTest is BaseTest {
7
+ function setUp() public override {
8
+ super.setUp();
9
+
10
+ _deployCoin();
11
+ }
12
+
13
+ function test_supply_constants() public view {
14
+ assertEq(MAX_TOTAL_SUPPLY, POOL_LAUNCH_SUPPLY + CREATOR_LAUNCH_REWARD + PLATFORM_REFERRER_LAUNCH_REWARD + PROTOCOL_LAUNCH_REWARD);
15
+
16
+ assertEq(MAX_TOTAL_SUPPLY, 1_000_000_000e18);
17
+ assertEq(POOL_LAUNCH_SUPPLY, 980_000_000e18);
18
+ assertEq(CREATOR_LAUNCH_REWARD, 10_000_000e18);
19
+ assertEq(PLATFORM_REFERRER_LAUNCH_REWARD, 5_000_000e18);
20
+ assertEq(PROTOCOL_LAUNCH_REWARD, 5_000_000e18);
21
+
22
+ assertEq(coin.totalSupply(), MAX_TOTAL_SUPPLY);
23
+ assertEq(coin.balanceOf(coin.payoutRecipient()), CREATOR_LAUNCH_REWARD);
24
+ assertEq(coin.balanceOf(coin.platformReferrer()), PLATFORM_REFERRER_LAUNCH_REWARD);
25
+ assertEq(coin.balanceOf(coin.protocolRewardRecipient()), PROTOCOL_LAUNCH_REWARD);
26
+ assertApproxEqAbs(coin.balanceOf(address(pool)), POOL_LAUNCH_SUPPLY, 1e18);
27
+ }
28
+
29
+ function test_constructor_validation() public {
30
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
31
+ new Coin(address(0), address(protocolRewards), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER);
32
+
33
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
34
+ new Coin(users.feeRecipient, address(0), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER);
35
+
36
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
37
+ new Coin(users.feeRecipient, address(protocolRewards), address(0), NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER);
38
+
39
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
40
+ new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, address(0), SWAP_ROUTER);
41
+
42
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
43
+ new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, address(0));
44
+
45
+ Coin newToken = new Coin(users.feeRecipient, address(protocolRewards), WETH_ADDRESS, NONFUNGIBLE_POSITION_MANAGER, SWAP_ROUTER);
46
+ assertEq(address(newToken.protocolRewardRecipient()), users.feeRecipient);
47
+ assertEq(address(newToken.protocolRewards()), address(protocolRewards));
48
+ assertEq(address(newToken.WETH()), WETH_ADDRESS);
49
+ assertEq(address(newToken.nonfungiblePositionManager()), NONFUNGIBLE_POSITION_MANAGER);
50
+ assertEq(address(newToken.swapRouter()), SWAP_ROUTER);
51
+ }
52
+
53
+ function test_initialize_validation() public {
54
+ address[] memory owners = new address[](1);
55
+ owners[0] = users.creator;
56
+
57
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
58
+ coin = Coin(
59
+ payable(factory.deploy(address(0), owners, "https://init.com", "Init Token", "INIT", users.platformReferrer, address(weth), LP_TICK_LOWER_WETH, 0))
60
+ );
61
+
62
+ coin = Coin(payable(factory.deploy(users.creator, owners, "https://init.com", "Init Token", "INIT", address(0), address(weth), LP_TICK_LOWER_WETH, 0)));
63
+
64
+ assertEq(coin.payoutRecipient(), users.creator);
65
+ assertEq(coin.platformReferrer(), users.feeRecipient);
66
+ assertEq(coin.tokenURI(), "https://init.com");
67
+ assertEq(coin.name(), "Init Token");
68
+ assertEq(coin.symbol(), "INIT");
69
+ }
70
+
71
+ function test_revert_already_initialized() public {
72
+ address[] memory owners = new address[](1);
73
+ owners[0] = users.creator;
74
+
75
+ coin = Coin(
76
+ payable(
77
+ factory.deploy(users.creator, owners, "https://init.com", "Init Token", "INIT", users.platformReferrer, address(weth), LP_TICK_LOWER_WETH, 0)
78
+ )
79
+ );
80
+
81
+ vm.expectRevert(abi.encodeWithSignature("InvalidInitialization()"));
82
+ coin.initialize(users.creator, new address[](0), "https://init.com", "Init Token", "INIT", users.platformReferrer, address(weth), LP_TICK_LOWER_WETH);
83
+ }
84
+
85
+ function test_erc165_interface_support() public view {
86
+ assertEq(coin.supportsInterface(type(IERC165).interfaceId), true);
87
+ assertEq(coin.supportsInterface(type(IERC7572).interfaceId), true);
88
+ }
89
+
90
+ function test_buy_with_eth() public {
91
+ vm.deal(users.buyer, 1 ether);
92
+ vm.prank(users.buyer);
93
+ coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
94
+
95
+ assertGt(coin.balanceOf(users.coinRecipient), 0);
96
+ assertEq(users.seller.balance, 0);
97
+ }
98
+
99
+ function test_buy_with_eth_fuzz(uint256 ethOrderSize) public {
100
+ vm.assume(ethOrderSize >= MIN_ORDER_SIZE);
101
+ vm.assume(ethOrderSize < 10 ether);
102
+
103
+ uint256 platformReferrerBalanceBeforeSale = users.platformReferrer.balance;
104
+ uint256 orderReferrerBalanceBeforeSale = users.tradeReferrer.balance;
105
+ uint256 tokenCreatorBalanceBeforeSale = users.creator.balance;
106
+ uint256 feeRecipientBalanceBeforeSale = users.feeRecipient.balance;
107
+
108
+ vm.deal(users.buyer, ethOrderSize);
109
+ vm.prank(users.buyer);
110
+ coin.buy{value: ethOrderSize}(users.coinRecipient, ethOrderSize, 0, 0, users.tradeReferrer);
111
+
112
+ assertGt(coin.balanceOf(users.coinRecipient), 0, "coinRecipient coin balance");
113
+ assertGt(protocolRewards.balanceOf(users.feeRecipient), feeRecipientBalanceBeforeSale, "feeRecipient eth balance");
114
+ assertGt(protocolRewards.balanceOf(users.platformReferrer), platformReferrerBalanceBeforeSale, "platformReferrer eth balance");
115
+ assertGt(protocolRewards.balanceOf(users.tradeReferrer), orderReferrerBalanceBeforeSale, "tradeReferrer eth balance");
116
+ assertGt(protocolRewards.balanceOf(users.creator), tokenCreatorBalanceBeforeSale, "creator eth balance");
117
+ }
118
+
119
+ function test_buy_with_eth_too_small() public {
120
+ vm.expectRevert(abi.encodeWithSelector(ICoin.EthAmountTooSmall.selector));
121
+ coin.buy{value: MIN_ORDER_SIZE - 1}(users.coinRecipient, MIN_ORDER_SIZE - 1, 0, 0, users.tradeReferrer);
122
+ }
123
+
124
+ function test_buy_with_minimum_eth() public {
125
+ uint256 minEth = MIN_ORDER_SIZE;
126
+ vm.deal(users.buyer, minEth);
127
+ vm.prank(users.buyer);
128
+ coin.buy{value: minEth}(users.coinRecipient, minEth, 0, 0, users.tradeReferrer);
129
+
130
+ assertGt(coin.balanceOf(users.coinRecipient), 0, "coinRecipient coin balance");
131
+ }
132
+
133
+ function test_revert_buy_zero_address_recipient_legacy() public {
134
+ vm.deal(users.buyer, 1 ether);
135
+
136
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
137
+ vm.prank(users.buyer);
138
+ coin.buy{value: 1 ether}(address(0), 1 ether, 0, 0, users.tradeReferrer);
139
+ }
140
+
141
+ function test_revert_buy_zero_address_recipient() public {
142
+ vm.deal(users.buyer, 1 ether);
143
+
144
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
145
+ vm.prank(users.buyer);
146
+ coin.buy(address(0), 1 ether, 0, 0, users.tradeReferrer);
147
+ }
148
+
149
+ function test_buy_with_usdc() public {
150
+ _deployCoinUSDCPair();
151
+
152
+ deal(address(usdc), users.buyer, 100e6);
153
+
154
+ vm.prank(users.buyer);
155
+ usdc.approve(address(coin), 10e6);
156
+
157
+ vm.prank(users.buyer);
158
+ coin.buy(users.coinRecipient, 10e6, 0, 0, users.tradeReferrer);
159
+
160
+ assertGt(coin.balanceOf(users.coinRecipient), 0, "coinRecipient coin balance");
161
+ }
162
+
163
+ function test_buy_with_usdc_revert_no_approval() public {
164
+ _deployCoinUSDCPair();
165
+
166
+ deal(address(usdc), users.buyer, 100e6);
167
+
168
+ vm.prank(users.buyer);
169
+ vm.expectRevert("ERC20: transfer amount exceeds allowance");
170
+ coin.buy(users.coinRecipient, 100e6, 0, 0, users.tradeReferrer);
171
+ }
172
+
173
+ function test_sell_for_eth() public {
174
+ vm.deal(users.buyer, 1 ether);
175
+ vm.prank(users.buyer);
176
+ coin.buy{value: 1 ether}(users.seller, 1 ether, 0, 0, users.tradeReferrer);
177
+
178
+ uint256 tokensToSell = coin.balanceOf(users.seller);
179
+ vm.prank(users.seller);
180
+ coin.sell(users.seller, tokensToSell, 0, 0, users.tradeReferrer);
181
+
182
+ assertEq(coin.balanceOf(users.seller), 0, "seller coin balance");
183
+ assertGt(users.seller.balance, 0, "seller eth balance");
184
+ }
185
+
186
+ function test_sell_for_eth_fuzz(uint256 ethOrderSize) public {
187
+ vm.assume(ethOrderSize < 10 ether);
188
+ vm.assume(ethOrderSize >= MIN_ORDER_SIZE);
189
+
190
+ vm.deal(users.buyer, ethOrderSize);
191
+ vm.prank(users.buyer);
192
+ coin.buy{value: ethOrderSize}(users.seller, ethOrderSize, 0, 0, users.tradeReferrer);
193
+
194
+ uint256 platformReferrerBalanceBeforeSale = users.platformReferrer.balance;
195
+ uint256 orderReferrerBalanceBeforeSale = users.tradeReferrer.balance;
196
+ uint256 tokenCreatorBalanceBeforeSale = users.creator.balance;
197
+ uint256 feeRecipientBalanceBeforeSale = users.feeRecipient.balance;
198
+
199
+ uint256 tokensToSell = coin.balanceOf(users.seller);
200
+ vm.prank(users.seller);
201
+ coin.sell(users.coinRecipient, tokensToSell, 0, 0, users.tradeReferrer);
202
+
203
+ assertEq(coin.balanceOf(users.seller), 0, "seller coin balance");
204
+ assertEq(coin.balanceOf(users.coinRecipient), 0, "coinRecipient coin balance");
205
+
206
+ assertEq(users.seller.balance, 0, "seller eth balance");
207
+ assertGt(protocolRewards.balanceOf(users.feeRecipient), feeRecipientBalanceBeforeSale, "feeRecipient eth balance");
208
+ assertGt(protocolRewards.balanceOf(users.platformReferrer), platformReferrerBalanceBeforeSale, "platformReferrer eth balance");
209
+ assertGt(protocolRewards.balanceOf(users.tradeReferrer), orderReferrerBalanceBeforeSale, "tradeReferrer eth balance");
210
+ assertGt(protocolRewards.balanceOf(users.creator), tokenCreatorBalanceBeforeSale, "creator eth balance");
211
+ }
212
+
213
+ function test_sell_for_usdc() public {
214
+ _deployCoinUSDCPair();
215
+
216
+ deal(address(usdc), users.buyer, 10e6);
217
+
218
+ vm.prank(users.buyer);
219
+ usdc.approve(address(coin), 10e6);
220
+
221
+ vm.prank(users.buyer);
222
+ coin.buy(users.coinRecipient, 10e6, 0, 0, users.tradeReferrer);
223
+
224
+ uint256 coinBalance = coin.balanceOf(users.coinRecipient);
225
+
226
+ vm.prank(users.coinRecipient);
227
+ coin.sell(users.seller, coinBalance, 0, 0, users.tradeReferrer);
228
+ }
229
+
230
+ function test_revert_sell_zero_address_recipient() public {
231
+ vm.deal(users.buyer, 1 ether);
232
+ vm.prank(users.buyer);
233
+ coin.buy{value: 1 ether}(users.seller, 1 ether, 0, 0, users.tradeReferrer);
234
+
235
+ uint256 tokensToSell = coin.balanceOf(users.seller);
236
+ vm.prank(users.seller);
237
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
238
+ coin.sell(address(0), tokensToSell, 0, 0, users.tradeReferrer);
239
+ }
240
+
241
+ function test_revert_sell_insufficient_liquidity() public {
242
+ vm.deal(users.buyer, 1 ether);
243
+ vm.prank(users.buyer);
244
+ coin.buy{value: 1 ether}(users.seller, 1 ether, 0, 0, users.tradeReferrer);
245
+
246
+ uint256 balance = coin.balanceOf(users.seller);
247
+ vm.prank(users.seller);
248
+ vm.expectRevert(abi.encodeWithSignature("ERC20InsufficientBalance(address,uint256,uint256)", users.seller, balance, balance + 1));
249
+ coin.sell(users.coinRecipient, balance + 1, 0, 0, users.tradeReferrer);
250
+ }
251
+
252
+ function test_burn() public {
253
+ vm.deal(users.buyer, 1 ether);
254
+ vm.prank(users.buyer);
255
+ coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
256
+
257
+ uint256 beforeBalance = coin.balanceOf(users.coinRecipient);
258
+ uint256 beforeTotalSupply = coin.totalSupply();
259
+
260
+ vm.prank(users.coinRecipient);
261
+ coin.burn(1e18);
262
+
263
+ uint256 afterBalance = coin.balanceOf(users.coinRecipient);
264
+ uint256 afterTotalSupply = coin.totalSupply();
265
+
266
+ assertEq(beforeBalance - afterBalance, 1e18, "coinRecipient coin balance");
267
+ assertEq(beforeTotalSupply - afterTotalSupply, 1e18, "coin total supply");
268
+ }
269
+
270
+ function test_receive_from_weth() public {
271
+ uint256 orderSize = 1 ether;
272
+ vm.deal(users.buyer, orderSize);
273
+ vm.prank(users.buyer);
274
+ coin.buy{value: orderSize}(users.coinRecipient, orderSize, 0, 0, users.tradeReferrer);
275
+
276
+ vm.deal(WETH_ADDRESS, 1 ether);
277
+ vm.prank(WETH_ADDRESS);
278
+ (bool success, ) = address(coin).call{value: 1 ether}("");
279
+ assertTrue(success);
280
+ }
281
+
282
+ function test_only_pool_callback() public {
283
+ vm.expectRevert(abi.encodeWithSelector(ICoin.OnlyPool.selector));
284
+ coin.onERC721Received(address(0), address(0), 0, "");
285
+ }
286
+
287
+ function test_default_platform_referrer() public {
288
+ address[] memory owners = new address[](1);
289
+ owners[0] = users.creator;
290
+
291
+ Coin newCoin = Coin(
292
+ payable(
293
+ factory.deploy(users.creator, owners, "https://test.com", "Test Token", "TEST", users.platformReferrer, address(weth), LP_TICK_LOWER_WETH, 0)
294
+ )
295
+ );
296
+
297
+ vm.deal(users.buyer, 1 ether);
298
+ vm.prank(users.buyer);
299
+ newCoin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
300
+
301
+ uint256 fee = _calculateExpectedFee(1 ether);
302
+ TradeRewards memory expectedFees = _calculateTradeRewards(fee);
303
+
304
+ assertGt(protocolRewards.balanceOf(users.feeRecipient), expectedFees.platformReferrer, "feeRecipient eth balance");
305
+ }
306
+
307
+ function test_default_order_referrer() public {
308
+ vm.deal(users.buyer, 1 ether);
309
+ vm.prank(users.buyer);
310
+ coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, address(0));
311
+
312
+ uint256 fee = _calculateExpectedFee(1 ether);
313
+ TradeRewards memory expectedFees = _calculateTradeRewards(fee);
314
+
315
+ assertGt(protocolRewards.balanceOf(users.feeRecipient), expectedFees.tradeReferrer, "feeRecipient eth balance");
316
+ }
317
+
318
+ function test_market_slippage() public {
319
+ vm.deal(users.buyer, 1 ether);
320
+ vm.prank(users.buyer);
321
+ coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
322
+
323
+ vm.deal(users.buyer, 1 ether);
324
+ vm.prank(users.buyer);
325
+ vm.expectRevert("Too little received"); // Uniswap V3 revert
326
+ coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, type(uint256).max, 0, users.tradeReferrer);
327
+
328
+ vm.prank(users.coinRecipient);
329
+ vm.expectRevert("Too little received"); // Uniswap V3 revert
330
+ coin.sell(users.coinRecipient, 1e18, type(uint256).max, 0, users.tradeReferrer);
331
+ }
332
+
333
+ function test_uniswap_swap_callback() public {
334
+ // Test swap callback
335
+ vm.prank(address(pool));
336
+ coin.uniswapV3SwapCallback(100, -100, "");
337
+ }
338
+
339
+ function test_eth_transfer_fail() public {
340
+ vm.deal(users.buyer, 1 ether);
341
+ vm.prank(users.buyer);
342
+ coin.buy{value: 1 ether}(users.coinRecipient, 1 ether, 0, 0, users.tradeReferrer);
343
+
344
+ // Recipient reverts on ETH receive
345
+ address payable badRecipient = payable(makeAddr("badRecipient"));
346
+ vm.etch(badRecipient, hex"fe");
347
+
348
+ vm.prank(users.coinRecipient);
349
+ vm.expectRevert(abi.encodeWithSelector(Address.FailedInnerCall.selector));
350
+ coin.sell(badRecipient, 1e18, 0, 0, users.tradeReferrer);
351
+ }
352
+
353
+ function test_revert_receive_only_weth() public {
354
+ vm.deal(users.buyer, 1 ether);
355
+ vm.prank(users.buyer);
356
+ vm.expectRevert(abi.encodeWithSelector(ICoin.OnlyWeth.selector));
357
+ address(coin).call{value: 1 ether}("");
358
+
359
+ assertEq(address(coin).balance, 0, "coin balance");
360
+ }
361
+
362
+ function test_rewards() public {
363
+ uint256 initialPlatformReferrerBalance = protocolRewards.balanceOf(users.platformReferrer);
364
+ uint256 initialTokenCreatorBalance = protocolRewards.balanceOf(users.creator);
365
+ uint256 initialOrderReferrerBalance = protocolRewards.balanceOf(users.tradeReferrer);
366
+ uint256 initialFeeRecipientBalance = protocolRewards.balanceOf(users.feeRecipient);
367
+
368
+ uint256 buyAmount = 1 ether;
369
+ vm.deal(users.buyer, buyAmount);
370
+ vm.prank(users.buyer);
371
+ coin.buy{value: buyAmount}(users.coinRecipient, buyAmount, 0, 0, users.tradeReferrer);
372
+
373
+ uint256 orderFee = _calculateExpectedFee(buyAmount); // 1 ETH * 1% --> 0.01 ETH
374
+ TradeRewards memory orderFees = _calculateTradeRewards(orderFee);
375
+
376
+ uint256 expectedLpFee = 9900000000000000; // 0.99 ETH * 1% --> ~0.00989 ETH
377
+ MarketRewards memory marketRewards = _calculateMarketRewards(expectedLpFee);
378
+
379
+ assertEq(marketRewards.creator + marketRewards.platformReferrer + marketRewards.protocol, expectedLpFee, "Secondary rewards incorrect");
380
+ assertEq(
381
+ protocolRewards.balanceOf(users.creator),
382
+ initialTokenCreatorBalance + orderFees.creator + marketRewards.creator,
383
+ "Token creator rewards incorrect"
384
+ );
385
+ assertEq(
386
+ protocolRewards.balanceOf(users.platformReferrer),
387
+ initialPlatformReferrerBalance + orderFees.platformReferrer + marketRewards.platformReferrer,
388
+ "Platform referrer rewards incorrect"
389
+ );
390
+ assertEq(
391
+ protocolRewards.balanceOf(users.feeRecipient),
392
+ initialFeeRecipientBalance + orderFees.protocol + marketRewards.protocol,
393
+ "Protocol rewards incorrect"
394
+ );
395
+ assertEq(protocolRewards.balanceOf(users.tradeReferrer), initialOrderReferrerBalance + orderFees.tradeReferrer, "Order referrer rewards incorrect");
396
+ }
397
+
398
+ function test_contract_uri() public view {
399
+ assertEq(coin.contractURI(), "https://test.com");
400
+ }
401
+
402
+ function test_set_contract_uri() public {
403
+ string memory newURI = "https://new.com";
404
+
405
+ vm.prank(users.creator);
406
+ coin.setContractURI(newURI);
407
+ assertEq(coin.contractURI(), newURI);
408
+ }
409
+
410
+ function test_set_contract_uri_reverts_if_not_owner() public {
411
+ string memory newURI = "https://new.com";
412
+
413
+ vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
414
+ coin.setContractURI(newURI);
415
+ }
416
+
417
+ function test_set_payout_recipient() public {
418
+ address newPayoutRecipient = makeAddr("NewPayoutRecipient");
419
+
420
+ vm.prank(users.creator);
421
+ coin.setPayoutRecipient(newPayoutRecipient);
422
+ assertEq(coin.payoutRecipient(), newPayoutRecipient);
423
+ }
424
+
425
+ function test_revert_set_payout_recipient_address_zero() public {
426
+ address newPayoutRecipient = address(0);
427
+
428
+ vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
429
+ vm.prank(users.creator);
430
+ coin.setPayoutRecipient(newPayoutRecipient);
431
+ }
432
+
433
+ function test_revert_set_payout_recipient_only_owner() public {
434
+ address newPayoutRecipient = makeAddr("NewPayoutRecipient");
435
+
436
+ vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OnlyOwner.selector));
437
+ coin.setPayoutRecipient(newPayoutRecipient);
438
+ }
439
+
440
+ function test_contract_version() public view {
441
+ assertEq(coin.contractVersion(), "0.1.1");
442
+ }
443
+ }