@zoralabs/coins 2.1.2 → 2.3.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 (107) hide show
  1. package/.turbo/turbo-build$colon$js.log +152 -0
  2. package/CHANGELOG.md +93 -0
  3. package/README.md +4 -0
  4. package/abis/BaseCoin.json +26 -5
  5. package/abis/BaseTest.json +2 -7
  6. package/abis/ContentCoin.json +26 -5
  7. package/abis/CreatorCoin.json +30 -9
  8. package/abis/FeeEstimatorHook.json +94 -6
  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/{LiquidityMigrationReceiver.json → IUpgradeableDestinationV4HookWithUpdateableFee.json} +10 -18
  16. package/abis/IZoraFactory.json +121 -0
  17. package/abis/IZoraHookRegistry.json +188 -0
  18. package/abis/VmContractHelper226.json +233 -0
  19. package/abis/ZoraFactoryImpl.json +101 -6
  20. package/abis/ZoraHookRegistry.json +375 -0
  21. package/abis/{CreatorCoinHook.json → ZoraV4CoinHook.json} +95 -2
  22. package/addresses/8453.json +6 -5
  23. package/audits/report-cantinacode-zora-0827.pdf +3498 -4
  24. package/dist/index.cjs +93 -13
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.js +93 -13
  27. package/dist/index.js.map +1 -1
  28. package/dist/wagmiGenerated.d.ts +144 -22
  29. package/dist/wagmiGenerated.d.ts.map +1 -1
  30. package/foundry.toml +4 -1
  31. package/package/wagmiGenerated.ts +93 -13
  32. package/package.json +6 -4
  33. package/script/PrintRegisterUpgradePath.s.sol +0 -7
  34. package/script/TestBackingCoinSwap.s.sol +0 -3
  35. package/script/TestV4Swap.s.sol +0 -3
  36. package/script/UpgradeFactoryImpl.s.sol +1 -1
  37. package/src/BaseCoin.sol +19 -24
  38. package/src/ContentCoin.sol +11 -2
  39. package/src/CreatorCoin.sol +34 -15
  40. package/src/ZoraFactoryImpl.sol +163 -92
  41. package/src/deployment/CoinsDeployerBase.sol +24 -58
  42. package/src/hook-registry/ZoraHookRegistry.sol +97 -0
  43. package/src/hooks/{BaseZoraV4CoinHook.sol → ZoraV4CoinHook.sol} +77 -15
  44. package/src/interfaces/ICoin.sol +19 -1
  45. package/src/interfaces/ICreatorCoin.sol +4 -0
  46. package/src/interfaces/IUpgradeableV4Hook.sol +18 -0
  47. package/src/interfaces/IZoraFactory.sol +51 -10
  48. package/src/interfaces/IZoraHookRegistry.sol +47 -0
  49. package/src/libs/CoinConstants.sol +43 -32
  50. package/src/libs/CoinDopplerMultiCurve.sol +11 -11
  51. package/src/libs/CoinRewardsV4.sol +68 -37
  52. package/src/libs/CoinSetup.sol +2 -9
  53. package/src/libs/DopplerMath.sol +2 -2
  54. package/src/libs/HooksDeployment.sol +13 -65
  55. package/src/libs/V4Liquidity.sol +109 -15
  56. package/src/version/ContractVersionBase.sol +1 -1
  57. package/test/Coin.t.sol +5 -5
  58. package/test/CoinRewardsV4.t.sol +33 -0
  59. package/test/CoinUniV4.t.sol +32 -30
  60. package/test/ContentCoinRewards.t.sol +363 -0
  61. package/test/CreatorCoin.t.sol +53 -29
  62. package/test/CreatorCoinRewards.t.sol +375 -0
  63. package/test/DeploymentHooks.t.sol +64 -12
  64. package/test/Factory.t.sol +24 -7
  65. package/test/HooksDeployment.t.sol +4 -4
  66. package/test/LiquidityMigration.t.sol +149 -16
  67. package/test/Upgrades.t.sol +44 -48
  68. package/test/V4Liquidity.t.sol +178 -0
  69. package/test/ZoraHookRegistry.t.sol +266 -0
  70. package/test/utils/BaseTest.sol +25 -43
  71. package/test/utils/FeeEstimatorHook.sol +4 -6
  72. package/test/utils/RewardTestHelpers.sol +106 -0
  73. package/.turbo/turbo-build.log +0 -199
  74. package/abis/AutoSwapperTest.json +0 -618
  75. package/abis/BadImpl.json +0 -15
  76. package/abis/BaseZoraV4CoinHook.json +0 -1664
  77. package/abis/CoinConstants.json +0 -158
  78. package/abis/CoinRewardsV4.json +0 -67
  79. package/abis/CoinTest.json +0 -819
  80. package/abis/CoinUniV4Test.json +0 -1128
  81. package/abis/ContentCoinHook.json +0 -1733
  82. package/abis/CreatorCoinTest.json +0 -887
  83. package/abis/Deploy.json +0 -9
  84. package/abis/DeployHooks.json +0 -9
  85. package/abis/DeployScript.json +0 -35
  86. package/abis/DeployedCoinVersionLookupTest.json +0 -740
  87. package/abis/DifferentNamespaceVersionLookup.json +0 -39
  88. package/abis/FactoryTest.json +0 -748
  89. package/abis/FakeHookNoInterface.json +0 -21
  90. package/abis/GenerateDeterministicParams.json +0 -9
  91. package/abis/HooksDeploymentTest.json +0 -645
  92. package/abis/HooksTest.json +0 -709
  93. package/abis/InvalidLiquidityMigrationReceiver.json +0 -21
  94. package/abis/LiquidityMigrationTest.json +0 -889
  95. package/abis/MockBadFactory.json +0 -15
  96. package/abis/MultiOwnableTest.json +0 -766
  97. package/abis/PrintUpgradeCommand.json +0 -9
  98. package/abis/TestDeployedCoinVersionLookupImplementation.json +0 -39
  99. package/abis/TestV4Swap.json +0 -9
  100. package/abis/UpgradeFactoryImpl.json +0 -9
  101. package/abis/UpgradeHooks.json +0 -35
  102. package/abis/UpgradesTest.json +0 -723
  103. package/src/hooks/ContentCoinHook.sol +0 -27
  104. package/src/hooks/CreatorCoinHook.sol +0 -27
  105. package/src/libs/CreatorCoinConstants.sol +0 -16
  106. package/src/libs/CreatorCoinRewards.sol +0 -34
  107. package/src/libs/MarketConstants.sol +0 -15
@@ -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,97 @@
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
+ /// @notice Constructor for deployment pathway
27
+ /// @dev This contract needs to be atomically initialized, otherwise it is unsafe to deploy.
28
+ /// @dev Initial owners need to be automatically set by the deployer contract.
29
+ /// @dev The initial owners is not a part of the constructor() to allow for multichain deterministic addresses.
30
+ constructor() {}
31
+
32
+ /// @notice Initializes the registry with initial owners
33
+ function initialize(address[] memory initialOwners) external initializer {
34
+ __MultiOwnable_init(initialOwners);
35
+ }
36
+
37
+ /// @notice Returns whether a hook is currently registered
38
+ function isRegisteredHook(address hook) external view returns (bool) {
39
+ return registeredHooks.contains(hook);
40
+ }
41
+
42
+ /// @notice Returns all registered hooks
43
+ function getHooks() external view returns (ZoraHook[] memory) {
44
+ uint256 numHooks = registeredHooks.length();
45
+
46
+ ZoraHook[] memory hooks = new ZoraHook[](numHooks);
47
+
48
+ for (uint256 i; i < numHooks; i++) {
49
+ address hook = registeredHooks.at(i);
50
+
51
+ hooks[i] = ZoraHook({hook: hook, tag: getHookTag(hook), version: getHookVersion(hook)});
52
+ }
53
+
54
+ return hooks;
55
+ }
56
+
57
+ /// @notice Returns all registered hook addresses
58
+ function getHookAddresses() external view returns (address[] memory) {
59
+ return registeredHooks.values();
60
+ }
61
+
62
+ /// @notice Returns the tag for a hook
63
+ function getHookTag(address hook) public view returns (string memory) {
64
+ return hookTags[hook];
65
+ }
66
+
67
+ /// @notice Returns the contract version for a hook if it exists
68
+ function getHookVersion(address hook) public pure returns (string memory version) {
69
+ try IVersionedContract(hook).contractVersion() returns (string memory _version) {
70
+ version = _version;
71
+ } catch {}
72
+ }
73
+
74
+ /// @notice Adds hooks to the registry
75
+ function registerHooks(address[] calldata hooks, string[] calldata tags) external onlyOwner {
76
+ require(hooks.length == tags.length, ArrayLengthMismatch());
77
+
78
+ for (uint256 i; i < hooks.length; i++) {
79
+ if (registeredHooks.add(hooks[i])) {
80
+ hookTags[hooks[i]] = tags[i];
81
+
82
+ emit ZoraHookRegistered(hooks[i], tags[i], getHookVersion(hooks[i]));
83
+ }
84
+ }
85
+ }
86
+
87
+ /// @notice Removes hooks from the registry
88
+ function removeHooks(address[] calldata hooks) external onlyOwner {
89
+ for (uint256 i; i < hooks.length; i++) {
90
+ if (registeredHooks.remove(hooks[i])) {
91
+ emit ZoraHookRemoved(hooks[i], hookTags[hooks[i]], getHookVersion(hooks[i]));
92
+
93
+ delete hookTags[hooks[i]];
94
+ }
95
+ }
96
+ }
97
+ }
@@ -31,12 +31,14 @@ import {IUpgradeableV4Hook} from "../interfaces/IUpgradeableV4Hook.sol";
31
31
  import {IHooksUpgradeGate} from "../interfaces/IHooksUpgradeGate.sol";
32
32
  import {MultiOwnable} from "../utils/MultiOwnable.sol";
33
33
  import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
34
- import {IUpgradeableDestinationV4Hook} from "../interfaces/IUpgradeableV4Hook.sol";
34
+ import {IUpgradeableDestinationV4Hook, IUpgradeableDestinationV4HookWithUpdateableFee} from "../interfaces/IUpgradeableV4Hook.sol";
35
35
  import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
36
36
  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";
41
+ import {CoinConstants} from "../libs/CoinConstants.sol";
40
42
 
41
43
  /// @title ZoraV4CoinHook
42
44
  /// @notice Uniswap V4 hook that automatically handles fee collection and reward distributions on every swap,
@@ -48,7 +50,14 @@ import {ContractVersionBase, IVersionedContract} from "../version/ContractVersio
48
50
  /// 2. Swaps collected fees to the backing currency through multi-hop paths
49
51
  /// 3. Distributes converted fees as rewards
50
52
  /// @author oveddan
51
- abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4CoinHook, ERC165, IUpgradeableDestinationV4Hook {
53
+ contract ZoraV4CoinHook is
54
+ BaseHook,
55
+ ContractVersionBase,
56
+ IZoraV4CoinHook,
57
+ ERC165,
58
+ IUpgradeableDestinationV4Hook,
59
+ IUpgradeableDestinationV4HookWithUpdateableFee
60
+ {
52
61
  using BalanceDeltaLibrary for BalanceDelta;
53
62
 
54
63
  /// @notice Mapping of trusted message senders - these are addresses that are trusted to provide a
@@ -64,8 +73,6 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
64
73
  /// @notice The upgrade gate contract - used to verify allowed upgrade paths
65
74
  IHooksUpgradeGate internal immutable upgradeGate;
66
75
 
67
- uint256 internal immutable totalSupplyForPositions;
68
-
69
76
  /// @notice The constructor for the ZoraV4CoinHook.
70
77
  /// @param poolManager_ The Uniswap V4 pool manager
71
78
  /// @param coinVersionLookup_ The coin version lookup contract - used to determine if an address is a coin and what version it is.
@@ -75,8 +82,7 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
75
82
  IPoolManager poolManager_,
76
83
  IDeployedCoinVersionLookup coinVersionLookup_,
77
84
  address[] memory trustedMessageSenders_,
78
- IHooksUpgradeGate upgradeGate_,
79
- uint256 totalSupplyForPositions_
85
+ IHooksUpgradeGate upgradeGate_
80
86
  ) BaseHook(poolManager_) {
81
87
  require(address(coinVersionLookup_) != address(0), CoinVersionLookupCannotBeZeroAddress());
82
88
 
@@ -84,7 +90,6 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
84
90
 
85
91
  coinVersionLookup = coinVersionLookup_;
86
92
  upgradeGate = upgradeGate_;
87
- totalSupplyForPositions = totalSupplyForPositions_;
88
93
 
89
94
  for (uint256 i = 0; i < trustedMessageSenders_.length; i++) {
90
95
  trustedMessageSender[trustedMessageSenders_[i]] = true;
@@ -133,6 +138,7 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
133
138
  return
134
139
  super.supportsInterface(interfaceId) ||
135
140
  interfaceId == type(IUpgradeableDestinationV4Hook).interfaceId ||
141
+ interfaceId == type(IUpgradeableDestinationV4HookWithUpdateableFee).interfaceId ||
136
142
  interfaceId == type(IVersionedContract).interfaceId;
137
143
  }
138
144
 
@@ -140,10 +146,18 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
140
146
  /// @param coin The coin address.
141
147
  /// @param key The pool key for the coin.
142
148
  /// @return positions The contract-created liquidity positions the positions for the coin's pool.
143
- function _generatePositions(ICoin coin, PoolKey memory key) internal view returns (LpPosition[] memory positions) {
149
+ function _generatePositions(ICoin coin, PoolKey memory key) internal view returns (LpPosition[] memory) {
144
150
  bool isCoinToken0 = Currency.unwrap(key.currency0) == address(coin);
145
151
 
146
- positions = CoinDopplerMultiCurve.calculatePositions(isCoinToken0, coin.getPoolConfiguration(), totalSupplyForPositions);
152
+ LpPosition[] memory calculatedPositions = CoinDopplerMultiCurve.calculatePositions(
153
+ isCoinToken0,
154
+ coin.getPoolConfiguration(),
155
+ coin.totalSupplyForPositions()
156
+ );
157
+
158
+ // sometimes the calculated positions have liquidity added in duplicated positions. So here we dedupe them
159
+ // to save on gas in future swaps.
160
+ return V4Liquidity.dedupePositions(calculatedPositions);
147
161
  }
148
162
 
149
163
  /// @notice Internal fn called when a pool is initialized.
@@ -171,17 +185,50 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
171
185
  }
172
186
 
173
187
  /// @inheritdoc IUpgradeableDestinationV4Hook
188
+ /// @dev left for backward compatibility for migrating from hooks that dont support
189
+ /// updating the fee
174
190
  function initializeFromMigration(
175
191
  PoolKey calldata poolKey,
176
192
  address coin,
177
193
  uint160 sqrtPriceX96,
178
194
  BurnedPosition[] calldata migratedLiquidity,
179
- bytes calldata
195
+ bytes calldata additionalData
180
196
  ) external {
197
+ // keep the existing fee and tick spacing.
198
+ uint24 fee = poolKey.fee;
199
+ int24 tickSpacing = poolKey.tickSpacing;
200
+
201
+ _initializeFromMigration(poolKey, coin, sqrtPriceX96, migratedLiquidity, additionalData, fee, tickSpacing);
202
+ }
203
+
204
+ /// @inheritdoc IUpgradeableDestinationV4HookWithUpdateableFee
205
+ function initializeFromMigrationWithUpdateableFee(
206
+ PoolKey calldata poolKey,
207
+ address coin,
208
+ uint160 sqrtPriceX96,
209
+ BurnedPosition[] calldata migratedLiquidity,
210
+ bytes calldata additionalData
211
+ ) external returns (uint24 fee, int24 tickSpacing) {
212
+ // update the fee to the current one.
213
+ fee = CoinConstants.LP_FEE_V4;
214
+ tickSpacing = CoinConstants.TICK_SPACING;
215
+
216
+ _initializeFromMigration(poolKey, coin, sqrtPriceX96, migratedLiquidity, additionalData, fee, tickSpacing);
217
+ }
218
+
219
+ function _initializeFromMigration(
220
+ PoolKey calldata poolKey,
221
+ address coin,
222
+ uint160 sqrtPriceX96,
223
+ BurnedPosition[] calldata migratedLiquidity,
224
+ bytes calldata,
225
+ uint24 fee,
226
+ int24 tickSpacing
227
+ ) internal {
181
228
  address oldHook = msg.sender;
182
229
  address newHook = address(this);
183
230
 
184
- // Verify that the caller (new hook) is authorized to perform this migration
231
+ // Verify that the caller (old hook) is authorized to perform this migration
185
232
  // Only registered upgrade paths in the upgrade gate are allowed to migrate liquidity
186
233
  if (!upgradeGate.isRegisteredUpgradePath(oldHook, newHook)) {
187
234
  revert IUpgradeableV4Hook.UpgradePathNotRegistered(oldHook, newHook);
@@ -192,8 +239,8 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
192
239
  PoolKey memory newKey = PoolKey({
193
240
  currency0: poolKey.currency0,
194
241
  currency1: poolKey.currency1,
195
- fee: poolKey.fee,
196
- tickSpacing: poolKey.tickSpacing,
242
+ fee: fee,
243
+ tickSpacing: tickSpacing,
197
244
  hooks: IHooks(newHook)
198
245
  });
199
246
 
@@ -336,9 +383,17 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
336
383
  }
337
384
 
338
385
  /// @dev Internal fn to allow for overriding market reward distribution logic
339
- function _distributeMarketRewards(Currency currency, uint128 fees, IHasRewardsRecipients coin, address tradeReferrer) internal virtual;
386
+ function _distributeMarketRewards(Currency currency, uint128 fees, IHasRewardsRecipients coin, address tradeReferrer) internal virtual {
387
+ // get rewards distribution methodology from the coin
388
+ IHasCoinType.CoinType coinType = _getCoinType(coin);
389
+ CoinRewardsV4.distributeMarketRewards(currency, fees, coin, tradeReferrer, coinType);
390
+ }
340
391
 
341
- /// @notice Internal fn called when the PoolManager is unlocked. Used to mint initial liquidity positions.
392
+ function _getCoinType(IHasRewardsRecipients coin) internal view returns (IHasCoinType.CoinType) {
393
+ return CoinRewardsV4.getCoinType(coin);
394
+ }
395
+
396
+ /// @notice Internal fn called when the PoolManager is unlocked. Used to mint initial liquidity positions and burn positions during migration.
342
397
  function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) {
343
398
  return V4Liquidity.handleCallback(poolManager, data);
344
399
  }
@@ -371,7 +426,14 @@ abstract contract BaseZoraV4CoinHook is BaseHook, ContractVersionBase, IZoraV4Co
371
426
  }
372
427
 
373
428
  newPoolKey = V4Liquidity.lockAndMigrate(poolManager, poolKey, poolCoin.positions, poolCoin.coin, newHook, additionalData);
429
+
430
+ // Delete the old pool key mapping to prevent future operations on the migrated pool
431
+ delete poolCoins[poolKeyHash];
374
432
  }
375
433
 
434
+ /// @notice Receives ETH from the pool manager for ETH-backed coins during fee collection.
435
+ /// @dev Only required for coins using ETH as backing currency (currency = address(0)).
436
+ /// Restricted to onlyPoolManager to prevent ETH from getting stuck in the contract.
437
+ /// Unused for ERC20-backed coins.
376
438
  receive() external payable onlyPoolManager {}
377
439
  }
@@ -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
  }
@@ -50,3 +50,21 @@ interface IUpgradeableDestinationV4Hook {
50
50
  bytes calldata additionalData
51
51
  ) external;
52
52
  }
53
+
54
+ interface IUpgradeableDestinationV4HookWithUpdateableFee {
55
+ /// @notice Initialize after migration from old hook
56
+ /// @param poolKey The pool key being migrated
57
+ /// @param coin The coin address
58
+ /// @param sqrtPriceX96 The current sqrt price
59
+ /// @param migratedLiquidity The migrated liquidity
60
+ /// @param additionalData Additional data for initialization
61
+ /// @return fee The new fee for the migrated liquidity
62
+ /// @return tickSpacing The new tick spacing for the migrated liquidity
63
+ function initializeFromMigrationWithUpdateableFee(
64
+ PoolKey calldata poolKey,
65
+ address coin,
66
+ uint160 sqrtPriceX96,
67
+ BurnedPosition[] calldata migratedLiquidity,
68
+ bytes calldata additionalData
69
+ ) external returns (uint24 fee, int24 tickSpacing);
70
+ }
@@ -83,6 +83,21 @@ 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
+ /// @dev Deprecated: use `deployCreatorCoin` instead that has a salt and post-deploy hook specified
86
101
  function deployCreatorCoin(
87
102
  address payoutRecipient,
88
103
  address[] memory owners,
@@ -94,6 +109,33 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
94
109
  bytes32 coinSalt
95
110
  ) external returns (address);
96
111
 
112
+ /// @notice Creates a new creator coin contract with an optional hook that runs after the coin is deployed.
113
+ /// Enables buying initial supply by supporting ETH transfers to the post-deploy hook.
114
+ /// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
115
+ /// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
116
+ /// @param uri The coin metadata uri
117
+ /// @param name The name of the coin
118
+ /// @param symbol The symbol of the coin
119
+ /// @param poolConfig The config parameters for the coin's pool
120
+ /// @param platformReferrer The address of the platform referrer
121
+ /// @param postDeployHook The address of the hook to run after the coin is deployed
122
+ /// @param postDeployHookData The data to pass to the hook
123
+ /// @param coinSalt The salt used to deploy the coin
124
+ /// @return coin The address of the deployed creator coin
125
+ /// @return postDeployHookDataOut The data returned by the hook
126
+ function deployCreatorCoin(
127
+ address payoutRecipient,
128
+ address[] memory owners,
129
+ string memory uri,
130
+ string memory name,
131
+ string memory symbol,
132
+ bytes memory poolConfig,
133
+ address platformReferrer,
134
+ address postDeployHook,
135
+ bytes calldata postDeployHookData,
136
+ bytes32 coinSalt
137
+ ) external payable returns (address coin, bytes memory postDeployHookDataOut);
138
+
97
139
  /// @notice Creates a new coin contract with an optional hook that runs after the coin is deployed.
98
140
  /// Requires a salt to be specified, which enabled the coin to be deployed deterministically, and at
99
141
  /// a predictable address.
@@ -163,19 +205,18 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
163
205
  bytes calldata hookData
164
206
  ) external payable returns (address coin, bytes memory hookDataOut);
165
207
 
208
+ /// @notice The implementation address of the factory contract
166
209
  function implementation() external view returns (address);
167
210
 
168
- /// @notice Thrown when the hook is invalid
169
- error InvalidHook();
211
+ /// @notice The address of the latest coin hook
212
+ function hook() external view returns (address);
170
213
 
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);
214
+ /// @notice The address of the latest content coin hook
215
+ function contentCoinHook() external view returns (address);
175
216
 
176
- /// @notice Thrown when a method is deprecated
177
- error Deprecated();
217
+ /// @notice The address of the latestcreator coin hook
218
+ function creatorCoinHook() external view returns (address);
178
219
 
179
- /// @notice Thrwon when an invalid config version is provided
180
- error InvalidConfig();
220
+ /// @notice The address of the Zora hook registry
221
+ function zoraHookRegistry() external view returns (address);
181
222
  }
@@ -0,0 +1,47 @@
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
+ interface IZoraHookRegistry {
11
+ /// @notice Zora hook data
12
+ struct ZoraHook {
13
+ address hook;
14
+ string tag;
15
+ string version;
16
+ }
17
+
18
+ /// @notice Emitted when a hook is added to the registry
19
+ event ZoraHookRegistered(address indexed hook, string tag, string version);
20
+
21
+ /// @notice Emitted when a hook is removed from the registry
22
+ event ZoraHookRemoved(address indexed hook, string tag, string version);
23
+
24
+ /// @dev Reverts when the length of the hooks and tags arrays do not match
25
+ error ArrayLengthMismatch();
26
+
27
+ /// @notice Returns whether a hook is currently registered
28
+ function isRegisteredHook(address hook) external view returns (bool);
29
+
30
+ /// @notice Returns all registered hooks
31
+ function getHooks() external view returns (ZoraHook[] memory);
32
+
33
+ /// @notice Returns all registered hook addresses
34
+ function getHookAddresses() external view returns (address[] memory);
35
+
36
+ /// @notice Returns the tag for a hook
37
+ function getHookTag(address hook) external view returns (string memory);
38
+
39
+ /// @notice Returns the contract version for a hook if it exists
40
+ function getHookVersion(address hook) external view returns (string memory);
41
+
42
+ /// @notice Adds hooks to the registry
43
+ function registerHooks(address[] calldata hooks, string[] calldata tags) external;
44
+
45
+ /// @notice Removes hooks from the registry
46
+ function removeHooks(address[] calldata hooks) external;
47
+ }