@zoralabs/coins 2.1.2 → 2.2.1

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 (97) hide show
  1. package/.turbo/turbo-build$colon$js.log +152 -0
  2. package/CHANGELOG.md +54 -0
  3. package/abis/BaseCoin.json +26 -0
  4. package/abis/BaseTest.json +2 -7
  5. package/abis/CoinConstants.json +0 -104
  6. package/abis/ContentCoin.json +26 -0
  7. package/abis/CreatorCoin.json +30 -4
  8. package/abis/FeeEstimatorHook.json +0 -5
  9. package/abis/ICoin.json +26 -0
  10. package/abis/ICoinV3.json +26 -0
  11. package/abis/ICreatorCoin.json +39 -0
  12. package/abis/IERC721.json +36 -36
  13. package/abis/IHasCoinType.json +15 -0
  14. package/abis/IHasTotalSupplyForPositions.json +15 -0
  15. package/abis/IZoraFactory.json +52 -0
  16. package/abis/IZoraHookRegistry.json +188 -0
  17. package/abis/VmContractHelper227.json +233 -0
  18. package/abis/ZoraFactoryImpl.json +32 -6
  19. package/abis/ZoraHookRegistry.json +375 -0
  20. package/abis/{CreatorCoinHook.json → ZoraV4CoinHook.json} +1 -1
  21. package/addresses/8453.json +2 -1
  22. package/dist/index.cjs +72 -10
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.js +72 -10
  25. package/dist/index.js.map +1 -1
  26. package/dist/wagmiGenerated.d.ts +90 -10
  27. package/dist/wagmiGenerated.d.ts.map +1 -1
  28. package/foundry.toml +4 -1
  29. package/package/wagmiGenerated.ts +72 -10
  30. package/package.json +6 -4
  31. package/script/PrintRegisterUpgradePath.s.sol +0 -7
  32. package/script/TestBackingCoinSwap.s.sol +0 -1
  33. package/script/TestV4Swap.s.sol +0 -1
  34. package/script/UpgradeFactoryImpl.s.sol +1 -1
  35. package/src/BaseCoin.sol +15 -12
  36. package/src/ContentCoin.sol +10 -0
  37. package/src/CreatorCoin.sol +28 -7
  38. package/src/ZoraFactoryImpl.sol +62 -23
  39. package/src/deployment/CoinsDeployerBase.sol +24 -58
  40. package/src/hook-registry/ZoraHookRegistry.sol +93 -0
  41. package/src/hooks/{BaseZoraV4CoinHook.sol → ZoraV4CoinHook.sol} +13 -8
  42. package/src/interfaces/ICoin.sol +19 -1
  43. package/src/interfaces/ICreatorCoin.sol +4 -0
  44. package/src/interfaces/IZoraFactory.sol +32 -10
  45. package/src/interfaces/IZoraHookRegistry.sol +47 -0
  46. package/src/libs/CoinConstants.sol +0 -32
  47. package/src/libs/CoinRewardsV4.sol +53 -15
  48. package/src/libs/CreatorCoinConstants.sol +0 -1
  49. package/src/libs/HooksDeployment.sol +13 -65
  50. package/src/libs/MarketConstants.sol +11 -3
  51. package/src/libs/V4Liquidity.sol +30 -0
  52. package/src/version/ContractVersionBase.sol +1 -1
  53. package/test/CoinUniV4.t.sol +33 -30
  54. package/test/ContentCoinRewards.t.sol +320 -0
  55. package/test/CreatorCoin.t.sol +1 -1
  56. package/test/CreatorCoinRewards.t.sol +375 -0
  57. package/test/DeploymentHooks.t.sol +10 -10
  58. package/test/Factory.t.sol +24 -7
  59. package/test/HooksDeployment.t.sol +4 -4
  60. package/test/LiquidityMigration.t.sol +4 -9
  61. package/test/Upgrades.t.sol +44 -48
  62. package/test/ZoraHookRegistry.t.sol +266 -0
  63. package/test/utils/BaseTest.sol +25 -42
  64. package/test/utils/FeeEstimatorHook.sol +4 -6
  65. package/test/utils/RewardTestHelpers.sol +106 -0
  66. package/.turbo/turbo-build.log +0 -199
  67. package/abis/AutoSwapperTest.json +0 -618
  68. package/abis/BadImpl.json +0 -15
  69. package/abis/BaseZoraV4CoinHook.json +0 -1664
  70. package/abis/CoinTest.json +0 -819
  71. package/abis/CoinUniV4Test.json +0 -1128
  72. package/abis/ContentCoinHook.json +0 -1733
  73. package/abis/CreatorCoinTest.json +0 -887
  74. package/abis/Deploy.json +0 -9
  75. package/abis/DeployHooks.json +0 -9
  76. package/abis/DeployScript.json +0 -35
  77. package/abis/DeployedCoinVersionLookupTest.json +0 -740
  78. package/abis/DifferentNamespaceVersionLookup.json +0 -39
  79. package/abis/FactoryTest.json +0 -748
  80. package/abis/FakeHookNoInterface.json +0 -21
  81. package/abis/GenerateDeterministicParams.json +0 -9
  82. package/abis/HooksDeploymentTest.json +0 -645
  83. package/abis/HooksTest.json +0 -709
  84. package/abis/InvalidLiquidityMigrationReceiver.json +0 -21
  85. package/abis/LiquidityMigrationReceiver.json +0 -103
  86. package/abis/LiquidityMigrationTest.json +0 -889
  87. package/abis/MockBadFactory.json +0 -15
  88. package/abis/MultiOwnableTest.json +0 -766
  89. package/abis/PrintUpgradeCommand.json +0 -9
  90. package/abis/TestDeployedCoinVersionLookupImplementation.json +0 -39
  91. package/abis/TestV4Swap.json +0 -9
  92. package/abis/UpgradeFactoryImpl.json +0 -9
  93. package/abis/UpgradeHooks.json +0 -35
  94. package/abis/UpgradesTest.json +0 -723
  95. package/src/hooks/ContentCoinHook.sol +0 -27
  96. package/src/hooks/CreatorCoinHook.sol +0 -27
  97. package/src/libs/CreatorCoinRewards.sol +0 -34
@@ -8,6 +8,7 @@
8
8
  pragma solidity ^0.8.23;
9
9
 
10
10
  import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
11
+ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
11
12
  import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
12
13
  import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
13
14
  import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
@@ -35,6 +36,7 @@ import {CoinDopplerMultiCurve} from "./libs/CoinDopplerMultiCurve.sol";
35
36
  import {ICreatorCoin} from "./interfaces/ICreatorCoin.sol";
36
37
  import {MarketConstants} from "./libs/MarketConstants.sol";
37
38
  import {DeployedCoinVersionLookup} from "./utils/DeployedCoinVersionLookup.sol";
39
+ import {IZoraHookRegistry} from "./interfaces/IZoraHookRegistry.sol";
38
40
 
39
41
  contract ZoraFactoryImpl is
40
42
  IZoraFactory,
@@ -47,21 +49,33 @@ contract ZoraFactoryImpl is
47
49
  {
48
50
  using SafeERC20 for IERC20;
49
51
 
50
- /// @notice The coin contract implementation address
52
+ /// @notice The ZORA coin contract implementation address
51
53
  address public immutable coinV4Impl;
54
+ /// @notice The creator coin contract implementation address
52
55
  address public immutable creatorCoinImpl;
53
- address public immutable contentCoinHook;
54
- address public immutable creatorCoinHook;
56
+ /// @notice The uniswap v4 coin hook address
57
+ address public immutable hook;
58
+ /// @notice The zora hook registry address
59
+ address public immutable zoraHookRegistry;
55
60
 
56
- constructor(address _coinV4Impl, address _creatorCoinImpl, address _contentCoinHook, address _creatorCoinHook) {
61
+ constructor(address coinV4Impl_, address creatorCoinImpl_, address hook_, address zoraHookRegistry_) {
57
62
  _disableInitializers();
58
63
 
59
- coinV4Impl = _coinV4Impl;
60
- creatorCoinImpl = _creatorCoinImpl;
61
- contentCoinHook = _contentCoinHook;
62
- creatorCoinHook = _creatorCoinHook;
64
+ coinV4Impl = coinV4Impl_;
65
+ creatorCoinImpl = creatorCoinImpl_;
66
+ hook = hook_;
67
+ zoraHookRegistry = zoraHookRegistry_;
63
68
  }
64
69
 
70
+ /// @notice Creates a new creator coin contract
71
+ /// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
72
+ /// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
73
+ /// @param uri The coin metadata uri
74
+ /// @param name The name of the coin
75
+ /// @param symbol The symbol of the coin
76
+ /// @param poolConfig The config parameters for the coin's pool
77
+ /// @param platformReferrer The address of the platform referrer
78
+ /// @param coinSalt The salt used to deploy the coin
65
79
  function deployCreatorCoin(
66
80
  address payoutRecipient,
67
81
  address[] memory owners,
@@ -87,7 +101,7 @@ contract ZoraFactoryImpl is
87
101
  poolConfig
88
102
  );
89
103
 
90
- PoolKey memory poolKey = CoinSetup.buildPoolKey(address(creatorCoin), currency, isCoinToken0, IHooks(creatorCoinHook));
104
+ PoolKey memory poolKey = CoinSetup.buildPoolKey(address(creatorCoin), currency, isCoinToken0, IHooks(hook));
91
105
 
92
106
  ICreatorCoin(creatorCoin).initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolKey, sqrtPriceX96, poolConfiguration);
93
107
 
@@ -125,6 +139,7 @@ contract ZoraFactoryImpl is
125
139
  return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, postDeployHook, postDeployHookData, salt);
126
140
  }
127
141
 
142
+ /// @inheritdoc IZoraFactory
128
143
  function coinAddress(
129
144
  address msgSender,
130
145
  string memory name,
@@ -137,6 +152,7 @@ contract ZoraFactoryImpl is
137
152
  return Clones.predictDeterministicAddress(getCoinImpl(CoinConfigurationVersions.getVersion(poolConfig)), salt, address(this));
138
153
  }
139
154
 
155
+ /// @dev Internal function to deploy a coin with a hook
140
156
  function _deployWithHook(
141
157
  address payoutRecipient,
142
158
  address[] memory owners,
@@ -145,24 +161,26 @@ contract ZoraFactoryImpl is
145
161
  string memory symbol,
146
162
  bytes memory poolConfig,
147
163
  address platformReferrer,
148
- address hook,
164
+ address deployHook,
149
165
  bytes calldata hookData,
150
166
  bytes32 salt
151
167
  ) internal returns (address coin, bytes memory hookDataOut) {
152
168
  coin = address(_createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt));
153
169
 
154
- if (hook != address(0)) {
155
- if (!IERC165(hook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
170
+ if (deployHook != address(0)) {
171
+ if (!IERC165(deployHook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
156
172
  revert InvalidHook();
157
173
  }
158
- hookDataOut = IHasAfterCoinDeploy(hook).afterCoinDeploy{value: msg.value}(msg.sender, ICoin(coin), hookData);
174
+ hookDataOut = IHasAfterCoinDeploy(deployHook).afterCoinDeploy{value: msg.value}(msg.sender, ICoin(coin), hookData);
159
175
  } else if (msg.value > 0) {
160
176
  // cannot send eth without a hook
161
177
  revert EthTransferInvalid();
162
178
  }
163
179
  }
164
180
 
165
- /** Deprecated deploy functions */
181
+ /**
182
+ * Deprecated deploy functions
183
+ */
166
184
 
167
185
  /// @dev Deprecated: use `deploy` instead that has a salt and hook specified
168
186
  function deploy(
@@ -193,11 +211,11 @@ contract ZoraFactoryImpl is
193
211
  string memory symbol,
194
212
  bytes memory poolConfig,
195
213
  address platformReferrer,
196
- address hook,
214
+ address deployHook,
197
215
  bytes calldata hookData
198
216
  ) public payable nonReentrant returns (address coin, bytes memory hookDataOut) {
199
217
  bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
200
- return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, hook, hookData, salt);
218
+ return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, deployHook, hookData, salt);
201
219
  }
202
220
 
203
221
  /// @dev deprecated Use deploy() with poolConfig instead
@@ -210,8 +228,9 @@ contract ZoraFactoryImpl is
210
228
  address platformReferrer,
211
229
  address currency,
212
230
  // tickLower is no longer used
213
- int24 /*tickLower*/,
214
- uint256 orderSize
231
+ int24 /* tickLower */,
232
+ // orderSize is no longer used
233
+ uint256 /* orderSize */
215
234
  ) public payable nonReentrant returns (address, uint256) {
216
235
  bytes memory poolConfig = CoinConfigurationVersions.defaultConfig(currency);
217
236
  bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
@@ -221,6 +240,10 @@ contract ZoraFactoryImpl is
221
240
  return (address(coin), 0);
222
241
  }
223
242
 
243
+ /**
244
+ * End Deprecated deploy functions
245
+ */
246
+
224
247
  function getCoinImpl(uint8 version) internal view returns (address) {
225
248
  if (CoinConfigurationVersions.isV4(version)) {
226
249
  return coinV4Impl;
@@ -246,7 +269,7 @@ contract ZoraFactoryImpl is
246
269
  string memory symbol,
247
270
  address platformReferrer
248
271
  ) internal {
249
- PoolKey memory poolKey = CoinSetup.buildPoolKey(address(coin), currency, isCoinToken0, IHooks(contentCoinHook));
272
+ PoolKey memory poolKey = CoinSetup.buildPoolKey(address(coin), currency, isCoinToken0, IHooks(hook));
250
273
 
251
274
  // Initialize coin with pre-configured pool
252
275
  coin.initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolKey, sqrtPriceX96, poolConfiguration);
@@ -310,7 +333,6 @@ contract ZoraFactoryImpl is
310
333
  return keccak256(abi.encodePacked(msgSender, name, symbol, poolConfig, platformReferrer, coinSalt));
311
334
  }
312
335
 
313
- /// @dev Generates a unique salt for deterministic deployment
314
336
  function _randomSalt(address payoutRecipient, string memory uri, bytes32 coinSalt) internal view returns (bytes32) {
315
337
  return
316
338
  keccak256(
@@ -354,13 +376,30 @@ contract ZoraFactoryImpl is
354
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
355
377
  // unfortunately we cannot use supportsInterface here because the existing implementation did not have that function
356
378
  try IHasContractName(newImpl).contractName() returns (string memory name) {
357
- if (!_equals(name, contractName())) {
379
+ if (!Strings.equal(name, contractName())) {
358
380
  revert UpgradeToMismatchedContractName(contractName(), name);
359
381
  }
360
382
  } catch {}
383
+
384
+ // Auto-register the new hooks in the Zora hook registry
385
+ address[] memory hooks = new address[](1);
386
+ string[] memory tags = new string[](1);
387
+
388
+ hooks[0] = IZoraFactory(newImpl).hook();
389
+ tags[0] = "CoinHook";
390
+
391
+ IZoraHookRegistry(zoraHookRegistry).registerHooks(hooks, tags);
392
+ }
393
+
394
+ /// @notice The address of the latest creator coin hook
395
+ /// @dev Deprecated: use `hook` instead
396
+ function creatorCoinHook() external view returns (address) {
397
+ return hook;
361
398
  }
362
399
 
363
- function _equals(string memory a, string memory b) internal pure returns (bool) {
364
- return (keccak256(bytes(a)) == keccak256(bytes(b)));
400
+ /// @notice The address of the latest coin hook
401
+ /// @dev Deprecated: use `hook` instead
402
+ function contentCoinHook() external view returns (address) {
403
+ return hook;
365
404
  }
366
405
  }
@@ -37,12 +37,12 @@ contract CoinsDeployerBase is ProxyDeployerScript {
37
37
  // hooks
38
38
  address buySupplyWithSwapRouterHook;
39
39
  address zoraV4CoinHook;
40
- address creatorCoinHook;
41
40
  address hookUpgradeGate;
42
41
  // Hook deployment salt (for deterministic deployment)
43
42
  bytes32 zoraV4CoinHookSalt;
44
- bytes32 creatorCoinHookSalt;
45
43
  bool isDev;
44
+ // Hook registry
45
+ address zoraHookRegistry;
46
46
  }
47
47
 
48
48
  function addressesFile() internal view returns (string memory) {
@@ -67,9 +67,8 @@ contract CoinsDeployerBase is ProxyDeployerScript {
67
67
  vm.serializeAddress(objectKey, "ZORA_V4_COIN_HOOK", deployment.zoraV4CoinHook);
68
68
  vm.serializeBytes32(objectKey, "ZORA_V4_COIN_HOOK_SALT", deployment.zoraV4CoinHookSalt);
69
69
  vm.serializeAddress(objectKey, "CREATOR_COIN_IMPL", deployment.creatorCoinImpl);
70
- vm.serializeAddress(objectKey, "CREATOR_COIN_HOOK", deployment.creatorCoinHook);
71
- vm.serializeBytes32(objectKey, "CREATOR_COIN_HOOK_SALT", deployment.creatorCoinHookSalt);
72
70
  vm.serializeAddress(objectKey, "HOOK_UPGRADE_GATE", deployment.hookUpgradeGate);
71
+ vm.serializeAddress(objectKey, "ZORA_HOOK_REGISTRY", deployment.zoraHookRegistry);
73
72
  string memory result = vm.serializeAddress(objectKey, "COIN_V4_IMPL", deployment.coinV4Impl);
74
73
 
75
74
  vm.writeJson(result, addressesFile(deployment.isDev));
@@ -96,12 +95,11 @@ contract CoinsDeployerBase is ProxyDeployerScript {
96
95
  deployment.zoraV4CoinHook = readAddressOrDefaultToZero(json, "ZORA_V4_COIN_HOOK");
97
96
  deployment.zoraV4CoinHookSalt = readBytes32OrDefaultToZero(json, "ZORA_V4_COIN_HOOK_SALT");
98
97
  deployment.creatorCoinImpl = readAddressOrDefaultToZero(json, "CREATOR_COIN_IMPL");
99
- deployment.creatorCoinHook = readAddressOrDefaultToZero(json, "CREATOR_COIN_HOOK");
100
- deployment.creatorCoinHookSalt = readBytes32OrDefaultToZero(json, "CREATOR_COIN_HOOK_SALT");
101
98
  deployment.hookUpgradeGate = readAddressOrDefaultToZero(json, "HOOK_UPGRADE_GATE");
99
+ deployment.zoraHookRegistry = readAddressOrDefaultToZero(json, "ZORA_HOOK_REGISTRY");
102
100
  }
103
101
 
104
- function deployCoinV4Impl(address zoraV4CoinHook) internal returns (ContentCoin) {
102
+ function deployCoinV4Impl() internal returns (ContentCoin) {
105
103
  return
106
104
  new ContentCoin({
107
105
  protocolRewardRecipient_: getZoraRecipient(),
@@ -111,29 +109,18 @@ contract CoinsDeployerBase is ProxyDeployerScript {
111
109
  });
112
110
  }
113
111
 
114
- function deployCreatorCoinImpl(address creatorCoinHook) internal returns (CreatorCoin) {
112
+ function deployCreatorCoinImpl() internal returns (CreatorCoin) {
115
113
  return
116
114
  new CreatorCoin({
117
- _protocolRewardRecipient: getZoraRecipient(),
118
- _protocolRewards: PROTOCOL_REWARDS,
119
- _poolManager: IPoolManager(getUniswapV4PoolManager()),
120
- _airlock: getDopplerAirlock()
115
+ protocolRewardRecipient_: getZoraRecipient(),
116
+ protocolRewards_: PROTOCOL_REWARDS,
117
+ poolManager_: IPoolManager(getUniswapV4PoolManager()),
118
+ airlock_: getDopplerAirlock()
121
119
  });
122
120
  }
123
121
 
124
- function deployZoraFactoryImpl(
125
- address _coinV4Impl,
126
- address _creatorCoinImpl,
127
- address _contentCoinHook,
128
- address _creatorCoinHook
129
- ) internal returns (ZoraFactoryImpl) {
130
- return
131
- new ZoraFactoryImpl({
132
- _coinV4Impl: _coinV4Impl,
133
- _creatorCoinImpl: _creatorCoinImpl,
134
- _contentCoinHook: _contentCoinHook,
135
- _creatorCoinHook: _creatorCoinHook
136
- });
122
+ function deployZoraFactoryImpl(address coinV4Impl_, address creatorCoinImpl_, address hook_, address zoraHookRegistry_) internal returns (ZoraFactoryImpl) {
123
+ return new ZoraFactoryImpl({coinV4Impl_: coinV4Impl_, creatorCoinImpl_: creatorCoinImpl_, hook_: hook_, zoraHookRegistry_: zoraHookRegistry_});
137
124
  }
138
125
 
139
126
  // function deployBuySupplyWithSwapRouterHook(CoinsDeployment memory deployment) internal returns (BuySupplyWithSwapRouterHook) {
@@ -151,11 +138,11 @@ contract CoinsDeployerBase is ProxyDeployerScript {
151
138
  return deployment;
152
139
  }
153
140
 
154
- function deployContentCoinHook(CoinsDeployment memory deployment) internal returns (IHooks hook, bytes32 salt) {
141
+ function deployZoraV4CoinHook(CoinsDeployment memory deployment) internal returns (IHooks hook, bytes32 salt) {
155
142
  return
156
143
  HooksDeployment.deployHookWithExistingOrNewSalt(
157
144
  HooksDeployment.FOUNDRY_SCRIPT_ADDRESS,
158
- HooksDeployment.contentCoinCreationCode(
145
+ HooksDeployment.makeHookCreationCode(
159
146
  getUniswapV4PoolManager(),
160
147
  deployment.zoraFactory,
161
148
  getDefaultTrustedMessageSenders(),
@@ -165,20 +152,6 @@ contract CoinsDeployerBase is ProxyDeployerScript {
165
152
  );
166
153
  }
167
154
 
168
- function deployCreatorCoinHook(CoinsDeployment memory deployment) internal returns (IHooks hook, bytes32 salt) {
169
- return
170
- HooksDeployment.deployHookWithExistingOrNewSalt(
171
- HooksDeployment.FOUNDRY_SCRIPT_ADDRESS,
172
- HooksDeployment.creatorCoinHookCreationCode(
173
- getUniswapV4PoolManager(),
174
- deployment.zoraFactory,
175
- getDefaultTrustedMessageSenders(),
176
- deployment.hookUpgradeGate
177
- ),
178
- deployment.creatorCoinHookSalt
179
- );
180
- }
181
-
182
155
  function getDefaultTrustedMessageSenders() internal view returns (address[] memory) {
183
156
  address[] memory trustedMessageSenders = new address[](2);
184
157
  trustedMessageSenders[0] = getUniswapUniversalRouter();
@@ -190,10 +163,10 @@ contract CoinsDeployerBase is ProxyDeployerScript {
190
163
  return
191
164
  address(
192
165
  deployZoraFactoryImpl({
193
- _coinV4Impl: deployment.coinV4Impl,
194
- _creatorCoinImpl: deployment.creatorCoinImpl,
195
- _contentCoinHook: deployment.zoraV4CoinHook,
196
- _creatorCoinHook: deployment.creatorCoinHook
166
+ coinV4Impl_: deployment.coinV4Impl,
167
+ creatorCoinImpl_: deployment.creatorCoinImpl,
168
+ hook_: deployment.zoraV4CoinHook,
169
+ zoraHookRegistry_: deployment.zoraHookRegistry
197
170
  })
198
171
  );
199
172
  }
@@ -203,17 +176,12 @@ contract CoinsDeployerBase is ProxyDeployerScript {
203
176
 
204
177
  // Deploy hook first, then use its address for coin v4 impl
205
178
  console.log("deploying content coin hook");
206
- (IHooks zoraV4CoinHook, bytes32 usedSalt) = deployContentCoinHook(deployment);
179
+ (IHooks zoraV4CoinHook, bytes32 usedSalt) = deployZoraV4CoinHook(deployment);
207
180
  deployment.zoraV4CoinHook = address(zoraV4CoinHook);
208
181
  deployment.zoraV4CoinHookSalt = usedSalt;
209
182
 
210
- console.log("deploying creator coin hook");
211
- (IHooks creatorCoinHook, bytes32 usedCreatorCoinHookSalt) = deployCreatorCoinHook(deployment);
212
- deployment.creatorCoinHook = address(creatorCoinHook);
213
- deployment.creatorCoinHookSalt = usedCreatorCoinHookSalt;
214
-
215
- deployment.coinV4Impl = address(deployCoinV4Impl(deployment.zoraV4CoinHook));
216
- deployment.creatorCoinImpl = address(deployCreatorCoinImpl(deployment.creatorCoinHook));
183
+ deployment.coinV4Impl = address(deployCoinV4Impl());
184
+ deployment.creatorCoinImpl = address(deployCreatorCoinImpl());
217
185
  deployment.zoraFactoryImpl = deployFactoryImpl(deployment);
218
186
  deployment.coinVersion = IVersionedContract(deployment.coinV4Impl).contractVersion();
219
187
  // deployment.buySupplyWithSwapRouterHook = address(deployBuySupplyWithSwapRouterHook(deployment));
@@ -223,14 +191,10 @@ contract CoinsDeployerBase is ProxyDeployerScript {
223
191
 
224
192
  function deployHooks(CoinsDeployment memory deployment) internal returns (CoinsDeployment memory) {
225
193
  // Deploy hook first, then use its address for coin v4 impl
226
- (IHooks zoraV4CoinHook, bytes32 usedSalt) = deployContentCoinHook(deployment);
194
+ (IHooks zoraV4CoinHook, bytes32 usedSalt) = deployZoraV4CoinHook(deployment);
227
195
  deployment.zoraV4CoinHook = address(zoraV4CoinHook);
228
196
  deployment.zoraV4CoinHookSalt = usedSalt;
229
197
 
230
- (IHooks creatorCoinHook, bytes32 usedCreatorCoinHookSalt) = deployCreatorCoinHook(deployment);
231
- deployment.creatorCoinHook = address(creatorCoinHook);
232
- deployment.creatorCoinHookSalt = usedCreatorCoinHookSalt;
233
-
234
198
  deployment.zoraFactoryImpl = deployFactoryImpl(deployment);
235
199
 
236
200
  return deployment;
@@ -284,6 +248,8 @@ contract CoinsDeployerBase is ProxyDeployerScript {
284
248
  ZoraFactoryImpl(deployment.zoraFactory).initialize(owner);
285
249
 
286
250
  saveDeployment(deployment);
251
+
252
+ return ZoraFactory(payable(deployment.zoraFactory));
287
253
  }
288
254
 
289
255
  function printUpgradeFactoryCommand(CoinsDeployment memory deployment) internal view {
@@ -0,0 +1,93 @@
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
8
+ pragma solidity ^0.8.23;
9
+
10
+ import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
11
+ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersionedContract.sol";
12
+ import {MultiOwnable} from "../utils/MultiOwnable.sol";
13
+ import {IZoraHookRegistry} from "../interfaces/IZoraHookRegistry.sol";
14
+
15
+ /// @title Zora Hook Registry
16
+ /// @notice A registry of Zora hook contracts for Uniswap V4
17
+ contract ZoraHookRegistry is IZoraHookRegistry, MultiOwnable {
18
+ using EnumerableSet for EnumerableSet.AddressSet;
19
+
20
+ /// @dev The set of registered hook addresses
21
+ EnumerableSet.AddressSet internal registeredHooks;
22
+
23
+ /// @dev The tag for each hook
24
+ mapping(address hook => string tag) internal hookTags;
25
+
26
+ constructor() {}
27
+
28
+ /// @notice Initializes the registry with initial owners
29
+ function initialize(address[] memory initialOwners) external initializer {
30
+ __MultiOwnable_init(initialOwners);
31
+ }
32
+
33
+ /// @notice Returns whether a hook is currently registered
34
+ function isRegisteredHook(address hook) external view returns (bool) {
35
+ return registeredHooks.contains(hook);
36
+ }
37
+
38
+ /// @notice Returns all registered hooks
39
+ function getHooks() external view returns (ZoraHook[] memory) {
40
+ uint256 numHooks = registeredHooks.length();
41
+
42
+ ZoraHook[] memory hooks = new ZoraHook[](numHooks);
43
+
44
+ for (uint256 i; i < numHooks; i++) {
45
+ address hook = registeredHooks.at(i);
46
+
47
+ hooks[i] = ZoraHook({hook: hook, tag: getHookTag(hook), version: getHookVersion(hook)});
48
+ }
49
+
50
+ return hooks;
51
+ }
52
+
53
+ /// @notice Returns all registered hook addresses
54
+ function getHookAddresses() external view returns (address[] memory) {
55
+ return registeredHooks.values();
56
+ }
57
+
58
+ /// @notice Returns the tag for a hook
59
+ function getHookTag(address hook) public view returns (string memory) {
60
+ return hookTags[hook];
61
+ }
62
+
63
+ /// @notice Returns the contract version for a hook if it exists
64
+ function getHookVersion(address hook) public pure returns (string memory version) {
65
+ try IVersionedContract(hook).contractVersion() returns (string memory _version) {
66
+ version = _version;
67
+ } catch {}
68
+ }
69
+
70
+ /// @notice Adds hooks to the registry
71
+ function registerHooks(address[] calldata hooks, string[] calldata tags) external onlyOwner {
72
+ require(hooks.length == tags.length, ArrayLengthMismatch());
73
+
74
+ for (uint256 i; i < hooks.length; i++) {
75
+ if (registeredHooks.add(hooks[i])) {
76
+ hookTags[hooks[i]] = tags[i];
77
+
78
+ emit ZoraHookRegistered(hooks[i], tags[i], getHookVersion(hooks[i]));
79
+ }
80
+ }
81
+ }
82
+
83
+ /// @notice Removes hooks from the registry
84
+ function removeHooks(address[] calldata hooks) external onlyOwner {
85
+ for (uint256 i; i < hooks.length; i++) {
86
+ if (registeredHooks.remove(hooks[i])) {
87
+ emit ZoraHookRemoved(hooks[i], hookTags[hooks[i]], getHookVersion(hooks[i]));
88
+
89
+ delete hookTags[hooks[i]];
90
+ }
91
+ }
92
+ }
93
+ }
@@ -37,6 +37,7 @@ import {BurnedPosition} from "../interfaces/IUpgradeableV4Hook.sol";
37
37
  import {LiquidityAmounts} from "../utils/uniswap/LiquidityAmounts.sol";
38
38
  import {TickMath} from "../utils/uniswap/TickMath.sol";
39
39
  import {ContractVersionBase, IVersionedContract} from "../version/ContractVersionBase.sol";
40
+ import {IHasCoinType} from "../interfaces/ICoin.sol";
40
41
 
41
42
  /// @title ZoraV4CoinHook
42
43
  /// @notice Uniswap V4 hook that automatically handles fee collection and reward distributions on every swap,
@@ -48,7 +49,7 @@ import {ContractVersionBase, IVersionedContract} from "../version/ContractVersio
48
49
  /// 2. Swaps collected fees to the backing currency through multi-hop paths
49
50
  /// 3. Distributes converted fees as rewards
50
51
  /// @author oveddan
51
- abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4CoinHook, ERC165, IUpgradeableDestinationV4Hook {
52
+ contract ZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4CoinHook, ERC165, IUpgradeableDestinationV4Hook {
52
53
  using BalanceDeltaLibrary for BalanceDelta;
53
54
 
54
55
  /// @notice Mapping of trusted message senders - these are addresses that are trusted to provide a
@@ -64,8 +65,6 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
64
65
  /// @notice The upgrade gate contract - used to verify allowed upgrade paths
65
66
  IHooksUpgradeGate internal immutable upgradeGate;
66
67
 
67
- uint256 internal immutable totalSupplyForPositions;
68
-
69
68
  /// @notice The constructor for the ZoraV4CoinHook.
70
69
  /// @param poolManager_ The Uniswap V4 pool manager
71
70
  /// @param coinVersionLookup_ The coin version lookup contract - used to determine if an address is a coin and what version it is.
@@ -75,8 +74,7 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
75
74
  IPoolManager poolManager_,
76
75
  IDeployedCoinVersionLookup coinVersionLookup_,
77
76
  address[] memory trustedMessageSenders_,
78
- IHooksUpgradeGate upgradeGate_,
79
- uint256 totalSupplyForPositions_
77
+ IHooksUpgradeGate upgradeGate_
80
78
  ) BaseHook(poolManager_) {
81
79
  require(address(coinVersionLookup_) != address(0), CoinVersionLookupCannotBeZeroAddress());
82
80
 
@@ -84,7 +82,6 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
84
82
 
85
83
  coinVersionLookup = coinVersionLookup_;
86
84
  upgradeGate = upgradeGate_;
87
- totalSupplyForPositions = totalSupplyForPositions_;
88
85
 
89
86
  for (uint256 i = 0; i < trustedMessageSenders_.length; i++) {
90
87
  trustedMessageSender[trustedMessageSenders_[i]] = true;
@@ -143,7 +140,7 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
143
140
  function _generatePositions(ICoin coin, PoolKey memory key) internal view returns (LpPosition[] memory positions) {
144
141
  bool isCoinToken0 = Currency.unwrap(key.currency0) == address(coin);
145
142
 
146
- positions = CoinDopplerMultiCurve.calculatePositions(isCoinToken0, coin.getPoolConfiguration(), totalSupplyForPositions);
143
+ positions = CoinDopplerMultiCurve.calculatePositions(isCoinToken0, coin.getPoolConfiguration(), coin.totalSupplyForPositions());
147
144
  }
148
145
 
149
146
  /// @notice Internal fn called when a pool is initialized.
@@ -336,7 +333,15 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
336
333
  }
337
334
 
338
335
  /// @dev Internal fn to allow for overriding market reward distribution logic
339
- function _distributeMarketRewards(Currency currency, uint128 fees, IHasRewardsRecipients coin, address tradeReferrer) internal virtual;
336
+ function _distributeMarketRewards(Currency currency, uint128 fees, IHasRewardsRecipients coin, address tradeReferrer) internal virtual {
337
+ // get rewards distribution methodology from the coin
338
+ IHasCoinType.CoinType coinType = _getCoinType(coin);
339
+ CoinRewardsV4.distributeMarketRewards(currency, fees, coin, tradeReferrer, coinType);
340
+ }
341
+
342
+ function _getCoinType(IHasRewardsRecipients coin) internal view returns (IHasCoinType.CoinType) {
343
+ return CoinRewardsV4.getCoinType(coin);
344
+ }
340
345
 
341
346
  /// @notice Internal fn called when the PoolManager is unlocked. Used to mint initial liquidity positions.
342
347
  function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) {
@@ -50,7 +50,25 @@ interface IHasSwapPath {
50
50
  function getPayoutSwapPath(IDeployedCoinVersionLookup coinVersionLookup) external view returns (PayoutSwapPath memory);
51
51
  }
52
52
 
53
- interface ICoin is IERC165, IERC7572, IDopplerErrors, IHasRewardsRecipients, IHasPoolKey, IHasSwapPath {
53
+ interface IHasTotalSupplyForPositions {
54
+ /// @notice Returns the total supply of all positions for this coin
55
+ /// @return The total supply of all positions
56
+ function totalSupplyForPositions() external view returns (uint256);
57
+ }
58
+
59
+ interface IHasCoinType {
60
+ /// @notice The type of coin
61
+ enum CoinType {
62
+ Creator,
63
+ Content
64
+ }
65
+
66
+ /// @notice Returns the type of coin
67
+ /// @return The type of coin
68
+ function coinType() external view returns (CoinType);
69
+ }
70
+
71
+ interface ICoin is IERC165, IERC7572, IDopplerErrors, IHasRewardsRecipients, IHasPoolKey, IHasSwapPath, IHasTotalSupplyForPositions, IHasCoinType {
54
72
  /// @notice Thrown when the name is required for the coin
55
73
  error NameIsRequired();
56
74
 
@@ -18,4 +18,8 @@ interface ICreatorCoin is ICoin {
18
18
  /// @notice Allows the creator payout recipient to claim vested tokens
19
19
  /// @return claimAmount The amount of tokens claimed
20
20
  function claimVesting() external returns (uint256);
21
+
22
+ /// @notice Get the amount of vested tokens that can be claimed
23
+ /// @return claimAmount The amount of tokens that can be claimed
24
+ function getClaimableAmount() external view returns (uint256);
21
25
  }
@@ -83,6 +83,29 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
83
83
  /// @notice Thrown when ETH is sent with a transaction but the currency is not WETH
84
84
  error EthTransferInvalid();
85
85
 
86
+ /// @notice Thrown when the hook is invalid
87
+ error InvalidHook();
88
+
89
+ /// @notice Occurs when attempting to upgrade to a contract with a name that doesn't match the current contract's name
90
+ /// @param currentName The name of the current contract
91
+ /// @param newName The name of the contract being upgraded to
92
+ error UpgradeToMismatchedContractName(string currentName, string newName);
93
+
94
+ /// @notice Thrown when a method is deprecated
95
+ error Deprecated();
96
+
97
+ /// @notice Thrwon when an invalid config version is provided
98
+ error InvalidConfig();
99
+
100
+ /// @notice Creates a new creator coin contract
101
+ /// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
102
+ /// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
103
+ /// @param uri The coin metadata uri
104
+ /// @param name The name of the coin
105
+ /// @param symbol The symbol of the coin
106
+ /// @param poolConfig The config parameters for the coin's pool
107
+ /// @param platformReferrer The address of the platform referrer
108
+ /// @param coinSalt The salt used to deploy the coin
86
109
  function deployCreatorCoin(
87
110
  address payoutRecipient,
88
111
  address[] memory owners,
@@ -163,19 +186,18 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
163
186
  bytes calldata hookData
164
187
  ) external payable returns (address coin, bytes memory hookDataOut);
165
188
 
189
+ /// @notice The implementation address of the factory contract
166
190
  function implementation() external view returns (address);
167
191
 
168
- /// @notice Thrown when the hook is invalid
169
- error InvalidHook();
192
+ /// @notice The address of the latest coin hook
193
+ function hook() external view returns (address);
170
194
 
171
- /// @notice Occurs when attempting to upgrade to a contract with a name that doesn't match the current contract's name
172
- /// @param currentName The name of the current contract
173
- /// @param newName The name of the contract being upgraded to
174
- error UpgradeToMismatchedContractName(string currentName, string newName);
195
+ /// @notice The address of the latest content coin hook
196
+ function contentCoinHook() external view returns (address);
175
197
 
176
- /// @notice Thrown when a method is deprecated
177
- error Deprecated();
198
+ /// @notice The address of the latestcreator coin hook
199
+ function creatorCoinHook() external view returns (address);
178
200
 
179
- /// @notice Thrwon when an invalid config version is provided
180
- error InvalidConfig();
201
+ /// @notice The address of the Zora hook registry
202
+ function zoraHookRegistry() external view returns (address);
181
203
  }