@zoralabs/coins 0.7.1 → 1.0.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 (167) hide show
  1. package/.turbo/turbo-build.log +106 -84
  2. package/CHANGELOG.md +68 -0
  3. package/abis/BadImpl.json +15 -0
  4. package/abis/BalanceDeltaLibrary.json +15 -0
  5. package/abis/BaseCoin.json +1350 -0
  6. package/abis/BaseCoinDeployHook.json +78 -0
  7. package/abis/BaseHook.json +897 -0
  8. package/abis/BaseTest.json +60 -91
  9. package/abis/BeforeSwapDeltaLibrary.json +15 -0
  10. package/abis/BuySupplyWithSwapRouterHook.json +126 -0
  11. package/abis/Coin.json +214 -150
  12. package/abis/CoinConstants.json +65 -0
  13. package/abis/CoinDopplerMultiCurve.json +38 -0
  14. package/abis/CoinRewardsV4.json +54 -0
  15. package/abis/CoinTest.json +66 -111
  16. package/abis/CoinUniV4Test.json +1053 -0
  17. package/abis/CoinV4.json +1687 -0
  18. package/abis/CurrencyLibrary.json +25 -0
  19. package/abis/DeployHooks.json +9 -0
  20. package/abis/DeployScript.json +47 -0
  21. package/abis/DeployedCoinVersionLookup.json +21 -0
  22. package/abis/DeployedCoinVersionLookupTest.json +716 -0
  23. package/abis/DifferentNamespaceVersionLookup.json +39 -0
  24. package/abis/DopplerUniswapV3Test.json +62 -184
  25. package/abis/ERC20.json +310 -0
  26. package/abis/FactoryTest.json +98 -98
  27. package/abis/FakeHookNoInterface.json +21 -0
  28. package/abis/FeeEstimatorHook.json +1528 -0
  29. package/abis/Hooks.json +28 -0
  30. package/abis/HooksDeployment.json +23 -0
  31. package/abis/HooksTest.json +698 -0
  32. package/abis/IAllowanceTransfer.json +486 -0
  33. package/abis/ICoin.json +62 -69
  34. package/abis/ICoinDeployHook.json +31 -0
  35. package/abis/ICoinV3.json +879 -0
  36. package/abis/ICoinV4.json +915 -0
  37. package/abis/IContractMetadata.json +28 -0
  38. package/abis/IDeployedCoinVersionLookup.json +21 -0
  39. package/abis/IEIP712.json +15 -0
  40. package/abis/IEIP712_v4.json +15 -0
  41. package/abis/IERC20Minimal.json +172 -0
  42. package/abis/IERC6909Claims.json +288 -0
  43. package/abis/IERC721.json +36 -36
  44. package/abis/IERC721Permit_v4.json +88 -0
  45. package/abis/IExtsload.json +64 -0
  46. package/abis/IExttload.json +40 -0
  47. package/abis/IHasAfterCoinDeploy.json +31 -0
  48. package/abis/IHasContractName.json +15 -0
  49. package/abis/IHasPoolKey.json +42 -0
  50. package/abis/IHasRewardsRecipients.json +54 -0
  51. package/abis/IHasSwapPath.json +60 -0
  52. package/abis/IHooks.json +789 -0
  53. package/abis/IImmutableState.json +15 -0
  54. package/abis/IMsgSender.json +15 -0
  55. package/abis/IMulticall_v4.json +21 -0
  56. package/abis/INotifier.json +187 -0
  57. package/abis/IPermit2.json +865 -0
  58. package/abis/IPermit2Forwarder.json +138 -0
  59. package/abis/IPoolConfigEncoding.json +46 -0
  60. package/abis/IPoolInitializer_v4.json +53 -0
  61. package/abis/IPoolManager.json +1286 -0
  62. package/abis/IPositionManager.json +712 -0
  63. package/abis/IProtocolFees.json +174 -0
  64. package/abis/ISignatureTransfer.json +394 -0
  65. package/abis/ISubscriber.json +89 -0
  66. package/abis/ISwapPathRouter.json +92 -0
  67. package/abis/ISwapRouter.json +82 -0
  68. package/abis/IUniversalRouter.json +61 -0
  69. package/abis/IUnlockCallback.json +21 -0
  70. package/abis/IUnorderedNonce.json +44 -0
  71. package/abis/IV4Quoter.json +310 -0
  72. package/abis/IV4Router.json +47 -0
  73. package/abis/IZoraFactory.json +328 -4
  74. package/abis/IZoraV4CoinHook.json +427 -0
  75. package/abis/ImmutableState.json +36 -0
  76. package/abis/LPFeeLibrary.json +65 -0
  77. package/abis/MockERC20.json +21 -0
  78. package/abis/MultiOwnableTest.json +60 -91
  79. package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
  80. package/abis/PrintUpgradeCommand.json +9 -0
  81. package/abis/ProxyShim.json +24 -0
  82. package/abis/Simulate.json +0 -91
  83. package/abis/StateLibrary.json +80 -0
  84. package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
  85. package/abis/TestV4Swap.json +9 -0
  86. package/abis/{CoinSetup.json → UniV3BuySell.json} +5 -0
  87. package/abis/UniV3Errors.json +32 -0
  88. package/abis/UpgradeCoinImpl.json +47 -0
  89. package/abis/UpgradeFactoryImpl.json +9 -0
  90. package/abis/UpgradesTest.json +671 -0
  91. package/abis/Vm.json +1482 -111
  92. package/abis/VmSafe.json +856 -32
  93. package/abis/ZoraFactoryImpl.json +450 -1
  94. package/abis/ZoraV4CoinHook.json +1439 -0
  95. package/addresses/8453.json +8 -3
  96. package/addresses/84532.json +8 -3
  97. package/dist/index.cjs +1998 -184
  98. package/dist/index.cjs.map +1 -1
  99. package/dist/index.js +1989 -178
  100. package/dist/index.js.map +1 -1
  101. package/dist/wagmiGenerated.d.ts +2852 -688
  102. package/dist/wagmiGenerated.d.ts.map +1 -1
  103. package/package/wagmiGenerated.ts +1992 -173
  104. package/package.json +7 -2
  105. package/remappings.txt +6 -1
  106. package/script/CoinsDeployerBase.sol +105 -10
  107. package/script/DeployDevFactory.s.sol +21 -0
  108. package/script/DeployHooks.s.sol +22 -0
  109. package/script/PrintUpgradeCommand.s.sol +13 -0
  110. package/script/Simulate.s.sol +4 -12
  111. package/script/TestBackingCoinSwap.s.sol +146 -0
  112. package/script/TestV4Swap.s.sol +136 -0
  113. package/script/UpgradeCoinImpl.sol +2 -2
  114. package/script/UpgradeFactoryImpl.s.sol +23 -0
  115. package/src/BaseCoin.sol +176 -0
  116. package/src/Coin.sol +93 -515
  117. package/src/CoinV4.sol +121 -0
  118. package/src/ZoraFactoryImpl.sol +257 -57
  119. package/src/hooks/ZoraV4CoinHook.sol +195 -0
  120. package/src/hooks/deployment/BaseCoinDeployHook.sol +62 -0
  121. package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +80 -0
  122. package/src/interfaces/ICoin.sol +35 -39
  123. package/src/interfaces/ICoinDeployHook.sol +8 -0
  124. package/src/interfaces/ICoinV3.sol +71 -0
  125. package/src/interfaces/ICoinV4.sol +69 -0
  126. package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
  127. package/src/interfaces/IMsgSender.sol +9 -0
  128. package/src/interfaces/IPoolConfigEncoding.sol +14 -0
  129. package/src/interfaces/ISwapPathRouter.sol +14 -0
  130. package/src/interfaces/ISwapRouter.sol +1 -35
  131. package/src/interfaces/IZoraFactory.sol +97 -7
  132. package/src/interfaces/IZoraV4CoinHook.sol +116 -0
  133. package/src/libs/CoinCommon.sol +15 -0
  134. package/src/libs/CoinConfigurationVersions.sol +116 -1
  135. package/src/{utils → libs}/CoinConstants.sol +11 -6
  136. package/src/libs/CoinDopplerMultiCurve.sol +134 -0
  137. package/src/libs/CoinDopplerUniV3.sol +19 -171
  138. package/src/libs/CoinRewards.sol +195 -0
  139. package/src/libs/CoinRewardsV4.sol +180 -0
  140. package/src/libs/CoinSetup.sol +40 -20
  141. package/src/libs/CoinSetupV3.sol +50 -0
  142. package/src/libs/DopplerMath.sol +156 -0
  143. package/src/libs/HooksDeployment.sol +84 -0
  144. package/src/libs/MarketConstants.sol +4 -0
  145. package/src/libs/PoolStateReader.sol +22 -0
  146. package/src/libs/UniV3BuySell.sol +231 -0
  147. package/src/libs/UniV3Errors.sol +11 -0
  148. package/src/libs/UniV4SwapHelper.sol +65 -0
  149. package/src/libs/UniV4SwapToCurrency.sol +109 -0
  150. package/src/libs/V4Liquidity.sol +129 -0
  151. package/src/types/PoolConfiguration.sol +15 -0
  152. package/src/utils/DeployedCoinVersionLookup.sol +52 -0
  153. package/src/version/ContractVersionBase.sol +1 -1
  154. package/test/Coin.t.sol +94 -101
  155. package/test/CoinDopplerUniV3.t.sol +35 -184
  156. package/test/CoinUniV4.t.sol +752 -0
  157. package/test/DeploymentHooks.t.sol +270 -0
  158. package/test/Factory.t.sol +84 -50
  159. package/test/MultiOwnable.t.sol +6 -3
  160. package/test/Upgrades.t.sol +68 -0
  161. package/test/mocks/MockERC20.sol +12 -0
  162. package/test/utils/BaseTest.sol +124 -59
  163. package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
  164. package/test/utils/FeeEstimatorHook.sol +84 -0
  165. package/test/utils/ProxyShim.sol +17 -0
  166. package/wagmi.config.ts +10 -9
  167. package/src/libs/CoinLegacy.sol +0 -48
@@ -0,0 +1,270 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import {BaseTest} from "./utils/BaseTest.sol";
5
+ import {BuySupplyWithSwapRouterHook} from "../src/hooks/deployment/BuySupplyWithSwapRouterHook.sol";
6
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7
+ import {IUniswapV3Pool} from "../src/interfaces/IUniswapV3Pool.sol";
8
+ import {Coin} from "../src/Coin.sol";
9
+ import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
10
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
11
+ import {ICoin} from "../src/interfaces/ICoin.sol";
12
+ import {IHasAfterCoinDeploy} from "../src/hooks/deployment/BaseCoinDeployHook.sol";
13
+ import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
14
+ import {ISwapRouter} from "../src/interfaces/ISwapRouter.sol";
15
+ import {CoinConstants} from "../src/libs/CoinConstants.sol";
16
+
17
+ // Create a fake hook that doesn't support the IHasAfterCoinDeploy interface
18
+ contract FakeHookNoInterface {
19
+ function supportsInterface(bytes4) external pure returns (bool) {
20
+ return false; // Always returns false, doesn't support any interface
21
+ }
22
+ }
23
+
24
+ contract HooksTest is BaseTest {
25
+ address constant zora = 0x1111111111166b7FE7bd91427724B487980aFc69;
26
+
27
+ function _generateDefaultPoolConfig(address currency) internal pure returns (bytes memory) {
28
+ return
29
+ _generatePoolConfig(
30
+ CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
31
+ currency,
32
+ DEFAULT_DISCOVERY_TICK_LOWER,
33
+ DEFAULT_DISCOVERY_TICK_UPPER,
34
+ DEFAULT_NUM_DISCOVERY_POSITIONS,
35
+ DEFAULT_DISCOVERY_SUPPLY_SHARE
36
+ );
37
+ }
38
+
39
+ function setUp() public override {
40
+ super.setUpWithBlockNumber(29585474);
41
+ }
42
+
43
+ function _deployWithHook(address hook, bytes memory hookData, address currency) internal returns (address, bytes memory) {
44
+ bytes memory poolConfig = _generateDefaultPoolConfig(currency);
45
+ return
46
+ factory.deployWithHook(
47
+ users.creator,
48
+ _getDefaultOwners(),
49
+ "https://test.com",
50
+ "Testcoin",
51
+ "TEST",
52
+ poolConfig,
53
+ users.platformReferrer,
54
+ hook,
55
+ hookData
56
+ );
57
+ }
58
+
59
+ function _encodeAfterCoinDeploy(address buyRecipient, bytes memory swapRouterCall) internal pure returns (bytes memory) {
60
+ return abi.encode(buyRecipient, swapRouterCall);
61
+ }
62
+
63
+ function _encodeExactInputSingle(address buyRecipient, ISwapRouter.ExactInputSingleParams memory params) internal pure returns (bytes memory) {
64
+ return _encodeAfterCoinDeploy(buyRecipient, abi.encodeWithSelector(ISwapRouter.exactInputSingle.selector, params));
65
+ }
66
+
67
+ function _encodeExactInput(address buyRecipient, ISwapRouter.ExactInputParams memory params) internal pure returns (bytes memory) {
68
+ return _encodeAfterCoinDeploy(buyRecipient, abi.encodeWithSelector(ISwapRouter.exactInput.selector, params));
69
+ }
70
+
71
+ function test_buySupplyWithEthUsingV3Hook_withExactInputSingle(uint256 initialOrderSize) public {
72
+ vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
73
+ vm.assume(initialOrderSize < 1 ether);
74
+
75
+ vm.deal(users.creator, initialOrderSize);
76
+
77
+ BuySupplyWithSwapRouterHook hook = new BuySupplyWithSwapRouterHook(factory, address(swapRouter));
78
+
79
+ bytes memory hookData = _encodeExactInputSingle(
80
+ users.creator,
81
+ ISwapRouter.ExactInputSingleParams({
82
+ tokenIn: address(weth),
83
+ tokenOut: zora,
84
+ fee: 3000,
85
+ recipient: address(hook),
86
+ amountIn: initialOrderSize,
87
+ amountOutMinimum: 0,
88
+ sqrtPriceLimitX96: 0
89
+ })
90
+ );
91
+
92
+ vm.prank(users.creator);
93
+ (address coinAddress, bytes memory hookDataOut) = factory.deployWithHook{value: initialOrderSize}(
94
+ users.creator,
95
+ _getDefaultOwners(),
96
+ "https://test.com",
97
+ "Testcoin",
98
+ "TEST",
99
+ _generateDefaultPoolConfig(zora),
100
+ users.platformReferrer,
101
+ address(hook),
102
+ hookData
103
+ );
104
+
105
+ coin = Coin(payable(coinAddress));
106
+ pool = IUniswapV3Pool(coin.poolAddress());
107
+
108
+ (uint256 amountCurrency, uint256 coinsPurchased) = abi.decode(hookDataOut, (uint256, uint256));
109
+
110
+ assertEq(coin.currency(), zora, "currency");
111
+ assertGt(amountCurrency, 0, "amountCurrency > 0");
112
+ assertGt(coinsPurchased, 0, "coinsPurchased > 0");
113
+ assertEq(coin.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased, "balanceOf creator");
114
+ assertGt(IERC20(zora).balanceOf(address(pool)), 0, "Pool ZORA balance");
115
+ }
116
+
117
+ function test_buySupplyWithEthUsingV3Hook_withExactInputMultiHop(uint256 initialOrderSize) public {
118
+ vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
119
+ vm.assume(initialOrderSize < 1 ether);
120
+
121
+ vm.deal(users.creator, initialOrderSize);
122
+
123
+ // lets try weth to usdc to zora
124
+
125
+ BuySupplyWithSwapRouterHook hook = new BuySupplyWithSwapRouterHook(factory, address(swapRouter));
126
+
127
+ uint24 poolFee = 3000;
128
+
129
+ bytes memory hookData = _encodeExactInput(
130
+ users.creator,
131
+ ISwapRouter.ExactInputParams({
132
+ path: abi.encodePacked(address(weth), poolFee, USDC_ADDRESS, poolFee, zora),
133
+ recipient: address(hook),
134
+ amountIn: initialOrderSize,
135
+ amountOutMinimum: 0
136
+ })
137
+ );
138
+
139
+ vm.prank(users.creator);
140
+ (address coinAddress, bytes memory hookDataOut) = factory.deployWithHook{value: initialOrderSize}(
141
+ users.creator,
142
+ _getDefaultOwners(),
143
+ "https://test.com",
144
+ "Testcoin",
145
+ "TEST",
146
+ _generateDefaultPoolConfig(zora),
147
+ users.platformReferrer,
148
+ address(hook),
149
+ hookData
150
+ );
151
+
152
+ coin = Coin(payable(coinAddress));
153
+ pool = IUniswapV3Pool(coin.poolAddress());
154
+
155
+ (uint256 amountCurrency, uint256 coinsPurchased) = abi.decode(hookDataOut, (uint256, uint256));
156
+
157
+ assertEq(coin.currency(), zora, "currency");
158
+ assertGt(amountCurrency, 0, "amountCurrency > 0");
159
+ assertGt(coinsPurchased, 0, "coinsPurchased > 0");
160
+ assertEq(coin.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased, "balanceOf creator");
161
+ assertGt(IERC20(zora).balanceOf(address(pool)), 0, "Pool ZORA balance");
162
+ }
163
+
164
+ function test_buySupplyWithEthUsingV3Hook_revertsWhenBadCall() public {
165
+ vm.deal(users.creator, 0.0001 ether);
166
+
167
+ BuySupplyWithSwapRouterHook hook = new BuySupplyWithSwapRouterHook(factory, address(swapRouter));
168
+
169
+ uint24 poolFee = 3000;
170
+
171
+ // exact output single is not supported
172
+ bytes memory hookData = _encodeAfterCoinDeploy(
173
+ users.creator,
174
+ abi.encodeWithSelector(
175
+ ISwapRouter.exactOutputSingle.selector,
176
+ ISwapRouter.ExactOutputParams({
177
+ path: abi.encodePacked(address(weth), poolFee, USDC_ADDRESS, poolFee, zora),
178
+ recipient: address(hook),
179
+ amountOut: 0.0001 ether,
180
+ amountInMaximum: 0
181
+ })
182
+ )
183
+ );
184
+
185
+ vm.prank(users.creator);
186
+ vm.expectRevert(BuySupplyWithSwapRouterHook.InvalidSwapRouterCall.selector);
187
+ factory.deployWithHook{value: 0.0001 ether}(
188
+ users.creator,
189
+ _getDefaultOwners(),
190
+ "https://test.com",
191
+ "Testcoin",
192
+ "TEST",
193
+ _generateDefaultPoolConfig(zora),
194
+ users.platformReferrer,
195
+ address(hook),
196
+ hookData
197
+ );
198
+ }
199
+
200
+ function test_buySupplyWithEthUsingV3Hook_revertsWhenHookNotRecipient() public {
201
+ uint256 initialOrderSize = 0.0001 ether;
202
+ vm.deal(users.creator, initialOrderSize);
203
+
204
+ BuySupplyWithSwapRouterHook hook = new BuySupplyWithSwapRouterHook(factory, address(swapRouter));
205
+
206
+ bytes memory hookData = _encodeExactInputSingle(
207
+ users.creator,
208
+ ISwapRouter.ExactInputSingleParams({
209
+ tokenIn: address(weth),
210
+ tokenOut: zora,
211
+ fee: 3000,
212
+ recipient: address(users.creator),
213
+ amountIn: initialOrderSize,
214
+ amountOutMinimum: 0,
215
+ sqrtPriceLimitX96: 0
216
+ })
217
+ );
218
+
219
+ vm.prank(users.creator);
220
+ vm.expectRevert(BuySupplyWithSwapRouterHook.Erc20NotReceived.selector);
221
+ factory.deployWithHook{value: initialOrderSize}(
222
+ users.creator,
223
+ _getDefaultOwners(),
224
+ "https://test.com",
225
+ "Testcoin",
226
+ "TEST",
227
+ _generateDefaultPoolConfig(zora),
228
+ users.platformReferrer,
229
+ address(hook),
230
+ hookData
231
+ );
232
+ }
233
+
234
+ function test_deployWithHook_revertsWhenEthAndNoHook() public {
235
+ uint256 initialOrderSize = 0.0001 ether;
236
+ vm.deal(users.creator, initialOrderSize);
237
+
238
+ vm.prank(users.creator);
239
+ vm.expectRevert(IZoraFactory.EthTransferInvalid.selector);
240
+ factory.deployWithHook{value: initialOrderSize}(
241
+ users.creator,
242
+ _getDefaultOwners(),
243
+ "https://test.com",
244
+ "Testcoin",
245
+ "TEST",
246
+ _generateDefaultPoolConfig(zora),
247
+ users.platformReferrer,
248
+ address(0),
249
+ ""
250
+ );
251
+ }
252
+
253
+ function test_invalidHookReverts() public {
254
+ // Deploy a fake hook that doesn't support the IHasAfterCoinDeploy interface
255
+ FakeHookNoInterface fakeHook = new FakeHookNoInterface();
256
+
257
+ bytes memory hookData = "";
258
+
259
+ // Expect the transaction to revert with InvalidHook error
260
+ vm.expectRevert(IZoraFactory.InvalidHook.selector);
261
+ vm.prank(users.creator);
262
+ _deployWithHook(address(fakeHook), hookData, zora);
263
+ }
264
+
265
+ function test_noHookWorksAsNormal() public {
266
+ // Expect the transaction to revert with InvalidHook error
267
+ vm.prank(users.creator);
268
+ _deployWithHook(address(0), bytes(""), zora);
269
+ }
270
+ }
@@ -2,6 +2,7 @@
2
2
  pragma solidity ^0.8.13;
3
3
 
4
4
  import "./utils/BaseTest.sol";
5
+ import {CoinConstants} from "../src/libs/CoinConstants.sol";
5
6
 
6
7
  contract FactoryTest is BaseTest {
7
8
  function setUp() public override {
@@ -9,8 +10,9 @@ contract FactoryTest is BaseTest {
9
10
  }
10
11
 
11
12
  function test_constructor() public view {
12
- assertEq(factory.coinImpl(), address(coinImpl));
13
- assertEq(factory.owner(), users.factoryOwner);
13
+ assertEq(ZoraFactoryImpl(address(factory)).coinImpl(), address(coinV3Impl));
14
+ assertEq(ZoraFactoryImpl(address(factory)).owner(), users.factoryOwner);
15
+ assertEq(ZoraFactoryImpl(address(factory)).coinV4Impl(), address(coinV4Impl));
14
16
  }
15
17
 
16
18
  function test_deploy_no_eth() public {
@@ -23,9 +25,8 @@ contract FactoryTest is BaseTest {
23
25
  "https://test2.com",
24
26
  "Test2 Token",
25
27
  "TEST2",
28
+ _generatePoolConfig(address(weth)),
26
29
  users.platformReferrer,
27
- address(weth),
28
- MarketConstants.LP_TICK_LOWER_WETH,
29
30
  0
30
31
  );
31
32
  coin = Coin(payable(coinAddress));
@@ -58,7 +59,7 @@ contract FactoryTest is BaseTest {
58
59
  }
59
60
 
60
61
  function test_deploy_with_eth(uint256 initialOrderSize) public {
61
- vm.assume(initialOrderSize > MIN_ORDER_SIZE);
62
+ vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
62
63
  vm.assume(initialOrderSize < 10 ether);
63
64
 
64
65
  address[] memory owners = new address[](1);
@@ -72,9 +73,8 @@ contract FactoryTest is BaseTest {
72
73
  "https://test2.com",
73
74
  "Test2 Token",
74
75
  "TEST2",
76
+ _generatePoolConfig(address(weth)),
75
77
  users.platformReferrer,
76
- address(weth),
77
- MarketConstants.LP_TICK_LOWER_WETH,
78
78
  initialOrderSize
79
79
  );
80
80
  coin = Coin(payable(coinAddress));
@@ -97,7 +97,7 @@ contract FactoryTest is BaseTest {
97
97
  }
98
98
 
99
99
  function test_deploy_with_weth(uint256 initialOrderSize) public {
100
- vm.assume(initialOrderSize > MIN_ORDER_SIZE);
100
+ vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
101
101
  vm.assume(initialOrderSize < 10 ether);
102
102
 
103
103
  address[] memory owners = new address[](1);
@@ -118,9 +118,8 @@ contract FactoryTest is BaseTest {
118
118
  "https://test2.com",
119
119
  "Test2 Token",
120
120
  "TEST2",
121
+ _generatePoolConfig(address(weth)),
121
122
  users.platformReferrer,
122
- address(weth),
123
- MarketConstants.LP_TICK_LOWER_WETH,
124
123
  initialOrderSize
125
124
  );
126
125
  }
@@ -138,9 +137,8 @@ contract FactoryTest is BaseTest {
138
137
  "https://test2.com",
139
138
  "Test2 Token",
140
139
  "TEST2",
140
+ _generatePoolConfig(address(weth)),
141
141
  users.platformReferrer,
142
- address(weth),
143
- MarketConstants.LP_TICK_LOWER_WETH,
144
142
  orderSize
145
143
  );
146
144
  coin = Coin(payable(coinAddress));
@@ -159,9 +157,8 @@ contract FactoryTest is BaseTest {
159
157
  "https://testcoinusdcpair.com",
160
158
  "Testcoinusdcpair",
161
159
  "TESTCOINUSDCPAIR",
160
+ _generatePoolConfig(USDC_ADDRESS),
162
161
  users.platformReferrer,
163
- USDC_ADDRESS,
164
- USDC_TICK_LOWER,
165
162
  0
166
163
  );
167
164
  coin = Coin(payable(coinAddress));
@@ -192,9 +189,8 @@ contract FactoryTest is BaseTest {
192
189
  "https://testcoinusdcpair.com",
193
190
  "Testcoinusdcpair",
194
191
  "TESTCOINUSDCPAIR",
192
+ _generatePoolConfig(USDC_ADDRESS),
195
193
  users.platformReferrer,
196
- USDC_ADDRESS,
197
- USDC_TICK_LOWER,
198
194
  orderSize
199
195
  );
200
196
  coin = Coin(payable(coinAddress));
@@ -203,7 +199,7 @@ contract FactoryTest is BaseTest {
203
199
  vm.label(address(pool), "POOL");
204
200
 
205
201
  assertEq(coin.currency(), USDC_ADDRESS, "currency");
206
- assertEq(coin.balanceOf(users.creator), CREATOR_LAUNCH_REWARD + coinsPurchased);
202
+ assertEq(coin.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased);
207
203
  }
208
204
 
209
205
  function test_deploy_with_usdc_revert_payout_recipient_zero() public {
@@ -217,9 +213,8 @@ contract FactoryTest is BaseTest {
217
213
  "https://testcoinusdcpair.com",
218
214
  "Testcoinusdcpair",
219
215
  "TESTCOINUSDCPAIR",
216
+ _generatePoolConfig(USDC_ADDRESS),
220
217
  users.platformReferrer,
221
- USDC_ADDRESS,
222
- USDC_TICK_LOWER,
223
218
  0
224
219
  );
225
220
  }
@@ -234,9 +229,8 @@ contract FactoryTest is BaseTest {
234
229
  "https://testcoinusdcpair.com",
235
230
  "Testcoinusdcpair",
236
231
  "TESTCOINUSDCPAIR",
232
+ _generatePoolConfig(USDC_ADDRESS),
237
233
  users.platformReferrer,
238
- USDC_ADDRESS,
239
- USDC_TICK_LOWER,
240
234
  0
241
235
  );
242
236
  }
@@ -251,9 +245,8 @@ contract FactoryTest is BaseTest {
251
245
  "https://testcoinusdcpair.com",
252
246
  "Testcoinusdcpair",
253
247
  "TESTCOINUSDCPAIR",
248
+ _generatePoolConfig(USDC_ADDRESS),
254
249
  address(0),
255
- USDC_ADDRESS,
256
- USDC_TICK_LOWER,
257
250
  0
258
251
  );
259
252
 
@@ -262,24 +255,6 @@ contract FactoryTest is BaseTest {
262
255
  assertEq(coin.platformReferrer(), coin.protocolRewardRecipient(), "platformReferrer");
263
256
  }
264
257
 
265
- function test_revert_deploy_with_invalid_currency_tick() public {
266
- address[] memory owners = new address[](1);
267
- owners[0] = users.creator;
268
-
269
- vm.expectRevert(abi.encodeWithSelector(ICoin.InvalidWethLowerTick.selector));
270
- factory.deploy(
271
- users.creator,
272
- owners,
273
- "https://testcoin.com",
274
- "Testcoin",
275
- "TESTCOIN",
276
- users.platformReferrer,
277
- address(0),
278
- MarketConstants.LP_TICK_LOWER_WETH + 1,
279
- 0
280
- );
281
- }
282
-
283
258
  function test_deploy_with_usdc_revert_invalid_eth_transfer() public {
284
259
  address[] memory owners = new address[](1);
285
260
  owners[0] = users.creator;
@@ -297,9 +272,8 @@ contract FactoryTest is BaseTest {
297
272
  "https://testcoinusdcpair.com",
298
273
  "Testcoinusdcpair",
299
274
  "TESTCOINUSDCPAIR",
275
+ _generatePoolConfig(USDC_ADDRESS),
300
276
  users.platformReferrer,
301
- USDC_ADDRESS,
302
- USDC_TICK_LOWER,
303
277
  0
304
278
  );
305
279
  }
@@ -314,9 +288,8 @@ contract FactoryTest is BaseTest {
314
288
  "https://test.com",
315
289
  "Test Token",
316
290
  "TEST",
291
+ _generatePoolConfig(address(weth)),
317
292
  users.platformReferrer,
318
- address(weth),
319
- MarketConstants.LP_TICK_LOWER_WETH,
320
293
  0
321
294
  );
322
295
  coin = Coin(payable(coinAddress));
@@ -325,10 +298,10 @@ contract FactoryTest is BaseTest {
325
298
  }
326
299
 
327
300
  function test_upgrade() public {
328
- ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinImpl));
301
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV3Impl), address(coinV4Impl));
329
302
 
330
303
  vm.prank(users.factoryOwner);
331
- factory.upgradeToAndCall(address(newImpl), "");
304
+ ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
332
305
 
333
306
  assertEq(factory.implementation(), address(newImpl), "implementation");
334
307
  }
@@ -342,14 +315,75 @@ contract FactoryTest is BaseTest {
342
315
 
343
316
  vm.prank(users.factoryOwner);
344
317
  vm.expectRevert(abi.encodeWithSelector(ERC1967Utils.ERC1967InvalidImplementation.selector, address(newImpl)));
345
- factory.upgradeToAndCall(address(newImpl), "");
318
+ ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
346
319
  }
347
320
 
348
321
  function test_revert_invalid_owner() public {
349
- ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinImpl));
322
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV3Impl), address(coinV4Impl));
350
323
 
351
324
  vm.prank(users.creator);
352
325
  vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, users.creator));
353
- factory.upgradeToAndCall(address(newImpl), "");
326
+ ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
327
+ }
328
+
329
+ function test_coinAddress_canBePredicted(
330
+ bool msgSenderChanged,
331
+ bool saltChanged,
332
+ bool poolConfigChanged,
333
+ bool platformReferrerChanged,
334
+ bool nameChanged,
335
+ bool symbolChanged
336
+ ) public {
337
+ address[] memory owners = new address[](1);
338
+ owners[0] = users.creator;
339
+
340
+ address payoutRecipient = users.creator;
341
+
342
+ bytes32 salt = keccak256(abi.encode(bytes("randomSalt")));
343
+
344
+ address msgSender = makeAddr("msgSender");
345
+
346
+ string memory uri = "https://test.com";
347
+ string memory name = "Testcoin";
348
+ string memory symbol = "TEST";
349
+
350
+ address platformReferrer = users.platformReferrer;
351
+
352
+ bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(weth));
353
+ bytes memory poolConfigForGettingAddress = poolConfigChanged ? CoinConfigurationVersions.defaultDopplerUniV3(address(weth)) : poolConfig;
354
+
355
+ address expectedCoinAddress = factory.coinAddress(msgSender, name, symbol, poolConfigForGettingAddress, platformReferrer, salt);
356
+
357
+ if (msgSenderChanged) {
358
+ msgSender = makeAddr("msgSender2");
359
+ }
360
+
361
+ if (saltChanged) {
362
+ salt = keccak256(abi.encode(bytes("randomSalt2")));
363
+ }
364
+
365
+ if (platformReferrerChanged) {
366
+ platformReferrer = makeAddr("platformReferrer2");
367
+ }
368
+
369
+ if (nameChanged) {
370
+ name = "Testcoin2";
371
+ }
372
+
373
+ if (symbolChanged) {
374
+ symbol = "TEST2";
375
+ }
376
+
377
+ // now deploy the coin
378
+ vm.prank(msgSender);
379
+ (address coinAddress, ) = factory.deploy(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, address(0), bytes(""), salt);
380
+
381
+ bool addressShouldMismatch = msgSenderChanged || saltChanged || poolConfigChanged || platformReferrerChanged || nameChanged || symbolChanged;
382
+
383
+ if (addressShouldMismatch) {
384
+ assertNotEq(coinAddress, expectedCoinAddress, "coinAddress should mismatch");
385
+ } else {
386
+ assertEq(coinAddress, expectedCoinAddress, "coinAddress should match");
387
+ }
354
388
  }
355
389
  }
@@ -135,22 +135,25 @@ contract MultiOwnableTest is BaseTest {
135
135
 
136
136
  function test_revert_init_with_zero_owners() public {
137
137
  address[] memory emptyOwners = new address[](0);
138
+ bytes memory poolConfig_ = _generatePoolConfig(address(weth));
138
139
  vm.expectRevert(MultiOwnable.OneOwnerRequired.selector);
139
- factory.deploy(users.creator, emptyOwners, "https://test.com", "Test Token", "TEST", users.platformReferrer, address(0), 0, 0);
140
+ factory.deploy(users.creator, emptyOwners, "https://test.com", "Test Token", "TEST", poolConfig_, users.platformReferrer, 0);
140
141
  }
141
142
 
142
143
  function test_revert_init_with_zero_address() public {
143
144
  address[] memory owners = new address[](1);
144
145
  owners[0] = address(0);
146
+ bytes memory poolConfig_ = _generatePoolConfig(address(weth));
145
147
  vm.expectRevert(MultiOwnable.OwnerCannotBeAddressZero.selector);
146
- factory.deploy(users.creator, owners, "https://test.com", "Test Token", "TEST", users.platformReferrer, address(0), 0, 0);
148
+ factory.deploy(users.creator, owners, "https://test.com", "Test Token", "TEST", poolConfig_, users.platformReferrer, 0);
147
149
  }
148
150
 
149
151
  function test_revert_init_with_duplicate_owner() public {
150
152
  address[] memory owners = new address[](2);
151
153
  owners[0] = users.creator;
152
154
  owners[1] = users.creator;
155
+ bytes memory poolConfig_ = _generatePoolConfig(address(weth));
153
156
  vm.expectRevert(MultiOwnable.AlreadyOwner.selector);
154
- factory.deploy(users.creator, owners, "https://test.com", "Test Token", "TEST", users.platformReferrer, address(0), 0, 0);
157
+ factory.deploy(users.creator, owners, "https://test.com", "Test Token", "TEST", poolConfig_, users.platformReferrer, 0);
155
158
  }
156
159
  }
@@ -0,0 +1,68 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+ import {Test} from "forge-std/Test.sol";
4
+ import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
5
+ import {ZoraFactoryImpl} from "../src/ZoraFactoryImpl.sol";
6
+ import {BaseTest} from "./utils/BaseTest.sol";
7
+
8
+ contract BadImpl {
9
+ function contractName() public pure returns (string memory) {
10
+ return "BadImpl";
11
+ }
12
+ }
13
+
14
+ contract UpgradesTest is BaseTest {
15
+ ZoraFactoryImpl public factoryProxy;
16
+
17
+ function test_canUpgradeFromVersionWithoutContractName() public {
18
+ // this test that we can upgrade from the current version, which doesn't have a contract name
19
+ vm.createSelectFork("base", 29675508);
20
+
21
+ factoryProxy = ZoraFactoryImpl(0x777777751622c0d3258f214F9DF38E35BF45baF3);
22
+
23
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(factoryProxy.coinImpl(), address(coinV4Impl));
24
+
25
+ vm.prank(factoryProxy.owner());
26
+ factoryProxy.upgradeToAndCall(address(newImpl), "");
27
+
28
+ assertEq(factoryProxy.implementation(), address(newImpl));
29
+ }
30
+
31
+ function test_cannotUpgradeToMismatchedContractName() public {
32
+ // this test that we cannot upgrade to a contract with a mismatched contract name
33
+ // once we have upgraded to the version that checks the contract name when upgrading
34
+ vm.createSelectFork("base", 29675508);
35
+
36
+ factoryProxy = ZoraFactoryImpl(0x777777751622c0d3258f214F9DF38E35BF45baF3);
37
+
38
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(factoryProxy.coinImpl(), address(coinV4Impl));
39
+
40
+ vm.prank(factoryProxy.owner());
41
+ factoryProxy.upgradeToAndCall(address(newImpl), "");
42
+
43
+ BadImpl badImpl = new BadImpl();
44
+
45
+ vm.prank(factoryProxy.owner());
46
+ vm.expectRevert(abi.encodeWithSelector(IZoraFactory.UpgradeToMismatchedContractName.selector, "ZoraCoinFactory", "BadImpl"));
47
+ factoryProxy.upgradeToAndCall(address(badImpl), "");
48
+ }
49
+
50
+ function test_canUpgradeToSameContractName() public {
51
+ // this test that we can upgrade to the same contract name, when we have already upgraded to a version that has a contract name
52
+ vm.createSelectFork("base", 29675508);
53
+
54
+ factoryProxy = ZoraFactoryImpl(0x777777751622c0d3258f214F9DF38E35BF45baF3);
55
+
56
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(factoryProxy.coinImpl(), address(coinV4Impl));
57
+
58
+ vm.prank(factoryProxy.owner());
59
+ factoryProxy.upgradeToAndCall(address(newImpl), "");
60
+
61
+ ZoraFactoryImpl newImpl2 = new ZoraFactoryImpl(factoryProxy.coinImpl(), factoryProxy.coinV4Impl());
62
+
63
+ vm.prank(factoryProxy.owner());
64
+ factoryProxy.upgradeToAndCall(address(newImpl2), "");
65
+
66
+ assertEq(factoryProxy.implementation(), address(newImpl2));
67
+ }
68
+ }
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5
+
6
+ contract MockERC20 is ERC20 {
7
+ constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
8
+
9
+ function mint(address to, uint256 amount) external {
10
+ _mint(to, amount);
11
+ }
12
+ }