@zoralabs/coins 0.9.0 → 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 (124) hide show
  1. package/.turbo/turbo-build.log +131 -114
  2. package/CHANGELOG.md +40 -0
  3. package/abis/BaseCoin.json +26 -118
  4. package/abis/BaseTest.json +47 -0
  5. package/abis/Coin.json +171 -63
  6. package/abis/CoinDopplerMultiCurve.json +38 -0
  7. package/abis/CoinRewardsV4.json +54 -0
  8. package/abis/CoinTest.json +53 -20
  9. package/abis/CoinUniV4Test.json +1053 -0
  10. package/abis/CoinV4.json +234 -211
  11. package/abis/DeployScript.json +47 -0
  12. package/abis/DeployedCoinVersionLookup.json +21 -0
  13. package/abis/DeployedCoinVersionLookupTest.json +716 -0
  14. package/abis/DifferentNamespaceVersionLookup.json +39 -0
  15. package/abis/DopplerUniswapV3Test.json +49 -93
  16. package/abis/ERC20.json +310 -0
  17. package/abis/FactoryTest.json +85 -7
  18. package/abis/FeeEstimatorHook.json +1528 -0
  19. package/abis/HooksDeployment.json +23 -0
  20. package/abis/HooksTest.json +47 -0
  21. package/abis/ICoin.json +40 -71
  22. package/abis/ICoinV3.json +879 -0
  23. package/abis/ICoinV4.json +915 -0
  24. package/abis/IDeployedCoinVersionLookup.json +21 -0
  25. package/abis/IERC721.json +36 -36
  26. package/abis/IHasPoolKey.json +42 -0
  27. package/abis/IHasRewardsRecipients.json +54 -0
  28. package/abis/IHasSwapPath.json +60 -0
  29. package/abis/IMsgSender.json +15 -0
  30. package/abis/IPoolConfigEncoding.json +46 -0
  31. package/abis/ISwapPathRouter.json +92 -0
  32. package/abis/IUniversalRouter.json +61 -0
  33. package/abis/IUnlockCallback.json +21 -0
  34. package/abis/IV4Quoter.json +310 -0
  35. package/abis/IZoraFactory.json +191 -11
  36. package/abis/IZoraV4CoinHook.json +348 -4
  37. package/abis/MockERC20.json +21 -0
  38. package/abis/MultiOwnableTest.json +47 -0
  39. package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
  40. package/abis/PrintUpgradeCommand.json +9 -0
  41. package/abis/ProxyShim.json +24 -0
  42. package/abis/StateLibrary.json +80 -0
  43. package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
  44. package/abis/TestV4Swap.json +9 -0
  45. package/abis/UpgradeCoinImpl.json +47 -0
  46. package/abis/UpgradesTest.json +67 -0
  47. package/abis/Vm.json +1482 -111
  48. package/abis/VmSafe.json +856 -32
  49. package/abis/ZoraFactoryImpl.json +339 -1
  50. package/abis/ZoraV4CoinHook.json +455 -5
  51. package/addresses/8453.json +8 -4
  52. package/addresses/84532.json +8 -4
  53. package/dist/index.cjs +1920 -169
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.js +1916 -169
  56. package/dist/index.js.map +1 -1
  57. package/dist/wagmiGenerated.d.ts +2599 -183
  58. package/dist/wagmiGenerated.d.ts.map +1 -1
  59. package/package/wagmiGenerated.ts +1928 -165
  60. package/package.json +8 -3
  61. package/remappings.txt +6 -1
  62. package/script/CoinsDeployerBase.sol +74 -11
  63. package/script/DeployDevFactory.s.sol +21 -0
  64. package/script/PrintUpgradeCommand.s.sol +13 -0
  65. package/script/Simulate.s.sol +1 -10
  66. package/script/TestBackingCoinSwap.s.sol +146 -0
  67. package/script/TestV4Swap.s.sol +136 -0
  68. package/script/UpgradeFactoryImpl.s.sol +1 -1
  69. package/src/BaseCoin.sol +176 -0
  70. package/src/Coin.sol +87 -202
  71. package/src/CoinV4.sol +121 -0
  72. package/src/ZoraFactoryImpl.sol +208 -36
  73. package/src/hooks/ZoraV4CoinHook.sol +195 -0
  74. package/src/hooks/{BaseCoinDeployHook.sol → deployment/BaseCoinDeployHook.sol} +3 -3
  75. package/src/hooks/{BuySupplyWithSwapRouterHook.sol → deployment/BuySupplyWithSwapRouterHook.sol} +7 -5
  76. package/src/interfaces/ICoin.sol +31 -39
  77. package/src/interfaces/ICoinV3.sol +71 -0
  78. package/src/interfaces/ICoinV4.sol +69 -0
  79. package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
  80. package/src/interfaces/IMsgSender.sol +9 -0
  81. package/src/interfaces/IPoolConfigEncoding.sol +14 -0
  82. package/src/interfaces/ISwapPathRouter.sol +14 -0
  83. package/src/interfaces/IZoraFactory.sol +65 -27
  84. package/src/interfaces/IZoraV4CoinHook.sol +116 -0
  85. package/src/libs/CoinCommon.sol +15 -0
  86. package/src/libs/CoinConfigurationVersions.sol +116 -1
  87. package/src/libs/CoinConstants.sol +5 -0
  88. package/src/libs/CoinDopplerMultiCurve.sol +134 -0
  89. package/src/libs/CoinDopplerUniV3.sol +19 -171
  90. package/src/libs/CoinRewards.sol +195 -0
  91. package/src/libs/CoinRewardsV4.sol +180 -0
  92. package/src/libs/CoinSetup.sol +57 -0
  93. package/src/libs/CoinSetupV3.sol +6 -67
  94. package/src/libs/DopplerMath.sol +156 -0
  95. package/src/libs/HooksDeployment.sol +84 -0
  96. package/src/libs/MarketConstants.sol +4 -0
  97. package/src/libs/PoolStateReader.sol +22 -0
  98. package/src/libs/UniV3BuySell.sol +74 -292
  99. package/src/libs/UniV4SwapHelper.sol +65 -0
  100. package/src/libs/UniV4SwapToCurrency.sol +109 -0
  101. package/src/libs/V4Liquidity.sol +129 -0
  102. package/src/types/PoolConfiguration.sol +15 -0
  103. package/src/utils/DeployedCoinVersionLookup.sol +52 -0
  104. package/src/version/ContractVersionBase.sol +1 -1
  105. package/test/Coin.t.sol +78 -88
  106. package/test/CoinDopplerUniV3.t.sol +32 -171
  107. package/test/CoinUniV4.t.sol +752 -0
  108. package/test/{Hooks.t.sol → DeploymentHooks.t.sol} +2 -6
  109. package/test/Factory.t.sol +80 -47
  110. package/test/MultiOwnable.t.sol +6 -3
  111. package/test/Upgrades.t.sol +6 -5
  112. package/test/mocks/MockERC20.sol +12 -0
  113. package/test/utils/BaseTest.sol +106 -56
  114. package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
  115. package/test/utils/FeeEstimatorHook.sol +84 -0
  116. package/test/utils/ProxyShim.sol +17 -0
  117. package/wagmi.config.ts +4 -0
  118. package/.env +0 -1
  119. package/.turbo/turbo-update-contract-version.log +0 -22
  120. package/abis/CoinSetupV3.json +0 -7
  121. package/abis/HookDeployer.json +0 -68
  122. package/abis/IHookDeployer.json +0 -42
  123. package/src/libs/CoinLegacy.sol +0 -48
  124. package/src/libs/CoinLegacyMarket.sol +0 -182
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
+ }
@@ -12,21 +12,46 @@ import {CoinConfigurationVersions} from "./libs/CoinConfigurationVersions.sol";
12
12
  import {ISwapRouter} from "./interfaces/ISwapRouter.sol";
13
13
  import {IWETH} from "./interfaces/IWETH.sol";
14
14
  import {IZoraFactory} from "./interfaces/IZoraFactory.sol";
15
- import {IHasAfterCoinDeploy} from "./hooks/BaseCoinDeployHook.sol";
15
+ import {IHasAfterCoinDeploy} from "./hooks/deployment/BaseCoinDeployHook.sol";
16
16
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
17
17
  import {Coin} from "./Coin.sol";
18
- import {ICoin} from "./interfaces/ICoin.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";
19
22
  import {IHasContractName} from "@zoralabs/shared-contracts/interfaces/IContractMetadata.sol";
20
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";
21
34
 
22
- contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgradeable, OwnableUpgradeable, IHasContractName, ContractVersionBase {
35
+ import {DeployedCoinVersionLookup} from "./utils/DeployedCoinVersionLookup.sol";
36
+
37
+ contract ZoraFactoryImpl is
38
+ IZoraFactory,
39
+ UUPSUpgradeable,
40
+ ReentrancyGuardUpgradeable,
41
+ OwnableUpgradeable,
42
+ IHasContractName,
43
+ ContractVersionBase,
44
+ DeployedCoinVersionLookup
45
+ {
23
46
  using SafeERC20 for IERC20;
24
47
 
25
48
  /// @notice The coin contract implementation address
26
49
  address public immutable coinImpl;
50
+ address public immutable coinV4Impl;
27
51
 
28
- constructor(address _coinImpl) initializer {
52
+ constructor(address _coinImpl, address _coinV4Impl) initializer {
29
53
  coinImpl = _coinImpl;
54
+ coinV4Impl = _coinV4Impl;
30
55
  }
31
56
 
32
57
  /// @inheritdoc IZoraFactory
@@ -38,17 +63,27 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
38
63
  string memory symbol,
39
64
  bytes memory poolConfig,
40
65
  address platformReferrer,
41
- uint256 orderSize
42
- ) public payable nonReentrant returns (address, uint256) {
43
- Coin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
44
-
45
- uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
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
+ }
46
73
 
47
- return (address(coin), coinsPurchased);
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));
48
84
  }
49
85
 
50
- /// @inheritdoc IZoraFactory
51
- function deployWithHook(
86
+ function _deployWithHook(
52
87
  address payoutRecipient,
53
88
  address[] memory owners,
54
89
  string memory uri,
@@ -57,9 +92,10 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
57
92
  bytes memory poolConfig,
58
93
  address platformReferrer,
59
94
  address hook,
60
- bytes calldata hookData
61
- ) public payable nonReentrant returns (address coin, bytes memory hookDataOut) {
62
- coin = address(_createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer));
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));
63
99
 
64
100
  if (hook != address(0)) {
65
101
  if (!IERC165(hook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
@@ -72,60 +108,195 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
72
108
  }
73
109
  }
74
110
 
75
- /// @inheritdoc IZoraFactory
111
+ /** Deprecated deploy functions */
112
+
113
+ /// @dev Deprecated: use `deploy` instead that has a salt and hook specified
76
114
  function deploy(
77
115
  address payoutRecipient,
78
116
  address[] memory owners,
79
117
  string memory uri,
80
118
  string memory name,
81
119
  string memory symbol,
120
+ bytes memory poolConfig,
82
121
  address platformReferrer,
83
- address currency,
84
- int24 tickLower,
85
122
  uint256 orderSize
86
123
  ) public payable nonReentrant returns (address, uint256) {
87
- bytes memory poolConfig = abi.encode(CoinConfigurationVersions.LEGACY_POOL_VERSION, currency, tickLower);
124
+ bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
88
125
 
89
- Coin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
126
+ ICoin coin = _createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt);
90
127
 
91
128
  uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
92
129
 
93
130
  return (address(coin), coinsPurchased);
94
131
  }
95
132
 
96
- function _createAndInitializeCoin(
133
+ /// @dev Deprecated: use `deploy` instead that has a salt and hook specified
134
+ function deployWithHook(
97
135
  address payoutRecipient,
98
136
  address[] memory owners,
99
137
  string memory uri,
100
138
  string memory name,
101
139
  string memory symbol,
102
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
150
+ function deploy(
151
+ address payoutRecipient,
152
+ address[] memory owners,
153
+ string memory uri,
154
+ string memory name,
155
+ string memory symbol,
156
+ address platformReferrer,
157
+ address currency,
158
+ // tickLower is no longer used
159
+ int24 /*tickLower*/,
160
+ uint256 orderSize
161
+ ) public payable nonReentrant returns (address, uint256) {
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);
166
+
167
+ uint256 coinsPurchased = _handleFirstOrder(coin, orderSize);
168
+
169
+ return (address(coin), coinsPurchased);
170
+ }
171
+
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
+ }
178
+
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,
103
197
  address platformReferrer
104
- ) internal returns (Coin) {
105
- bytes32 salt = _generateSalt(payoutRecipient, uri);
198
+ ) internal {
199
+ address v3Factory = coin.v3Factory();
106
200
 
107
- Coin coin = Coin(payable(Clones.cloneDeterministic(coinImpl, salt)));
201
+ address poolAddress = CoinSetupV3.createV3Pool(address(coin), currency, isCoinToken0, sqrtPriceX96, v3Factory);
108
202
 
109
- coin.initialize(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer);
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);
110
207
 
111
208
  emit CoinCreated(
112
209
  msg.sender,
113
210
  payoutRecipient,
114
- coin.platformReferrer(),
115
- coin.currency(),
211
+ platformReferrer,
212
+ currency,
116
213
  uri,
117
214
  name,
118
215
  symbol,
119
216
  address(coin),
120
- coin.poolAddress(),
121
- coin.contractVersion()
217
+ poolAddress,
218
+ IVersionedContract(address(coin)).contractVersion()
122
219
  );
220
+ }
123
221
 
124
- return coin;
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));
125
296
  }
126
297
 
127
298
  /// @dev Generates a unique salt for deterministic deployment
128
- 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) {
129
300
  return
130
301
  keccak256(
131
302
  abi.encodePacked(
@@ -137,7 +308,8 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
137
308
  block.prevrandao,
138
309
  block.timestamp,
139
310
  tx.gasprice,
140
- tx.origin
311
+ tx.origin,
312
+ coinSalt
141
313
  )
142
314
  );
143
315
  }
@@ -145,12 +317,12 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
145
317
  /// @dev Handles the first buy of a newly created coin
146
318
  /// @param coin The newly created coin contract
147
319
  /// @param orderSize The size of the first buy order; must match msg.value for ETH/WETH pairs
148
- function _handleFirstOrder(Coin coin, uint256 orderSize) internal returns (uint256 coinsPurchased) {
320
+ function _handleFirstOrder(ICoin coin, uint256 orderSize) internal returns (uint256 coinsPurchased) {
149
321
  if (msg.value > 0 || orderSize > 0) {
150
322
  address currency = coin.currency();
151
323
  address payoutRecipient = coin.payoutRecipient();
152
324
 
153
- if (currency != coin.WETH()) {
325
+ if (currency != Coin(payable(address(coin))).WETH()) {
154
326
  if (msg.value != 0) {
155
327
  revert EthTransferInvalid();
156
328
  }
@@ -159,9 +331,9 @@ contract ZoraFactoryImpl is IZoraFactory, UUPSUpgradeable, ReentrancyGuardUpgrad
159
331
 
160
332
  IERC20(currency).approve(address(coin), orderSize);
161
333
 
162
- (, coinsPurchased) = coin.buy(payoutRecipient, orderSize, 0, 0, address(0));
334
+ (, coinsPurchased) = Coin(payable(address(coin))).buy(payoutRecipient, orderSize, 0, 0, address(0));
163
335
  } else {
164
- (, 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));
165
337
  }
166
338
  }
167
339
  }
@@ -0,0 +1,195 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {BaseHook} from "@uniswap/v4-periphery/src/utils/BaseHook.sol";
5
+ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
6
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
7
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
8
+ import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
9
+ import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
10
+ import {IZoraV4CoinHook} from "../interfaces/IZoraV4CoinHook.sol";
11
+ import {IMsgSender} from "../interfaces/IMsgSender.sol";
12
+ import {IHasSwapPath} from "../interfaces/ICoinV4.sol";
13
+ import {LpPosition} from "../types/LpPosition.sol";
14
+ import {V4Liquidity} from "../libs/V4Liquidity.sol";
15
+ import {CoinRewardsV4} from "../libs/CoinRewardsV4.sol";
16
+ import {ICoinV4} from "../interfaces/ICoinV4.sol";
17
+ import {IDeployedCoinVersionLookup} from "../interfaces/IDeployedCoinVersionLookup.sol";
18
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
19
+ import {CoinCommon} from "../libs/CoinCommon.sol";
20
+ import {PoolConfiguration} from "../types/PoolConfiguration.sol";
21
+ import {CoinDopplerMultiCurve} from "../libs/CoinDopplerMultiCurve.sol";
22
+ import {PoolStateReader} from "../libs/PoolStateReader.sol";
23
+ import {IHasSwapPath} from "../interfaces/ICoinV4.sol";
24
+ import {ContractVersionBase} from "../version/ContractVersionBase.sol";
25
+ import {CoinConfigurationVersions} from "../libs/CoinConfigurationVersions.sol";
26
+
27
+ contract ZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4CoinHook {
28
+ using BalanceDeltaLibrary for BalanceDelta;
29
+
30
+ mapping(address => bool) internal trustedMessageSender;
31
+
32
+ IDeployedCoinVersionLookup internal immutable coinVersionLookup;
33
+
34
+ /// @inheritdoc IZoraV4CoinHook
35
+ function isTrustedMessageSender(address sender) external view returns (bool) {
36
+ return trustedMessageSender[sender];
37
+ }
38
+
39
+ /// @notice The constructor for the ZoraV4CoinHook.
40
+ /// @param poolManager_ The pool manager for the coin.
41
+ /// @param trustedMessageSenders_ The addresses of the trusted message senders.
42
+ constructor(IPoolManager poolManager_, IDeployedCoinVersionLookup coinVersionLookup_, address[] memory trustedMessageSenders_) BaseHook(poolManager_) {
43
+ if (address(coinVersionLookup_) == address(0)) {
44
+ revert CoinVersionLookupCannotBeZeroAddress();
45
+ }
46
+
47
+ coinVersionLookup = coinVersionLookup_;
48
+
49
+ for (uint256 i = 0; i < trustedMessageSenders_.length; i++) {
50
+ trustedMessageSender[trustedMessageSenders_[i]] = true;
51
+ }
52
+ }
53
+
54
+ /// @notice Returns the uniswap v4 hook settings / permissions.
55
+ /// @dev The permissions currently requested are: afterInitialize and afterSwap.
56
+ function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
57
+ return
58
+ Hooks.Permissions({
59
+ beforeInitialize: false,
60
+ afterInitialize: true,
61
+ beforeAddLiquidity: false,
62
+ afterAddLiquidity: false,
63
+ beforeRemoveLiquidity: false,
64
+ afterRemoveLiquidity: false,
65
+ beforeSwap: false,
66
+ afterSwap: true,
67
+ beforeDonate: false,
68
+ afterDonate: false,
69
+ beforeSwapReturnDelta: false,
70
+ afterSwapReturnDelta: false,
71
+ afterAddLiquidityReturnDelta: false,
72
+ afterRemoveLiquidityReturnDelta: false
73
+ });
74
+ }
75
+
76
+ mapping(bytes32 => IZoraV4CoinHook.PoolCoin) internal poolCoins;
77
+
78
+ function getPoolCoinByHash(bytes23 poolKeyHash) external view returns (IZoraV4CoinHook.PoolCoin memory) {
79
+ return poolCoins[poolKeyHash];
80
+ }
81
+
82
+ function getPoolCoin(PoolKey memory key) external view returns (IZoraV4CoinHook.PoolCoin memory) {
83
+ return poolCoins[CoinCommon.hashPoolKey(key)];
84
+ }
85
+
86
+ /// @notice Internal fn generating the positions for a given pool key.
87
+ /// @param coin The coin address.
88
+ /// @param key The pool key for the coin.
89
+ /// @return positions The contract-created liquidity positions the positions for the coin's pool.
90
+ function _generatePositions(ICoinV4 coin, PoolKey memory key) internal view returns (LpPosition[] memory positions) {
91
+ bool isCoinToken0 = Currency.unwrap(key.currency0) == address(coin);
92
+
93
+ positions = CoinDopplerMultiCurve.calculatePositions(isCoinToken0, coin.getPoolConfiguration());
94
+ }
95
+
96
+ /// @notice Internal fn called when a pool is initialized.
97
+ /// @dev This hook is called from BaseHook library from uniswap v4.
98
+ /// @param sender The address of the sender.
99
+ /// @param key The pool key.
100
+ /// @return selector The selector of the afterInitialize hook to confirm the action.
101
+ function _afterInitialize(address sender, PoolKey calldata key, uint160, int24) internal override returns (bytes4) {
102
+ address coin = sender;
103
+ if (!CoinConfigurationVersions.isV4(coinVersionLookup.getVersionForDeployedCoin(coin))) {
104
+ revert NotACoin(coin);
105
+ }
106
+
107
+ LpPosition[] memory positions = _generatePositions(ICoinV4(coin), key);
108
+
109
+ poolCoins[CoinCommon.hashPoolKey(key)] = PoolCoin({coin: coin, positions: positions});
110
+
111
+ V4Liquidity.lockAndMint(poolManager, key, positions);
112
+
113
+ return BaseHook.afterInitialize.selector;
114
+ }
115
+
116
+ /// @notice Internal fn called when a swap is executed.
117
+ /// @dev This hook is called from BaseHook library from uniswap v4.
118
+ /// @param sender The address of the sender.
119
+ /// @param key The pool key.
120
+ /// @param params The swap parameters.
121
+ /// @param delta The balance delta.
122
+ /// @param hookData The hook data.
123
+ /// @return selector The selector of the afterSwap hook to confirm the action.
124
+ function _afterSwap(
125
+ address sender,
126
+ PoolKey calldata key,
127
+ SwapParams calldata params,
128
+ BalanceDelta delta,
129
+ bytes calldata hookData
130
+ ) internal virtual override returns (bytes4, int128) {
131
+ bytes32 poolKeyHash = CoinCommon.hashPoolKey(key);
132
+
133
+ // get the coin address and positions for the pool key; they must have been set in the afterInitialize callback
134
+ address coin = poolCoins[poolKeyHash].coin;
135
+ require(coin != address(0), NoCoinForHook(key));
136
+
137
+ // get path for swapping the payout to a single currency
138
+ IHasSwapPath.PayoutSwapPath memory payoutSwapPath = IHasSwapPath(coin).getPayoutSwapPath(coinVersionLookup);
139
+
140
+ // Collect accrued LP fees from all positions, swap them to the target payout currency,
141
+ // and transfer the converted amount to this hook contract for distribution
142
+ (, , Currency receivedCurrency, uint128 receivedAmount) = CoinRewardsV4.collectFeesAndConvertToPayout(
143
+ poolManager,
144
+ key,
145
+ poolCoins[poolKeyHash].positions,
146
+ payoutSwapPath
147
+ );
148
+
149
+ // Distribute the collected and converted fees to all reward recipients (creator, referrers, protocol, etc.)
150
+ CoinRewardsV4.distributeMarketRewards(receivedCurrency, receivedAmount, ICoinV4(coin), CoinRewardsV4.getTradeReferral(hookData));
151
+
152
+ {
153
+ (address swapper, bool isTrustedSwapSenderAddress) = _getOriginalMsgSender(sender);
154
+ bool isCoinBuy = params.zeroForOne ? Currency.unwrap(key.currency1) == address(coin) : Currency.unwrap(key.currency0) == address(coin);
155
+ emit Swapped(
156
+ sender,
157
+ swapper,
158
+ isTrustedSwapSenderAddress,
159
+ key,
160
+ poolKeyHash,
161
+ params,
162
+ delta.amount0(),
163
+ delta.amount1(),
164
+ isCoinBuy,
165
+ hookData,
166
+ PoolStateReader.getSqrtPriceX96(key, poolManager)
167
+ );
168
+ }
169
+
170
+ return (BaseHook.afterSwap.selector, 0);
171
+ }
172
+
173
+ /// @notice Internal fn called when a liquidity is unlocked. Only callable from the PoolManager.
174
+ /// @dev This hook is called from BaseHook library from uniswap v4.
175
+ /// @param data The data.
176
+ /// @return selector The selector of the unlockCallback hook to confirm the action.
177
+ function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) {
178
+ return abi.encode(V4Liquidity.handleMintPositionsCallback(poolManager, data));
179
+ }
180
+
181
+ /// @notice Internal fn to get the original message sender.
182
+ /// @param sender The address of the sender.
183
+ /// @return swapper The original message sender.
184
+ /// @return senderIsTrusted Whether the sender is a trusted message sender.
185
+ function _getOriginalMsgSender(address sender) internal view returns (address swapper, bool senderIsTrusted) {
186
+ senderIsTrusted = trustedMessageSender[sender];
187
+
188
+ // If getter function reverts, we return a 0 address by default and continue execution.
189
+ try IMsgSender(sender).msgSender() returns (address _swapper) {
190
+ swapper = _swapper;
191
+ } catch {
192
+ swapper = address(0);
193
+ }
194
+ }
195
+ }
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import {ICoin} from "../interfaces/ICoin.sol";
5
- import {IZoraFactory} from "../interfaces/IZoraFactory.sol";
6
- import {ICoinDeployHook} from "../interfaces/ICoinDeployHook.sol";
4
+ import {ICoin} from "../../interfaces/ICoin.sol";
5
+ import {IZoraFactory} from "../../interfaces/IZoraFactory.sol";
6
+ import {ICoinDeployHook} from "../../interfaces/ICoinDeployHook.sol";
7
7
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
8
8
 
9
9
  /// @title Immutable State