@zoralabs/coins 2.2.1 → 2.3.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 (88) hide show
  1. package/.turbo/turbo-build$colon$js.log +125 -106
  2. package/CHANGELOG.md +50 -5
  3. package/README.md +5 -0
  4. package/abis/AddressConstants.json +7 -0
  5. package/abis/BaseCoin.json +0 -5
  6. package/abis/BaseTest.json +62 -0
  7. package/abis/BuySupplyWithV4SwapHook.json +429 -0
  8. package/abis/ContentCoin.json +0 -5
  9. package/abis/CreatorCoin.json +0 -5
  10. package/abis/FeeEstimatorHook.json +94 -1
  11. package/abis/IUniswapV4Router04.json +484 -0
  12. package/abis/IUpgradeableDestinationV4HookWithUpdateableFee.json +95 -0
  13. package/abis/IZoraFactory.json +69 -0
  14. package/abis/MockAirlock.json +39 -0
  15. package/abis/SimpleERC20.json +326 -0
  16. package/abis/ZoraFactoryImpl.json +69 -0
  17. package/abis/ZoraV4CoinHook.json +94 -1
  18. package/addresses/8453.json +8 -10
  19. package/audits/report-cantinacode-zora-0827.pdf +3498 -4
  20. package/audits/report-cantinacode-zora-1021.pdf +0 -0
  21. package/dist/index.cjs +161 -22
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.js +160 -21
  24. package/dist/index.js.map +1 -1
  25. package/dist/wagmiGenerated.d.ts +259 -40
  26. package/dist/wagmiGenerated.d.ts.map +1 -1
  27. package/foundry.toml +3 -3
  28. package/package/wagmiGenerated.ts +160 -21
  29. package/package.json +1 -1
  30. package/script/DeployPostDeploymentHooks.s.sol +1 -3
  31. package/script/TestBackingCoinSwap.s.sol +0 -2
  32. package/script/TestV4Swap.s.sol +0 -2
  33. package/src/BaseCoin.sol +4 -12
  34. package/src/ContentCoin.sol +3 -4
  35. package/src/CreatorCoin.sol +8 -10
  36. package/src/ZoraFactoryImpl.sol +115 -83
  37. package/src/deployment/CoinsDeployerBase.sol +9 -8
  38. package/src/hook-registry/ZoraHookRegistry.sol +4 -0
  39. package/src/hooks/ZoraV4CoinHook.sol +66 -9
  40. package/src/hooks/deployment/BuySupplyWithV4SwapHook.sol +310 -0
  41. package/src/interfaces/IUpgradeableV4Hook.sol +18 -0
  42. package/src/interfaces/IZoraFactory.sol +21 -2
  43. package/src/libs/CoinConstants.sol +51 -8
  44. package/src/libs/CoinDopplerMultiCurve.sol +11 -11
  45. package/src/libs/CoinRewardsV4.sol +26 -33
  46. package/src/libs/CoinSetup.sol +2 -9
  47. package/src/libs/DopplerMath.sol +2 -2
  48. package/src/libs/V4Liquidity.sol +79 -15
  49. package/src/utils/AutoSwapper.sol +1 -1
  50. package/src/version/ContractVersionBase.sol +1 -1
  51. package/test/BuySupplyWithV4SwapHook.t.sol +509 -0
  52. package/test/Coin.t.sol +26 -14
  53. package/test/CoinRewardsV4.t.sol +33 -0
  54. package/test/CoinUniV4.t.sol +3 -5
  55. package/test/ContentCoinRewards.t.sol +44 -3
  56. package/test/CreatorCoin.t.sol +54 -33
  57. package/test/CreatorCoinRewards.t.sol +1 -3
  58. package/test/DeploymentHooks.t.sol +54 -2
  59. package/test/Factory.t.sol +3 -3
  60. package/test/LiquidityMigration.t.sol +145 -7
  61. package/test/MultiOwnable.t.sol +4 -4
  62. package/test/Upgrades.t.sol +26 -17
  63. package/test/V4Liquidity.t.sol +178 -0
  64. package/test/ZoraHookRegistry.t.sol +19 -9
  65. package/test/mocks/MockAirlock.sol +22 -0
  66. package/test/mocks/SimpleERC20.sol +8 -0
  67. package/test/utils/BaseTest.sol +155 -3
  68. package/test/utils/RewardTestHelpers.sol +4 -4
  69. package/test/utils/hookmate/README.md +50 -0
  70. package/test/utils/hookmate/artifacts/DeployHelper.sol +20 -0
  71. package/test/utils/hookmate/artifacts/Permit2.sol +16 -0
  72. package/test/utils/hookmate/artifacts/UniversalRouter.sol +29 -0
  73. package/test/utils/hookmate/artifacts/V4PoolManager.sol +17 -0
  74. package/test/utils/hookmate/artifacts/V4PositionManager.sol +23 -0
  75. package/test/utils/hookmate/artifacts/V4Quoter.sol +17 -0
  76. package/test/utils/hookmate/artifacts/V4Router.sol +18 -0
  77. package/test/utils/hookmate/constants/AddressConstants.sol +193 -0
  78. package/test/utils/hookmate/interfaces/router/IUniswapV4Router04.sol +173 -0
  79. package/test/utils/hookmate/interfaces/router/PathKey.sol +34 -0
  80. package/test/utils/hookmate/test/utils/SwapFeeEventAsserter.sol +24 -0
  81. package/wagmi.config.ts +1 -1
  82. package/abis/CoinConstants.json +0 -54
  83. package/abis/CoinRewardsV4.json +0 -67
  84. package/src/libs/CreatorCoinConstants.sol +0 -15
  85. package/src/libs/MarketConstants.sol +0 -23
  86. package/src/utils/uniswap/BytesLib.sol +0 -35
  87. package/src/utils/uniswap/Path.sol +0 -31
  88. /package/abis/{VmContractHelper227.json → VmContractHelper239.json} +0 -0
package/test/Coin.t.sol CHANGED
@@ -16,7 +16,7 @@ contract CoinTest is BaseTest {
16
16
  using stdJson for string;
17
17
 
18
18
  function setUp() public override {
19
- super.setUp();
19
+ super.setUpNonForked();
20
20
  }
21
21
 
22
22
  function test_contract_ierc165_support() public {
@@ -37,16 +37,16 @@ contract CoinTest is BaseTest {
37
37
  }
38
38
 
39
39
  function test_supply_constants() public {
40
- assertEq(CoinConstants.MAX_TOTAL_SUPPLY, CoinConstants.POOL_LAUNCH_SUPPLY + CoinConstants.CREATOR_LAUNCH_REWARD);
40
+ assertEq(CoinConstants.MAX_TOTAL_SUPPLY, CoinConstants.CONTENT_COIN_MARKET_SUPPLY + CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY);
41
41
 
42
42
  assertEq(CoinConstants.MAX_TOTAL_SUPPLY, 1_000_000_000e18);
43
- assertEq(CoinConstants.POOL_LAUNCH_SUPPLY, 990_000_000e18);
44
- assertEq(CoinConstants.CREATOR_LAUNCH_REWARD, 10_000_000e18);
43
+ assertEq(CoinConstants.CONTENT_COIN_MARKET_SUPPLY, 990_000_000e18);
44
+ assertEq(CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY, 10_000_000e18);
45
45
 
46
46
  _deployV4Coin();
47
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);
48
+ assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY);
49
+ assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())), CoinConstants.CONTENT_COIN_MARKET_SUPPLY, 1e18);
50
50
  }
51
51
 
52
52
  function test_initialize_validation() public {
@@ -107,23 +107,35 @@ contract CoinTest is BaseTest {
107
107
  }
108
108
 
109
109
  function test_burn() public {
110
- _deployV4Coin();
111
- vm.deal(users.buyer, 1 ether);
112
- vm.prank(users.buyer);
113
- _swapSomeCurrencyForCoin(coinV4, address(weth), 1 ether, users.coinRecipient);
110
+ // Deploy a mock ERC20 currency
111
+ MockERC20 mockCurrency = new MockERC20("Mock Currency", "MOCK");
112
+ mockCurrency.mint(users.buyer, 1000 ether);
113
+ // Pool manager needs currency for liquidity operations
114
+ mockCurrency.mint(address(poolManager), 1000000 ether);
115
+
116
+ // Deploy coin with mock currency
117
+ coinV4 = ContentCoin(payable(address(_deployV4Coin(address(mockCurrency), address(0), bytes32(0)))));
114
118
 
115
- uint256 beforeBalance = coinV4.balanceOf(users.coinRecipient);
119
+ // Approve with permit2 and swap
120
+ uint128 swapAmount = 1 ether;
121
+ vm.startPrank(users.buyer);
122
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(mockCurrency), swapAmount, uint48(block.timestamp + 1 days));
123
+ vm.stopPrank();
124
+
125
+ _swapSomeCurrencyForCoin(coinV4, address(mockCurrency), swapAmount, users.buyer);
126
+
127
+ uint256 beforeBalance = coinV4.balanceOf(users.buyer);
116
128
  uint256 beforeTotalSupply = coinV4.totalSupply();
117
129
 
118
130
  uint256 burnAmount = beforeBalance / 2;
119
131
 
120
- vm.prank(users.coinRecipient);
132
+ vm.prank(users.buyer);
121
133
  coinV4.burn(burnAmount);
122
134
 
123
- uint256 afterBalance = coinV4.balanceOf(users.coinRecipient);
135
+ uint256 afterBalance = coinV4.balanceOf(users.buyer);
124
136
  uint256 afterTotalSupply = coinV4.totalSupply();
125
137
 
126
- assertEq(beforeBalance - afterBalance, burnAmount, "coinRecipient coin balance");
138
+ assertEq(beforeBalance - afterBalance, burnAmount, "buyer coin balance");
127
139
  assertEq(beforeTotalSupply - afterTotalSupply, burnAmount, "coin total supply");
128
140
  }
129
141
 
@@ -0,0 +1,33 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import "forge-std/Test.sol";
5
+ import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
6
+ import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
7
+
8
+ contract CoinRewardsV4Test is Test {
9
+ function test_convertDeltaToPositiveUint128_success_with_valid_positive_values() public pure {
10
+ // Test with small positive value
11
+ int256 smallDelta = 1000;
12
+ uint128 result = CoinRewardsV4.convertDeltaToPositiveUint128(smallDelta);
13
+ assertEq(result, uint128(uint256(smallDelta)));
14
+
15
+ // Test with large but valid positive value (within uint128 range)
16
+ int256 largeDelta = int256(uint256(type(uint128).max));
17
+ uint128 result2 = CoinRewardsV4.convertDeltaToPositiveUint128(largeDelta);
18
+ assertEq(result2, type(uint128).max);
19
+
20
+ // Test with zero
21
+ int256 zeroDelta = 0;
22
+ uint128 result3 = CoinRewardsV4.convertDeltaToPositiveUint128(zeroDelta);
23
+ assertEq(result3, 0);
24
+ }
25
+
26
+ /// forge-config: default.allow_internal_expect_revert = true
27
+ function test_convertDeltaToPositiveUint128_edge_cases_and_reverts(int8 difference) public {
28
+ if (difference < 0) {
29
+ vm.expectRevert(SafeCast.SafeCastOverflow.selector);
30
+ }
31
+ CoinRewardsV4.convertDeltaToPositiveUint128(difference);
32
+ }
33
+ }
@@ -17,7 +17,6 @@ import {LpPosition} from "../src/types/LpPosition.sol";
17
17
  import {CoinCommon} from "../src/libs/CoinCommon.sol";
18
18
  import {IZoraV4CoinHook} from "../src/interfaces/IZoraV4CoinHook.sol";
19
19
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
20
- import {MarketConstants} from "../src/libs/MarketConstants.sol";
21
20
  import {IMsgSender} from "../src/interfaces/IMsgSender.sol";
22
21
  import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
23
22
  import {toBalanceDelta, BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
@@ -39,9 +38,8 @@ contract CoinUniV4Test is BaseTest {
39
38
  MockERC20 internal mockERC20B;
40
39
 
41
40
  function setUp() public override {
42
- super.setUpWithBlockNumber(30267794);
41
+ super.setUpNonForked();
43
42
 
44
- quoter = IV4Quoter(V4_QUOTER);
45
43
  mockERC20A = new MockERC20("MockERC20A", "MCKA");
46
44
  mockERC20B = new MockERC20("MockERC20B", "MCKB");
47
45
 
@@ -106,8 +104,8 @@ contract CoinUniV4Test is BaseTest {
106
104
 
107
105
  // Verify total supply equals maximum allowed
108
106
  assertEq(coinV4.totalSupply(), CoinConstants.MAX_TOTAL_SUPPLY, "total supply");
109
- assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())), MarketConstants.CONTENT_COIN_MARKET_SUPPLY, 1000, "pool launch supply");
110
- assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.CREATOR_LAUNCH_REWARD, "creator launch reward");
107
+ assertApproxEqAbs(coinV4.balanceOf(address(coinV4.poolManager())), CoinConstants.CONTENT_COIN_MARKET_SUPPLY, 1000, "pool launch supply");
108
+ assertEq(coinV4.balanceOf(coinV4.payoutRecipient()), CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY, "creator launch reward");
111
109
  }
112
110
 
113
111
  function test_estimateLpFees() public {
@@ -21,9 +21,7 @@ contract ContentCoinRewardsTest is BaseTest {
21
21
  address internal tradeReferrer;
22
22
 
23
23
  function setUp() public override {
24
- super.setUpWithBlockNumber(30267794);
25
-
26
- deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
24
+ super.setUpNonForked();
27
25
 
28
26
  backingCreatorCoin = CreatorCoin(_deployCreatorCoin());
29
27
 
@@ -317,4 +315,47 @@ contract ContentCoinRewardsTest is BaseTest {
317
315
 
318
316
  assertFalse(isLegacy, "Content coin should NOT be categorized as legacy creator coin");
319
317
  }
318
+
319
+ /// @notice Test reward distribution when platform referrer rejects ETH - should fallback to protocol recipient
320
+ function test_rewards_platform_referrer_eth_rejection_fallback() public {
321
+ // Deploy ETH-rejecting contract to use as platform referrer
322
+ EthRejectingContract ethRejecter = new EthRejectingContract();
323
+
324
+ // Deploy ETH-backed coin (like test_distributesMarketRewardsInEth)
325
+ address currency = address(0); // ETH backing
326
+ bytes32 salt = keccak256(abi.encodePacked("eth-reject-test"));
327
+ _deployV4Coin(currency, address(ethRejecter), salt); // ethRejecter becomes platform referrer
328
+
329
+ // Fund trader with ETH
330
+ uint128 ethAmount = 0.1 ether;
331
+ address trader = makeAddr("trader");
332
+ deal(trader, ethAmount);
333
+
334
+ // Record initial ETH balances
335
+ uint256 initialProtocolEth = coinV4.protocolRewardRecipient().balance;
336
+ uint256 initialRejecterEth = address(ethRejecter).balance;
337
+
338
+ // Execute ETH -> Coin trade using the working BaseTest function
339
+ _swapSomeCurrencyForCoin(coinV4, currency, ethAmount, trader);
340
+
341
+ // Calculate ETH balance deltas
342
+ uint256 protocolEthDelta = coinV4.protocolRewardRecipient().balance - initialProtocolEth;
343
+ uint256 rejecterEthDelta = address(ethRejecter).balance - initialRejecterEth;
344
+
345
+ // Verify ETH-rejecting contract got no ETH
346
+ assertEq(rejecterEthDelta, 0, "Platform referrer should receive no ETH");
347
+
348
+ // Verify protocol got ETH (backup mechanism worked)
349
+ assertGt(protocolEthDelta, 0, "Protocol should receive backup ETH from failed platform referrer");
350
+ }
351
+ }
352
+
353
+ // Contract that rejects ETH transfers (no payable functions)
354
+ contract EthRejectingContract {
355
+ // This contract has no receive() or fallback() payable functions
356
+ // so ETH transfers will fail
357
+ receive() external payable {
358
+ console.log("EthRejectingContract received ETH");
359
+ revert("EthRejectingContract received ETH");
360
+ }
320
361
  }
@@ -5,7 +5,7 @@ import "./utils/BaseTest.sol";
5
5
 
6
6
  import {ICreatorCoin} from "../src/interfaces/ICreatorCoin.sol";
7
7
  import {ICreatorCoinHook} from "../src/interfaces/ICreatorCoinHook.sol";
8
- import {CreatorCoinConstants} from "../src/libs/CreatorCoinConstants.sol";
8
+ import {CoinConstants} from "../src/libs/CoinConstants.sol";
9
9
  import {CoinRewardsV4} from "../src/libs/CoinRewardsV4.sol";
10
10
  import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
11
11
 
@@ -13,10 +13,7 @@ contract CreatorCoinTest is BaseTest {
13
13
  CreatorCoin internal creatorCoin;
14
14
 
15
15
  function setUp() public override {
16
- super.setUpWithBlockNumber(30267794);
17
-
18
- deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
19
-
16
+ super.setUpNonForked();
20
17
  _deployCreatorCoin();
21
18
  }
22
19
 
@@ -65,11 +62,11 @@ contract CreatorCoinTest is BaseTest {
65
62
  assertEq(creatorCoin.name(), "Testcoin");
66
63
  assertEq(creatorCoin.symbol(), "TEST");
67
64
  assertEq(creatorCoin.payoutRecipient(), users.creator);
68
- assertEq(creatorCoin.currency(), CreatorCoinConstants.CURRENCY);
69
- assertEq(creatorCoin.totalSupply(), CreatorCoinConstants.TOTAL_SUPPLY);
65
+ assertEq(creatorCoin.currency(), CoinConstants.CREATOR_COIN_CURRENCY);
66
+ assertEq(creatorCoin.totalSupply(), CoinConstants.TOTAL_SUPPLY);
70
67
 
71
- assertEq(creatorCoin.balanceOf(address(creatorCoin)), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
72
- assertEq(creatorCoin.balanceOf(address(creatorCoin.poolManager())), MarketConstants.CREATOR_COIN_MARKET_SUPPLY);
68
+ assertEq(creatorCoin.balanceOf(address(creatorCoin)), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
69
+ assertEq(creatorCoin.balanceOf(address(creatorCoin.poolManager())), CoinConstants.CREATOR_COIN_MARKET_SUPPLY);
73
70
  }
74
71
 
75
72
  function test_deploy_creator_coin_with_invalid_currency_reverts() public {
@@ -105,7 +102,7 @@ contract CreatorCoinTest is BaseTest {
105
102
  uint256 deploymentTime = block.timestamp;
106
103
 
107
104
  assertEq(creatorCoin.vestingStartTime(), deploymentTime);
108
- assertEq(creatorCoin.vestingEndTime(), deploymentTime + CreatorCoinConstants.CREATOR_VESTING_DURATION);
105
+ assertEq(creatorCoin.vestingEndTime(), deploymentTime + CoinConstants.CREATOR_VESTING_DURATION);
109
106
  assertEq(creatorCoin.totalClaimed(), 0);
110
107
  }
111
108
 
@@ -124,16 +121,16 @@ contract CreatorCoinTest is BaseTest {
124
121
  vm.warp(creatorCoin.vestingStartTime() + oneYear);
125
122
 
126
123
  // After 1 year out of 5, should be able to claim 20% of vesting supply
127
- uint256 expectedClaimable = (CreatorCoinConstants.CREATOR_VESTING_SUPPLY * oneYear) / CreatorCoinConstants.CREATOR_VESTING_DURATION;
124
+ uint256 expectedClaimable = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * oneYear) / CoinConstants.CREATOR_VESTING_DURATION;
128
125
  assertEq(creatorCoin.getClaimableAmount(), expectedClaimable);
129
126
  }
130
127
 
131
128
  function test_getClaimableAmount_after_half_vesting_period() public {
132
- uint256 halfVesting = CreatorCoinConstants.CREATOR_VESTING_DURATION / 2;
129
+ uint256 halfVesting = CoinConstants.CREATOR_VESTING_DURATION / 2;
133
130
  vm.warp(creatorCoin.vestingStartTime() + halfVesting);
134
131
 
135
132
  // After 2.5 years, should be able to claim 50% of vesting supply
136
- uint256 expectedClaimable = CreatorCoinConstants.CREATOR_VESTING_SUPPLY / 2;
133
+ uint256 expectedClaimable = CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 2;
137
134
  assertEq(creatorCoin.getClaimableAmount(), expectedClaimable);
138
135
  }
139
136
 
@@ -141,14 +138,14 @@ contract CreatorCoinTest is BaseTest {
141
138
  vm.warp(creatorCoin.vestingEndTime());
142
139
 
143
140
  // After full vesting period, should be able to claim entire vesting supply
144
- assertEq(creatorCoin.getClaimableAmount(), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
141
+ assertEq(creatorCoin.getClaimableAmount(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
145
142
  }
146
143
 
147
144
  function test_getClaimableAmount_after_vesting_period_ends() public {
148
145
  vm.warp(creatorCoin.vestingEndTime() + 365 days);
149
146
 
150
147
  // Even after vesting ends, should still be able to claim entire vesting supply
151
- assertEq(creatorCoin.getClaimableAmount(), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
148
+ assertEq(creatorCoin.getClaimableAmount(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
152
149
  }
153
150
 
154
151
  function test_getClaimableAmount_after_one_day() public {
@@ -156,12 +153,12 @@ contract CreatorCoinTest is BaseTest {
156
153
  uint256 oneDay = 1 days;
157
154
  vm.warp(creatorCoin.vestingStartTime() + oneDay);
158
155
 
159
- uint256 expectedClaimable = (CreatorCoinConstants.CREATOR_VESTING_SUPPLY * oneDay) / CreatorCoinConstants.CREATOR_VESTING_DURATION;
156
+ uint256 expectedClaimable = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * oneDay) / CoinConstants.CREATOR_VESTING_DURATION;
160
157
  assertEq(creatorCoin.getClaimableAmount(), expectedClaimable);
161
158
 
162
159
  // Verify it's a small but non-zero amount
163
160
  assertGt(expectedClaimable, 0);
164
- assertLt(expectedClaimable, CreatorCoinConstants.CREATOR_VESTING_SUPPLY / 1000); // Less than 0.1%
161
+ assertLt(expectedClaimable, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 1000); // Less than 0.1%
165
162
  }
166
163
 
167
164
  function test_claimVesting_at_launch() public {
@@ -175,7 +172,7 @@ contract CreatorCoinTest is BaseTest {
175
172
  uint256 oneYear = 365 days;
176
173
  vm.warp(creatorCoin.vestingStartTime() + oneYear);
177
174
 
178
- uint256 expectedClaimable = (CreatorCoinConstants.CREATOR_VESTING_SUPPLY * oneYear) / CreatorCoinConstants.CREATOR_VESTING_DURATION;
175
+ uint256 expectedClaimable = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * oneYear) / CoinConstants.CREATOR_VESTING_DURATION;
179
176
  uint256 initialCreatorBalance = creatorCoin.balanceOf(users.creator);
180
177
  uint256 initialContractBalance = creatorCoin.balanceOf(address(creatorCoin));
181
178
 
@@ -201,7 +198,7 @@ contract CreatorCoinTest is BaseTest {
201
198
 
202
199
  // First claim after 1 year
203
200
  vm.warp(creatorCoin.vestingStartTime() + oneYear);
204
- uint256 expectedClaim1 = (CreatorCoinConstants.CREATOR_VESTING_SUPPLY * oneYear) / CreatorCoinConstants.CREATOR_VESTING_DURATION;
201
+ uint256 expectedClaim1 = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * oneYear) / CoinConstants.CREATOR_VESTING_DURATION;
205
202
  uint256 claimed1 = creatorCoin.claimVesting();
206
203
 
207
204
  assertEq(claimed1, expectedClaim1);
@@ -210,7 +207,7 @@ contract CreatorCoinTest is BaseTest {
210
207
 
211
208
  // Second claim after another year (2 years total)
212
209
  vm.warp(creatorCoin.vestingStartTime() + 2 * oneYear);
213
- uint256 totalVestedAfter2Years = (CreatorCoinConstants.CREATOR_VESTING_SUPPLY * 2 * oneYear) / CreatorCoinConstants.CREATOR_VESTING_DURATION;
210
+ uint256 totalVestedAfter2Years = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * 2 * oneYear) / CoinConstants.CREATOR_VESTING_DURATION;
214
211
  uint256 expectedClaim2 = totalVestedAfter2Years - expectedClaim1;
215
212
 
216
213
  uint256 claimed2 = creatorCoin.claimVesting();
@@ -241,9 +238,9 @@ contract CreatorCoinTest is BaseTest {
241
238
 
242
239
  uint256 claimedAmount = creatorCoin.claimVesting();
243
240
 
244
- assertEq(claimedAmount, CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
245
- assertEq(creatorCoin.totalClaimed(), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
246
- assertEq(creatorCoin.balanceOf(users.creator), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
241
+ assertEq(claimedAmount, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
242
+ assertEq(creatorCoin.totalClaimed(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
243
+ assertEq(creatorCoin.balanceOf(users.creator), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
247
244
 
248
245
  // Subsequent claims should return 0
249
246
  uint256 secondClaim = creatorCoin.claimVesting();
@@ -251,21 +248,21 @@ contract CreatorCoinTest is BaseTest {
251
248
  }
252
249
 
253
250
  function test_claimVesting_partial_then_full() public {
254
- uint256 halfVesting = CreatorCoinConstants.CREATOR_VESTING_DURATION / 2;
251
+ uint256 halfVesting = CoinConstants.CREATOR_VESTING_DURATION / 2;
255
252
 
256
253
  // Claim half way through vesting
257
254
  vm.warp(creatorCoin.vestingStartTime() + halfVesting);
258
255
  uint256 partialClaim = creatorCoin.claimVesting();
259
- assertEq(partialClaim, CreatorCoinConstants.CREATOR_VESTING_SUPPLY / 2);
256
+ assertEq(partialClaim, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 2);
260
257
 
261
258
  // Claim the rest after full vesting
262
259
  vm.warp(creatorCoin.vestingEndTime());
263
260
  uint256 remainingClaim = creatorCoin.claimVesting();
264
- assertEq(remainingClaim, CreatorCoinConstants.CREATOR_VESTING_SUPPLY / 2);
261
+ assertEq(remainingClaim, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 2);
265
262
 
266
263
  // Total should equal full vesting supply
267
- assertEq(creatorCoin.totalClaimed(), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
268
- assertEq(creatorCoin.balanceOf(users.creator), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
264
+ assertEq(creatorCoin.totalClaimed(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
265
+ assertEq(creatorCoin.balanceOf(users.creator), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
269
266
  }
270
267
 
271
268
  function test_vesting_calculation_edge_cases() public {
@@ -277,17 +274,17 @@ contract CreatorCoinTest is BaseTest {
277
274
  vm.warp(creatorCoin.vestingStartTime() + 1);
278
275
  uint256 claimableAfterOneSecond = creatorCoin.getClaimableAmount();
279
276
  assertGt(claimableAfterOneSecond, 0);
280
- assertLt(claimableAfterOneSecond, CreatorCoinConstants.CREATOR_VESTING_SUPPLY / 1000000); // Very small amount
277
+ assertLt(claimableAfterOneSecond, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 1000000); // Very small amount
281
278
 
282
279
  // Test one second before vesting ends
283
280
  vm.warp(creatorCoin.vestingEndTime() - 1);
284
281
  uint256 claimableBeforeEnd = creatorCoin.getClaimableAmount();
285
- assertLt(claimableBeforeEnd, CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
286
- assertGt(claimableBeforeEnd, CreatorCoinConstants.CREATOR_VESTING_SUPPLY - (CreatorCoinConstants.CREATOR_VESTING_SUPPLY / 1000000));
282
+ assertLt(claimableBeforeEnd, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
283
+ assertGt(claimableBeforeEnd, CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY - (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY / 1000000));
287
284
 
288
285
  // Test at exact vesting end time
289
286
  vm.warp(creatorCoin.vestingEndTime());
290
- assertEq(creatorCoin.getClaimableAmount(), CreatorCoinConstants.CREATOR_VESTING_SUPPLY);
287
+ assertEq(creatorCoin.getClaimableAmount(), CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY);
291
288
  }
292
289
 
293
290
  function test_vesting_frequent_small_claims() public {
@@ -302,11 +299,35 @@ contract CreatorCoinTest is BaseTest {
302
299
  }
303
300
 
304
301
  // Verify total claimed matches expected amount for 7 days
305
- uint256 expectedTotal = (CreatorCoinConstants.CREATOR_VESTING_SUPPLY * 7 days) / CreatorCoinConstants.CREATOR_VESTING_DURATION;
302
+ uint256 expectedTotal = (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * 7 days) / CoinConstants.CREATOR_VESTING_DURATION;
306
303
  assertEq(totalClaimed, expectedTotal);
307
304
  assertEq(creatorCoin.totalClaimed(), expectedTotal);
308
305
  }
309
306
 
307
+ function test_vesting_duration_accounts_for_leap_years() public pure {
308
+ // Verify the vesting duration is exactly 5 years accounting for leap years
309
+ // 365.25 days per year * 5 years = 1826.25 days = 157,788,000 seconds
310
+ uint256 expectedDuration = 5 * 365.25 days;
311
+ uint256 expectedSeconds = 157_788_000; // 5 * 365.25 * 24 * 60 * 60
312
+
313
+ assertEq(CoinConstants.CREATOR_VESTING_DURATION, expectedDuration);
314
+ assertEq(CoinConstants.CREATOR_VESTING_DURATION, expectedSeconds);
315
+
316
+ // Verify it's longer than 5 * 365 days (which would be the old incorrect duration)
317
+ uint256 oldIncorrectDuration = 5 * 365 days;
318
+ uint256 differenceInSeconds = expectedDuration - oldIncorrectDuration;
319
+ uint256 expectedDifferenceInDays = 1.25 days; // 1.25 days = 108,000 seconds
320
+
321
+ assertEq(differenceInSeconds, expectedDifferenceInDays);
322
+ assertEq(differenceInSeconds, 108_000); // 1.25 * 24 * 60 * 60
323
+
324
+ // Verify this matches exactly 5 years with leap year correction
325
+ // Over 5 years, there's typically 1 leap day (Feb 29), plus 0.25 day per year
326
+ // for the quarter-day that accumulates: 1 + (5 * 0.25) = 2.25 days total
327
+ // But we use 365.25 average, so: 5 * 0.25 = 1.25 additional days
328
+ assertTrue(CoinConstants.CREATOR_VESTING_DURATION > oldIncorrectDuration);
329
+ }
330
+
310
331
  function test_buy(uint128 amountIn) public {
311
332
  vm.assume(amountIn > 0.00001e18);
312
333
  vm.assume(amountIn < 500_000e18);
@@ -22,9 +22,7 @@ contract CreatorCoinRewardsTest is BaseTest {
22
22
  address internal tradeReferrer;
23
23
 
24
24
  function setUp() public override {
25
- super.setUpWithBlockNumber(30267794);
26
-
27
- deal(address(zoraToken), address(poolManager), 1_000_000_000e18);
25
+ super.setUpNonForked();
28
26
 
29
27
  // Set up referrer addresses for all tests
30
28
  platformReferrer = makeAddr("platformReferrer");
@@ -4,6 +4,7 @@ pragma solidity ^0.8.13;
4
4
  import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {BuySupplyWithSwapRouterHook} from "../src/hooks/deployment/BuySupplyWithSwapRouterHook.sol";
6
6
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7
+ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
7
8
  import {IUniswapV3Pool} from "../src/interfaces/IUniswapV3Pool.sol";
8
9
  import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
9
10
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
@@ -64,7 +65,7 @@ contract DeploymentsHooksTest is BaseTest {
64
65
  }
65
66
 
66
67
  function test_buySupplyWithEthUsingV4Hook_withExactInputMultiHop(uint256 initialOrderSize) public {
67
- vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
68
+ vm.assume(initialOrderSize > 0.0001 ether);
68
69
  vm.assume(initialOrderSize < 1 ether);
69
70
 
70
71
  vm.deal(users.creator, initialOrderSize);
@@ -105,7 +106,7 @@ contract DeploymentsHooksTest is BaseTest {
105
106
  assertEq(coinV4.currency(), zora, "currency");
106
107
  assertGt(amountCurrency, 0, "amountCurrency > 0");
107
108
  assertGt(coinsPurchased, 0, "coinsPurchased > 0");
108
- assertEq(coinV4.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased, "balanceOf creator");
109
+ assertEq(coinV4.balanceOf(users.creator), CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY + coinsPurchased, "balanceOf creator");
109
110
  // assertGt(IERC20(zora).balanceOf(address(pool)), 0, "Pool ZORA balance");
110
111
  }
111
112
 
@@ -211,4 +212,55 @@ contract DeploymentsHooksTest is BaseTest {
211
212
  vm.prank(users.creator);
212
213
  _deployWithHook(address(0), bytes(""), zora);
213
214
  }
215
+
216
+ function test_creatorCoin_deployWithHook_buySupplyWithEth() public {
217
+ uint256 initialOrderSize = 0.0001 ether;
218
+ vm.deal(users.creator, initialOrderSize);
219
+
220
+ uint24 poolFee = 3000;
221
+
222
+ bytes memory hookData = _encodeExactInputSingle(
223
+ users.creator,
224
+ ISwapRouter.ExactInputSingleParams({
225
+ tokenIn: address(weth),
226
+ tokenOut: zora,
227
+ fee: poolFee,
228
+ recipient: address(buySupplyWithSwapRouterHook),
229
+ amountIn: initialOrderSize,
230
+ amountOutMinimum: 0,
231
+ sqrtPriceLimitX96: 0
232
+ })
233
+ );
234
+
235
+ bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(zora);
236
+
237
+ vm.prank(users.creator);
238
+ (address creatorCoinAddress, bytes memory hookDataOut) = factory.deployCreatorCoin{value: initialOrderSize}(
239
+ users.creator,
240
+ _getDefaultOwners(),
241
+ "https://test.com",
242
+ "Creator Coin Test",
243
+ "CCT",
244
+ poolConfig,
245
+ users.platformReferrer,
246
+ address(buySupplyWithSwapRouterHook),
247
+ hookData,
248
+ bytes32(uint256(123)) // coinSalt
249
+ );
250
+
251
+ (uint256 amountCurrency, uint256 coinsPurchased) = abi.decode(hookDataOut, (uint256, uint256));
252
+
253
+ // Verify the creator coin was deployed successfully
254
+ ICoin creatorCoin = ICoin(creatorCoinAddress);
255
+ assertEq(creatorCoin.currency(), zora, "currency should be ZORA");
256
+ assertEq(IERC20Metadata(creatorCoinAddress).name(), "Creator Coin Test", "name should match");
257
+ assertEq(IERC20Metadata(creatorCoinAddress).symbol(), "CCT", "symbol should match");
258
+
259
+ // Verify the hook executed and purchased coins
260
+ assertGt(amountCurrency, 0, "amountCurrency should be > 0");
261
+ assertGt(coinsPurchased, 0, "coinsPurchased should be > 0");
262
+
263
+ // Verify creator received the purchased coins (launch reward vests over time for creator coins)
264
+ assertEq(IERC20(creatorCoinAddress).balanceOf(users.creator), coinsPurchased, "creator should have purchased coins");
265
+ }
214
266
  }
@@ -8,7 +8,7 @@ import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
8
8
 
9
9
  contract FactoryTest is BaseTest {
10
10
  function setUp() public override {
11
- super.setUp();
11
+ super.setUpNonForked();
12
12
  }
13
13
 
14
14
  function test_factory_constructor_and_proxy_setup() public {
@@ -115,8 +115,8 @@ contract FactoryTest is BaseTest {
115
115
 
116
116
  address platformReferrer = users.platformReferrer;
117
117
 
118
- bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(weth));
119
- bytes memory poolConfigForGettingAddress = poolConfigChanged ? CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(0)) : poolConfig;
118
+ bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(0));
119
+ bytes memory poolConfigForGettingAddress = poolConfigChanged ? CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(ZORA_TOKEN_ADDRESS) : poolConfig;
120
120
 
121
121
  address expectedCoinAddress = factory.coinAddress(msgSender, name, symbol, poolConfigForGettingAddress, platformReferrer, salt);
122
122