@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
@@ -8,53 +8,64 @@
8
8
  pragma solidity ^0.8.23;
9
9
 
10
10
  library CoinConstants {
11
+ /// @dev Constant used to increase precision during calculations
12
+ uint256 internal constant WAD = 1e18;
13
+
11
14
  /// @notice The maximum total supply
12
15
  /// @dev Set to 1 billion coins with 18 decimals
13
- uint256 public constant MAX_TOTAL_SUPPLY = 1_000_000_000e18;
16
+ uint256 internal constant MAX_TOTAL_SUPPLY = 1_000_000_000e18;
17
+
18
+ /// @notice The total supply for creator coins (same as MAX_TOTAL_SUPPLY)
19
+ /// @dev 1 billion coins
20
+ uint256 internal constant TOTAL_SUPPLY = 1_000_000_000e18;
14
21
 
15
- /// @notice The number of coins allocated to the liquidity pool
22
+ /// @notice The number of coins allocated to the liquidity pool for content coins
16
23
  /// @dev 990 million coins
17
- uint256 public constant POOL_LAUNCH_SUPPLY = 990_000_000e18;
24
+ uint256 internal constant CONTENT_COIN_MARKET_SUPPLY = 990_000_000e18;
18
25
 
19
- /// @notice The number of coins rewarded to the creator
26
+ /// @notice The number of coins allocated to the liquidity pool for creator coins
27
+ /// @dev 500 million coins
28
+ uint256 internal constant CREATOR_COIN_MARKET_SUPPLY = 500_000_000e18;
29
+
30
+ /// @notice The number of coins rewarded to the creator for content coins on launch
20
31
  /// @dev 10 million coins
21
- uint256 public constant CREATOR_LAUNCH_REWARD = 10_000_000e18;
32
+ uint256 internal constant CONTENT_COIN_INITIAL_CREATOR_SUPPLY = TOTAL_SUPPLY - CONTENT_COIN_MARKET_SUPPLY;
33
+
34
+ /// @notice Creator coin vesting supply for creator
35
+ /// @dev 500 million coins
36
+ uint256 internal constant CREATOR_COIN_CREATOR_VESTING_SUPPLY = TOTAL_SUPPLY - CREATOR_COIN_MARKET_SUPPLY;
22
37
 
23
- /// @notice The minimum order size allowed for trades
24
- /// @dev Set to 0.0000001 ETH to prevent dust transactions
25
- uint256 public constant MIN_ORDER_SIZE = 0.0000001 ether;
38
+ /// @notice Creator coin vesting duration
39
+ /// @dev 5 years with leap years accounted for
40
+ uint256 internal constant CREATOR_VESTING_DURATION = (5 * 365.25 days);
26
41
 
27
- /// @notice The total fee percentage in basis points
28
- /// @dev 100 basis points = 1%
29
- uint256 public constant TOTAL_FEE_BPS = 100;
42
+ /// @notice The backing currency for creator coins
43
+ /// @dev ETH backing currency address
44
+ address internal constant CREATOR_COIN_CURRENCY = 0x1111111111166b7FE7bd91427724B487980aFc69;
30
45
 
31
- /// @notice The percentage of the total fee allocated to creators
32
- /// @dev 5000 basis points = 50% of TOTAL_FEE_BPS
33
- uint256 public constant TOKEN_CREATOR_FEE_BPS = 5000;
46
+ /// @notice The LP fee
47
+ /// @dev 10000 basis points = 1%
48
+ uint24 internal constant LP_FEE_V4 = 10_000;
34
49
 
35
- /// @notice The percentage of the total fee allocated to the protocol
36
- /// @dev 2000 basis points = 20% of TOTAL_FEE_BPS
37
- uint256 public constant PROTOCOL_FEE_BPS = 2000;
50
+ /// @notice The spacing for 1% pools
51
+ /// @dev 200 ticks
52
+ int24 internal constant TICK_SPACING = 200;
38
53
 
39
- /// @notice The percentage of the total fee allocated to platform referrers
40
- /// @dev 1500 basis points = 15% of TOTAL_FEE_BPS
41
- uint256 public constant PLATFORM_REFERRER_FEE_BPS = 1500;
54
+ // Creator gets 62.5% of market rewards (0.50% of total 1% fee)
55
+ // Market rewards = 80% of total fee (0.80% of 1%)
56
+ uint256 internal constant CREATOR_REWARD_BPS = 6250;
42
57
 
43
- /// @notice The percentage of the total fee allocated to trade referrers
44
- /// @dev 1500 basis points = 15% of TOTAL_FEE_BPS
45
- uint256 public constant TRADE_REFERRER_FEE_BPS = 1500;
58
+ // Platform referrer gets 25% of market rewards (0.20% of total 1% fee)
59
+ uint256 internal constant CREATE_REFERRAL_REWARD_BPS = 2500;
46
60
 
47
- /// @notice The percentage of the LP fee allocated to creators
48
- /// @dev 5000 basis points = 50% of the 1% LP FEE
49
- uint256 public constant CREATOR_MARKET_REWARD_BPS = 5000;
61
+ // Trade referrer gets 5% of market rewards (0.04% of total 1% fee)
62
+ uint256 internal constant TRADE_REFERRAL_REWARD_BPS = 500;
50
63
 
51
- /// @notice The percentage of the LP fee allocated to platform referrers
52
- /// @dev 2500 basis points = 25% of the 1% LP FEE
53
- uint256 public constant PLATFORM_REFERRER_MARKET_REWARD_BPS = 2500;
64
+ // Doppler gets 1.25% of market rewards (0.01% of total 1% fee)
65
+ uint256 internal constant DOPPLER_REWARD_BPS = 125;
54
66
 
55
- /// @notice The percentage of the LP fee allocated to the Doppler protocol
56
- /// @dev 500 basis points = 5% of the 1% LP FEE
57
- uint256 public constant DOPPLER_MARKET_REWARD_BPS = 500;
67
+ // LPs get 20% of total fee (0.20% of 1%)
68
+ uint256 internal constant LP_REWARD_BPS = 2000;
58
69
 
59
70
  int24 internal constant DEFAULT_DISCOVERY_TICK_LOWER = -777000;
60
71
  int24 internal constant DEFAULT_DISCOVERY_TICK_UPPER = 222000;
@@ -10,7 +10,7 @@ pragma solidity ^0.8.23;
10
10
  import {PoolConfiguration} from "../interfaces/ICoin.sol";
11
11
  import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
12
12
  import {LpPosition} from "../types/LpPosition.sol";
13
- import {MarketConstants} from "./MarketConstants.sol";
13
+ import {CoinConstants} from "./CoinConstants.sol";
14
14
  import {FullMath} from "../utils/uniswap/FullMath.sol";
15
15
  import {TickMath} from "../utils/uniswap/TickMath.sol";
16
16
  import {IDopplerErrors} from "../interfaces/IDopplerErrors.sol";
@@ -56,8 +56,8 @@ library CoinDopplerMultiCurve {
56
56
  uint256 totalDiscoverySupplyShare;
57
57
  uint256 totalDiscoveryPositions;
58
58
 
59
- int24 boundryTickLower = DopplerMath.alignTickToTickSpacing(isCoinToken0, TickMath.MAX_TICK, MarketConstants.TICK_SPACING);
60
- int24 boundryTickUpper = DopplerMath.alignTickToTickSpacing(isCoinToken0, TickMath.MIN_TICK, MarketConstants.TICK_SPACING);
59
+ int24 boundryTickLower = DopplerMath.alignTickToTickSpacing(isCoinToken0, TickMath.MAX_TICK, CoinConstants.TICK_SPACING);
60
+ int24 boundryTickUpper = DopplerMath.alignTickToTickSpacing(isCoinToken0, TickMath.MIN_TICK, CoinConstants.TICK_SPACING);
61
61
 
62
62
  // For each curve:
63
63
  for (uint256 i; i < numCurves; i++) {
@@ -69,8 +69,8 @@ library CoinDopplerMultiCurve {
69
69
  totalDiscoveryPositions += numDiscoveryPositions_[i];
70
70
  totalDiscoverySupplyShare += maxDiscoverySupplyShare_[i];
71
71
 
72
- int24 currentTickLower = DopplerMath.alignTickToTickSpacing(isCoinToken0, tickLower_[i], MarketConstants.TICK_SPACING);
73
- int24 currentTickUpper = DopplerMath.alignTickToTickSpacing(isCoinToken0, tickUpper_[i], MarketConstants.TICK_SPACING);
72
+ int24 currentTickLower = DopplerMath.alignTickToTickSpacing(isCoinToken0, tickLower_[i], CoinConstants.TICK_SPACING);
73
+ int24 currentTickUpper = DopplerMath.alignTickToTickSpacing(isCoinToken0, tickUpper_[i], CoinConstants.TICK_SPACING);
74
74
 
75
75
  require(currentTickLower < currentTickUpper, ConfigTickLowerMustBeLessThanTickUpper());
76
76
 
@@ -84,15 +84,15 @@ library CoinDopplerMultiCurve {
84
84
 
85
85
  require(boundryTickLower < boundryTickUpper, InvalidTickRangeMisordered(boundryTickLower, boundryTickUpper));
86
86
  require(totalDiscoveryPositions > 1 && totalDiscoveryPositions <= 200, IDopplerErrors.NumDiscoveryPositionsOutOfRange());
87
- require(totalDiscoverySupplyShare < MarketConstants.WAD, IDopplerErrors.MaxShareToBeSoldExceeded(totalDiscoverySupplyShare, MarketConstants.WAD));
87
+ require(totalDiscoverySupplyShare < CoinConstants.WAD, IDopplerErrors.MaxShareToBeSoldExceeded(totalDiscoverySupplyShare, CoinConstants.WAD));
88
88
 
89
89
  sqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? boundryTickLower : boundryTickUpper);
90
90
 
91
91
  poolConfiguration = PoolConfiguration({
92
92
  version: CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION,
93
93
  numPositions: uint16(totalDiscoveryPositions + 1), // Add one for the final tail position
94
- fee: MarketConstants.LP_FEE_V4,
95
- tickSpacing: MarketConstants.TICK_SPACING,
94
+ fee: CoinConstants.LP_FEE_V4,
95
+ tickSpacing: CoinConstants.TICK_SPACING,
96
96
  numDiscoveryPositions: numDiscoveryPositions_,
97
97
  tickLower: tickLower_,
98
98
  tickUpper: tickUpper_,
@@ -113,12 +113,12 @@ library CoinDopplerMultiCurve {
113
113
  uint256 numCurves = poolConfiguration.tickLower.length;
114
114
 
115
115
  for (uint256 i; i < numCurves; i++) {
116
- uint256 curveSupply = FullMath.mulDiv(totalSupply, poolConfiguration.maxDiscoverySupplyShare[i], MarketConstants.WAD);
116
+ uint256 curveSupply = FullMath.mulDiv(totalSupply, poolConfiguration.maxDiscoverySupplyShare[i], CoinConstants.WAD);
117
117
 
118
118
  (positions, curveSupply) = DopplerMath.calculateLogNormalDistribution(
119
119
  poolConfiguration.tickLower[i],
120
120
  poolConfiguration.tickUpper[i],
121
- MarketConstants.TICK_SPACING,
121
+ CoinConstants.TICK_SPACING,
122
122
  isCoinToken0,
123
123
  curveSupply,
124
124
  poolConfiguration.numDiscoveryPositions[i],
@@ -138,7 +138,7 @@ library CoinDopplerMultiCurve {
138
138
  poolConfiguration.tickUpper[numCurves - 1],
139
139
  isCoinToken0,
140
140
  tailSupply,
141
- MarketConstants.TICK_SPACING
141
+ CoinConstants.TICK_SPACING
142
142
  );
143
143
  }
144
144
  }
@@ -29,29 +29,15 @@ import {IZoraV4CoinHook} from "../interfaces/IZoraV4CoinHook.sol";
29
29
  import {IHasSwapPath} from "../interfaces/ICoin.sol";
30
30
  import {V4Liquidity} from "./V4Liquidity.sol";
31
31
  import {UniV4SwapToCurrency} from "./UniV4SwapToCurrency.sol";
32
+ import {ICreatorCoinHook} from "../interfaces/ICreatorCoinHook.sol";
33
+ import {IHasCoinType} from "../interfaces/ICoin.sol";
34
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
35
+ import {ICreatorCoin} from "../interfaces/ICreatorCoin.sol";
36
+ import {CoinConstants} from "./CoinConstants.sol";
32
37
 
33
38
  library CoinRewardsV4 {
34
39
  using SafeERC20 for IERC20;
35
40
 
36
- // creator gets 50% of the market rewards
37
- // market rewards are 2/3 of the total fee
38
- uint256 public constant CREATOR_REWARD_BPS = 5000;
39
-
40
- // create referrer gets 15% of the market rewards
41
- // market rewards are 2/3 of the total fee
42
- uint256 public constant CREATE_REFERRAL_REWARD_BPS = 1500;
43
-
44
- // trade referrer gets 10% of the market rewards
45
- // market rewards are 2/3 of the total fee
46
- uint256 public constant TRADE_REFERRAL_REWARD_BPS = 1500;
47
-
48
- // doppler gets 5% of the market rewards
49
- // market rewards are 2/3 of the total fee
50
- uint256 public constant DOPPLER_REWARD_BPS = 500;
51
-
52
- // LPs get 1/3 of the total fee
53
- uint256 public constant LP_REWARD_BPS = 3333;
54
-
55
41
  function getTradeReferral(bytes calldata hookData) internal pure returns (address) {
56
42
  return hookData.length >= 20 ? abi.decode(hookData, (address)) : address(0);
57
43
  }
@@ -82,13 +68,16 @@ library CoinRewardsV4 {
82
68
 
83
69
  /// @dev Computes the LP reward and remaining amount for market rewards from the total amount
84
70
  function computeLpReward(uint128 totalBackingAmount) internal pure returns (uint128 lpRewardAmount) {
85
- lpRewardAmount = uint128(calculateReward(uint256(totalBackingAmount), LP_REWARD_BPS));
71
+ lpRewardAmount = uint128(calculateReward(uint256(totalBackingAmount), CoinConstants.LP_REWARD_BPS));
86
72
  }
87
73
 
88
74
  function convertDeltaToPositiveUint128(int256 delta) internal pure returns (uint128) {
89
75
  if (delta < 0) {
90
76
  revert SafeCast.SafeCastOverflow();
91
77
  }
78
+ if (delta > int256(uint256(type(uint128).max))) {
79
+ revert SafeCast.SafeCastOverflow();
80
+ }
92
81
  return uint128(uint256(delta));
93
82
  }
94
83
 
@@ -180,7 +169,14 @@ library CoinRewardsV4 {
180
169
  /// @param fees The total amount of fees collected to be distributed
181
170
  /// @param coin The coin contract instance that implements IHasRewardsRecipients to get recipient addresses
182
171
  /// @param tradeReferrer The address of the trade referrer who should receive trade referral rewards (can be zero address)
183
- function distributeMarketRewards(Currency currency, uint128 fees, IHasRewardsRecipients coin, address tradeReferrer) internal {
172
+ /// @param coinType The type of coin (Creator or Content) which affects reward distribution percentages
173
+ function distributeMarketRewards(
174
+ Currency currency,
175
+ uint128 fees,
176
+ IHasRewardsRecipients coin,
177
+ address tradeReferrer,
178
+ IHasCoinType.CoinType coinType
179
+ ) internal {
184
180
  address payoutRecipient = coin.payoutRecipient();
185
181
  address platformReferrer = coin.platformReferrer();
186
182
  address protocolRewardRecipient = coin.protocolRewardRecipient();
@@ -219,6 +215,17 @@ library CoinRewardsV4 {
219
215
  doppler,
220
216
  marketRewards
221
217
  );
218
+
219
+ if (coinType == IHasCoinType.CoinType.Creator) {
220
+ emit ICreatorCoinHook.CreatorCoinRewards(
221
+ address(coin),
222
+ Currency.unwrap(currency),
223
+ payoutRecipient,
224
+ protocolRewardRecipient,
225
+ rewards.creatorAmount,
226
+ rewards.protocolAmount
227
+ );
228
+ }
222
229
  }
223
230
 
224
231
  struct MarketRewards {
@@ -240,26 +247,30 @@ library CoinRewardsV4 {
240
247
  ) internal returns (MarketRewards memory rewards) {
241
248
  rewards = _computeMarketRewards(fee, tradeReferral != address(0), platformReferrer != address(0));
242
249
 
243
- if (platformReferrer != address(0)) {
244
- _transferCurrency(currency, rewards.platformReferrerAmount, platformReferrer);
245
- }
246
- if (tradeReferral != address(0)) {
247
- _transferCurrency(currency, rewards.tradeReferrerAmount, tradeReferral);
248
- }
249
- _transferCurrency(currency, rewards.creatorAmount, payoutRecipient);
250
- _transferCurrency(currency, rewards.dopplerAmount, doppler);
251
- _transferCurrency(currency, rewards.protocolAmount, protocolRewardRecipient);
250
+ // Notes on ETH transfer fallback behavior:
251
+ // - If the platform referrer is immutable; if it is set to an address that cannot receive ETH, it can brick swaps on the coin, as they would revert.
252
+ // - Both the creator recipient and trade referral are changeable, the former via updating the payout recipient on the coin, and the latter via updating the trade referrer argument when doing a swap;
253
+ // therefore we do not need to worry about permanently bricking swaps, and don't need a backup recipient.
254
+ _transferCurrency(currency, rewards.platformReferrerAmount, platformReferrer, protocolRewardRecipient);
255
+ _transferCurrency(currency, rewards.tradeReferrerAmount, tradeReferral, address(0));
256
+ _transferCurrency(currency, rewards.creatorAmount, payoutRecipient, address(0));
257
+ _transferCurrency(currency, rewards.dopplerAmount, doppler, protocolRewardRecipient);
258
+ _transferCurrency(currency, rewards.protocolAmount, protocolRewardRecipient, address(0));
252
259
  }
253
260
 
254
- function _transferCurrency(Currency currency, uint256 amount, address to) internal {
255
- if (amount == 0) {
261
+ function _transferCurrency(Currency currency, uint256 amount, address to, address backupRecipient) internal {
262
+ if (amount == 0 || to == address(0)) {
256
263
  return;
257
264
  }
258
265
 
259
266
  if (currency.isAddressZero()) {
260
267
  (bool success, ) = payable(to).call{value: amount}("");
261
268
  if (!success) {
262
- revert ICoin.EthTransferFailed();
269
+ if (backupRecipient == address(0)) {
270
+ revert ICoin.EthTransferFailed();
271
+ } else {
272
+ _transferCurrency(currency, amount, backupRecipient, address(0));
273
+ }
263
274
  }
264
275
  } else {
265
276
  IERC20(Currency.unwrap(currency)).safeTransfer(to, amount);
@@ -272,14 +283,34 @@ library CoinRewardsV4 {
272
283
  }
273
284
 
274
285
  uint256 totalAmount = uint256(fee);
275
- rewards.platformReferrerAmount = hasCreateReferral ? calculateReward(totalAmount, CREATE_REFERRAL_REWARD_BPS) : 0;
276
- rewards.tradeReferrerAmount = hasTradeReferral ? calculateReward(totalAmount, TRADE_REFERRAL_REWARD_BPS) : 0;
277
- rewards.creatorAmount = calculateReward(totalAmount, CREATOR_REWARD_BPS);
278
- rewards.dopplerAmount = calculateReward(totalAmount, DOPPLER_REWARD_BPS);
286
+ rewards.platformReferrerAmount = hasCreateReferral ? calculateReward(totalAmount, CoinConstants.CREATE_REFERRAL_REWARD_BPS) : 0;
287
+ rewards.tradeReferrerAmount = hasTradeReferral ? calculateReward(totalAmount, CoinConstants.TRADE_REFERRAL_REWARD_BPS) : 0;
288
+ rewards.creatorAmount = calculateReward(totalAmount, CoinConstants.CREATOR_REWARD_BPS);
289
+ rewards.dopplerAmount = calculateReward(totalAmount, CoinConstants.DOPPLER_REWARD_BPS);
279
290
  rewards.protocolAmount = totalAmount - rewards.platformReferrerAmount - rewards.tradeReferrerAmount - rewards.creatorAmount - rewards.dopplerAmount;
280
291
  }
281
292
 
282
293
  function calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
283
294
  return (amount * bps) / 10_000;
284
295
  }
296
+
297
+ function getCoinType(IHasRewardsRecipients coin) internal view returns (IHasCoinType.CoinType) {
298
+ // first check if the coin supports the IHasCoinType interface - if it does, we can use that
299
+ if (IERC165(address(coin)).supportsInterface(type(IHasCoinType).interfaceId)) {
300
+ return IHasCoinType(address(coin)).coinType();
301
+ }
302
+
303
+ // see if its a legacy creator coin
304
+ return isLegacyCreatorCoin(coin) ? IHasCoinType.CoinType.Creator : IHasCoinType.CoinType.Content;
305
+ }
306
+
307
+ function isLegacyCreatorCoin(IHasRewardsRecipients coin) internal view returns (bool) {
308
+ // try to call the method `getClaimableAmount` on the legacy creator coin, if it succeeds, then it is a legacy creator coin,
309
+ // otherwise we can assume it is a content coin
310
+ try ICreatorCoin(address(coin)).getClaimableAmount() returns (uint256) {
311
+ return true;
312
+ } catch {
313
+ return false;
314
+ }
315
+ }
285
316
  }
@@ -11,12 +11,11 @@ import {PoolConfigurationV4} from "../interfaces/ICoin.sol";
11
11
  import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
12
12
  import {ICoin} from "../interfaces/ICoin.sol";
13
13
  import {CoinCommon} from "./CoinCommon.sol";
14
- import {MarketConstants} from "./MarketConstants.sol";
14
+ import {CoinConstants} from "./CoinConstants.sol";
15
15
  import {TickMath} from "../utils/uniswap/TickMath.sol";
16
16
  import {IPoolManager, PoolKey, Currency, IHooks} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
17
17
  import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
18
18
  import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
19
- import {MarketConstants} from "./MarketConstants.sol";
20
19
  import {LpPosition} from "../types/LpPosition.sol";
21
20
  import {CoinDopplerMultiCurve, PoolConfiguration} from "./CoinDopplerMultiCurve.sol";
22
21
 
@@ -37,13 +36,7 @@ library CoinSetup {
37
36
  Currency currency0 = isCoinToken0 ? Currency.wrap(coin) : Currency.wrap(currency);
38
37
  Currency currency1 = isCoinToken0 ? Currency.wrap(currency) : Currency.wrap(coin);
39
38
 
40
- poolKey = PoolKey({
41
- currency0: currency0,
42
- currency1: currency1,
43
- fee: MarketConstants.LP_FEE_V4,
44
- tickSpacing: MarketConstants.TICK_SPACING,
45
- hooks: hooks
46
- });
39
+ poolKey = PoolKey({currency0: currency0, currency1: currency1, fee: CoinConstants.LP_FEE_V4, tickSpacing: CoinConstants.TICK_SPACING, hooks: hooks});
47
40
  }
48
41
 
49
42
  function setupPoolWithVersion(
@@ -13,7 +13,7 @@ import {FullMath} from "../utils/uniswap/FullMath.sol";
13
13
  import {SqrtPriceMath} from "../utils/uniswap/SqrtPriceMath.sol";
14
14
  import {LiquidityAmounts} from "../utils/uniswap/LiquidityAmounts.sol";
15
15
  import {LpPosition} from "../types/LpPosition.sol";
16
- import {MarketConstants} from "./MarketConstants.sol";
16
+ import {CoinConstants} from "./CoinConstants.sol";
17
17
 
18
18
  /// @author Whetstone Research
19
19
  /// @notice Calculates liquidity provisioning with Uniswap v3
@@ -54,7 +54,7 @@ library DopplerMath {
54
54
  int24 spread = tickUpper - tickLower;
55
55
 
56
56
  uint160 farSqrtPriceX96 = TickMath.getSqrtPriceAtTick(farTick);
57
- uint256 amountPerPosition = FullMath.mulDiv(discoverySupply, MarketConstants.WAD, totalPositions * MarketConstants.WAD);
57
+ uint256 amountPerPosition = FullMath.mulDiv(discoverySupply, CoinConstants.WAD, totalPositions * CoinConstants.WAD);
58
58
  uint256 totalAssetsSold;
59
59
 
60
60
  for (uint256 i; i < totalPositions; i++) {
@@ -9,8 +9,7 @@ pragma solidity ^0.8.23;
9
9
 
10
10
  import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
11
11
  import {Vm} from "forge-std/Vm.sol";
12
- import {ContentCoinHook} from "../hooks/ContentCoinHook.sol";
13
- import {CreatorCoinHook} from "../hooks/CreatorCoinHook.sol";
12
+ import {ZoraV4CoinHook} from "../hooks/ZoraV4CoinHook.sol";
14
13
  import {HookMiner} from "@uniswap/v4-periphery/src/utils/HookMiner.sol";
15
14
  import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
16
15
  import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
@@ -83,26 +82,14 @@ library HooksDeployment {
83
82
  }
84
83
  }
85
84
 
86
- function mineForCreatorCoinSalt(
85
+ function mineForCoinSalt(
87
86
  address deployer,
88
87
  address poolManager,
89
88
  address coinVersionLookup,
90
89
  address[] memory trustedMessageSenders,
91
90
  address upgradeGate
92
91
  ) internal returns (address hookAddress, bytes32 salt) {
93
- bytes memory hookCreationCode = creatorCoinHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
94
- (salt, ) = mineAndCacheSalt(deployer, hookCreationCode);
95
- hookAddress = HookMinerWithCreationCodeArgs.deterministicHookAddress(deployer, salt, hookCreationCode);
96
- }
97
-
98
- function mineForContentCoinSalt(
99
- address deployer,
100
- address poolManager,
101
- address coinVersionLookup,
102
- address[] memory trustedMessageSenders,
103
- address upgradeGate
104
- ) internal returns (address hookAddress, bytes32 salt) {
105
- bytes memory hookCreationCode = contentCoinCreationCode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
92
+ bytes memory hookCreationCode = makeHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
106
93
  (salt, ) = mineAndCacheSalt(deployer, hookCreationCode);
107
94
  hookAddress = HookMinerWithCreationCodeArgs.deterministicHookAddress(deployer, salt, hookCreationCode);
108
95
  }
@@ -141,7 +128,7 @@ library HooksDeployment {
141
128
  isDeployed = hookAddress.code.length > 0;
142
129
  }
143
130
 
144
- function contentCoinConstructorArgs(
131
+ function hookConstructorArgs(
145
132
  address poolManager,
146
133
  address coinVersionLookup,
147
134
  address[] memory trustedMessageSenders,
@@ -150,82 +137,43 @@ library HooksDeployment {
150
137
  return abi.encode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
151
138
  }
152
139
 
153
- function contentCoinCreationCode(
154
- address poolManager,
155
- address coinVersionLookup,
156
- address[] memory trustedMessageSenders,
157
- address upgradeGate
158
- ) internal pure returns (bytes memory) {
159
- return
160
- abi.encodePacked(
161
- type(ContentCoinHook).creationCode,
162
- contentCoinConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate)
163
- );
164
- }
165
-
166
- function creatorCoinCreationCode(
140
+ function makeHookCreationCode(
167
141
  address poolManager,
168
142
  address coinVersionLookup,
169
143
  address[] memory trustedMessageSenders,
170
144
  address upgradeGate
171
145
  ) internal pure returns (bytes memory) {
172
- return
173
- abi.encodePacked(
174
- type(CreatorCoinHook).creationCode,
175
- creatorCoinConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate)
176
- );
146
+ return abi.encodePacked(type(ZoraV4CoinHook).creationCode, hookConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate));
177
147
  }
178
148
 
179
149
  /// @notice Deploys or returns existing ContentCoinHook using deterministic deployment. Ensures that if a hooks is already
180
150
  /// deployed with the provided salt, it will be returned.
181
- function deployContentCoinHook(
151
+ function deployZoraV4CoinHook(
182
152
  address poolManager,
183
153
  address coinVersionLookup,
184
154
  address[] memory trustedMessageSenders,
185
155
  address upgradeGate,
186
156
  bytes32 salt
187
157
  ) internal returns (IHooks hook) {
188
- bytes memory hookCreationCode = contentCoinCreationCode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
189
- return deployHookWithSalt(hookCreationCode, salt);
190
- }
191
-
192
- function creatorCoinConstructorArgs(
193
- address poolManager,
194
- address coinVersionLookup,
195
- address[] memory trustedMessageSenders,
196
- address upgradeGate
197
- ) internal pure returns (bytes memory) {
198
- return abi.encode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
199
- }
200
-
201
- function creatorCoinHookCreationCode(
202
- address poolManager,
203
- address coinVersionLookup,
204
- address[] memory trustedMessageSenders,
205
- address upgradeGate
206
- ) internal pure returns (bytes memory) {
207
- return
208
- abi.encodePacked(
209
- type(CreatorCoinHook).creationCode,
210
- creatorCoinConstructorArgs(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate)
211
- );
158
+ bytes memory creationCode = makeHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders, upgradeGate);
159
+ return deployHookWithSalt(creationCode, salt);
212
160
  }
213
161
 
214
162
  address constant FOUNDRY_SCRIPT_ADDRESS = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
215
163
 
216
164
  function deployHookWithExistingOrNewSalt(
217
165
  address deployer,
218
- bytes memory hookCreationCode,
166
+ bytes memory _hookCreationCode,
219
167
  bytes32 salt
220
168
  ) internal returns (IHooks hook, bytes32 resultingSalt) {
221
- (bool isDeployed, address existingHookAddress) = hooksIsDeployed(deployer, hookCreationCode, salt);
169
+ (bool isDeployed, address existingHookAddress) = hooksIsDeployed(deployer, _hookCreationCode, salt);
222
170
 
223
171
  if (isDeployed) {
224
172
  hook = IHooks(existingHookAddress);
225
173
  resultingSalt = salt;
226
174
  } else {
227
- (, resultingSalt) = mineForSalt(deployer, hookCreationCode);
228
- hook = IHooks(Create2.deploy(0, resultingSalt, hookCreationCode));
175
+ (, resultingSalt) = mineForSalt(deployer, _hookCreationCode);
176
+ hook = IHooks(Create2.deploy(0, resultingSalt, _hookCreationCode));
229
177
  }
230
178
  }
231
179
  }