@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.
- package/.turbo/turbo-build$colon$js.log +152 -0
- package/CHANGELOG.md +93 -0
- package/README.md +4 -0
- package/abis/BaseCoin.json +26 -5
- package/abis/BaseTest.json +2 -7
- package/abis/ContentCoin.json +26 -5
- package/abis/CreatorCoin.json +30 -9
- package/abis/FeeEstimatorHook.json +94 -6
- package/abis/ICoin.json +26 -0
- package/abis/ICoinV3.json +26 -0
- package/abis/ICreatorCoin.json +39 -0
- package/abis/IERC721.json +36 -36
- package/abis/IHasCoinType.json +15 -0
- package/abis/IHasTotalSupplyForPositions.json +15 -0
- package/abis/{LiquidityMigrationReceiver.json → IUpgradeableDestinationV4HookWithUpdateableFee.json} +10 -18
- package/abis/IZoraFactory.json +121 -0
- package/abis/IZoraHookRegistry.json +188 -0
- package/abis/VmContractHelper226.json +233 -0
- package/abis/ZoraFactoryImpl.json +101 -6
- package/abis/ZoraHookRegistry.json +375 -0
- package/abis/{CreatorCoinHook.json → ZoraV4CoinHook.json} +95 -2
- package/addresses/8453.json +6 -5
- package/audits/report-cantinacode-zora-0827.pdf +3498 -4
- package/dist/index.cjs +93 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +93 -13
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +144 -22
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/foundry.toml +4 -1
- package/package/wagmiGenerated.ts +93 -13
- package/package.json +6 -4
- package/script/PrintRegisterUpgradePath.s.sol +0 -7
- package/script/TestBackingCoinSwap.s.sol +0 -3
- package/script/TestV4Swap.s.sol +0 -3
- package/script/UpgradeFactoryImpl.s.sol +1 -1
- package/src/BaseCoin.sol +19 -24
- package/src/ContentCoin.sol +11 -2
- package/src/CreatorCoin.sol +34 -15
- package/src/ZoraFactoryImpl.sol +163 -92
- package/src/deployment/CoinsDeployerBase.sol +24 -58
- package/src/hook-registry/ZoraHookRegistry.sol +97 -0
- package/src/hooks/{BaseZoraV4CoinHook.sol → ZoraV4CoinHook.sol} +77 -15
- package/src/interfaces/ICoin.sol +19 -1
- package/src/interfaces/ICreatorCoin.sol +4 -0
- package/src/interfaces/IUpgradeableV4Hook.sol +18 -0
- package/src/interfaces/IZoraFactory.sol +51 -10
- package/src/interfaces/IZoraHookRegistry.sol +47 -0
- package/src/libs/CoinConstants.sol +43 -32
- package/src/libs/CoinDopplerMultiCurve.sol +11 -11
- package/src/libs/CoinRewardsV4.sol +68 -37
- package/src/libs/CoinSetup.sol +2 -9
- package/src/libs/DopplerMath.sol +2 -2
- package/src/libs/HooksDeployment.sol +13 -65
- package/src/libs/V4Liquidity.sol +109 -15
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/Coin.t.sol +5 -5
- package/test/CoinRewardsV4.t.sol +33 -0
- package/test/CoinUniV4.t.sol +32 -30
- package/test/ContentCoinRewards.t.sol +363 -0
- package/test/CreatorCoin.t.sol +53 -29
- package/test/CreatorCoinRewards.t.sol +375 -0
- package/test/DeploymentHooks.t.sol +64 -12
- package/test/Factory.t.sol +24 -7
- package/test/HooksDeployment.t.sol +4 -4
- package/test/LiquidityMigration.t.sol +149 -16
- package/test/Upgrades.t.sol +44 -48
- package/test/V4Liquidity.t.sol +178 -0
- package/test/ZoraHookRegistry.t.sol +266 -0
- package/test/utils/BaseTest.sol +25 -43
- package/test/utils/FeeEstimatorHook.sol +4 -6
- package/test/utils/RewardTestHelpers.sol +106 -0
- package/.turbo/turbo-build.log +0 -199
- package/abis/AutoSwapperTest.json +0 -618
- package/abis/BadImpl.json +0 -15
- package/abis/BaseZoraV4CoinHook.json +0 -1664
- package/abis/CoinConstants.json +0 -158
- package/abis/CoinRewardsV4.json +0 -67
- package/abis/CoinTest.json +0 -819
- package/abis/CoinUniV4Test.json +0 -1128
- package/abis/ContentCoinHook.json +0 -1733
- package/abis/CreatorCoinTest.json +0 -887
- package/abis/Deploy.json +0 -9
- package/abis/DeployHooks.json +0 -9
- package/abis/DeployScript.json +0 -35
- package/abis/DeployedCoinVersionLookupTest.json +0 -740
- package/abis/DifferentNamespaceVersionLookup.json +0 -39
- package/abis/FactoryTest.json +0 -748
- package/abis/FakeHookNoInterface.json +0 -21
- package/abis/GenerateDeterministicParams.json +0 -9
- package/abis/HooksDeploymentTest.json +0 -645
- package/abis/HooksTest.json +0 -709
- package/abis/InvalidLiquidityMigrationReceiver.json +0 -21
- package/abis/LiquidityMigrationTest.json +0 -889
- package/abis/MockBadFactory.json +0 -15
- package/abis/MultiOwnableTest.json +0 -766
- package/abis/PrintUpgradeCommand.json +0 -9
- package/abis/TestDeployedCoinVersionLookupImplementation.json +0 -39
- package/abis/TestV4Swap.json +0 -9
- package/abis/UpgradeFactoryImpl.json +0 -9
- package/abis/UpgradeHooks.json +0 -35
- package/abis/UpgradesTest.json +0 -723
- package/src/hooks/ContentCoinHook.sol +0 -27
- package/src/hooks/CreatorCoinHook.sol +0 -27
- package/src/libs/CreatorCoinConstants.sol +0 -16
- package/src/libs/CreatorCoinRewards.sol +0 -34
- 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(
|
|
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(
|
|
112
|
+
function deployCreatorCoinImpl() internal returns (CreatorCoin) {
|
|
115
113
|
return
|
|
116
114
|
new CreatorCoin({
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
protocolRewardRecipient_: getZoraRecipient(),
|
|
116
|
+
protocolRewards_: PROTOCOL_REWARDS,
|
|
117
|
+
poolManager_: IPoolManager(getUniswapV4PoolManager()),
|
|
118
|
+
airlock_: getDopplerAirlock()
|
|
121
119
|
});
|
|
122
120
|
}
|
|
123
121
|
|
|
124
|
-
function deployZoraFactoryImpl(
|
|
125
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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) =
|
|
179
|
+
(IHooks zoraV4CoinHook, bytes32 usedSalt) = deployZoraV4CoinHook(deployment);
|
|
207
180
|
deployment.zoraV4CoinHook = address(zoraV4CoinHook);
|
|
208
181
|
deployment.zoraV4CoinHookSalt = usedSalt;
|
|
209
182
|
|
|
210
|
-
|
|
211
|
-
|
|
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) =
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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:
|
|
196
|
-
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
|
-
|
|
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
|
}
|
package/src/interfaces/ICoin.sol
CHANGED
|
@@ -50,7 +50,25 @@ interface IHasSwapPath {
|
|
|
50
50
|
function getPayoutSwapPath(IDeployedCoinVersionLookup coinVersionLookup) external view returns (PayoutSwapPath memory);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
interface
|
|
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
|
|
169
|
-
|
|
211
|
+
/// @notice The address of the latest coin hook
|
|
212
|
+
function hook() external view returns (address);
|
|
170
213
|
|
|
171
|
-
/// @notice
|
|
172
|
-
|
|
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
|
|
177
|
-
|
|
217
|
+
/// @notice The address of the latestcreator coin hook
|
|
218
|
+
function creatorCoinHook() external view returns (address);
|
|
178
219
|
|
|
179
|
-
/// @notice
|
|
180
|
-
|
|
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
|
+
}
|