Skip to content

Commit 7ec6134

Browse files
oveddaniainnash
andauthored
[CRE-14] flatten content coin hook + creator coin hook into a single hook (#1322)
* Apply suggested changes Apply suggested changes * merge both hooks into a single hook * fixed merge conflicts * remove added creator coin rewards * Factory impl code update * refactoring for arguments * update constant definitions * adding a new test for checking determining old coin type * fix test --------- Co-authored-by: Iain Nash <[email protected]>
1 parent fdd68b0 commit 7ec6134

33 files changed

+338
-434
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@zoralabs/coins": patch
3+
---
4+
5+
Flatten hooks into single hook implementation
6+
7+
Consolidates multiple hook contracts into a single unified hook for simplified architecture and better maintainability.

CLAUDE.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,14 +249,17 @@ Uses Graphite for branch management:
249249
Before working with the documentation, build the packages that are imported:
250250

251251
1. **Install dependencies** from the root directory:
252+
252253
```bash
253254
pnpm install
254255
```
255256

256257
2. **Build packages** from the root directory:
258+
257259
```bash
258260
pnpm build:js
259261
```
262+
260263
Note: Documentation builds use `build:js` to avoid unnecessary contract compilation. If the build fails due to missing TypeScript dependencies, check individual package devDependencies.
261264

262265
3. **Start docs development server**:
@@ -273,6 +276,7 @@ Some packages may fail during `build:js` if TypeScript is not available as a dev
273276

274277
**Error Pattern**: `tsc: command not found` during tsup build
275278
**Solution**: Add TypeScript as devDependency to the failing package:
279+
276280
```json
277281
{
278282
"devDependencies": {
@@ -331,15 +335,15 @@ When renaming or moving documentation files, always add redirects to `docs/verce
331335
```json
332336
{
333337
"source": "/coins/contracts/factory",
334-
"destination": "/coins/contracts/creating-a-coin",
338+
"destination": "/coins/contracts/creating-a-coin",
335339
"permanent": true
336340
}
337341
```
338342

339343
#### Common Redirect Scenarios
340344

341345
- **Page renamed**: Redirect old URL to new URL
342-
- **Page moved**: Redirect old path to new path
346+
- **Page moved**: Redirect old path to new path
343347
- **Page removed**: Redirect to most relevant existing page
344348
- **Section reorganized**: Redirect old structure to new structure
345349

packages/coins/script/PrintRegisterUpgradePath.s.sol

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,17 @@ contract DeployScript is CoinsDeployerBase {
1212
CoinsDeployment memory deployment = readDeployment(false);
1313

1414
address existingContentCoinHook = 0xd3D133469ADC85e01A4887404D8AC12d630e9040;
15-
address existingCreatorCoinHook = 0xffF800B76768dA8AB6aab527021e4a6A91219040;
1615

1716
address[] memory baseImpls = new address[](1);
1817
baseImpls[0] = existingContentCoinHook;
1918

2019
bytes memory contentCoinUpgradeCall = abi.encodeWithSelector(IHooksUpgradeGate.registerUpgradePath.selector, baseImpls, deployment.zoraV4CoinHook);
2120

22-
baseImpls[0] = existingCreatorCoinHook;
23-
24-
bytes memory creatorCoinUpgradeCall = abi.encodeWithSelector(IHooksUpgradeGate.registerUpgradePath.selector, baseImpls, deployment.creatorCoinHook);
25-
2621
printUpgradeFactoryCommand(deployment);
2722

2823
console.log("register upgrade gate target", deployment.hookUpgradeGate);
2924

3025
console.log("contentCoinUpgradeCall");
3126
console.logBytes(contentCoinUpgradeCall);
32-
console.log("creatorCoinUpgradeCall");
33-
console.logBytes(creatorCoinUpgradeCall);
3427
}
3528
}

packages/coins/script/TestBackingCoinSwap.s.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ contract TestV4Swap is CoinsDeployerBase {
104104
vm.startBroadcast(trader);
105105

106106
address createReferral = 0xC077e4cC02fa01A5b7fAca1acE9BBe9f5ac5Af9F;
107-
address tradeReferral = 0xC077e4cC02fa01A5b7fAca1acE9BBe9f5ac5Af9F;
108107

109108
ICoin backingCoin = _deployCoin(zora, trader, "Backing Coin", "BACK", "https://testc.com", createReferral, bytes32("creator"));
110109
ICoin contentCoin = _deployCoin(

packages/coins/script/TestV4Swap.s.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ contract TestV4Swap is CoinsDeployerBase {
9696

9797
vm.startBroadcast(trader);
9898

99-
address createReferral = 0xC077e4cC02fa01A5b7fAca1acE9BBe9f5ac5Af9F;
10099
address tradeReferral = 0xC077e4cC02fa01A5b7fAca1acE9BBe9f5ac5Af9F;
101100

102101
// MockERC20 currency = _deployMockCurrency();

packages/coins/script/UpgradeFactoryImpl.s.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ contract UpgradeFactoryImpl is CoinsDeployerBase {
1414
deployment.coinV4Impl,
1515
deployment.creatorCoinImpl,
1616
deployment.zoraV4CoinHook,
17-
deployment.creatorCoinHook,
1817
deployment.zoraHookRegistry
1918
);
2019

packages/coins/src/BaseCoin.sol

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pragma solidity ^0.8.23;
99

1010
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
1111
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
12-
import {ICoin} from "./interfaces/ICoin.sol";
12+
import {ICoin, IHasTotalSupplyForPositions, IHasCoinType} from "./interfaces/ICoin.sol";
1313
import {IHasRewardsRecipients} from "./interfaces/IHasRewardsRecipients.sol";
1414
import {ICoinComments} from "./interfaces/ICoinComments.sol";
1515
import {IERC7572} from "./interfaces/IERC7572.sol";
@@ -89,28 +89,29 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
8989

9090
/**
9191
* @notice The constructor for the static Coin contract deployment shared across all Coins.
92-
* @param _protocolRewardRecipient The address of the protocol reward recipient
93-
* @param _protocolRewards The address of the protocol rewards contract
94-
* @param _airlock The address of the Airlock contract
92+
* @param protocolRewardRecipient_ The address of the protocol reward recipient
93+
* @param protocolRewards_ The address of the protocol rewards contract
94+
* @param poolManager_ The address of the pool manager
95+
* @param airlock_ The address of the Airlock contract
9596
*/
96-
constructor(address _protocolRewardRecipient, address _protocolRewards, IPoolManager poolManager_, address _airlock) initializer {
97-
if (_protocolRewardRecipient == address(0)) {
97+
constructor(address protocolRewardRecipient_, address protocolRewards_, IPoolManager poolManager_, address airlock_) initializer {
98+
if (protocolRewardRecipient_ == address(0)) {
9899
revert AddressZero();
99100
}
100-
if (_protocolRewards == address(0)) {
101+
if (protocolRewards_ == address(0)) {
101102
revert AddressZero();
102103
}
103104
if (address(poolManager_) == address(0)) {
104105
revert AddressZero();
105106
}
106-
if (_airlock == address(0)) {
107+
if (airlock_ == address(0)) {
107108
revert AddressZero();
108109
}
109110

110-
protocolRewardRecipient = _protocolRewardRecipient;
111-
protocolRewards = _protocolRewards;
111+
protocolRewardRecipient = protocolRewardRecipient_;
112+
protocolRewards = protocolRewards_;
112113
poolManager = poolManager_;
113-
airlock = _airlock;
114+
airlock = airlock_;
114115
}
115116

116117
/// @inheritdoc ICoin
@@ -258,7 +259,9 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
258259
interfaceId == type(IERC7572).interfaceId ||
259260
interfaceId == type(IHasRewardsRecipients).interfaceId ||
260261
interfaceId == type(IHasPoolKey).interfaceId ||
261-
type(IHasSwapPath).interfaceId == interfaceId;
262+
interfaceId == type(IHasCoinType).interfaceId ||
263+
interfaceId == type(IHasTotalSupplyForPositions).interfaceId ||
264+
interfaceId == type(IHasSwapPath).interfaceId;
262265
}
263266

264267
/// @dev Overrides ERC20's _update function to emit a superset `CoinTransfer` event

packages/coins/src/ContentCoin.sol

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pragma solidity ^0.8.23;
1010
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
1111
import {BaseCoin} from "./BaseCoin.sol";
1212
import {CoinConstants} from "./libs/CoinConstants.sol";
13+
import {MarketConstants} from "./libs/MarketConstants.sol";
14+
import {IHasCoinType} from "./interfaces/ICoin.sol";
1315

1416
/**
1517
* @title ContentCoin
@@ -42,4 +44,12 @@ contract ContentCoin is BaseCoin {
4244
// Transfer the market supply to the hook for liquidity
4345
_transfer(address(this), address(poolKey.hooks), balanceOf(address(this)));
4446
}
47+
48+
function totalSupplyForPositions() external pure override returns (uint256) {
49+
return MarketConstants.CONTENT_COIN_MARKET_SUPPLY;
50+
}
51+
52+
function coinType() external pure override returns (IHasCoinType.CoinType) {
53+
return IHasCoinType.CoinType.Content;
54+
}
4555
}

packages/coins/src/CreatorCoin.sol

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,28 @@ import {CreatorCoinConstants} from "./libs/CreatorCoinConstants.sol";
1212
import {IHooks, PoolConfiguration, PoolKey, ICoin} from "./interfaces/ICoin.sol";
1313
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
1414
import {BaseCoin} from "./BaseCoin.sol";
15+
import {IHasCoinType} from "./interfaces/ICoin.sol";
16+
import {MarketConstants} from "./libs/MarketConstants.sol";
1517

1618
contract CreatorCoin is ICreatorCoin, BaseCoin {
1719
uint256 public vestingStartTime;
1820
uint256 public vestingEndTime;
1921
uint256 public totalClaimed;
2022

2123
constructor(
22-
address _protocolRewardRecipient,
23-
address _protocolRewards,
24-
IPoolManager _poolManager,
25-
address _airlock
26-
) BaseCoin(_protocolRewardRecipient, _protocolRewards, _poolManager, _airlock) initializer {}
24+
address protocolRewardRecipient_,
25+
address protocolRewards_,
26+
IPoolManager poolManager_,
27+
address airlock_
28+
) BaseCoin(protocolRewardRecipient_, protocolRewards_, poolManager_, airlock_) initializer {}
29+
30+
function totalSupplyForPositions() external pure override returns (uint256) {
31+
return MarketConstants.CREATOR_COIN_MARKET_SUPPLY;
32+
}
33+
34+
function coinType() external pure override returns (IHasCoinType.CoinType) {
35+
return IHasCoinType.CoinType.Creator;
36+
}
2737

2838
function initialize(
2939
address payoutRecipient_,
@@ -39,7 +49,18 @@ contract CreatorCoin is ICreatorCoin, BaseCoin {
3949
) public override(BaseCoin, ICoin) {
4050
require(currency_ == CreatorCoinConstants.CURRENCY, InvalidCurrency());
4151

42-
super.initialize(payoutRecipient_, owners_, tokenURI_, name_, symbol_, platformReferrer_, currency_, poolKey_, sqrtPriceX96, poolConfiguration_);
52+
super.initialize({
53+
payoutRecipient_: payoutRecipient_,
54+
owners_: owners_,
55+
tokenURI_: tokenURI_,
56+
name_: name_,
57+
symbol_: symbol_,
58+
platformReferrer_: platformReferrer_,
59+
currency_: currency_,
60+
poolKey_: poolKey_,
61+
sqrtPriceX96: sqrtPriceX96,
62+
poolConfiguration_: poolConfiguration_
63+
});
4364

4465
vestingStartTime = block.timestamp;
4566
vestingEndTime = block.timestamp + CreatorCoinConstants.CREATOR_VESTING_DURATION;
@@ -50,7 +71,7 @@ contract CreatorCoin is ICreatorCoin, BaseCoin {
5071
function _handleInitialDistribution() internal override {
5172
_mint(address(this), CreatorCoinConstants.TOTAL_SUPPLY);
5273

53-
_transfer(address(this), address(poolKey.hooks), CreatorCoinConstants.MARKET_SUPPLY);
74+
_transfer(address(this), address(poolKey.hooks), MarketConstants.CREATOR_COIN_MARKET_SUPPLY);
5475
}
5576

5677
/// @notice Allows the creator payout recipient to claim vested tokens

packages/coins/src/ZoraFactoryImpl.sol

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
pragma solidity ^0.8.23;
99

1010
import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
11+
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
1112
import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
1213
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
1314
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
@@ -51,18 +52,16 @@ contract ZoraFactoryImpl is
5152
/// @notice The coin contract implementation address
5253
address public immutable coinV4Impl;
5354
address public immutable creatorCoinImpl;
54-
address public immutable contentCoinHook;
55-
address public immutable creatorCoinHook;
55+
address public immutable hook;
5656
address public immutable zoraHookRegistry;
5757

58-
constructor(address _coinV4Impl, address _creatorCoinImpl, address _contentCoinHook, address _creatorCoinHook, address _zoraHookRegistry) {
58+
constructor(address coinV4Impl_, address creatorCoinImpl_, address hook_, address zoraHookRegistry_) {
5959
_disableInitializers();
6060

61-
coinV4Impl = _coinV4Impl;
62-
creatorCoinImpl = _creatorCoinImpl;
63-
contentCoinHook = _contentCoinHook;
64-
creatorCoinHook = _creatorCoinHook;
65-
zoraHookRegistry = _zoraHookRegistry;
61+
coinV4Impl = coinV4Impl_;
62+
creatorCoinImpl = creatorCoinImpl_;
63+
hook = hook_;
64+
zoraHookRegistry = zoraHookRegistry_;
6665
}
6766

6867
/// @notice Creates a new creator coin contract
@@ -99,7 +98,7 @@ contract ZoraFactoryImpl is
9998
poolConfig
10099
);
101100

102-
PoolKey memory poolKey = CoinSetup.buildPoolKey(address(creatorCoin), currency, isCoinToken0, IHooks(creatorCoinHook));
101+
PoolKey memory poolKey = CoinSetup.buildPoolKey(address(creatorCoin), currency, isCoinToken0, IHooks(hook));
103102

104103
ICreatorCoin(creatorCoin).initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolKey, sqrtPriceX96, poolConfiguration);
105104

@@ -157,17 +156,17 @@ contract ZoraFactoryImpl is
157156
string memory symbol,
158157
bytes memory poolConfig,
159158
address platformReferrer,
160-
address hook,
159+
address deployHook,
161160
bytes calldata hookData,
162161
bytes32 salt
163162
) internal returns (address coin, bytes memory hookDataOut) {
164163
coin = address(_createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt));
165164

166-
if (hook != address(0)) {
167-
if (!IERC165(hook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
165+
if (deployHook != address(0)) {
166+
if (!IERC165(deployHook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
168167
revert InvalidHook();
169168
}
170-
hookDataOut = IHasAfterCoinDeploy(hook).afterCoinDeploy{value: msg.value}(msg.sender, ICoin(coin), hookData);
169+
hookDataOut = IHasAfterCoinDeploy(deployHook).afterCoinDeploy{value: msg.value}(msg.sender, ICoin(coin), hookData);
171170
} else if (msg.value > 0) {
172171
// cannot send eth without a hook
173172
revert EthTransferInvalid();
@@ -205,11 +204,11 @@ contract ZoraFactoryImpl is
205204
string memory symbol,
206205
bytes memory poolConfig,
207206
address platformReferrer,
208-
address hook,
207+
address hook_,
209208
bytes calldata hookData
210209
) public payable nonReentrant returns (address coin, bytes memory hookDataOut) {
211210
bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
212-
return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, hook, hookData, salt);
211+
return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, hook_, hookData, salt);
213212
}
214213

215214
/// @dev deprecated Use deploy() with poolConfig instead
@@ -222,8 +221,9 @@ contract ZoraFactoryImpl is
222221
address platformReferrer,
223222
address currency,
224223
// tickLower is no longer used
225-
int24 /*tickLower*/,
226-
uint256 orderSize
224+
int24 /* tickLower */,
225+
// orderSize is no longer used
226+
uint256 /* orderSize */
227227
) public payable nonReentrant returns (address, uint256) {
228228
bytes memory poolConfig = CoinConfigurationVersions.defaultConfig(currency);
229229
bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
@@ -258,7 +258,7 @@ contract ZoraFactoryImpl is
258258
string memory symbol,
259259
address platformReferrer
260260
) internal {
261-
PoolKey memory poolKey = CoinSetup.buildPoolKey(address(coin), currency, isCoinToken0, IHooks(contentCoinHook));
261+
PoolKey memory poolKey = CoinSetup.buildPoolKey(address(coin), currency, isCoinToken0, IHooks(hook));
262262

263263
// Initialize coin with pre-configured pool
264264
coin.initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolKey, sqrtPriceX96, poolConfiguration);
@@ -366,24 +366,30 @@ contract ZoraFactoryImpl is
366366
// try to get the existing contract name - if it reverts, the existing contract was an older version that didn't have the contract name
367367
// unfortunately we cannot use supportsInterface here because the existing implementation did not have that function
368368
try IHasContractName(newImpl).contractName() returns (string memory name) {
369-
if (!_equals(name, contractName())) {
369+
if (!Strings.equal(name, contractName())) {
370370
revert UpgradeToMismatchedContractName(contractName(), name);
371371
}
372372
} catch {}
373373

374374
// Auto-register the new hooks in the Zora hook registry
375-
address[] memory hooks = new address[](2);
376-
string[] memory tags = new string[](2);
375+
address[] memory hooks = new address[](1);
376+
string[] memory tags = new string[](1);
377377

378-
hooks[0] = IZoraFactory(newImpl).contentCoinHook();
379-
hooks[1] = IZoraFactory(newImpl).creatorCoinHook();
380-
tags[0] = "ContentCoinHook";
381-
tags[1] = "CreatorCoinHook";
378+
hooks[0] = IZoraFactory(newImpl).hook();
379+
tags[0] = "CoinHook";
382380

383381
IZoraHookRegistry(zoraHookRegistry).registerHooks(hooks, tags);
384382
}
385383

386-
function _equals(string memory a, string memory b) internal pure returns (bool) {
387-
return (keccak256(bytes(a)) == keccak256(bytes(b)));
384+
/// @notice The address of the latest creator coin hook
385+
/// @dev Deprecated: use `hook` instead
386+
function creatorCoinHook() external view returns (address) {
387+
return hook;
388+
}
389+
390+
/// @notice The address of the latest coin hook
391+
/// @dev Deprecated: use `hook` instead
392+
function contentCoinHook() external view returns (address) {
393+
return hook;
388394
}
389395
}

0 commit comments

Comments
 (0)