Skip to content

Commit c976b83

Browse files
authored
feat(coins): zora hook registry (#1359)
* feat(coins): zora hook registry * feat(coins): auto-register new hooks on factory upgrades (#1362) * feat(coins): auto-register new hooks on factory upgrades * feat(docs): add hook registry (#1365) * feat(docs): add hook registry * refactor(coins): remove try-catch in factory (#1369) * refactor(coins): remove try-catch in factory * feat(coins): add tags and version to registered hooks (#1372) * feat(coins): add tags and version to registered hooks * fix: update invalid impl test * feat(coins): update hook registry getters (#1373) * feat(coins): update hook registry getters * chore: update registry tests * chore: add deployment * feat(coins): update docs for hook registry (#1374)
1 parent 4e6d738 commit c976b83

File tree

13 files changed

+631
-46
lines changed

13 files changed

+631
-46
lines changed
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Hook Registry
2+
3+
A canonical registry of Zora Uniswap V4 hook addresses to discover and track changes.
4+
5+
## Overview
6+
7+
- Tracks Uniswap V4 hook contract addresses used by Zora Coins
8+
- Onchain lookup of hooks, tags, and versions
9+
- Emits events on add/remove (includes tag and version)
10+
- `tag` is a label set to distinguish hook types (e.g. `CONTENT` vs `CREATOR`)
11+
- `version` is read via `contractVersion()` on the hook contract if it exists
12+
13+
## Deployment
14+
15+
| Chain | Chain ID | Hook Contract | Address |
16+
|-------|----------|---------------|---------|
17+
| Base | 8453 | ZoraHookRegistry | `0x777777C4c14b133858c3982D41Dbf02509fc18d7` |
18+
19+
20+
## Interface
21+
22+
### Events
23+
24+
```solidity
25+
event ZoraHookRegistered(address indexed hook, string tag, string version);
26+
event ZoraHookRemoved(address indexed hook, string tag, string version);
27+
```
28+
29+
### Structs
30+
31+
```solidity
32+
struct ZoraHook {
33+
address hook;
34+
string tag;
35+
string version;
36+
}
37+
```
38+
39+
### Read Functions
40+
41+
```solidity
42+
function isRegisteredHook(address hook) external view returns (bool);
43+
function getHooks() external view returns (ZoraHook[] memory);
44+
function getHookAddresses() external view returns (address[] memory);
45+
function getHookTag(address hook) external view returns (string memory);
46+
function getHookVersion(address hook) external view returns (string memory);
47+
```

docs/vocs.config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ export default defineConfig({
6969
text: "Hook System",
7070
link: "/coins/contracts/hook",
7171
},
72+
{
73+
text: "Hook Registry",
74+
link: "/coins/contracts/hook-registry",
75+
},
7276
{
7377
text: "Coin Rewards",
7478
link: "/coins/contracts/rewards",
@@ -157,6 +161,10 @@ export default defineConfig({
157161
text: "Hook System",
158162
link: "/coins/contracts/hook",
159163
},
164+
{
165+
text: "Hook Registry",
166+
link: "/coins/contracts/hook-registry",
167+
},
160168
{
161169
text: "Coin Rewards",
162170
link: "/coins/contracts/rewards",

packages/coins/addresses/8453.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
"ZORA_FACTORY": "0x777777751622c0d3258f214F9DF38E35BF45baF3",
1111
"ZORA_FACTORY_IMPL": "0x0e2ea62E5377D46FeF114A60AfBE3d5eA7490577",
1212
"ZORA_V4_COIN_HOOK": "0x9ea932730A7787000042e34390B8E435dD839040",
13-
"ZORA_V4_COIN_HOOK_SALT": "0x0000000000000000000000000000000000000000000000000000000000002fa2"
13+
"ZORA_V4_COIN_HOOK_SALT": "0x0000000000000000000000000000000000000000000000000000000000002fa2",
14+
"ZORA_HOOK_REGISTRY": "0x777777C4c14b133858c3982D41Dbf02509fc18d7"
1415
}

packages/coins/script/UpgradeFactoryImpl.s.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ contract UpgradeFactoryImpl is CoinsDeployerBase {
1414
deployment.coinV4Impl,
1515
deployment.creatorCoinImpl,
1616
deployment.zoraV4CoinHook,
17-
deployment.creatorCoinHook
17+
deployment.creatorCoinHook,
18+
deployment.zoraHookRegistry
1819
);
1920

2021
deployment.zoraFactoryImpl = address(zoraFactoryImpl);

packages/coins/src/ZoraFactoryImpl.sol

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {CoinDopplerMultiCurve} from "./libs/CoinDopplerMultiCurve.sol";
3535
import {ICreatorCoin} from "./interfaces/ICreatorCoin.sol";
3636
import {MarketConstants} from "./libs/MarketConstants.sol";
3737
import {DeployedCoinVersionLookup} from "./utils/DeployedCoinVersionLookup.sol";
38+
import {IZoraHookRegistry} from "./interfaces/IZoraHookRegistry.sol";
3839

3940
contract ZoraFactoryImpl is
4041
IZoraFactory,
@@ -52,16 +53,27 @@ contract ZoraFactoryImpl is
5253
address public immutable creatorCoinImpl;
5354
address public immutable contentCoinHook;
5455
address public immutable creatorCoinHook;
56+
address public immutable zoraHookRegistry;
5557

56-
constructor(address _coinV4Impl, address _creatorCoinImpl, address _contentCoinHook, address _creatorCoinHook) {
58+
constructor(address _coinV4Impl, address _creatorCoinImpl, address _contentCoinHook, address _creatorCoinHook, address _zoraHookRegistry) {
5759
_disableInitializers();
5860

5961
coinV4Impl = _coinV4Impl;
6062
creatorCoinImpl = _creatorCoinImpl;
6163
contentCoinHook = _contentCoinHook;
6264
creatorCoinHook = _creatorCoinHook;
65+
zoraHookRegistry = _zoraHookRegistry;
6366
}
6467

68+
/// @notice Creates a new creator coin contract
69+
/// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
70+
/// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
71+
/// @param uri The coin metadata uri
72+
/// @param name The name of the coin
73+
/// @param symbol The symbol of the coin
74+
/// @param poolConfig The config parameters for the coin's pool
75+
/// @param platformReferrer The address of the platform referrer
76+
/// @param coinSalt The salt used to deploy the coin
6577
function deployCreatorCoin(
6678
address payoutRecipient,
6779
address[] memory owners,
@@ -358,6 +370,17 @@ contract ZoraFactoryImpl is
358370
revert UpgradeToMismatchedContractName(contractName(), name);
359371
}
360372
} catch {}
373+
374+
// Auto-register the new hooks in the Zora hook registry
375+
address[] memory hooks = new address[](2);
376+
string[] memory tags = new string[](2);
377+
378+
hooks[0] = IZoraFactory(newImpl).contentCoinHook();
379+
hooks[1] = IZoraFactory(newImpl).creatorCoinHook();
380+
tags[0] = "ContentCoinHook";
381+
tags[1] = "CreatorCoinHook";
382+
383+
IZoraHookRegistry(zoraHookRegistry).registerHooks(hooks, tags);
361384
}
362385

363386
function _equals(string memory a, string memory b) internal pure returns (bool) {

packages/coins/src/deployment/CoinsDeployerBase.sol

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ contract CoinsDeployerBase is ProxyDeployerScript {
4343
bytes32 zoraV4CoinHookSalt;
4444
bytes32 creatorCoinHookSalt;
4545
bool isDev;
46+
// Hook registry
47+
address zoraHookRegistry;
4648
}
4749

4850
function addressesFile() internal view returns (string memory) {
@@ -70,6 +72,7 @@ contract CoinsDeployerBase is ProxyDeployerScript {
7072
vm.serializeAddress(objectKey, "CREATOR_COIN_HOOK", deployment.creatorCoinHook);
7173
vm.serializeBytes32(objectKey, "CREATOR_COIN_HOOK_SALT", deployment.creatorCoinHookSalt);
7274
vm.serializeAddress(objectKey, "HOOK_UPGRADE_GATE", deployment.hookUpgradeGate);
75+
vm.serializeAddress(objectKey, "ZORA_HOOK_REGISTRY", deployment.zoraHookRegistry);
7376
string memory result = vm.serializeAddress(objectKey, "COIN_V4_IMPL", deployment.coinV4Impl);
7477

7578
vm.writeJson(result, addressesFile(deployment.isDev));
@@ -99,6 +102,7 @@ contract CoinsDeployerBase is ProxyDeployerScript {
99102
deployment.creatorCoinHook = readAddressOrDefaultToZero(json, "CREATOR_COIN_HOOK");
100103
deployment.creatorCoinHookSalt = readBytes32OrDefaultToZero(json, "CREATOR_COIN_HOOK_SALT");
101104
deployment.hookUpgradeGate = readAddressOrDefaultToZero(json, "HOOK_UPGRADE_GATE");
105+
deployment.zoraHookRegistry = readAddressOrDefaultToZero(json, "ZORA_HOOK_REGISTRY");
102106
}
103107

104108
function deployCoinV4Impl(address zoraV4CoinHook) internal returns (ContentCoin) {
@@ -125,14 +129,16 @@ contract CoinsDeployerBase is ProxyDeployerScript {
125129
address _coinV4Impl,
126130
address _creatorCoinImpl,
127131
address _contentCoinHook,
128-
address _creatorCoinHook
132+
address _creatorCoinHook,
133+
address _zoraHookRegistry
129134
) internal returns (ZoraFactoryImpl) {
130135
return
131136
new ZoraFactoryImpl({
132137
_coinV4Impl: _coinV4Impl,
133138
_creatorCoinImpl: _creatorCoinImpl,
134139
_contentCoinHook: _contentCoinHook,
135-
_creatorCoinHook: _creatorCoinHook
140+
_creatorCoinHook: _creatorCoinHook,
141+
_zoraHookRegistry: _zoraHookRegistry
136142
});
137143
}
138144

@@ -193,7 +199,8 @@ contract CoinsDeployerBase is ProxyDeployerScript {
193199
_coinV4Impl: deployment.coinV4Impl,
194200
_creatorCoinImpl: deployment.creatorCoinImpl,
195201
_contentCoinHook: deployment.zoraV4CoinHook,
196-
_creatorCoinHook: deployment.creatorCoinHook
202+
_creatorCoinHook: deployment.creatorCoinHook,
203+
_zoraHookRegistry: deployment.zoraHookRegistry
197204
})
198205
);
199206
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

packages/coins/src/interfaces/IZoraFactory.sol

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,29 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
8383
/// @notice Thrown when ETH is sent with a transaction but the currency is not WETH
8484
error EthTransferInvalid();
8585

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
86109
function deployCreatorCoin(
87110
address payoutRecipient,
88111
address[] memory owners,
@@ -163,19 +186,15 @@ interface IZoraFactory is IDeployedCoinVersionLookup {
163186
bytes calldata hookData
164187
) external payable returns (address coin, bytes memory hookDataOut);
165188

189+
/// @notice The implementation address of the factory contract
166190
function implementation() external view returns (address);
167191

168-
/// @notice Thrown when the hook is invalid
169-
error InvalidHook();
170-
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);
192+
/// @notice The address of the latest content coin hook
193+
function contentCoinHook() external view returns (address);
175194

176-
/// @notice Thrown when a method is deprecated
177-
error Deprecated();
195+
/// @notice The address of the latestcreator coin hook
196+
function creatorCoinHook() external view returns (address);
178197

179-
/// @notice Thrwon when an invalid config version is provided
180-
error InvalidConfig();
198+
/// @notice The address of the Zora hook registry
199+
function zoraHookRegistry() external view returns (address);
181200
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

0 commit comments

Comments
 (0)