@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
package/src/BaseCoin.sol CHANGED
@@ -9,7 +9,7 @@ pragma solidity ^0.8.23;
9
9
 
10
10
  import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11
11
  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";
13
13
  import {IHasRewardsRecipients} from "./interfaces/IHasRewardsRecipients.sol";
14
14
  import {ICoinComments} from "./interfaces/ICoinComments.sol";
15
15
  import {IERC7572} from "./interfaces/IERC7572.sol";
@@ -32,7 +32,6 @@ import {CoinCommon} from "./libs/CoinCommon.sol";
32
32
  import {Address} from "@openzeppelin/contracts/utils/Address.sol";
33
33
  import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
34
34
  import {ERC165Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable.sol";
35
- import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
36
35
  import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
37
36
  import {ContractVersionBase} from "./version/ContractVersionBase.sol";
38
37
  import {MultiOwnable} from "./utils/MultiOwnable.sol";
@@ -40,7 +39,6 @@ import {FullMath} from "./utils/uniswap/FullMath.sol";
40
39
  import {TickMath} from "./utils/uniswap/TickMath.sol";
41
40
  import {LiquidityAmounts} from "./utils/uniswap/LiquidityAmounts.sol";
42
41
  import {CoinConstants} from "./libs/CoinConstants.sol";
43
- import {MarketConstants} from "./libs/MarketConstants.sol";
44
42
  import {LpPosition} from "./types/LpPosition.sol";
45
43
  import {PoolState} from "./types/PoolState.sol";
46
44
 
@@ -54,7 +52,7 @@ import {PoolState} from "./types/PoolState.sol";
54
52
  \$$$$$$ | $$$$$$ |$$$$$$\ $$ | \$$ |
55
53
  \______/ \______/ \______|\__| \__|
56
54
  */
57
- abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable, MultiOwnable, ReentrancyGuardUpgradeable, ERC165Upgradeable {
55
+ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable, MultiOwnable, ERC165Upgradeable {
58
56
  using SafeERC20 for IERC20;
59
57
 
60
58
  /// @notice The address of the protocol rewards contract
@@ -89,28 +87,29 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
89
87
 
90
88
  /**
91
89
  * @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
90
+ * @param protocolRewardRecipient_ The address of the protocol reward recipient
91
+ * @param protocolRewards_ The address of the protocol rewards contract
92
+ * @param poolManager_ The address of the pool manager
93
+ * @param airlock_ The address of the Airlock contract
95
94
  */
96
- constructor(address _protocolRewardRecipient, address _protocolRewards, IPoolManager poolManager_, address _airlock) initializer {
97
- if (_protocolRewardRecipient == address(0)) {
95
+ constructor(address protocolRewardRecipient_, address protocolRewards_, IPoolManager poolManager_, address airlock_) initializer {
96
+ if (protocolRewardRecipient_ == address(0)) {
98
97
  revert AddressZero();
99
98
  }
100
- if (_protocolRewards == address(0)) {
99
+ if (protocolRewards_ == address(0)) {
101
100
  revert AddressZero();
102
101
  }
103
102
  if (address(poolManager_) == address(0)) {
104
103
  revert AddressZero();
105
104
  }
106
- if (_airlock == address(0)) {
105
+ if (airlock_ == address(0)) {
107
106
  revert AddressZero();
108
107
  }
109
108
 
110
- protocolRewardRecipient = _protocolRewardRecipient;
111
- protocolRewards = _protocolRewards;
109
+ protocolRewardRecipient = protocolRewardRecipient_;
110
+ protocolRewards = protocolRewards_;
112
111
  poolManager = poolManager_;
113
- airlock = _airlock;
112
+ airlock = airlock_;
114
113
  }
115
114
 
116
115
  /// @inheritdoc ICoin
@@ -167,7 +166,6 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
167
166
  __ERC20Permit_init("");
168
167
 
169
168
  __MultiOwnable_init(owners_);
170
- __ReentrancyGuard_init();
171
169
 
172
170
  // Set mutable state
173
171
  _setPayoutRecipient(payoutRecipient_);
@@ -180,14 +178,9 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
180
178
  _handleInitialDistribution();
181
179
  }
182
180
 
183
- /// @dev The initial mint and distribution of the coin supply.
184
- function _handleInitialDistribution() internal virtual {
185
- // Mint the total supply to the coin contract
186
- _mint(address(this), CoinConstants.MAX_TOTAL_SUPPLY);
187
-
188
- // Distribute the creator launch reward to the payout recipient
189
- _transfer(address(this), payoutRecipient, CoinConstants.CREATOR_LAUNCH_REWARD);
190
- }
181
+ /// @notice The initial mint and distribution of the coin supply.
182
+ /// @dev This function must be overridden by the child contract.
183
+ function _handleInitialDistribution() internal virtual;
191
184
 
192
185
  /// @notice Returns the name of the token for EIP712 domain.
193
186
  /// @notice This can change when the user changes the "name" of the token.
@@ -258,7 +251,9 @@ abstract contract BaseCoin is ICoin, ContractVersionBase, ERC20PermitUpgradeable
258
251
  interfaceId == type(IERC7572).interfaceId ||
259
252
  interfaceId == type(IHasRewardsRecipients).interfaceId ||
260
253
  interfaceId == type(IHasPoolKey).interfaceId ||
261
- type(IHasSwapPath).interfaceId == interfaceId;
254
+ interfaceId == type(IHasCoinType).interfaceId ||
255
+ interfaceId == type(IHasTotalSupplyForPositions).interfaceId ||
256
+ interfaceId == type(IHasSwapPath).interfaceId;
262
257
  }
263
258
 
264
259
  /// @dev Overrides ERC20's _update function to emit a superset `CoinTransfer` event
@@ -10,10 +10,11 @@ pragma solidity ^0.8.23;
10
10
  import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
11
11
  import {BaseCoin} from "./BaseCoin.sol";
12
12
  import {CoinConstants} from "./libs/CoinConstants.sol";
13
+ import {IHasCoinType} from "./interfaces/ICoin.sol";
13
14
 
14
15
  /**
15
16
  * @title ContentCoin
16
- * @notice Content coin implementation that uses creator coins as backing currency
17
+ * @notice Content coin implementation that typically uses creator coins as backing currency, but can be set to any currency as the backing currency
17
18
  * @dev Inherits from BaseCoin and implements content-specific distribution logic
18
19
  */
19
20
  contract ContentCoin is BaseCoin {
@@ -37,9 +38,17 @@ contract ContentCoin is BaseCoin {
37
38
  _mint(address(this), CoinConstants.MAX_TOTAL_SUPPLY);
38
39
 
39
40
  // Distribute the creator launch reward to the payout recipient
40
- _transfer(address(this), payoutRecipient, CoinConstants.CREATOR_LAUNCH_REWARD);
41
+ _transfer(address(this), payoutRecipient, CoinConstants.CONTENT_COIN_INITIAL_CREATOR_SUPPLY);
41
42
 
42
43
  // Transfer the market supply to the hook for liquidity
43
44
  _transfer(address(this), address(poolKey.hooks), balanceOf(address(this)));
44
45
  }
46
+
47
+ function totalSupplyForPositions() external pure override returns (uint256) {
48
+ return CoinConstants.CONTENT_COIN_MARKET_SUPPLY;
49
+ }
50
+
51
+ function coinType() external pure override returns (IHasCoinType.CoinType) {
52
+ return IHasCoinType.CoinType.Content;
53
+ }
45
54
  }
@@ -8,10 +8,11 @@
8
8
  pragma solidity ^0.8.28;
9
9
 
10
10
  import {ICreatorCoin} from "./interfaces/ICreatorCoin.sol";
11
- import {CreatorCoinConstants} from "./libs/CreatorCoinConstants.sol";
11
+ import {CoinConstants} from "./libs/CoinConstants.sol";
12
12
  import {IHooks, PoolConfiguration, PoolKey, ICoin} from "./interfaces/ICoin.sol";
13
13
  import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
14
14
  import {BaseCoin} from "./BaseCoin.sol";
15
+ import {IHasCoinType} from "./interfaces/ICoin.sol";
15
16
 
16
17
  contract CreatorCoin is ICreatorCoin, BaseCoin {
17
18
  uint256 public vestingStartTime;
@@ -19,11 +20,19 @@ contract CreatorCoin is ICreatorCoin, BaseCoin {
19
20
  uint256 public totalClaimed;
20
21
 
21
22
  constructor(
22
- address _protocolRewardRecipient,
23
- address _protocolRewards,
24
- IPoolManager _poolManager,
25
- address _airlock
26
- ) BaseCoin(_protocolRewardRecipient, _protocolRewards, _poolManager, _airlock) initializer {}
23
+ address protocolRewardRecipient_,
24
+ address protocolRewards_,
25
+ IPoolManager poolManager_,
26
+ address airlock_
27
+ ) BaseCoin(protocolRewardRecipient_, protocolRewards_, poolManager_, airlock_) initializer {}
28
+
29
+ function totalSupplyForPositions() external pure override returns (uint256) {
30
+ return CoinConstants.CREATOR_COIN_MARKET_SUPPLY;
31
+ }
32
+
33
+ function coinType() external pure override returns (IHasCoinType.CoinType) {
34
+ return IHasCoinType.CoinType.Creator;
35
+ }
27
36
 
28
37
  function initialize(
29
38
  address payoutRecipient_,
@@ -37,24 +46,34 @@ contract CreatorCoin is ICreatorCoin, BaseCoin {
37
46
  uint160 sqrtPriceX96,
38
47
  PoolConfiguration memory poolConfiguration_
39
48
  ) public override(BaseCoin, ICoin) {
40
- require(currency_ == CreatorCoinConstants.CURRENCY, InvalidCurrency());
41
-
42
- super.initialize(payoutRecipient_, owners_, tokenURI_, name_, symbol_, platformReferrer_, currency_, poolKey_, sqrtPriceX96, poolConfiguration_);
49
+ require(currency_ == CoinConstants.CREATOR_COIN_CURRENCY, InvalidCurrency());
50
+
51
+ super.initialize({
52
+ payoutRecipient_: payoutRecipient_,
53
+ owners_: owners_,
54
+ tokenURI_: tokenURI_,
55
+ name_: name_,
56
+ symbol_: symbol_,
57
+ platformReferrer_: platformReferrer_,
58
+ currency_: currency_,
59
+ poolKey_: poolKey_,
60
+ sqrtPriceX96: sqrtPriceX96,
61
+ poolConfiguration_: poolConfiguration_
62
+ });
43
63
 
44
64
  vestingStartTime = block.timestamp;
45
- vestingEndTime = block.timestamp + CreatorCoinConstants.CREATOR_VESTING_DURATION;
65
+ vestingEndTime = block.timestamp + CoinConstants.CREATOR_VESTING_DURATION;
46
66
  }
47
67
 
48
68
  /// @dev The initial mint and distribution of the coin supply.
49
69
  /// Implements creator coin specific distribution: 500M to liquidity pool, 500M vested to creator.
50
70
  function _handleInitialDistribution() internal override {
51
- _mint(address(this), CreatorCoinConstants.TOTAL_SUPPLY);
71
+ _mint(address(this), CoinConstants.TOTAL_SUPPLY);
52
72
 
53
- _transfer(address(this), address(poolKey.hooks), CreatorCoinConstants.MARKET_SUPPLY);
73
+ _transfer(address(this), address(poolKey.hooks), CoinConstants.CREATOR_COIN_MARKET_SUPPLY);
54
74
  }
55
75
 
56
76
  /// @notice Allows the creator payout recipient to claim vested tokens
57
- /// @dev Optimized for frequent calls from Uniswap V4 hooks
58
77
  /// @return claimAmount The amount of tokens claimed
59
78
  function claimVesting() external returns (uint256) {
60
79
  uint256 claimAmount = getClaimableAmount();
@@ -93,13 +112,13 @@ contract CreatorCoin is ICreatorCoin, BaseCoin {
93
112
 
94
113
  // After vesting ends - fully vested
95
114
  if (timestamp >= vestingEndTime) {
96
- return CreatorCoinConstants.CREATOR_VESTING_SUPPLY;
115
+ return CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY;
97
116
  }
98
117
 
99
118
  // Linear vesting: (elapsed_time / total_duration) * total_amount
100
119
  uint256 elapsedTime = timestamp - vestingStartTime;
101
120
 
102
121
  // Multiply first to avoid precision loss
103
- return (CreatorCoinConstants.CREATOR_VESTING_SUPPLY * elapsedTime) / CreatorCoinConstants.CREATOR_VESTING_DURATION;
122
+ return (CoinConstants.CREATOR_COIN_CREATOR_VESTING_SUPPLY * elapsedTime) / CoinConstants.CREATOR_VESTING_DURATION;
104
123
  }
105
124
  }
@@ -8,6 +8,7 @@
8
8
  pragma solidity ^0.8.23;
9
9
 
10
10
  import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
11
+ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
11
12
  import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
12
13
  import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
13
14
  import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
@@ -33,8 +34,8 @@ import {IVersionedContract} from "@zoralabs/shared-contracts/interfaces/IVersion
33
34
  import {CoinSetup} from "./libs/CoinSetup.sol";
34
35
  import {CoinDopplerMultiCurve} from "./libs/CoinDopplerMultiCurve.sol";
35
36
  import {ICreatorCoin} from "./interfaces/ICreatorCoin.sol";
36
- import {MarketConstants} from "./libs/MarketConstants.sol";
37
37
  import {DeployedCoinVersionLookup} from "./utils/DeployedCoinVersionLookup.sol";
38
+ import {IZoraHookRegistry} from "./interfaces/IZoraHookRegistry.sol";
38
39
 
39
40
  contract ZoraFactoryImpl is
40
41
  IZoraFactory,
@@ -47,21 +48,25 @@ contract ZoraFactoryImpl is
47
48
  {
48
49
  using SafeERC20 for IERC20;
49
50
 
50
- /// @notice The coin contract implementation address
51
+ /// @notice The ZORA coin contract implementation address
51
52
  address public immutable coinV4Impl;
53
+ /// @notice The creator coin contract implementation address
52
54
  address public immutable creatorCoinImpl;
53
- address public immutable contentCoinHook;
54
- address public immutable creatorCoinHook;
55
+ /// @notice The uniswap v4 coin hook address
56
+ address public immutable hook;
57
+ /// @notice The zora hook registry address
58
+ address public immutable zoraHookRegistry;
55
59
 
56
- constructor(address _coinV4Impl, address _creatorCoinImpl, address _contentCoinHook, address _creatorCoinHook) {
60
+ constructor(address coinV4Impl_, address creatorCoinImpl_, address hook_, address zoraHookRegistry_) {
57
61
  _disableInitializers();
58
62
 
59
- coinV4Impl = _coinV4Impl;
60
- creatorCoinImpl = _creatorCoinImpl;
61
- contentCoinHook = _contentCoinHook;
62
- creatorCoinHook = _creatorCoinHook;
63
+ coinV4Impl = coinV4Impl_;
64
+ creatorCoinImpl = creatorCoinImpl_;
65
+ hook = hook_;
66
+ zoraHookRegistry = zoraHookRegistry_;
63
67
  }
64
68
 
69
+ /// @inheritdoc IZoraFactory
65
70
  function deployCreatorCoin(
66
71
  address payoutRecipient,
67
72
  address[] memory owners,
@@ -73,39 +78,24 @@ contract ZoraFactoryImpl is
73
78
  bytes32 coinSalt
74
79
  ) public nonReentrant returns (address) {
75
80
  bytes32 salt = _buildSalt(msg.sender, name, symbol, poolConfig, platformReferrer, coinSalt);
81
+ return address(_createAndInitializeCreatorCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt));
82
+ }
76
83
 
77
- uint8 version = CoinConfigurationVersions.getVersion(poolConfig);
78
-
79
- require(version == CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION, InvalidConfig());
80
-
81
- address creatorCoin = Clones.cloneDeterministic(creatorCoinImpl, salt);
82
-
83
- _setVersionForDeployedCoin(address(creatorCoin), version);
84
-
85
- (, address currency, uint160 sqrtPriceX96, bool isCoinToken0, PoolConfiguration memory poolConfiguration) = CoinSetup.generatePoolConfig(
86
- address(creatorCoin),
87
- poolConfig
88
- );
89
-
90
- PoolKey memory poolKey = CoinSetup.buildPoolKey(address(creatorCoin), currency, isCoinToken0, IHooks(creatorCoinHook));
91
-
92
- ICreatorCoin(creatorCoin).initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolKey, sqrtPriceX96, poolConfiguration);
93
-
94
- emit CreatorCoinCreated(
95
- msg.sender,
96
- payoutRecipient,
97
- platformReferrer,
98
- currency,
99
- uri,
100
- name,
101
- symbol,
102
- address(creatorCoin),
103
- poolKey,
104
- CoinCommon.hashPoolKey(poolKey),
105
- IVersionedContract(address(creatorCoin)).contractVersion()
106
- );
107
-
108
- return creatorCoin;
84
+ /// @inheritdoc IZoraFactory
85
+ function deployCreatorCoin(
86
+ address payoutRecipient,
87
+ address[] memory owners,
88
+ string memory uri,
89
+ string memory name,
90
+ string memory symbol,
91
+ bytes memory poolConfig,
92
+ address platformReferrer,
93
+ address postDeployHook,
94
+ bytes calldata postDeployHookData,
95
+ bytes32 coinSalt
96
+ ) external payable nonReentrant returns (address coin, bytes memory postDeployHookDataOut) {
97
+ bytes32 salt = _buildSalt(msg.sender, name, symbol, poolConfig, platformReferrer, coinSalt);
98
+ return _deployCreatorCoinWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, postDeployHook, postDeployHookData, salt);
109
99
  }
110
100
 
111
101
  /// @inheritdoc IZoraFactory
@@ -125,6 +115,7 @@ contract ZoraFactoryImpl is
125
115
  return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, postDeployHook, postDeployHookData, salt);
126
116
  }
127
117
 
118
+ /// @inheritdoc IZoraFactory
128
119
  function coinAddress(
129
120
  address msgSender,
130
121
  string memory name,
@@ -137,6 +128,19 @@ contract ZoraFactoryImpl is
137
128
  return Clones.predictDeterministicAddress(getCoinImpl(CoinConfigurationVersions.getVersion(poolConfig)), salt, address(this));
138
129
  }
139
130
 
131
+ function _executePostDeployHook(address coin, address deployHook, bytes calldata hookData) internal returns (bytes memory hookDataOut) {
132
+ if (deployHook != address(0)) {
133
+ if (!IERC165(deployHook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
134
+ revert InvalidHook();
135
+ }
136
+ hookDataOut = IHasAfterCoinDeploy(deployHook).afterCoinDeploy{value: msg.value}(msg.sender, ICoin(coin), hookData);
137
+ } else if (msg.value > 0) {
138
+ // cannot send eth without a hook
139
+ revert EthTransferInvalid();
140
+ }
141
+ }
142
+
143
+ /// @dev Internal function to deploy a coin with a hook
140
144
  function _deployWithHook(
141
145
  address payoutRecipient,
142
146
  address[] memory owners,
@@ -145,24 +149,34 @@ contract ZoraFactoryImpl is
145
149
  string memory symbol,
146
150
  bytes memory poolConfig,
147
151
  address platformReferrer,
148
- address hook,
152
+ address deployHook,
149
153
  bytes calldata hookData,
150
154
  bytes32 salt
151
155
  ) internal returns (address coin, bytes memory hookDataOut) {
152
156
  coin = address(_createAndInitializeCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt));
157
+ hookDataOut = _executePostDeployHook(coin, deployHook, hookData);
158
+ }
153
159
 
154
- if (hook != address(0)) {
155
- if (!IERC165(hook).supportsInterface(type(IHasAfterCoinDeploy).interfaceId)) {
156
- revert InvalidHook();
157
- }
158
- hookDataOut = IHasAfterCoinDeploy(hook).afterCoinDeploy{value: msg.value}(msg.sender, ICoin(coin), hookData);
159
- } else if (msg.value > 0) {
160
- // cannot send eth without a hook
161
- revert EthTransferInvalid();
162
- }
160
+ /// @dev Internal function to deploy a creator coin with a hook
161
+ function _deployCreatorCoinWithHook(
162
+ address payoutRecipient,
163
+ address[] memory owners,
164
+ string memory uri,
165
+ string memory name,
166
+ string memory symbol,
167
+ bytes memory poolConfig,
168
+ address platformReferrer,
169
+ address deployHook,
170
+ bytes calldata hookData,
171
+ bytes32 salt
172
+ ) internal returns (address coin, bytes memory hookDataOut) {
173
+ coin = address(_createAndInitializeCreatorCoin(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, salt));
174
+ hookDataOut = _executePostDeployHook(coin, deployHook, hookData);
163
175
  }
164
176
 
165
- /** Deprecated deploy functions */
177
+ /**
178
+ * Deprecated deploy functions
179
+ */
166
180
 
167
181
  /// @dev Deprecated: use `deploy` instead that has a salt and hook specified
168
182
  function deploy(
@@ -193,11 +207,11 @@ contract ZoraFactoryImpl is
193
207
  string memory symbol,
194
208
  bytes memory poolConfig,
195
209
  address platformReferrer,
196
- address hook,
210
+ address deployHook,
197
211
  bytes calldata hookData
198
212
  ) public payable nonReentrant returns (address coin, bytes memory hookDataOut) {
199
213
  bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
200
- return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, hook, hookData, salt);
214
+ return _deployWithHook(payoutRecipient, owners, uri, name, symbol, poolConfig, platformReferrer, deployHook, hookData, salt);
201
215
  }
202
216
 
203
217
  /// @dev deprecated Use deploy() with poolConfig instead
@@ -210,8 +224,9 @@ contract ZoraFactoryImpl is
210
224
  address platformReferrer,
211
225
  address currency,
212
226
  // tickLower is no longer used
213
- int24 /*tickLower*/,
214
- uint256 orderSize
227
+ int24 /* tickLower */,
228
+ // orderSize is no longer used
229
+ uint256 /* orderSize */
215
230
  ) public payable nonReentrant returns (address, uint256) {
216
231
  bytes memory poolConfig = CoinConfigurationVersions.defaultConfig(currency);
217
232
  bytes32 salt = _randomSalt(payoutRecipient, uri, bytes32(0));
@@ -221,6 +236,10 @@ contract ZoraFactoryImpl is
221
236
  return (address(coin), 0);
222
237
  }
223
238
 
239
+ /**
240
+ * End Deprecated deploy functions
241
+ */
242
+
224
243
  function getCoinImpl(uint8 version) internal view returns (address) {
225
244
  if (CoinConfigurationVersions.isV4(version)) {
226
245
  return coinV4Impl;
@@ -229,29 +248,55 @@ contract ZoraFactoryImpl is
229
248
  revert ICoin.InvalidPoolVersion();
230
249
  }
231
250
 
232
- function _createCoin(uint8 version, bytes32 salt) internal returns (address payable) {
233
- return payable(Clones.cloneDeterministic(getCoinImpl(version), salt));
251
+ function _createCoinWithPoolConfig(
252
+ address _implementation,
253
+ bytes memory poolConfig,
254
+ bytes32 coinSalt,
255
+ address payoutRecipient,
256
+ address[] memory owners,
257
+ string memory uri,
258
+ string memory name,
259
+ string memory symbol,
260
+ address platformReferrer
261
+ ) internal returns (address coin, uint8 version, PoolKey memory poolKey, address currency) {
262
+ version = CoinConfigurationVersions.getVersion(poolConfig);
263
+ coin = Clones.cloneDeterministic(_implementation, coinSalt);
264
+ _setVersionForDeployedCoin(coin, version);
265
+
266
+ uint160 sqrtPriceX96;
267
+ bool isCoinToken0;
268
+ PoolConfiguration memory poolConfiguration;
269
+ (, currency, sqrtPriceX96, isCoinToken0, poolConfiguration) = CoinSetup.generatePoolConfig(coin, poolConfig);
270
+
271
+ poolKey = CoinSetup.buildPoolKey(coin, currency, isCoinToken0, IHooks(hook));
272
+ ICoin(coin).initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolKey, sqrtPriceX96, poolConfiguration);
234
273
  }
235
274
 
236
- function _setupV4Coin(
237
- ICoin coin,
238
- address currency,
239
- bool isCoinToken0,
240
- uint160 sqrtPriceX96,
241
- PoolConfiguration memory poolConfiguration,
275
+ function _createAndInitializeCreatorCoin(
242
276
  address payoutRecipient,
243
277
  address[] memory owners,
244
278
  string memory uri,
245
279
  string memory name,
246
280
  string memory symbol,
247
- address platformReferrer
248
- ) internal {
249
- PoolKey memory poolKey = CoinSetup.buildPoolKey(address(coin), currency, isCoinToken0, IHooks(contentCoinHook));
281
+ bytes memory poolConfig,
282
+ address platformReferrer,
283
+ bytes32 coinSalt
284
+ ) internal returns (ICreatorCoin) {
285
+ (address creatorCoin, uint8 version, PoolKey memory poolKey, address currency) = _createCoinWithPoolConfig(
286
+ creatorCoinImpl,
287
+ poolConfig,
288
+ coinSalt,
289
+ payoutRecipient,
290
+ owners,
291
+ uri,
292
+ name,
293
+ symbol,
294
+ platformReferrer
295
+ );
250
296
 
251
- // Initialize coin with pre-configured pool
252
- coin.initialize(payoutRecipient, owners, uri, name, symbol, platformReferrer, currency, poolKey, sqrtPriceX96, poolConfiguration);
297
+ require(version == CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION, InvalidConfig());
253
298
 
254
- emit CoinCreatedV4(
299
+ emit CreatorCoinCreated(
255
300
  msg.sender,
256
301
  payoutRecipient,
257
302
  platformReferrer,
@@ -259,11 +304,13 @@ contract ZoraFactoryImpl is
259
304
  uri,
260
305
  name,
261
306
  symbol,
262
- address(coin),
307
+ creatorCoin,
263
308
  poolKey,
264
309
  CoinCommon.hashPoolKey(poolKey),
265
- IVersionedContract(address(coin)).contractVersion()
310
+ IVersionedContract(creatorCoin).contractVersion()
266
311
  );
312
+
313
+ return ICreatorCoin(creatorCoin);
267
314
  }
268
315
 
269
316
  function _createAndInitializeCoin(
@@ -276,26 +323,34 @@ contract ZoraFactoryImpl is
276
323
  address platformReferrer,
277
324
  bytes32 coinSalt
278
325
  ) internal returns (ICoin) {
279
- uint8 version = CoinConfigurationVersions.getVersion(poolConfig);
280
-
281
- address payable coin = _createCoin(version, coinSalt);
326
+ (address coin, uint8 version, PoolKey memory poolKey, address currency) = _createCoinWithPoolConfig(
327
+ coinV4Impl,
328
+ poolConfig,
329
+ coinSalt,
330
+ payoutRecipient,
331
+ owners,
332
+ uri,
333
+ name,
334
+ symbol,
335
+ platformReferrer
336
+ );
282
337
 
283
- _setVersionForDeployedCoin(address(coin), version);
338
+ require(version == CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION, ICoin.InvalidPoolVersion());
284
339
 
285
- (, address currency, uint160 sqrtPriceX96, bool isCoinToken0, PoolConfiguration memory poolConfiguration) = CoinSetup.generatePoolConfig(
286
- address(coin),
287
- poolConfig
340
+ emit CoinCreatedV4(
341
+ msg.sender,
342
+ payoutRecipient,
343
+ platformReferrer,
344
+ currency,
345
+ uri,
346
+ name,
347
+ symbol,
348
+ coin,
349
+ poolKey,
350
+ CoinCommon.hashPoolKey(poolKey),
351
+ IVersionedContract(coin).contractVersion()
288
352
  );
289
353
 
290
- if (CoinConfigurationVersions.isV3(version)) {
291
- // V3 is no longer supported
292
- revert ICoin.InvalidPoolVersion();
293
- } else if (CoinConfigurationVersions.isV4(version)) {
294
- _setupV4Coin(ICoin(coin), currency, isCoinToken0, sqrtPriceX96, poolConfiguration, payoutRecipient, owners, uri, name, symbol, platformReferrer);
295
- } else {
296
- revert ICoin.InvalidPoolVersion();
297
- }
298
-
299
354
  return ICoin(coin);
300
355
  }
301
356
 
@@ -310,7 +365,6 @@ contract ZoraFactoryImpl is
310
365
  return keccak256(abi.encodePacked(msgSender, name, symbol, poolConfig, platformReferrer, coinSalt));
311
366
  }
312
367
 
313
- /// @dev Generates a unique salt for deterministic deployment
314
368
  function _randomSalt(address payoutRecipient, string memory uri, bytes32 coinSalt) internal view returns (bytes32) {
315
369
  return
316
370
  keccak256(
@@ -354,13 +408,30 @@ contract ZoraFactoryImpl is
354
408
  // try to get the existing contract name - if it reverts, the existing contract was an older version that didn't have the contract name
355
409
  // unfortunately we cannot use supportsInterface here because the existing implementation did not have that function
356
410
  try IHasContractName(newImpl).contractName() returns (string memory name) {
357
- if (!_equals(name, contractName())) {
411
+ if (!Strings.equal(name, contractName())) {
358
412
  revert UpgradeToMismatchedContractName(contractName(), name);
359
413
  }
360
414
  } catch {}
415
+
416
+ // Auto-register the new hooks in the Zora hook registry
417
+ address[] memory hooks = new address[](1);
418
+ string[] memory tags = new string[](1);
419
+
420
+ hooks[0] = IZoraFactory(newImpl).hook();
421
+ tags[0] = "CoinHook";
422
+
423
+ IZoraHookRegistry(zoraHookRegistry).registerHooks(hooks, tags);
424
+ }
425
+
426
+ /// @notice The address of the latest creator coin hook
427
+ /// @dev Deprecated: use `hook` instead
428
+ function creatorCoinHook() external view returns (address) {
429
+ return hook;
361
430
  }
362
431
 
363
- function _equals(string memory a, string memory b) internal pure returns (bool) {
364
- return (keccak256(bytes(a)) == keccak256(bytes(b)));
432
+ /// @notice The address of the latest coin hook
433
+ /// @dev Deprecated: use `hook` instead
434
+ function contentCoinHook() external view returns (address) {
435
+ return hook;
365
436
  }
366
437
  }