@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
package/src/CoinV4.sol ADDED
@@ -0,0 +1,121 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {IPoolManager, PoolKey, Currency, IHooks} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
5
+
6
+ import {BaseCoin} from "./BaseCoin.sol";
7
+ import {ICoinV4, IHasPoolKey, IHasSwapPath} from "./interfaces/ICoinV4.sol";
8
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
9
+ import {PoolConfiguration} from "./types/PoolConfiguration.sol";
10
+ import {UniV4SwapToCurrency} from "./libs/UniV4SwapToCurrency.sol";
11
+ import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
12
+ import {IDeployedCoinVersionLookup} from "./interfaces/IDeployedCoinVersionLookup.sol";
13
+
14
+ contract CoinV4 is BaseCoin, ICoinV4 {
15
+ /// @notice The Uniswap v4 pool manager singleton contract reference.
16
+ IPoolManager public immutable poolManager;
17
+
18
+ /// @notice The hooks contract used by this coin.
19
+ IHooks public immutable hooks;
20
+
21
+ /// @notice The pool key for the coin. Type from Uniswap V4 core.
22
+ PoolKey private poolKey;
23
+
24
+ /// @notice The configuration for the pool.
25
+ PoolConfiguration private poolConfiguration;
26
+
27
+ /// @notice The constructor for the static CoinV4 contract deployment shared across all Coins.
28
+ /// @dev All arguments are required and cannot be set to teh 0 address.
29
+ /// @param protocolRewardRecipient_ The address of the protocol reward recipient
30
+ /// @param protocolRewards_ The address of the protocol rewards contract
31
+ /// @param poolManager_ The address of the pool manager
32
+ /// @param airlock_ The address of the Airlock contract, ownership is used for a protocol fee split.
33
+ /// @param hooks_ The address of the hooks contract
34
+ /// @notice Returns the pool key for the coin
35
+ constructor(
36
+ address protocolRewardRecipient_,
37
+ address protocolRewards_,
38
+ IPoolManager poolManager_,
39
+ address airlock_,
40
+ IHooks hooks_
41
+ ) BaseCoin(protocolRewardRecipient_, protocolRewards_, airlock_) {
42
+ if (address(poolManager_) == address(0)) {
43
+ revert AddressZero();
44
+ }
45
+ if (address(hooks_) == address(0)) {
46
+ revert AddressZero();
47
+ }
48
+
49
+ poolManager = poolManager_;
50
+ hooks = hooks_;
51
+ }
52
+
53
+ /// @inheritdoc IHasPoolKey
54
+ function getPoolKey() public view returns (PoolKey memory) {
55
+ return poolKey;
56
+ }
57
+
58
+ /// @inheritdoc ICoinV4
59
+ function getPoolConfiguration() public view returns (PoolConfiguration memory) {
60
+ return poolConfiguration;
61
+ }
62
+
63
+ /// @inheritdoc ICoinV4
64
+ function initialize(
65
+ address payoutRecipient_,
66
+ address[] memory owners_,
67
+ string memory tokenURI_,
68
+ string memory name_,
69
+ string memory symbol_,
70
+ address platformReferrer_,
71
+ address currency_,
72
+ PoolKey memory poolKey_,
73
+ uint160 sqrtPriceX96,
74
+ PoolConfiguration memory poolConfiguration_
75
+ ) public initializer {
76
+ super._initialize(payoutRecipient_, owners_, tokenURI_, name_, symbol_, platformReferrer_);
77
+
78
+ currency = currency_;
79
+ poolKey = poolKey_;
80
+ poolConfiguration = poolConfiguration_;
81
+
82
+ // transfer the supply to the hook
83
+ _transfer(address(this), address(hooks), balanceOf(address(this)));
84
+ // initialize the pool - the hook will mint its positions in the afterInitialize callback
85
+ poolManager.initialize(poolKey, sqrtPriceX96);
86
+ }
87
+
88
+ function supportsInterface(bytes4 interfaceId) public pure virtual override(BaseCoin, IERC165) returns (bool) {
89
+ return interfaceId == type(IHasPoolKey).interfaceId || type(IHasSwapPath).interfaceId == interfaceId || super.supportsInterface(interfaceId);
90
+ }
91
+
92
+ /// @inheritdoc IHasSwapPath
93
+ function getPayoutSwapPath(IDeployedCoinVersionLookup coinVersionLookup) external view returns (IHasSwapPath.PayoutSwapPath memory payoutSwapPath) {
94
+ // if to swap in is this currency,
95
+ // if backing currency is a coin, then recursively get the path from the coin
96
+ payoutSwapPath.currencyIn = Currency.wrap(address(this));
97
+
98
+ // swap to backing currency
99
+ PathKey memory thisPathKey = PathKey({
100
+ intermediateCurrency: Currency.wrap(currency),
101
+ fee: poolKey.fee,
102
+ tickSpacing: poolKey.tickSpacing,
103
+ hooks: poolKey.hooks,
104
+ hookData: ""
105
+ });
106
+
107
+ // get backing currency swap path - if the backing currency is a v4 coin and has a swap path.
108
+ PathKey[] memory subPath = UniV4SwapToCurrency.getSubSwapPath(currency, coinVersionLookup);
109
+
110
+ if (subPath.length > 0) {
111
+ payoutSwapPath.path = new PathKey[](1 + subPath.length);
112
+ payoutSwapPath.path[0] = thisPathKey;
113
+ for (uint256 i = 0; i < subPath.length; i++) {
114
+ payoutSwapPath.path[i + 1] = subPath[i];
115
+ }
116
+ } else {
117
+ payoutSwapPath.path = new PathKey[](1);
118
+ payoutSwapPath.path[0] = thisPathKey;
119
+ }
120
+ }
121
+ }
@@ -9,29 +9,108 @@ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.s
9
9
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
10
10
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
11
11
  import {CoinConfigurationVersions} from "./libs/CoinConfigurationVersions.sol";
12
-
12
+ import {ISwapRouter} from "./interfaces/ISwapRouter.sol";
13
+ import {IWETH} from "./interfaces/IWETH.sol";
13
14
  import {IZoraFactory} from "./interfaces/IZoraFactory.sol";
15
+ import {IHasAfterCoinDeploy} from "./hooks/deployment/BaseCoinDeployHook.sol";
16
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
14
17
  import {Coin} from "./Coin.sol";
18
+ import {CoinV4} from "./CoinV4.sol";
19
+ import {ICoin, PoolKeyStruct} from "./interfaces/ICoin.sol";
20
+ import {ICoinV3} from "./interfaces/ICoinV3.sol";
21
+ import {ICoinV4} from "./interfaces/ICoinV4.sol";
22
+ import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
23
+ import {ContractVersionBase} from "./version/ContractVersionBase.sol";
24
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
25
+ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
26
+ import {CoinCommon} from "./libs/CoinCommon.sol";
27
+ import {UniV3Config} from "./libs/CoinSetupV3.sol";
28
+ import {CoinSetupV3} from "./libs/CoinSetupV3.sol";
29
+ import {PoolConfiguration} from "./types/PoolConfiguration.sol";
30
+ import {LpPosition} from "./types/LpPosition.sol";
31
+ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersionedContract.sol";
32
+ import {CoinSetup} from "./libs/CoinSetup.sol";
33
+ import {CoinDopplerMultiCurve} from "./libs/CoinDopplerMultiCurve.sol";
34
+
35
+ import {DeployedCoinVersionLookup} from "./utils/DeployedCoinVersionLookup.sol";
15
36
 
16
- contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgradeable, OwnableUpgradeable {
37
+ contract ZoraFactoryImpl is
38
+ IZoraFactory,
39
+ UUPSUpgradeable,
40
+ ReentrancyGuardUpgradeable,
41
+ OwnableUpgradeable,
42
+ IHasContractName,
43
+ ContractVersionBase,
44
+ DeployedCoinVersionLookup
45
+ {
17
46
  using SafeERC20 for IERC20;
18
47
 
19
48
  /// @notice The coin contract implementation address
20
49
  address public immutable coinImpl;
50
+ address public immutable coinV4Impl;
21
51
 
22
- constructor(address _coinImpl) initializer {
52
+ constructor(address _coinImpl, address _coinV4Impl) initializer {
23
53
  coinImpl = _coinImpl;
54
+ coinV4Impl = _coinV4Impl;
55
+ }
56
+
57
+ /// @inheritdoc IZoraFactory
58
+ function deploy(
59
+ address payoutRecipient,
60
+ address[] memory owners,
61
+ string memory uri,
62
+ string memory name,
63
+ string memory symbol,
64
+ bytes memory poolConfig,
65
+ address platformReferrer,
66
+ address postDeployHook,
67
+ bytes calldata postDeployHookData,
68
+ bytes32 coinSalt
69
+ ) external payable returns (address coin, bytes memory postDeployHookDataOut) {
70
+ bytes32 salt = _buildSalt(msg.sender, name, symbol, poolConfig, platformReferrer, coinSalt);
71
+ return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, postDeployHook, postDeployHookData, salt);
72
+ }
73
+
74
+ function coinAddress(
75
+ address msgSender,
76
+ string memory name,
77
+ string memory symbol,
78
+ bytes memory poolConfig,
79
+ address platformReferrer,
80
+ bytes32 coinSalt
81
+ ) external view returns (address) {
82
+ bytes32 salt = _buildSalt(msgSender, name, symbol, poolConfig, platformReferrer, coinSalt);
83
+ return Clones.predictDeterministicAddress(getCoinImpl(CoinConfigurationVersions.getVersion(poolConfig)), salt, address(this));
84
+ }
85
+
86
+ function _deployWithHook(
87
+ address payoutRecipient,
88
+ address[] memory owners,
89
+ string memory uri,
90
+ string memory name,
91
+ string memory symbol,
92
+ bytes memory poolConfig,
93
+ address platformReferrer,
94
+ address hook,
95
+ bytes calldata hookData,
96
+ bytes32 salt
97
+ ) internal returns (address coin, bytes memory hookDataOut) {
98
+ coin = address(_createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt));
99
+
100
+ if (hook != address(0)) {
101
+ if (!IERC165(hook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
102
+ revert InvalidHook();
103
+ }
104
+ hookDataOut = IHasAfterCoinDeploy(hook).afterCoinDeploy{value: msg.value}(msg.sender, ICoin(coin), hookData);
105
+ } else if (msg.value > 0) {
106
+ // cannot send eth without a hook
107
+ revert EthTransferInvalid();
108
+ }
24
109
  }
25
110
 
26
- /// @notice Creates a new coin contract
27
- /// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
28
- /// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
29
- /// @param uri The coin metadata uri
30
- /// @param name The name of the coin
31
- /// @param symbol The symbol of the coin
32
- /// @param poolConfig The config parameters for the Uniswap v3 pool; `abi.encode(address currency, int24 tickLower, int24 tickUpper, uint16 numDiscoveryPositions, uint256 maxDiscoverySupplyShare)`
33
- /// @param platformReferrer The address of the platform referrer
34
- /// @param orderSize The order size for the first buy; must match msg.value for ETH/WETH pairs
111
+ /** Deprecated deploy functions */
112
+
113
+ /// @dev Deprecated: use `deploy` instead that has a salt and hook specified
35
114
  function deploy(
36
115
  address payoutRecipient,
37
116
  address[] memory owners,
@@ -42,40 +121,32 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
42
121
  address platformReferrer,
43
122
  uint256 orderSize
44
123
  ) public payable nonReentrant returns (address, uint256) {
45
- bytes32 salt = _generateSalt(payoutRecipient, uri);
124
+ bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
46
125
 
47
- Coin coin = Coin(payable(Clones.cloneDeterministic(coinImpl, salt)));
48
-
49
- coin.initialize(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
126
+ ICoin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt);
50
127
 
51
128
  uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
52
129
 
53
- emit CoinCreated(
54
- msg.sender,
55
- payoutRecipient,
56
- coin.platformReferrer(),
57
- coin.currency(),
58
- uri,
59
- name,
60
- symbol,
61
- address(coin),
62
- coin.poolAddress(),
63
- coin.contractVersion()
64
- );
65
-
66
130
  return (address(coin), coinsPurchased);
67
131
  }
68
132
 
69
- /// @notice Creates a new coin contract
70
- /// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
71
- /// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
72
- /// @param uri The coin metadata uri
73
- /// @param name The name of the coin
74
- /// @param symbol The symbol of the coin
75
- /// @param platformReferrer The address to receive platform referral rewards
76
- /// @param currency The address of the trading currency; address(0) for ETH/WETH
77
- /// @param tickLower The lower tick for the Uniswap V3 LP position; ignored for ETH/WETH pairs
78
- /// @param orderSize The order size for the first buy; must match msg.value for ETH/WETH pairs
133
+ /// @dev Deprecated: use `deploy` instead that has a salt and hook specified
134
+ function deployWithHook(
135
+ address payoutRecipient,
136
+ address[] memory owners,
137
+ string memory uri,
138
+ string memory name,
139
+ string memory symbol,
140
+ bytes memory poolConfig,
141
+ address platformReferrer,
142
+ address hook,
143
+ bytes calldata hookData
144
+ ) public payable nonReentrant returns (address coin, bytes memory hookDataOut) {
145
+ bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
146
+ return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, hook, hookData, salt);
147
+ }
148
+
149
+ /// @dev deprecated Use deploy() with poolConfig instead
79
150
  function deploy(
80
151
  address payoutRecipient,
81
152
  address[] memory owners,
@@ -84,37 +155,148 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
84
155
  string memory symbol,
85
156
  address platformReferrer,
86
157
  address currency,
87
- int24 tickLower,
158
+ // tickLower is no longer used
159
+ int24 /*tickLower*/,
88
160
  uint256 orderSize
89
161
  ) public payable nonReentrant returns (address, uint256) {
90
- bytes32 salt = _generateSalt(payoutRecipient, uri);
162
+ bytes memory poolConfig = CoinConfigurationVersions.defaultConfig(currency);
163
+ bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
164
+
165
+ ICoin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt);
91
166
 
92
- Coin coin = Coin(payable(Clones.cloneDeterministic(coinImpl, salt)));
167
+ uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
93
168
 
94
- bytes memory poolConfig = abi.encode(CoinConfigurationVersions.LEGACY_POOL_VERSION, currency, tickLower);
169
+ return (address(coin), coinsPurchased);
170
+ }
95
171
 
96
- coin.initialize(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
172
+ function getCoinImpl(uint8 version) internal view returns (address) {
173
+ if (CoinConfigurationVersions.isV3(version)) {
174
+ return coinImpl;
175
+ } else if (CoinConfigurationVersions.isV4(version)) {
176
+ return coinV4Impl;
177
+ }
97
178
 
98
- uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
179
+ revert ICoin.InvalidPoolVersion();
180
+ }
181
+
182
+ function _createCoin(uint8 version, bytes32 salt) internal returns (address payable) {
183
+ return payable(Clones.cloneDeterministic(getCoinImpl(version), salt));
184
+ }
185
+
186
+ function _setupV3Coin(
187
+ ICoinV3 coin,
188
+ address currency,
189
+ bool isCoinToken0,
190
+ uint160 sqrtPriceX96,
191
+ PoolConfiguration memory poolConfiguration,
192
+ address payoutRecipient,
193
+ address[] memory owners,
194
+ string memory uri,
195
+ string memory name,
196
+ string memory symbol,
197
+ address platformReferrer
198
+ ) internal {
199
+ address v3Factory = coin.v3Factory();
200
+
201
+ address poolAddress = CoinSetupV3.createV3Pool(address(coin), currency, isCoinToken0, sqrtPriceX96, v3Factory);
202
+
203
+ LpPosition[] memory positions = CoinDopplerMultiCurve.calculatePositions(isCoinToken0, poolConfiguration);
204
+
205
+ // Initialize coin with pre-configured pool
206
+ coin.initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolAddress, poolConfiguration, positions);
99
207
 
100
208
  emit CoinCreated(
101
209
  msg.sender,
102
210
  payoutRecipient,
103
- coin.platformReferrer(),
104
- coin.currency(),
211
+ platformReferrer,
212
+ currency,
105
213
  uri,
106
214
  name,
107
215
  symbol,
108
216
  address(coin),
109
- coin.poolAddress(),
110
- coin.contractVersion()
217
+ poolAddress,
218
+ IVersionedContract(address(coin)).contractVersion()
111
219
  );
220
+ }
112
221
 
113
- return (address(coin), coinsPurchased);
222
+ function _setupV4Coin(
223
+ ICoinV4 coin,
224
+ address currency,
225
+ bool isCoinToken0,
226
+ uint160 sqrtPriceX96,
227
+ PoolConfiguration memory poolConfiguration,
228
+ address payoutRecipient,
229
+ address[] memory owners,
230
+ string memory uri,
231
+ string memory name,
232
+ string memory symbol,
233
+ address platformReferrer
234
+ ) internal {
235
+ PoolKey memory poolKey = CoinSetup.buildPoolKey(address(coin), currency, isCoinToken0, coin.hooks());
236
+
237
+ // Initialize coin with pre-configured pool
238
+ coin.initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolKey, sqrtPriceX96, poolConfiguration);
239
+
240
+ emit CoinCreatedV4(
241
+ msg.sender,
242
+ payoutRecipient,
243
+ platformReferrer,
244
+ currency,
245
+ uri,
246
+ name,
247
+ symbol,
248
+ address(coin),
249
+ poolKey,
250
+ CoinCommon.hashPoolKey(poolKey),
251
+ IVersionedContract(address(coin)).contractVersion()
252
+ );
253
+ }
254
+
255
+ function _createAndInitializeCoin(
256
+ address payoutRecipient,
257
+ address[] memory owners,
258
+ string memory uri,
259
+ string memory name,
260
+ string memory symbol,
261
+ bytes memory poolConfig,
262
+ address platformReferrer,
263
+ bytes32 coinSalt
264
+ ) internal returns (ICoin) {
265
+ uint8 version = CoinConfigurationVersions.getVersion(poolConfig);
266
+
267
+ address payable coin = _createCoin(version, coinSalt);
268
+
269
+ _setVersionForDeployedCoin(address(coin), version);
270
+
271
+ (, address currency, uint160 sqrtPriceX96, bool isCoinToken0, PoolConfiguration memory poolConfiguration) = CoinSetup.generatePoolConfig(
272
+ address(coin),
273
+ poolConfig
274
+ );
275
+
276
+ if (CoinConfigurationVersions.isV3(version)) {
277
+ _setupV3Coin(ICoinV3(coin), currency, isCoinToken0, sqrtPriceX96, poolConfiguration, payoutRecipient, owners, uri, name, symbol, platformReferrer);
278
+ } else if (CoinConfigurationVersions.isV4(version)) {
279
+ _setupV4Coin(ICoinV4(coin), currency, isCoinToken0, sqrtPriceX96, poolConfiguration, payoutRecipient, owners, uri, name, symbol, platformReferrer);
280
+ } else {
281
+ revert ICoin.InvalidPoolVersion();
282
+ }
283
+
284
+ return ICoin(coin);
285
+ }
286
+
287
+ function _buildSalt(
288
+ address msgSender,
289
+ string memory name,
290
+ string memory symbol,
291
+ bytes memory poolConfig,
292
+ address platformReferrer,
293
+ bytes32 coinSalt
294
+ ) internal pure returns (bytes32) {
295
+ return keccak256(abi.encodePacked(msgSender, name, symbol, poolConfig, platformReferrer, coinSalt));
114
296
  }
115
297
 
116
298
  /// @dev Generates a unique salt for deterministic deployment
117
- function _generateSalt(address payoutRecipient, string memory uri) internal view returns (bytes32) {
299
+ function _randomSalt(address payoutRecipient, string memory uri, bytes32 coinSalt) internal view returns (bytes32) {
118
300
  return
119
301
  keccak256(
120
302
  abi.encodePacked(
@@ -126,7 +308,8 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
126
308
  block.prevrandao,
127
309
  block.timestamp,
128
310
  tx.gasprice,
129
- tx.origin
311
+ tx.origin,
312
+ coinSalt
130
313
  )
131
314
  );
132
315
  }
@@ -134,12 +317,12 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
134
317
  /// @dev Handles the first buy of a newly created coin
135
318
  /// @param coin The newly created coin contract
136
319
  /// @param orderSize The size of the first buy order; must match msg.value for ETH/WETH pairs
137
- function _handleFirstOrder(Coin coin, uint256 orderSize) internal returns (uint256 coinsPurchased) {
320
+ function _handleFirstOrder(ICoin coin, uint256 orderSize) internal returns (uint256 coinsPurchased) {
138
321
  if (msg.value > 0 || orderSize > 0) {
139
322
  address currency = coin.currency();
140
323
  address payoutRecipient = coin.payoutRecipient();
141
324
 
142
- if (currency != coin.WETH()) {
325
+ if (currency != Coin(payable(address(coin))).WETH()) {
143
326
  if (msg.value != 0) {
144
327
  revert EthTransferInvalid();
145
328
  }
@@ -148,9 +331,9 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
148
331
 
149
332
  IERC20(currency).approve(address(coin), orderSize);
150
333
 
151
- (, coinsPurchased) = coin.buy(payoutRecipient, orderSize, 0, 0, address(0));
334
+ (, coinsPurchased) = Coin(payable(address(coin))).buy(payoutRecipient, orderSize, 0, 0, address(0));
152
335
  } else {
153
- (, coinsPurchased) = coin.buy{value: msg.value}(payoutRecipient, orderSize, 0, 0, address(0));
336
+ (, coinsPurchased) = Coin(payable(address(coin))).buy{value: msg.value}(payoutRecipient, orderSize, 0, 0, address(0));
154
337
  }
155
338
  }
156
339
  }
@@ -182,7 +365,24 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
182
365
  return ERC1967Utils.getImplementation();
183
366
  }
184
367
 
368
+ /// @inheritdoc IHasContractName
369
+ function contractName() public pure override returns (string memory) {
370
+ return "ZoraCoinFactory";
371
+ }
372
+
185
373
  /// @dev Authorizes an upgrade to a new implementation
186
374
  /// @param newImpl The new implementation address
187
- function _authorizeUpgrade(address newImpl) internal override onlyOwner {}
375
+ function _authorizeUpgrade(address newImpl) internal override onlyOwner {
376
+ // try to get the existing contract name - if it reverts, the existing contract was an older version that didn't have the contract name
377
+ // unfortunately we cannot use supportsInterface here because the existing implementation did not have that function
378
+ try IHasContractName(newImpl).contractName() returns (string memory name) {
379
+ if (!_equals(name, contractName())) {
380
+ revert UpgradeToMismatchedContractName(contractName(), name);
381
+ }
382
+ } catch {}
383
+ }
384
+
385
+ function _equals(string memory a, string memory b) internal pure returns (bool) {
386
+ return (keccak256(bytes(a)) == keccak256(bytes(b)));
387
+ }
188
388
  }