@zoralabs/coins 1.1.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.turbo/turbo-build.log +107 -110
  2. package/CHANGELOG.md +50 -0
  3. package/README.md +48 -1
  4. package/abis/BaseCoin.json +442 -0
  5. package/abis/BaseZoraV4CoinHook.json +6 -2
  6. package/abis/CoinTest.json +3 -246
  7. package/abis/CoinUniV4Test.json +20 -0
  8. package/abis/ContentCoinHook.json +6 -2
  9. package/abis/CreatorCoinHook.json +6 -2
  10. package/abis/FactoryTest.json +8 -133
  11. package/abis/FeeEstimatorHook.json +6 -2
  12. package/abis/HooksTest.json +0 -26
  13. package/abis/ICoin.json +378 -0
  14. package/abis/ICoinV3.json +378 -0
  15. package/abis/IZoraFactory.json +0 -18
  16. package/abis/IZoraV4CoinHook.json +2 -2
  17. package/abis/LiquidityMigrationTest.json +101 -0
  18. package/abis/MockBadFactory.json +15 -0
  19. package/abis/Ownable2StepUpgradeable.json +138 -0
  20. package/abis/ZoraFactoryImpl.json +38 -65
  21. package/addresses/8453.json +5 -5
  22. package/dist/index.cjs +272 -268
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.js +270 -266
  25. package/dist/index.js.map +1 -1
  26. package/dist/wagmiGenerated.d.ts +397 -470
  27. package/dist/wagmiGenerated.d.ts.map +1 -1
  28. package/package/wagmiGenerated.ts +275 -271
  29. package/package.json +3 -3
  30. package/script/DeployPostDeploymentHooks.s.sol +2 -2
  31. package/script/TestBackingCoinSwap.s.sol +9 -9
  32. package/script/TestV4Swap.s.sol +9 -9
  33. package/script/UpgradeFactoryImpl.s.sol +0 -1
  34. package/src/BaseCoin.sol +109 -6
  35. package/src/ContentCoin.sol +45 -0
  36. package/src/CreatorCoin.sol +7 -5
  37. package/src/ZoraFactoryImpl.sol +12 -95
  38. package/src/deployment/CoinsDeployerBase.sol +13 -30
  39. package/src/hooks/BaseZoraV4CoinHook.sol +8 -6
  40. package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +4 -5
  41. package/src/interfaces/ICoin.sol +67 -1
  42. package/src/interfaces/ICreatorCoin.sol +2 -2
  43. package/src/interfaces/IZoraFactory.sol +0 -5
  44. package/src/interfaces/IZoraV4CoinHook.sol +1 -1
  45. package/src/libs/CoinConfigurationVersions.sol +1 -39
  46. package/src/libs/CoinRewardsV4.sol +2 -2
  47. package/src/libs/CoinSetup.sol +1 -4
  48. package/src/libs/UniV4SwapHelper.sol +1 -1
  49. package/src/libs/UniV4SwapToCurrency.sol +2 -2
  50. package/src/libs/V4Liquidity.sol +1 -1
  51. package/src/version/ContractVersionBase.sol +1 -1
  52. package/test/Coin.t.sol +112 -535
  53. package/test/CoinUniV4.t.sol +66 -10
  54. package/test/DeploymentHooks.t.sol +5 -102
  55. package/test/Factory.t.sol +49 -291
  56. package/test/LiquidityMigration.t.sol +160 -2
  57. package/test/MultiOwnable.t.sol +36 -36
  58. package/test/Upgrades.t.sol +23 -42
  59. package/test/utils/BaseTest.sol +39 -84
  60. package/test/utils/FeeEstimatorHook.sol +3 -3
  61. package/wagmi.config.ts +2 -2
  62. package/abis/Coin.json +0 -1912
  63. package/abis/DopplerUniswapV3Test.json +0 -800
  64. package/abis/ICoinV4.json +0 -1048
  65. package/abis/Simulate.json +0 -29
  66. package/abis/UniV3BuySell.json +0 -12
  67. package/abis/UniV3Errors.json +0 -32
  68. package/script/Simulate.s.sol +0 -59
  69. package/src/Coin.sol +0 -236
  70. package/src/CoinV4.sol +0 -151
  71. package/src/interfaces/ICoinV4.sol +0 -74
  72. package/src/libs/CoinDopplerUniV3.sol +0 -50
  73. package/src/libs/CoinRewards.sol +0 -201
  74. package/src/libs/CoinSetupV3.sol +0 -50
  75. package/src/libs/UniV3BuySell.sol +0 -231
  76. package/src/libs/UniV3Errors.sol +0 -11
  77. package/test/CoinDopplerUniV3.t.sol +0 -310
  78. /package/abis/{CoinV4.json → ContentCoin.json} +0 -0
@@ -29,7 +29,7 @@ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
29
29
  import {PoolStateReader} from "../src/libs/PoolStateReader.sol";
30
30
  import {CustomRevert} from "@uniswap/v4-core/src/libraries/CustomRevert.sol";
31
31
  import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
32
- import {ICoinV4, IHasSwapPath, PathKey} from "../src/interfaces/ICoinV4.sol";
32
+ import {ICoin, IHasSwapPath, PathKey} from "../src/interfaces/ICoin.sol";
33
33
  import {IDeployedCoinVersionLookup} from "../src/interfaces/IDeployedCoinVersionLookup.sol";
34
34
 
35
35
  contract CoinUniV4Test is BaseTest {
@@ -63,7 +63,7 @@ contract CoinUniV4Test is BaseTest {
63
63
  uint256 deadline = block.timestamp + 20;
64
64
  router.execute(commands, inputs, deadline);
65
65
 
66
- feeState = FeeEstimatorHook(address(contentCoinHook)).getFeeState();
66
+ feeState = FeeEstimatorHook(payable(address(contentCoinHook))).getFeeState();
67
67
 
68
68
  vm.revertToState(snapshot);
69
69
  }
@@ -80,8 +80,8 @@ contract CoinUniV4Test is BaseTest {
80
80
  uint256 deadline = block.timestamp + 20;
81
81
  router.execute(commands, inputs, deadline);
82
82
 
83
- delta = FeeEstimatorHook(address(contentCoinHook)).getFeeState().lastDelta;
84
- swapParams = FeeEstimatorHook(address(contentCoinHook)).getFeeState().lastSwapParams;
83
+ delta = FeeEstimatorHook(payable(address(contentCoinHook))).getFeeState().lastDelta;
84
+ swapParams = FeeEstimatorHook(payable(address(contentCoinHook))).getFeeState().lastSwapParams;
85
85
 
86
86
  sqrtPriceX96 = PoolStateReader.getSqrtPriceX96(coinV4.getPoolKey(), poolManager);
87
87
 
@@ -90,7 +90,7 @@ contract CoinUniV4Test is BaseTest {
90
90
 
91
91
  function test_setupZeroAddressForPoolManager() public {
92
92
  vm.expectRevert(ICoin.AddressZero.selector);
93
- new CoinV4({
93
+ new ContentCoin({
94
94
  protocolRewardRecipient_: address(0x1234),
95
95
  protocolRewards_: address(0x1234),
96
96
  poolManager_: IPoolManager(address(0)),
@@ -100,7 +100,7 @@ contract CoinUniV4Test is BaseTest {
100
100
 
101
101
  // function test_setupZeroAddressForHooks() public {
102
102
  // vm.expectRevert(ICoin.AddressZero.selector);
103
- // new CoinV4({
103
+ // new ContentCoin({
104
104
  // protocolRewardRecipient_: address(0x1234),
105
105
  // protocolRewards_: address(0x1234),
106
106
  // poolManager_: IPoolManager(address(0x1234)),
@@ -293,6 +293,62 @@ contract CoinUniV4Test is BaseTest {
293
293
  assertApproxEqAbs(mockERC20A.balanceOf(coinV4.protocolRewardRecipient()), totalRewards.protocol, 10, "protocol reward currency");
294
294
  }
295
295
 
296
+ function test_distributesMarketRewardsInEth() public {
297
+ uint64 amountIn = 0.1 ether;
298
+
299
+ // Use address(0) as currency to price the coin in ETH
300
+ address currency = address(0);
301
+ bytes32 salt = keccak256(abi.encodePacked("eth-rewards-test"));
302
+ _deployV4Coin(currency, address(0), salt);
303
+
304
+ address trader = makeAddr("trader");
305
+
306
+ // Give trader ETH
307
+ vm.deal(trader, amountIn);
308
+
309
+ // Record initial ETH balance of payout recipient
310
+ uint256 initialPayoutBalance = coinV4.payoutRecipient().balance;
311
+
312
+ // Swap ETH for coin
313
+ _swapSomeCurrencyForCoin(coinV4, currency, amountIn, trader);
314
+
315
+ // Verify that rewards were paid out in ETH
316
+ assertGt(coinV4.payoutRecipient().balance, initialPayoutBalance, "backing reward should be paid in ETH");
317
+ }
318
+
319
+ function test_canSwapEthForCoin(uint128 amountIn) public {
320
+ vm.assume(amountIn > 0.00001 ether);
321
+ vm.assume(amountIn < 1 ether);
322
+
323
+ // Use address(0) as currency to price the coin in ETH
324
+ address currency = address(0);
325
+ bytes32 salt = keccak256(abi.encodePacked("eth-coin-test"));
326
+ _deployV4Coin(currency, address(0), salt);
327
+
328
+ address trader = makeAddr("trader");
329
+
330
+ // Give trader ETH
331
+ vm.deal(trader, amountIn);
332
+
333
+ uint256 initialEthBalance = trader.balance;
334
+
335
+ // Swap ETH for coin
336
+ _swapSomeCurrencyForCoin(coinV4, currency, amountIn, trader);
337
+
338
+ // Verify the swap worked
339
+ assertEq(trader.balance, initialEthBalance - amountIn, "trader should have spent ETH");
340
+ assertGt(coinV4.balanceOf(trader), 0, "trader should have received coin");
341
+
342
+ // Now swap some coin back for ETH
343
+ uint128 coinBalance = uint128(coinV4.balanceOf(trader));
344
+
345
+ _swapSomeCoinForCurrency(coinV4, currency, coinBalance, trader);
346
+
347
+ // Verify the reverse swap worked
348
+ assertEq(coinV4.balanceOf(trader), 0, "trader should have no coins left");
349
+ assertGt(trader.balance, 0, "trader should have received ETH back");
350
+ }
351
+
296
352
  function test_swap_emitsCoinMarketRewardsV4(uint64 amountIn) public {
297
353
  vm.assume(amountIn > 0.00001 ether);
298
354
  address currency = address(mockERC20A);
@@ -649,9 +705,9 @@ contract CoinUniV4Test is BaseTest {
649
705
 
650
706
  function test_getSwapPath_whenBackingCurrencyProvidesPath() public {
651
707
  address zora = address(mockERC20A);
652
- ICoinV4 backingCoin = _deployV4Coin(zora);
708
+ ICoin backingCoin = _deployV4Coin(zora);
653
709
  // now create a final coin paired with the backing coin
654
- ICoinV4 contentCoin = _deployV4Coin(address(backingCoin));
710
+ ICoin contentCoin = _deployV4Coin(address(backingCoin));
655
711
 
656
712
  PathKey[] memory path = contentCoin.getPayoutSwapPath(IDeployedCoinVersionLookup(address(factory))).path;
657
713
 
@@ -690,9 +746,9 @@ contract CoinUniV4Test is BaseTest {
690
746
  mockERC20A.mint(address(poolManager), 10000000000000000 ether);
691
747
 
692
748
  // backing coin is a mock coin that is paired with zora
693
- ICoinV4 backingCoin = _deployV4Coin(zora);
749
+ ICoin backingCoin = _deployV4Coin(zora);
694
750
  // now create a final coin paired with the backing coin
695
- ICoinV4 contentCoin = _deployV4Coin(address(backingCoin));
751
+ ICoin contentCoin = _deployV4Coin(address(backingCoin));
696
752
 
697
753
  vm.assume(amountIn > 0.000000000001 ether);
698
754
  vm.assume(amountIn < 10000000000000000 ether);
@@ -5,7 +5,6 @@ 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
7
  import {IUniswapV3Pool} from "../src/interfaces/IUniswapV3Pool.sol";
8
- import {Coin} from "../src/Coin.sol";
9
8
  import {CoinConfigurationVersions} from "../src/libs/CoinConfigurationVersions.sol";
10
9
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
11
10
  import {ICoin} from "../src/interfaces/ICoin.sol";
@@ -13,6 +12,7 @@ import {IHasAfterCoinDeploy} from "../src/hooks/deployment/BaseCoinDeployHook.so
13
12
  import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
14
13
  import {ISwapRouter} from "../src/interfaces/ISwapRouter.sol";
15
14
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
15
+ import {ContentCoin} from "../src/ContentCoin.sol";
16
16
 
17
17
  // Create a fake hook that doesn't support the IHasAfterCoinDeploy interface
18
18
  contract FakeHookNoInterface {
@@ -26,15 +26,7 @@ contract HooksTest is BaseTest {
26
26
  BuySupplyWithSwapRouterHook hook;
27
27
 
28
28
  function _generateDefaultPoolConfig(address currency) internal pure returns (bytes memory) {
29
- return
30
- _generatePoolConfig(
31
- CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
32
- currency,
33
- DEFAULT_DISCOVERY_TICK_LOWER,
34
- DEFAULT_DISCOVERY_TICK_UPPER,
35
- DEFAULT_NUM_DISCOVERY_POSITIONS,
36
- DEFAULT_DISCOVERY_SUPPLY_SHARE
37
- );
29
+ return _generatePoolConfig(currency);
38
30
  }
39
31
 
40
32
  function setUp() public override {
@@ -71,50 +63,6 @@ contract HooksTest is BaseTest {
71
63
  return _encodeAfterCoinDeploy(buyRecipient, abi.encodeWithSelector(ISwapRouter.exactInput.selector, params));
72
64
  }
73
65
 
74
- function test_buySupplyWithEthUsingV3Hook_withExactInputSingle(uint256 initialOrderSize) public {
75
- vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
76
- vm.assume(initialOrderSize < 1 ether);
77
-
78
- vm.deal(users.creator, initialOrderSize);
79
-
80
- bytes memory hookData = _encodeExactInputSingle(
81
- users.creator,
82
- ISwapRouter.ExactInputSingleParams({
83
- tokenIn: address(weth),
84
- tokenOut: zora,
85
- fee: 3000,
86
- recipient: address(hook),
87
- amountIn: initialOrderSize,
88
- amountOutMinimum: 0,
89
- sqrtPriceLimitX96: 0
90
- })
91
- );
92
-
93
- vm.prank(users.creator);
94
- (address coinAddress, bytes memory hookDataOut) = factory.deployWithHook{value: initialOrderSize}(
95
- users.creator,
96
- _getDefaultOwners(),
97
- "https://test.com",
98
- "Testcoin",
99
- "TEST",
100
- _generateDefaultPoolConfig(zora),
101
- users.platformReferrer,
102
- address(hook),
103
- hookData
104
- );
105
-
106
- coin = Coin(payable(coinAddress));
107
- pool = IUniswapV3Pool(coin.poolAddress());
108
-
109
- (uint256 amountCurrency, uint256 coinsPurchased) = abi.decode(hookDataOut, (uint256, uint256));
110
-
111
- assertEq(coin.currency(), zora, "currency");
112
- assertGt(amountCurrency, 0, "amountCurrency > 0");
113
- assertGt(coinsPurchased, 0, "coinsPurchased > 0");
114
- assertEq(coin.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased, "balanceOf creator");
115
- assertGt(IERC20(zora).balanceOf(address(pool)), 0, "Pool ZORA balance");
116
- }
117
-
118
66
  function test_buySupplyWithEthUsingV4Hook_withExactInputMultiHop(uint256 initialOrderSize) public {
119
67
  vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
120
68
  vm.assume(initialOrderSize < 1 ether);
@@ -150,62 +98,17 @@ contract HooksTest is BaseTest {
150
98
  hookData
151
99
  );
152
100
 
153
- coin = Coin(payable(coinAddress));
101
+ coinV4 = ContentCoin(payable(coinAddress));
154
102
 
155
103
  (uint256 amountCurrency, uint256 coinsPurchased) = abi.decode(hookDataOut, (uint256, uint256));
156
104
 
157
- assertEq(coin.currency(), zora, "currency");
105
+ assertEq(coinV4.currency(), zora, "currency");
158
106
  assertGt(amountCurrency, 0, "amountCurrency > 0");
159
107
  assertGt(coinsPurchased, 0, "coinsPurchased > 0");
160
- assertEq(coin.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased, "balanceOf creator");
108
+ assertEq(coinV4.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased, "balanceOf creator");
161
109
  // assertGt(IERC20(zora).balanceOf(address(pool)), 0, "Pool ZORA balance");
162
110
  }
163
111
 
164
- function test_buySupplyWithEthUsingV3Hook_withExactInputMultiHop(uint256 initialOrderSize) public {
165
- vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
166
- vm.assume(initialOrderSize < 1 ether);
167
-
168
- vm.deal(users.creator, initialOrderSize);
169
-
170
- // lets try weth to usdc to zora
171
-
172
- uint24 poolFee = 3000;
173
-
174
- bytes memory hookData = _encodeExactInput(
175
- users.creator,
176
- ISwapRouter.ExactInputParams({
177
- path: abi.encodePacked(address(weth), poolFee, USDC_ADDRESS, poolFee, zora),
178
- recipient: address(hook),
179
- amountIn: initialOrderSize,
180
- amountOutMinimum: 0
181
- })
182
- );
183
-
184
- vm.prank(users.creator);
185
- (address coinAddress, bytes memory hookDataOut) = factory.deployWithHook{value: initialOrderSize}(
186
- users.creator,
187
- _getDefaultOwners(),
188
- "https://test.com",
189
- "Testcoin",
190
- "TEST",
191
- _generateDefaultPoolConfig(zora),
192
- users.platformReferrer,
193
- address(hook),
194
- hookData
195
- );
196
-
197
- coin = Coin(payable(coinAddress));
198
- pool = IUniswapV3Pool(coin.poolAddress());
199
-
200
- (uint256 amountCurrency, uint256 coinsPurchased) = abi.decode(hookDataOut, (uint256, uint256));
201
-
202
- assertEq(coin.currency(), zora, "currency");
203
- assertGt(amountCurrency, 0, "amountCurrency > 0");
204
- assertGt(coinsPurchased, 0, "coinsPurchased > 0");
205
- assertEq(coin.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased, "balanceOf creator");
206
- assertGt(IERC20(zora).balanceOf(address(pool)), 0, "Pool ZORA balance");
207
- }
208
-
209
112
  function test_buySupplyWithEthUsingV3Hook_revertsWhenBadCall() public {
210
113
  vm.deal(users.creator, 0.0001 ether);
211
114
 
@@ -3,6 +3,8 @@ pragma solidity ^0.8.13;
3
3
 
4
4
  import "./utils/BaseTest.sol";
5
5
  import {CoinConstants} from "../src/libs/CoinConstants.sol";
6
+ import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
7
+ import {IZoraFactory} from "../src/interfaces/IZoraFactory.sol";
6
8
 
7
9
  contract FactoryTest is BaseTest {
8
10
  function setUp() public override {
@@ -11,14 +13,7 @@ contract FactoryTest is BaseTest {
11
13
 
12
14
  function test_factory_constructor_and_proxy_setup() public {
13
15
  // Impl constructor test
14
- ZoraFactoryImpl impl = new ZoraFactoryImpl(
15
- address(coinV3Impl),
16
- address(coinV4Impl),
17
- address(creatorCoinImpl),
18
- address(contentCoinHook),
19
- address(creatorCoinHook)
20
- );
21
- assertEq(ZoraFactoryImpl(address(factory)).coinImpl(), address(coinV3Impl));
16
+ ZoraFactoryImpl impl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook));
22
17
  assertEq(ZoraFactoryImpl(address(factory)).owner(), users.factoryOwner);
23
18
  assertEq(ZoraFactoryImpl(address(factory)).coinV4Impl(), address(coinV4Impl));
24
19
 
@@ -29,296 +24,49 @@ contract FactoryTest is BaseTest {
29
24
  assertEq(ZoraFactoryImpl(address(factory)).owner(), initialOwner);
30
25
  }
31
26
 
32
- function test_deploy_no_eth() public {
33
- address[] memory owners = new address[](1);
34
- owners[0] = users.creator;
35
-
36
- (address coinAddress, ) = factory.deploy(
37
- users.creator,
38
- owners,
39
- "https://test2.com",
40
- "Test2 Token",
41
- "TEST2",
42
- _generatePoolConfig(address(weth)),
43
- users.platformReferrer,
44
- 0
45
- );
46
- coin = Coin(payable(coinAddress));
47
- pool = IUniswapV3Pool(coin.poolAddress());
48
- vm.label(address(coin), "COIN");
49
- vm.label(address(pool), "POOL");
50
-
51
- uint160 sqrtPriceX96 = pool.slot0().sqrtPriceX96;
52
- uint256 poolCoinBalance = coin.balanceOf(address(pool));
53
- uint256 poolEthBalance = weth.balanceOf(address(pool));
54
-
55
- console.log("POOL_TOKEN_0: ", pool.token0());
56
- console.log("POOL_TOKEN_1: ", pool.token1());
57
- console.log("POOL_SQRT_PRICE_X96: ", sqrtPriceX96);
58
- console.log("");
59
- console.log("POOL_COIN_BALANCE: ", poolCoinBalance);
60
- console.log("POOL_ETH_BALANCE: ", poolEthBalance);
61
- console.log("");
62
-
63
- assertEq(coin.payoutRecipient(), users.creator, "payoutRecipient");
64
- assertEq(coin.protocolRewardRecipient(), users.feeRecipient, "protocolRewardRecipient");
65
- assertEq(coin.platformReferrer(), users.platformReferrer, "platformReferrer");
66
- assertEq(coin.tokenURI(), "https://test2.com", "tokenURI");
67
- assertEq(coin.name(), "Test2 Token", "name");
68
- assertEq(coin.symbol(), "TEST2", "symbol");
69
- assertEq(coin.currency(), address(weth), "currency");
70
- assertEq(coin.totalSupply(), 1_000_000_000e18, "totalSupply");
71
- assertEq(coin.balanceOf(users.creator), 10_000_000e18, "balanceOf creator");
72
- assertGt(coin.balanceOf(coin.poolAddress()), 989_999_999e18, "balanceOf pool");
73
- }
74
-
75
- function test_deploy_with_eth(uint256 initialOrderSize) public {
76
- vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
77
- vm.assume(initialOrderSize < 10 ether);
78
-
79
- address[] memory owners = new address[](1);
80
- owners[0] = users.creator;
81
-
82
- vm.deal(users.creator, initialOrderSize);
83
- vm.prank(users.creator);
84
- (address coinAddress, ) = factory.deploy{value: initialOrderSize}(
85
- users.creator,
86
- owners,
87
- "https://test2.com",
88
- "Test2 Token",
89
- "TEST2",
90
- _generatePoolConfig(address(weth)),
91
- users.platformReferrer,
92
- initialOrderSize
93
- );
94
- coin = Coin(payable(coinAddress));
95
- pool = IUniswapV3Pool(coin.poolAddress());
96
- vm.label(address(coin), "COIN");
97
- vm.label(address(pool), "POOL");
98
-
99
- uint160 sqrtPriceX96 = pool.slot0().sqrtPriceX96;
100
- uint256 poolCoinBalance = coin.balanceOf(address(pool));
101
- uint256 poolEthBalance = weth.balanceOf(address(pool));
102
-
103
- console.log("POOL_TOKEN_0: ", pool.token0());
104
- console.log("POOL_TOKEN_1: ", pool.token1());
105
- console.log("POOL_SQRT_PRICE_X96: ", sqrtPriceX96);
106
- console.log("");
107
- console.log("POOL_COIN_BALANCE: ", poolCoinBalance);
108
- console.log("POOL_ETH_BALANCE: ", poolEthBalance);
109
- console.log("");
110
- console.log("BUYER_COIN_BALANCE ", coin.balanceOf(users.creator) - 10_000_000e18);
111
- }
112
-
113
- function test_deploy_with_weth(uint256 initialOrderSize) public {
114
- vm.assume(initialOrderSize > CoinConstants.MIN_ORDER_SIZE);
115
- vm.assume(initialOrderSize < 10 ether);
116
-
117
- address[] memory owners = new address[](1);
118
- owners[0] = users.creator;
119
-
120
- vm.deal(users.creator, initialOrderSize);
121
-
122
- vm.startPrank(users.creator);
123
- weth.deposit{value: initialOrderSize}();
124
-
125
- weth.approve(address(factory), type(uint256).max);
126
-
127
- // Expect this to revert because WETH needs to be sent with msg.value.
128
- vm.expectRevert();
129
- factory.deploy(
130
- users.creator,
131
- owners,
132
- "https://test2.com",
133
- "Test2 Token",
134
- "TEST2",
135
- _generatePoolConfig(address(weth)),
136
- users.platformReferrer,
137
- initialOrderSize
138
- );
139
- }
140
-
141
- function test_deploy_with_one_eth() public {
142
- address[] memory owners = new address[](1);
143
- owners[0] = users.creator;
144
-
145
- uint256 orderSize = 1 ether;
146
- vm.deal(users.creator, orderSize);
147
-
148
- (address coinAddress, ) = factory.deploy{value: orderSize}(
149
- users.creator,
150
- owners,
151
- "https://test2.com",
152
- "Test2 Token",
153
- "TEST2",
154
- _generatePoolConfig(address(weth)),
155
- users.platformReferrer,
156
- orderSize
157
- );
158
- coin = Coin(payable(coinAddress));
159
- pool = IUniswapV3Pool(coin.poolAddress());
160
- vm.label(address(coin), "COIN");
161
- vm.label(address(pool), "POOL");
162
- }
163
-
164
- function test_deploy_with_usdc() public {
165
- address[] memory owners = new address[](1);
166
- owners[0] = users.creator;
167
-
168
- (address coinAddress, ) = factory.deploy(
169
- users.creator,
170
- owners,
171
- "https://testcoinusdcpair.com",
172
- "Testcoinusdcpair",
173
- "TESTCOINUSDCPAIR",
174
- _generatePoolConfig(USDC_ADDRESS),
175
- users.platformReferrer,
176
- 0
177
- );
178
- coin = Coin(payable(coinAddress));
179
- pool = IUniswapV3Pool(coin.poolAddress());
180
- vm.label(address(coin), "COIN");
181
- vm.label(address(pool), "POOL");
182
-
183
- assertEq(coin.currency(), USDC_ADDRESS, "currency");
184
- assertEq(coin.payoutRecipient(), users.creator, "payoutRecipient");
185
- }
186
-
187
- function test_deploy_with_usdc_order() public {
188
- address[] memory owners = new address[](1);
189
- owners[0] = users.creator;
190
-
191
- uint256 orderSize = dealUSDC(users.creator, 100);
27
+ function test_ownable2Step() public {
28
+ // old current impl
29
+ assertEq(ZoraFactoryImpl(address(factory)).owner(), users.factoryOwner);
192
30
 
193
- vm.prank(users.creator);
194
- usdc.approve(address(factory), orderSize);
31
+ // 1st ensure owner slot is set at expected address
195
32
 
196
- assertEq(usdc.balanceOf(users.creator), orderSize);
197
- assertEq(usdc.allowance(users.creator, address(factory)), orderSize);
33
+ bytes32 ownableSlot = hex"9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300";
34
+ bytes32 ownable2StepSlot = hex"237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00";
198
35
 
199
- vm.prank(users.creator);
200
- (address coinAddress, uint256 coinsPurchased) = factory.deploy(
201
- users.creator,
202
- owners,
203
- "https://testcoinusdcpair.com",
204
- "Testcoinusdcpair",
205
- "TESTCOINUSDCPAIR",
206
- _generatePoolConfig(USDC_ADDRESS),
207
- users.platformReferrer,
208
- orderSize
209
- );
210
- coin = Coin(payable(coinAddress));
211
- pool = IUniswapV3Pool(coin.poolAddress());
212
- vm.label(address(coin), "COIN");
213
- vm.label(address(pool), "POOL");
36
+ address ownerAddress = address(uint160(uint256(vm.load(address(factory), ownableSlot))));
37
+ assertEq(ownerAddress, users.factoryOwner);
214
38
 
215
- assertEq(coin.currency(), USDC_ADDRESS, "currency");
216
- assertEq(coin.balanceOf(users.creator), CoinConstants.CREATOR_LAUNCH_REWARD + coinsPurchased);
217
- }
39
+ assertEq(ZoraFactoryImpl(address(factory)).pendingOwner(), address(0));
218
40
 
219
- function test_deploy_with_usdc_revert_payout_recipient_zero() public {
220
- address[] memory owners = new address[](1);
221
- owners[0] = users.creator;
222
-
223
- vm.expectRevert(abi.encodeWithSelector(ICoin.AddressZero.selector));
224
- factory.deploy(
225
- address(0),
226
- owners,
227
- "https://testcoinusdcpair.com",
228
- "Testcoinusdcpair",
229
- "TESTCOINUSDCPAIR",
230
- _generatePoolConfig(USDC_ADDRESS),
231
- users.platformReferrer,
232
- 0
41
+ address newFactoryImpl = address(
42
+ new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook))
233
43
  );
234
- }
235
44
 
236
- function test_deploy_with_usdc_revert_one_owner_required() public {
237
- address[] memory owners = new address[](0);
238
-
239
- vm.expectRevert(abi.encodeWithSelector(MultiOwnable.OneOwnerRequired.selector));
240
- factory.deploy(
241
- users.creator,
242
- owners,
243
- "https://testcoinusdcpair.com",
244
- "Testcoinusdcpair",
245
- "TESTCOINUSDCPAIR",
246
- _generatePoolConfig(USDC_ADDRESS),
247
- users.platformReferrer,
248
- 0
249
- );
250
- }
251
-
252
- function test_deploy_with_usdc_platform_referrer_zero() public {
253
- address[] memory owners = new address[](1);
254
- owners[0] = users.creator;
255
-
256
- (address coinAddress, ) = factory.deploy(
257
- users.creator,
258
- owners,
259
- "https://testcoinusdcpair.com",
260
- "Testcoinusdcpair",
261
- "TESTCOINUSDCPAIR",
262
- _generatePoolConfig(USDC_ADDRESS),
263
- address(0),
264
- 0
265
- );
266
-
267
- coin = Coin(payable(coinAddress));
268
-
269
- assertEq(coin.platformReferrer(), coin.protocolRewardRecipient(), "platformReferrer");
270
- }
271
-
272
- function test_deploy_with_usdc_revert_invalid_eth_transfer() public {
273
- address[] memory owners = new address[](1);
274
- owners[0] = users.creator;
275
-
276
- dealUSDC(users.creator, 1);
45
+ // Upgrade to current / new impl
46
+ vm.prank(users.factoryOwner);
47
+ ZoraFactoryImpl(address(factory)).upgradeToAndCall(newFactoryImpl, "");
277
48
 
278
- vm.deal(users.creator, 1e6);
49
+ // 2nd ensure owner is read from correct slot
50
+ assertEq(ZoraFactoryImpl(address(factory)).owner(), users.factoryOwner);
279
51
 
280
- vm.prank(users.creator);
281
- vm.expectRevert(abi.encodeWithSelector(ICoin.EthTransferInvalid.selector));
282
-
283
- factory.deploy{value: 1e6}(
284
- users.creator,
285
- owners,
286
- "https://testcoinusdcpair.com",
287
- "Testcoinusdcpair",
288
- "TESTCOINUSDCPAIR",
289
- _generatePoolConfig(USDC_ADDRESS),
290
- users.platformReferrer,
291
- 0
292
- );
293
- }
52
+ address newOwner = makeAddr("newOwner");
294
53
 
295
- function test_deploy_without_initial_order() public {
296
- address[] memory owners = new address[](1);
297
- owners[0] = users.creator;
54
+ // 3rd ensure pending owner is set correctly
55
+ vm.prank(users.factoryOwner);
56
+ ZoraFactoryImpl(address(factory)).transferOwnership(newOwner);
57
+ assertEq(ZoraFactoryImpl(address(factory)).pendingOwner(), newOwner);
298
58
 
299
- (address coinAddress, ) = factory.deploy(
300
- users.creator,
301
- owners,
302
- "https://test.com",
303
- "Test Token",
304
- "TEST",
305
- _generatePoolConfig(address(weth)),
306
- users.platformReferrer,
307
- 0
308
- );
309
- coin = Coin(payable(coinAddress));
59
+ address ownerAddress2Step = address(uint160(uint256(vm.load(address(factory), ownable2StepSlot))));
60
+ assertEq(ownerAddress2Step, newOwner);
310
61
 
311
- assertEq(coin.balanceOf(users.creator), 10_000_000e18, "Should only have initial creator allocation");
62
+ // 4th ensure owner is set correctly
63
+ vm.prank(newOwner);
64
+ ZoraFactoryImpl(address(factory)).acceptOwnership();
65
+ assertEq(ZoraFactoryImpl(address(factory)).owner(), newOwner);
312
66
  }
313
67
 
314
68
  function test_upgrade() public {
315
- ZoraFactoryImpl newImpl = new ZoraFactoryImpl(
316
- address(coinV3Impl),
317
- address(coinV4Impl),
318
- address(creatorCoinImpl),
319
- address(contentCoinHook),
320
- address(creatorCoinHook)
321
- );
69
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook));
322
70
 
323
71
  vm.prank(users.factoryOwner);
324
72
  ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(newImpl), "");
@@ -339,13 +87,7 @@ contract FactoryTest is BaseTest {
339
87
  }
340
88
 
341
89
  function test_revert_invalid_owner() public {
342
- ZoraFactoryImpl newImpl = new ZoraFactoryImpl(
343
- address(coinV3Impl),
344
- address(coinV4Impl),
345
- address(creatorCoinImpl),
346
- address(contentCoinHook),
347
- address(creatorCoinHook)
348
- );
90
+ ZoraFactoryImpl newImpl = new ZoraFactoryImpl(address(coinV4Impl), address(creatorCoinImpl), address(contentCoinHook), address(creatorCoinHook));
349
91
 
350
92
  vm.prank(users.creator);
351
93
  vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, users.creator));
@@ -376,7 +118,7 @@ contract FactoryTest is BaseTest {
376
118
  address platformReferrer = users.platformReferrer;
377
119
 
378
120
  bytes memory poolConfig = CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(weth));
379
- bytes memory poolConfigForGettingAddress = poolConfigChanged ? CoinConfigurationVersions.defaultDopplerUniV3(address(weth)) : poolConfig;
121
+ bytes memory poolConfigForGettingAddress = poolConfigChanged ? CoinConfigurationVersions.defaultDopplerMultiCurveUniV4(address(0)) : poolConfig;
380
122
 
381
123
  address expectedCoinAddress = factory.coinAddress(msgSender, name, symbol, poolConfigForGettingAddress, platformReferrer, salt);
382
124
 
@@ -412,4 +154,20 @@ contract FactoryTest is BaseTest {
412
154
  assertEq(coinAddress, expectedCoinAddress, "coinAddress should match");
413
155
  }
414
156
  }
157
+
158
+ function test_upgrade_with_mismatched_contract_name() public {
159
+ // Create a mock implementation with different contract name
160
+ MockBadFactory badImpl = new MockBadFactory();
161
+
162
+ vm.prank(users.factoryOwner);
163
+ vm.expectRevert(abi.encodeWithSelector(IZoraFactory.UpgradeToMismatchedContractName.selector, "ZoraCoinFactory", "BadFactory"));
164
+ ZoraFactoryImpl(address(factory)).upgradeToAndCall(address(badImpl), "");
165
+ }
166
+ }
167
+
168
+ // Mock contracts for testing
169
+ contract MockBadFactory is IHasContractName {
170
+ function contractName() external pure returns (string memory) {
171
+ return "BadFactory";
172
+ }
415
173
  }