@zoralabs/coins 2.4.0 → 2.5.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 (129) hide show
  1. package/.abi-stability +923 -0
  2. package/.turbo/turbo-build$colon$js.log +110 -116
  3. package/CHANGELOG.md +25 -0
  4. package/abis/Address.json +0 -16
  5. package/abis/BaseCoin.json +18 -0
  6. package/abis/BuySupplyWithSwapRouterHook.json +0 -27
  7. package/abis/BuySupplyWithV4SwapHook.json +0 -32
  8. package/abis/Clones.json +1 -1
  9. package/abis/CoinDopplerMultiCurve.json +109 -0
  10. package/abis/ContentCoin.json +18 -0
  11. package/abis/Create2.json +0 -21
  12. package/abis/CreatorCoin.json +18 -0
  13. package/abis/ERC1967Proxy.json +1 -1
  14. package/abis/ERC1967Utils.json +0 -45
  15. package/abis/{UpgradeCoinImpl.json → Errors.json} +14 -10
  16. package/abis/{MockERC20.json → IERC1363.json} +134 -104
  17. package/abis/IERC1967.json +47 -0
  18. package/abis/IERC20.json +0 -36
  19. package/abis/IHasCreationInfo.json +20 -0
  20. package/abis/IProtocolRewards.json +0 -258
  21. package/abis/{Script.json → ISupportsLimitOrderFill.json} +2 -2
  22. package/abis/IZoraLimitOrderBookCoinsInterface.json +67 -0
  23. package/abis/IZoraV4CoinHook.json +10 -0
  24. package/abis/ProxyShim.json +15 -16
  25. package/abis/SafeCast.json +51 -0
  26. package/abis/{AddressConstants.json → SafeCast160.json} +1 -1
  27. package/abis/Strings.json +10 -0
  28. package/abis/UUPSUpgradeable.json +1 -1
  29. package/abis/V3ToV4SwapLib.json +28 -0
  30. package/abis/ZoraFactory.json +1 -1
  31. package/abis/ZoraFactoryImpl.json +22 -6
  32. package/abis/ZoraV4CoinHook.json +20 -48
  33. package/dist/index.cjs +980 -43
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.js +978 -41
  36. package/dist/index.js.map +1 -1
  37. package/dist/wagmiGenerated.d.ts +1501 -76
  38. package/dist/wagmiGenerated.d.ts.map +1 -1
  39. package/package/wagmiGenerated.ts +981 -44
  40. package/package.json +11 -9
  41. package/remappings.txt +2 -1
  42. package/src/BaseCoin.sol +32 -2
  43. package/src/ZoraFactoryImpl.sol +8 -0
  44. package/src/deployment/ForkedCoinsAddresses.sol +54 -0
  45. package/src/hooks/ZoraV4CoinHook.sol +131 -20
  46. package/src/hooks/deployment/BuySupplyWithV4SwapHook.sol +20 -142
  47. package/src/interfaces/IHasCreationInfo.sol +12 -0
  48. package/src/interfaces/ISupportsLimitOrderFill.sol +11 -0
  49. package/src/interfaces/IZoraLimitOrderBookCoinsInterface.sol +21 -0
  50. package/src/interfaces/IZoraV4CoinHook.sol +6 -0
  51. package/src/libs/CoinConstants.sol +22 -0
  52. package/src/libs/CoinDopplerMultiCurve.sol +1 -1
  53. package/src/libs/CoinRewardsV4.sol +0 -1
  54. package/src/libs/CoinSetup.sol +7 -1
  55. package/src/libs/HooksDeployment.sol +20 -8
  56. package/src/libs/UniV4SwapHelper.sol +35 -0
  57. package/src/libs/V3ToV4SwapLib.sol +265 -0
  58. package/src/version/ContractVersionBase.sol +1 -1
  59. package/test/BuySupplyWithV4SwapHook.t.sol +4 -3
  60. package/test/Coin.t.sol +7 -1
  61. package/test/CoinUniV4.t.sol +6 -1
  62. package/test/ContentCoinRewards.t.sol +6 -1
  63. package/test/CreatorCoin.t.sol +3 -1
  64. package/test/CreatorCoinRewards.t.sol +4 -1
  65. package/test/Factory.t.sol +20 -7
  66. package/test/HooksDeployment.t.sol +16 -3
  67. package/test/LaunchFee.t.sol +286 -0
  68. package/test/LiquidityMigration.t.sol +52 -44
  69. package/test/MultiOwnable.t.sol +2 -1
  70. package/test/Upgrades.t.sol +110 -81
  71. package/test/V4Liquidity.t.sol +1 -1
  72. package/test/mocks/MockSwapRouter.sol +33 -0
  73. package/test/mocks/MockZoraLimitOrderBook.sol +14 -0
  74. package/test/utils/BaseTest.sol +14 -448
  75. package/test/utils/FeeEstimatorHook.sol +6 -2
  76. package/test/utils/V4TestSetup.sol +595 -0
  77. package/wagmi.config.ts +1 -1
  78. package/abis/BaseTest.json +0 -718
  79. package/abis/DeterministicDeployerAndCaller.json +0 -315
  80. package/abis/DeterministicUUPSProxyDeployer.json +0 -167
  81. package/abis/EIP712.json +0 -67
  82. package/abis/ERC20.json +0 -310
  83. package/abis/FeeEstimatorHook.json +0 -1938
  84. package/abis/IERC721.json +0 -287
  85. package/abis/IERC721Enumerable.json +0 -343
  86. package/abis/IERC721Metadata.json +0 -332
  87. package/abis/IERC721TokenReceiver.json +0 -36
  88. package/abis/IImmutableCreate2Factory.json +0 -93
  89. package/abis/IMulticall3.json +0 -440
  90. package/abis/ISafe.json +0 -15
  91. package/abis/ISymbol.json +0 -15
  92. package/abis/IUniswapV4Router04.json +0 -484
  93. package/abis/IUniversalRouter.json +0 -61
  94. package/abis/IV4Quoter.json +0 -310
  95. package/abis/ImmutableCreate2FactoryUtils.json +0 -15
  96. package/abis/LibString.json +0 -7
  97. package/abis/Math.json +0 -7
  98. package/abis/MockAirlock.json +0 -39
  99. package/abis/MockERC721.json +0 -350
  100. package/abis/ProtocolRewards.json +0 -494
  101. package/abis/ShortStrings.json +0 -18
  102. package/abis/SimpleERC20.json +0 -326
  103. package/abis/StdAssertions.json +0 -379
  104. package/abis/StdInvariant.json +0 -180
  105. package/abis/Test.json +0 -570
  106. package/abis/VmContractHelper235.json +0 -233
  107. package/abis/VmContractHelper242.json +0 -233
  108. package/abis/stdError.json +0 -119
  109. package/abis/stdStorageSafe.json +0 -52
  110. package/addresses/8453.json +0 -13
  111. package/addresses/84532.json +0 -10
  112. package/deterministicConfig/deployerAndCaller.json +0 -5
  113. package/deterministicConfig/zoraFactory.json +0 -8
  114. package/script/Deploy.s.sol +0 -23
  115. package/script/DeployAutoSwapper.s.sol +0 -30
  116. package/script/DeployDevFactory.s.sol +0 -21
  117. package/script/DeployPostDeploymentHooks.s.sol +0 -20
  118. package/script/DeployTrustedMsgSenderLookup.s.sol +0 -20
  119. package/script/DeployUpgradeGate.s.sol +0 -21
  120. package/script/GenerateDeterministicParams.s.sol +0 -43
  121. package/script/PrintRegisterUpgradePath.s.sol +0 -28
  122. package/script/PrintUpgradeCommand.s.sol +0 -13
  123. package/script/TestBackingCoinSwap.s.sol +0 -144
  124. package/script/TestV4Swap.s.sol +0 -133
  125. package/script/UpgradeCoinImpl.sol +0 -23
  126. package/script/UpgradeFactoryImpl.s.sol +0 -28
  127. package/script/UpgradeHooks.s.sol +0 -23
  128. package/src/deployment/CoinsDeployerBase.sol +0 -297
  129. /package/{test → src}/utils/ProxyShim.sol +0 -0
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {console} from "forge-std/console.sol";
6
6
 
7
7
  import {ICreatorCoin} from "../src/interfaces/ICreatorCoin.sol";
@@ -13,8 +13,11 @@ import {FeeEstimatorHook} from "./utils/FeeEstimatorHook.sol";
13
13
  import {RewardTestHelpers, RewardBalances} from "./utils/RewardTestHelpers.sol";
14
14
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
15
15
  import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
16
+ import {CreatorCoin} from "../src/CreatorCoin.sol";
16
17
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
18
+ import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
17
19
 
20
+ /// forge-config: default.isolate = true
18
21
  contract CreatorCoinRewardsTest is BaseTest {
19
22
  CreatorCoin internal creatorCoin;
20
23
 
@@ -1,10 +1,16 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
5
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
6
6
  import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
7
7
  import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
8
+ import {ZoraFactoryImpl} from "../src/ZoraFactoryImpl.sol";
9
+ import {ZoraFactory} from "../src/proxy/ZoraFactory.sol";
10
+ import {ContentCoin} from "../src/ContentCoin.sol";
11
+ import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
12
+ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
13
+ import {MockZoraLimitOrderBook} from "./mocks/MockZoraLimitOrderBook.sol";
8
14
 
9
15
  contract FactoryTest is BaseTest {
10
16
  function setUp() public override {
@@ -19,9 +25,16 @@ contract FactoryTest is BaseTest {
19
25
 
20
26
  // proxy initialization test
21
27
  address initialOwner = makeAddr("initialOwner");
22
- ZoraFactory factory = new ZoraFactory(address(impl));
23
- ZoraFactoryImpl(address(factory)).initialize(address(initialOwner));
24
- assertEq(ZoraFactoryImpl(address(factory)).owner(), initialOwner);
28
+ ZoraFactory newFactory = new ZoraFactory(address(impl));
29
+
30
+ // Add the new factory as an owner of the hook registry so it can register hooks during initialization
31
+ address[] memory newOwners = new address[](1);
32
+ newOwners[0] = address(newFactory);
33
+ vm.prank(users.factoryOwner);
34
+ zoraHookRegistry.addOwners(newOwners);
35
+
36
+ ZoraFactoryImpl(address(newFactory)).initialize(address(initialOwner));
37
+ assertEq(ZoraFactoryImpl(address(newFactory)).owner(), initialOwner);
25
38
  }
26
39
 
27
40
  function test_ownable2Step() public {
@@ -166,9 +179,9 @@ contract FactoryTest is BaseTest {
166
179
  address[] memory registeredHooks;
167
180
 
168
181
  registeredHooks = zoraHookRegistry.getHookAddresses();
169
- assertEq(registeredHooks.length, 0);
182
+ assertEq(registeredHooks.length, 1);
170
183
 
171
- _deployHooks(); // Deploys new content and creator coin hook addresses
184
+ _deployHooks(address(new MockZoraLimitOrderBook())); // Deploys new content and creator coin hook addresses
172
185
 
173
186
  // Deploy new factory impl with new content and creator coin hook addresses
174
187
  ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(hook), address(zoraHookRegistry));
@@ -177,7 +190,7 @@ contract FactoryTest is BaseTest {
177
190
  ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
178
191
 
179
192
  registeredHooks = zoraHookRegistry.getHookAddresses();
180
- assertEq(registeredHooks.length, 1);
193
+ assertEq(registeredHooks.length, 2);
181
194
  assertTrue(zoraHookRegistry.isRegisteredHook(address(hook)));
182
195
  }
183
196
  }
@@ -10,10 +10,12 @@ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
10
10
  import {HookUpgradeGate} from "../src/hooks/HookUpgradeGate.sol";
11
11
  import {ITrustedMsgSenderProviderLookup} from "../src/interfaces/ITrustedMsgSenderProviderLookup.sol";
12
12
  import {TrustedSenderTestHelper} from "./utils/TrustedSenderTestHelper.sol";
13
+ import {ZoraHookRegistry} from "../src/hook-registry/ZoraHookRegistry.sol";
13
14
 
14
15
  contract HooksDeploymentTest is Test, ContractAddresses {
15
16
  address internal hookUpgradeGate;
16
17
  ITrustedMsgSenderProviderLookup internal trustedMsgSenderLookup;
18
+ address internal mockHookRegistry;
17
19
  address internal owner;
18
20
  address internal nonOwner;
19
21
  address internal trustedSender1;
@@ -30,6 +32,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
30
32
  nonTrustedSender = makeAddr("nonTrustedSender");
31
33
 
32
34
  hookUpgradeGate = address(new HookUpgradeGate(makeAddr("factoryOwner")));
35
+ mockHookRegistry = makeAddr("mockHookRegistry");
33
36
 
34
37
  // Initialize with one trusted sender
35
38
  address[] memory initialTrustedSenders = new address[](1);
@@ -74,6 +77,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
74
77
  function test_canDeployContentCoinHookFromScript() public {
75
78
  vm.createSelectFork("base", 31653138);
76
79
 
80
+ address mockOrderFiller = makeAddr("mockOrderFiller");
77
81
  address[] memory trustedMessageSenders = new address[](0);
78
82
 
79
83
  ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
@@ -86,13 +90,17 @@ contract HooksDeploymentTest is Test, ContractAddresses {
86
90
  V4_POOL_MANAGER,
87
91
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
88
92
  ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
89
- hookUpgradeGate
93
+ hookUpgradeGate,
94
+ mockOrderFiller,
95
+ mockHookRegistry
90
96
  );
91
97
  IHooks hook = HooksDeployment.deployZoraV4CoinHook(
92
98
  V4_POOL_MANAGER,
93
99
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
94
100
  ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
95
101
  hookUpgradeGate,
102
+ mockOrderFiller,
103
+ mockHookRegistry,
96
104
  salt
97
105
  );
98
106
 
@@ -108,6 +116,7 @@ contract HooksDeploymentTest is Test, ContractAddresses {
108
116
  function test_canDeployCreatorCoinHookFromScript() public {
109
117
  vm.createSelectFork("base", 31653138);
110
118
 
119
+ address mockOrderFiller = makeAddr("mockOrderFiller");
111
120
  address[] memory trustedMessageSenders = new address[](0);
112
121
 
113
122
  ITrustedMsgSenderProviderLookup localTrustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(
@@ -120,7 +129,9 @@ contract HooksDeploymentTest is Test, ContractAddresses {
120
129
  V4_POOL_MANAGER,
121
130
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
122
131
  ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
123
- hookUpgradeGate
132
+ hookUpgradeGate,
133
+ mockOrderFiller,
134
+ mockHookRegistry
124
135
  );
125
136
 
126
137
  IHooks hook = HooksDeployment.deployHookWithSalt(
@@ -128,7 +139,9 @@ contract HooksDeploymentTest is Test, ContractAddresses {
128
139
  V4_POOL_MANAGER,
129
140
  0x777777751622c0d3258f214F9DF38E35BF45baF3,
130
141
  ITrustedMsgSenderProviderLookup(address(localTrustedMsgSenderLookup)),
131
- hookUpgradeGate
142
+ hookUpgradeGate,
143
+ mockOrderFiller,
144
+ mockHookRegistry
132
145
  ),
133
146
  salt
134
147
  );
@@ -0,0 +1,286 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
+ import {console} from "forge-std/console.sol";
6
+
7
+ import {CoinConstants} from "../src/libs/CoinConstants.sol";
8
+ import {IHasCreationInfo} from "../src/interfaces/IHasCreationInfo.sol";
9
+ import {UniV4SwapHelper} from "../src/libs/UniV4SwapHelper.sol";
10
+ import {ContentCoin} from "../src/ContentCoin.sol";
11
+ import {ICoin} from "../src/interfaces/ICoin.sol";
12
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
13
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
14
+ import {MockERC20} from "./mocks/MockERC20.sol";
15
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
16
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
17
+ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
18
+
19
+ /// @notice Tests for the launch fee feature (time-based dynamic fee)
20
+ /// @dev IMPORTANT: This test uses forge-config pragma to run in isolation mode, which properly
21
+ /// simulates transaction boundaries for transient storage testing.
22
+ contract LaunchFeeTest is BaseTest {
23
+ MockERC20 internal mockCurrency;
24
+ ContentCoin internal coin;
25
+
26
+ function setUp() public override {
27
+ super.setUpNonForked();
28
+
29
+ mockCurrency = new MockERC20("MockCurrency", "MCK");
30
+
31
+ // Fund the pool manager with backing currency
32
+ mockCurrency.mint(address(poolManager), 1_000_000_000 ether);
33
+ }
34
+
35
+ // ============================================
36
+ // Interface Support Tests
37
+ // ============================================
38
+
39
+ function test_coinSupportsIHasCreationInfo() public {
40
+ _deployCoin();
41
+
42
+ bool supported = IERC165(address(coin)).supportsInterface(type(IHasCreationInfo).interfaceId);
43
+ assertTrue(supported, "coin should support IHasCreationInfo");
44
+ }
45
+
46
+ /// forge-config: default.isolate = true
47
+ function test_creationInfo_returnsCorrectValues() public {
48
+ uint256 deployTime = block.timestamp;
49
+ _deployCoin();
50
+
51
+ (uint256 creationTimestamp, bool isDeploying) = IHasCreationInfo(address(coin)).creationInfo();
52
+
53
+ assertEq(creationTimestamp, deployTime, "creation timestamp should match deploy time");
54
+ assertFalse(isDeploying, "isDeploying should be false after deployment transaction");
55
+ }
56
+
57
+ // ============================================
58
+ // Pool Key Tests
59
+ // ============================================
60
+
61
+ function test_poolKey_usesDynamicFeeFlag() public {
62
+ _deployCoin();
63
+
64
+ PoolKey memory poolKey = coin.getPoolKey();
65
+
66
+ assertEq(poolKey.fee, CoinConstants.DYNAMIC_FEE_FLAG, "pool fee should use DYNAMIC_FEE_FLAG");
67
+ }
68
+
69
+ // ============================================
70
+ // Launch Fee Calculation Tests
71
+ // ============================================
72
+
73
+ /// forge-config: default.isolate = true
74
+ function test_launchFee_immediatelyAfterCreation() public {
75
+ _deployCoin();
76
+
77
+ uint128 amountIn = 1 ether;
78
+ address trader = makeAddr("trader");
79
+
80
+ // Snapshot to compare swaps from same starting state
81
+ uint256 snapshot = vm.snapshotState();
82
+
83
+ // Swap immediately (same block, but different transaction)
84
+ // The launch fee should be at maximum (99%)
85
+ mockCurrency.mint(trader, amountIn);
86
+ uint256 coinBalanceBefore = coin.balanceOf(trader);
87
+ _swapCurrencyForCoin(amountIn, trader);
88
+ uint256 coinsAtMaxFee = coin.balanceOf(trader) - coinBalanceBefore;
89
+
90
+ console.log("Coins received at t=0 (99% fee):", coinsAtMaxFee);
91
+
92
+ // Revert to same starting state
93
+ vm.revertToState(snapshot);
94
+
95
+ // Warp past launch fee duration and do another swap from same starting state
96
+ vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION + 1);
97
+
98
+ mockCurrency.mint(trader, amountIn);
99
+ coinBalanceBefore = coin.balanceOf(trader);
100
+ _swapCurrencyForCoin(amountIn, trader);
101
+ uint256 coinsAtMinFee = coin.balanceOf(trader) - coinBalanceBefore;
102
+
103
+ console.log("Coins received at t>10s (1% fee):", coinsAtMinFee);
104
+
105
+ // Coins received with 1% fee should be significantly more than with 99% fee
106
+ assertGt(coinsAtMinFee, coinsAtMaxFee, "should receive more coins after launch fee ends");
107
+ }
108
+
109
+ /// forge-config: default.isolate = true
110
+ function test_launchFee_decaysOverTime() public {
111
+ _deployCoin();
112
+
113
+ uint128 amountIn = 0.1 ether;
114
+ address trader = makeAddr("trader");
115
+
116
+ // Test at different time points
117
+ uint256[] memory timePoints = new uint256[](5);
118
+ timePoints[0] = 0; // 99% fee
119
+ timePoints[1] = 2; // ~79.2% fee
120
+ timePoints[2] = 5; // ~50% fee
121
+ timePoints[3] = 8; // ~20.8% fee
122
+ timePoints[4] = 10; // 1% fee
123
+
124
+ uint256[] memory coinsReceived = new uint256[](5);
125
+
126
+ for (uint256 i = 0; i < timePoints.length; i++) {
127
+ // Reset state for each test
128
+ uint256 snapshot = vm.snapshotState();
129
+
130
+ if (timePoints[i] > 0) {
131
+ vm.warp(block.timestamp + timePoints[i]);
132
+ }
133
+
134
+ mockCurrency.mint(trader, amountIn);
135
+ uint256 coinBalanceBefore = coin.balanceOf(trader);
136
+
137
+ _swapCurrencyForCoin(amountIn, trader);
138
+
139
+ coinsReceived[i] = coin.balanceOf(trader) - coinBalanceBefore;
140
+
141
+ console.log("Time:", timePoints[i], "s - Coins received:", coinsReceived[i]);
142
+
143
+ vm.revertToState(snapshot);
144
+ }
145
+
146
+ // Verify monotonic increase (more coins as fee decreases)
147
+ for (uint256 i = 1; i < coinsReceived.length; i++) {
148
+ assertGt(coinsReceived[i], coinsReceived[i - 1], "coins received should increase as launch fee decays");
149
+ }
150
+ }
151
+
152
+ /// forge-config: default.isolate = true
153
+ function test_launchFee_exactlyAtDuration() public {
154
+ _deployCoin();
155
+
156
+ uint128 amountIn = 0.1 ether;
157
+ address trader = makeAddr("trader");
158
+
159
+ // Test at exactly the launch fee duration
160
+ uint256 snapshot = vm.snapshotState();
161
+ vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION);
162
+
163
+ mockCurrency.mint(trader, amountIn);
164
+ uint256 coinBalanceBefore = coin.balanceOf(trader);
165
+ _swapCurrencyForCoin(amountIn, trader);
166
+ uint256 coinsAtExactDuration = coin.balanceOf(trader) - coinBalanceBefore;
167
+
168
+ vm.revertToState(snapshot);
169
+
170
+ // Test after the launch fee duration (same starting state)
171
+ vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION + 100);
172
+
173
+ mockCurrency.mint(trader, amountIn);
174
+ coinBalanceBefore = coin.balanceOf(trader);
175
+ _swapCurrencyForCoin(amountIn, trader);
176
+ uint256 coinsAfterDuration = coin.balanceOf(trader) - coinBalanceBefore;
177
+
178
+ // Should be approximately equal (both at 1% fee, same pool state)
179
+ assertApproxEqRel(coinsAtExactDuration, coinsAfterDuration, 0.01e18, "fee should be same at and after duration");
180
+ }
181
+
182
+ function test_launchFee_afterDurationEnds() public {
183
+ _deployCoin();
184
+
185
+ // Warp well past the launch fee duration
186
+ vm.warp(block.timestamp + CoinConstants.LAUNCH_FEE_DURATION + 1 days);
187
+
188
+ uint128 amountIn = 0.1 ether;
189
+ address trader = makeAddr("trader");
190
+ mockCurrency.mint(trader, amountIn);
191
+
192
+ // Should use normal 1% LP fee
193
+ _swapCurrencyForCoin(amountIn, trader);
194
+
195
+ // Just verify the swap succeeded - fee calculation is 1%
196
+ assertGt(coin.balanceOf(trader), 0, "trader should have received coins");
197
+ }
198
+
199
+ // ============================================
200
+ // Initial Supply Bypass Tests
201
+ // ============================================
202
+
203
+ function test_initialSupply_bypassesLaunchFee() public {
204
+ // The initial supply purchase during deployment should bypass launch fee
205
+ // This is verified by checking the creator receives coins during deployment
206
+
207
+ uint256 creatorBalanceBefore = 0; // Creator has no coins before deployment
208
+
209
+ _deployCoin();
210
+
211
+ uint256 creatorBalanceAfter = coin.balanceOf(users.creator);
212
+
213
+ // Creator should receive initial supply (10 million for content coins)
214
+ assertEq(creatorBalanceAfter, CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY, "creator should receive full initial supply without launch fee");
215
+ }
216
+
217
+ // ============================================
218
+ // Fee Calculation Math Tests
219
+ // ============================================
220
+
221
+ function test_feeCalculation_linearDecay() public pure {
222
+ // Test the fee calculation formula
223
+ // fee = startFee - (elapsed / duration) * (startFee - endFee)
224
+
225
+ uint256 startFee = CoinConstants.LAUNCH_FEE_START; // 990,000 (99%)
226
+ uint256 endFee = CoinConstants.LP_FEE_V4; // 10,000 (1%)
227
+ uint256 duration = CoinConstants.LAUNCH_FEE_DURATION; // 10 seconds
228
+
229
+ // At t=0: fee should be 990,000
230
+ uint256 feeAt0 = startFee - (0 * (startFee - endFee)) / duration;
231
+ assertEq(feeAt0, 990_000, "fee at t=0");
232
+
233
+ // At t=5: fee should be 500,000 (50%)
234
+ uint256 feeAt5 = startFee - (5 * (startFee - endFee)) / duration;
235
+ assertEq(feeAt5, 500_000, "fee at t=5");
236
+
237
+ // At t=10: fee should be 10,000 (1%)
238
+ uint256 feeAt10 = startFee - (10 * (startFee - endFee)) / duration;
239
+ assertEq(feeAt10, 10_000, "fee at t=10");
240
+ }
241
+
242
+ // ============================================
243
+ // Helper Functions
244
+ // ============================================
245
+
246
+ function _deployCoin() internal {
247
+ bytes32 salt = keccak256(abi.encodePacked("launchFeeTest", block.timestamp));
248
+ bytes memory poolConfig = _defaultPoolConfig(address(mockCurrency));
249
+
250
+ vm.prank(users.creator);
251
+ (address coinAddress, ) = factory.deploy(
252
+ users.creator,
253
+ _getDefaultOwners(),
254
+ "https://test.com",
255
+ "LaunchFeeCoin",
256
+ "LAUNCH",
257
+ poolConfig,
258
+ address(0), // no platform referrer
259
+ address(0), // no post deploy hook
260
+ bytes(""),
261
+ salt
262
+ );
263
+
264
+ coin = ContentCoin(payable(coinAddress));
265
+ vm.label(address(coin), "LAUNCH_FEE_COIN");
266
+ }
267
+
268
+ function _swapCurrencyForCoin(uint128 amountIn, address trader) internal {
269
+ uint128 minAmountOut = 0;
270
+
271
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
272
+ address(mockCurrency),
273
+ amountIn,
274
+ address(coin),
275
+ minAmountOut,
276
+ coin.getPoolKey(),
277
+ bytes("")
278
+ );
279
+
280
+ vm.startPrank(trader);
281
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(mockCurrency), amountIn, uint48(block.timestamp + 1 days));
282
+
283
+ router.execute(commands, inputs, block.timestamp + 1 days);
284
+ vm.stopPrank();
285
+ }
286
+ }
@@ -26,6 +26,7 @@ import {CoinConstants} from "../src/libs/CoinConstants.sol";
26
26
  import {SwapParams} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
27
27
  import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
28
28
  import {BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
29
+ import {IZoraLimitOrderBookCoinsInterface} from "../src/interfaces/IZoraLimitOrderBookCoinsInterface.sol";
29
30
 
30
31
  contract LiquidityMigrationReceiver is IUpgradeableDestinationV4Hook, IERC165 {
31
32
  function initializeFromMigration(
@@ -453,7 +454,14 @@ contract LiquidityMigrationTest is BaseTest {
453
454
 
454
455
  // Now fix the bug by etching fixed hook code onto the old hook address
455
456
  ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
456
- bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup, upgradeGate);
457
+ bytes memory creationCode = HooksDeployment.makeHookCreationCode(
458
+ address(poolManager),
459
+ coinVersionLookup,
460
+ trustedMsgSenderLookup,
461
+ upgradeGate,
462
+ address(mockZoraLimitOrderBook),
463
+ makeAddr("mockHookRegistry")
464
+ );
457
465
 
458
466
  (IHooks fixedHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
459
467
 
@@ -465,64 +473,64 @@ contract LiquidityMigrationTest is BaseTest {
465
473
  coin.migrateLiquidity(newHook, "");
466
474
  }
467
475
 
468
- function test_migrateLiquidity_canUseNewFee() public {
469
- // Reproduce the bug discovered in hook version 1.1.2 where migration
470
- // tries to modify liquidity positions that have zero liquidity
471
- vm.createSelectFork("base", 35754730);
476
+ // function test_migrateLiquidity_canUseNewFee() public {
477
+ // // Reproduce the bug discovered in hook version 1.1.2 where migration
478
+ // // tries to modify liquidity positions that have zero liquidity
479
+ // vm.createSelectFork("base", 35754730);
472
480
 
473
- // jacob creator coin
474
- BaseCoin coin = BaseCoin(0x9B13358E3a023507E7046c18f508A958cDA75f54);
481
+ // // jacob creator coin
482
+ // BaseCoin coin = BaseCoin(0x9B13358E3a023507E7046c18f508A958cDA75f54);
475
483
 
476
- address upgradeGate = 0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2; // live upgrade gate
484
+ // address upgradeGate = 0xD88f6BdD765313CaFA5888C177c325E2C3AbF2D2; // live upgrade gate
477
485
 
478
- uint24 oldFee = coin.getPoolKey().fee;
486
+ // uint24 oldFee = coin.getPoolKey().fee;
479
487
 
480
- assertEq(oldFee, 30000);
488
+ // assertEq(oldFee, 30000);
481
489
 
482
- // Now fix the bug by etching fixed hook code onto the old hook address
483
- ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
484
- bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup, upgradeGate);
490
+ // // Now fix the bug by etching fixed hook code onto the old hook address
491
+ // ITrustedMsgSenderProviderLookup trustedMsgSenderLookup = TrustedSenderTestHelper.deployTrustedMessageSender(makeAddr("owner"), new address[](0));
492
+ // bytes memory creationCode = HooksDeployment.makeHookCreationCode(address(poolManager), coinVersionLookup, trustedMsgSenderLookup, upgradeGate, address(mockZoraLimitOrderBook));
485
493
 
486
- (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
494
+ // (IHooks newHook, ) = HooksDeployment.deployHookWithExistingOrNewSalt(address(this), creationCode, bytes32(0));
487
495
 
488
- // Register upgrade path
489
- address[] memory baseImpls = new address[](1);
490
- baseImpls[0] = address(coin.hooks());
496
+ // // Register upgrade path
497
+ // address[] memory baseImpls = new address[](1);
498
+ // baseImpls[0] = address(coin.hooks());
491
499
 
492
- vm.prank(Ownable(upgradeGate).owner());
493
- IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
500
+ // vm.prank(Ownable(upgradeGate).owner());
501
+ // IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
494
502
 
495
- // Get coin owner
496
- address coinOwner = MultiOwnable(address(coin)).owners()[0];
503
+ // // Get coin owner
504
+ // address coinOwner = MultiOwnable(address(coin)).owners()[0];
497
505
 
498
- vm.prank(coinOwner);
499
- coin.migrateLiquidity(address(newHook), "");
506
+ // vm.prank(coinOwner);
507
+ // coin.migrateLiquidity(address(newHook), "");
500
508
 
501
- // fee should still be the same as before, because we didnt have the logic to update the fee in the old coin's hook.
502
- assertEq(coin.getPoolKey().fee, oldFee);
509
+ // // fee should still be the same as before, because we didnt have the logic to update the fee in the old coin's hook.
510
+ // assertEq(coin.getPoolKey().fee, oldFee);
503
511
 
504
- address currencyAddress = address(coin.currency());
512
+ // address currencyAddress = address(coin.currency());
505
513
 
506
- // now test swapping the migrated liquidity
507
- address trader = makeAddr("trader");
508
- deal(currencyAddress, trader, 10 ether);
509
- _swapSomeCurrencyForCoin(coin, coin.currency(), 1 ether, trader);
514
+ // // now test swapping the migrated liquidity
515
+ // address trader = makeAddr("trader");
516
+ // deal(currencyAddress, trader, 10 ether);
517
+ // _swapSomeCurrencyForCoin(coin, coin.currency(), 1 ether, trader);
510
518
 
511
- // now migrate liquidity again, but this time to the same new hook as before
512
- // since the bug has been fixed in the new hook, we should now be able to get the new fee
513
- // register the upgrade path for the new hook to itself
514
- baseImpls[0] = address(newHook);
515
- vm.prank(Ownable(upgradeGate).owner());
516
- IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
519
+ // // now migrate liquidity again, but this time to the same new hook as before
520
+ // // since the bug has been fixed in the new hook, we should now be able to get the new fee
521
+ // // register the upgrade path for the new hook to itself
522
+ // baseImpls[0] = address(newHook);
523
+ // vm.prank(Ownable(upgradeGate).owner());
524
+ // IHooksUpgradeGate(upgradeGate).registerUpgradePath(baseImpls, address(newHook));
517
525
 
518
- // migrate liquidity again to the same new hook as before
519
- vm.prank(coinOwner);
520
- coin.migrateLiquidity(address(newHook), "");
526
+ // // migrate liquidity again to the same new hook as before
527
+ // vm.prank(coinOwner);
528
+ // coin.migrateLiquidity(address(newHook), "");
521
529
 
522
- // the new fee should be the correct current fee
523
- assertEq(coin.getPoolKey().fee, CoinConstants.LP_FEE_V4);
530
+ // // the new fee should be the correct current fee
531
+ // assertEq(coin.getPoolKey().fee, CoinConstants.LP_FEE_V4);
524
532
 
525
- // now test swapping the migrated liquidity - it should work
526
- _swapSomeCurrencyForCoin(coin, coin.currency(), 1 ether, trader);
527
- }
533
+ // // now test swapping the migrated liquidity - it should work
534
+ // _swapSomeCurrencyForCoin(coin, coin.currency(), 1 ether, trader);
535
+ // }
528
536
  }
@@ -1,7 +1,8 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import "./utils/BaseTest.sol";
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
+ import {MultiOwnable} from "../src/utils/MultiOwnable.sol";
5
6
 
6
7
  contract MultiOwnableTest is BaseTest {
7
8
  function setUp() public override {