@zoralabs/coins 0.9.0 → 1.0.1

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 (131) hide show
  1. package/.turbo/turbo-build.log +179 -114
  2. package/CHANGELOG.md +46 -0
  3. package/abis/BaseCoin.json +26 -118
  4. package/abis/BaseTest.json +47 -0
  5. package/abis/BuySupplyWithSwapRouterHook.json +40 -0
  6. package/abis/Coin.json +171 -63
  7. package/abis/CoinDopplerMultiCurve.json +38 -0
  8. package/abis/CoinRewardsV4.json +54 -0
  9. package/abis/CoinTest.json +53 -20
  10. package/abis/CoinUniV4Test.json +1091 -0
  11. package/abis/CoinV4.json +234 -211
  12. package/abis/DeployScript.json +47 -0
  13. package/abis/DeployedCoinVersionLookup.json +21 -0
  14. package/abis/DeployedCoinVersionLookupTest.json +716 -0
  15. package/abis/DifferentNamespaceVersionLookup.json +39 -0
  16. package/abis/DopplerUniswapV3Test.json +49 -93
  17. package/abis/ERC20.json +310 -0
  18. package/abis/FactoryTest.json +85 -7
  19. package/abis/FeeEstimatorHook.json +1515 -0
  20. package/abis/HooksDeployment.json +23 -0
  21. package/abis/HooksTest.json +60 -0
  22. package/abis/ICoin.json +40 -71
  23. package/abis/ICoinV3.json +879 -0
  24. package/abis/ICoinV4.json +915 -0
  25. package/abis/IDeployedCoinVersionLookup.json +21 -0
  26. package/abis/IERC721.json +36 -36
  27. package/abis/IHasPoolKey.json +42 -0
  28. package/abis/IHasRewardsRecipients.json +54 -0
  29. package/abis/IHasSwapPath.json +60 -0
  30. package/abis/IMsgSender.json +15 -0
  31. package/abis/IPoolConfigEncoding.json +46 -0
  32. package/abis/ISwapPathRouter.json +92 -0
  33. package/abis/IUniversalRouter.json +61 -0
  34. package/abis/IUnlockCallback.json +21 -0
  35. package/abis/IV4Quoter.json +310 -0
  36. package/abis/IZoraFactory.json +210 -11
  37. package/abis/IZoraV4CoinHook.json +348 -4
  38. package/abis/MockERC20.json +21 -0
  39. package/abis/MultiOwnableTest.json +47 -0
  40. package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
  41. package/abis/PrintUpgradeCommand.json +9 -0
  42. package/abis/ProxyShim.json +24 -0
  43. package/abis/StateLibrary.json +80 -0
  44. package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
  45. package/abis/TestV4Swap.json +9 -0
  46. package/abis/UpgradeCoinImpl.json +47 -0
  47. package/abis/UpgradesTest.json +81 -0
  48. package/abis/Vm.json +1482 -111
  49. package/abis/VmSafe.json +856 -32
  50. package/abis/ZoraFactoryImpl.json +339 -1
  51. package/abis/ZoraV4CoinHook.json +442 -5
  52. package/addresses/8453.json +7 -4
  53. package/addresses/84532.json +8 -5
  54. package/addresses/dev/8453.json +10 -0
  55. package/dist/index.cjs +1932 -167
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.js +1928 -167
  58. package/dist/index.js.map +1 -1
  59. package/dist/wagmiGenerated.d.ts +2606 -160
  60. package/dist/wagmiGenerated.d.ts.map +1 -1
  61. package/foundry.toml +1 -0
  62. package/package/wagmiGenerated.ts +1941 -164
  63. package/package.json +8 -3
  64. package/remappings.txt +6 -1
  65. package/script/Deploy.s.sol +1 -1
  66. package/script/DeployDevFactory.s.sol +21 -0
  67. package/script/DeployHooks.s.sol +1 -1
  68. package/script/PrintUpgradeCommand.s.sol +13 -0
  69. package/script/Simulate.s.sol +1 -10
  70. package/script/TestBackingCoinSwap.s.sol +147 -0
  71. package/script/TestV4Swap.s.sol +136 -0
  72. package/script/UpgradeCoinImpl.sol +2 -2
  73. package/script/UpgradeFactoryImpl.s.sol +2 -2
  74. package/src/BaseCoin.sol +190 -0
  75. package/src/Coin.sol +87 -202
  76. package/src/CoinV4.sol +121 -0
  77. package/src/ZoraFactoryImpl.sol +208 -36
  78. package/{script → src/deployment}/CoinsDeployerBase.sol +111 -17
  79. package/src/hooks/ZoraV4CoinHook.sol +212 -0
  80. package/src/hooks/{BaseCoinDeployHook.sol → deployment/BaseCoinDeployHook.sol} +3 -3
  81. package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +140 -0
  82. package/src/interfaces/ICoin.sol +31 -39
  83. package/src/interfaces/ICoinV3.sol +71 -0
  84. package/src/interfaces/ICoinV4.sol +69 -0
  85. package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
  86. package/src/interfaces/IMsgSender.sol +9 -0
  87. package/src/interfaces/IPoolConfigEncoding.sol +14 -0
  88. package/src/interfaces/ISwapPathRouter.sol +14 -0
  89. package/src/interfaces/IZoraFactory.sol +67 -28
  90. package/src/interfaces/IZoraV4CoinHook.sol +116 -0
  91. package/src/libs/CoinCommon.sol +15 -0
  92. package/src/libs/CoinConfigurationVersions.sol +116 -1
  93. package/src/libs/CoinConstants.sol +5 -0
  94. package/src/libs/CoinDopplerMultiCurve.sol +134 -0
  95. package/src/libs/CoinDopplerUniV3.sol +19 -171
  96. package/src/libs/CoinRewards.sol +195 -0
  97. package/src/libs/CoinRewardsV4.sol +179 -0
  98. package/src/libs/CoinSetup.sol +57 -0
  99. package/src/libs/CoinSetupV3.sol +6 -67
  100. package/src/libs/DopplerMath.sol +156 -0
  101. package/src/libs/HooksDeployment.sol +128 -0
  102. package/src/libs/MarketConstants.sol +4 -0
  103. package/src/libs/PoolStateReader.sol +22 -0
  104. package/src/libs/UniV3BuySell.sol +74 -292
  105. package/src/libs/UniV4SwapHelper.sol +65 -0
  106. package/src/libs/UniV4SwapToCurrency.sol +109 -0
  107. package/src/libs/V4Liquidity.sol +122 -0
  108. package/src/types/PoolConfiguration.sol +15 -0
  109. package/src/utils/DeployedCoinVersionLookup.sol +52 -0
  110. package/src/version/ContractVersionBase.sol +1 -1
  111. package/test/Coin.t.sol +78 -88
  112. package/test/CoinDopplerUniV3.t.sol +32 -171
  113. package/test/CoinUniV4.t.sol +777 -0
  114. package/test/{Hooks.t.sol → DeploymentHooks.t.sol} +53 -16
  115. package/test/Factory.t.sol +80 -47
  116. package/test/MultiOwnable.t.sol +6 -3
  117. package/test/Upgrades.t.sol +97 -5
  118. package/test/mocks/MockERC20.sol +12 -0
  119. package/test/utils/BaseTest.sol +162 -57
  120. package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
  121. package/test/utils/FeeEstimatorHook.sol +84 -0
  122. package/test/utils/ProxyShim.sol +17 -0
  123. package/wagmi.config.ts +4 -0
  124. package/.env +0 -1
  125. package/.turbo/turbo-update-contract-version.log +0 -22
  126. package/abis/CoinSetupV3.json +0 -7
  127. package/abis/HookDeployer.json +0 -68
  128. package/abis/IHookDeployer.json +0 -42
  129. package/src/hooks/BuySupplyWithSwapRouterHook.sol +0 -78
  130. package/src/libs/CoinLegacy.sol +0 -48
  131. package/src/libs/CoinLegacyMarket.sol +0 -182
@@ -2,14 +2,14 @@
2
2
  pragma solidity ^0.8.23;
3
3
 
4
4
  import {PoolConfiguration} from "../interfaces/ICoin.sol";
5
- import {CoinLegacy} from "./CoinLegacy.sol";
6
5
  import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
7
6
  import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
8
7
  import {LpPosition} from "../types/LpPosition.sol";
9
8
  import {IUniswapV3Factory} from "../interfaces/IUniswapV3Factory.sol";
10
9
  import {MarketConstants} from "./MarketConstants.sol";
11
10
  import {IUniswapV3Pool} from "../interfaces/IUniswapV3Pool.sol";
12
-
11
+ import {ICoin} from "../interfaces/ICoin.sol";
12
+ import {CoinCommon} from "./CoinCommon.sol";
13
13
  struct UniV3Config {
14
14
  address weth;
15
15
  address v3Factory;
@@ -24,75 +24,12 @@ struct CoinV3Config {
24
24
  }
25
25
 
26
26
  library CoinSetupV3 {
27
- error InvalidPoolVersion();
28
-
29
- function setupPool(
30
- bytes memory poolConfig_,
31
- UniV3Config memory uniswapV3Config,
32
- address coin
33
- ) internal returns (address currency, address poolAddress, PoolConfiguration memory poolConfiguration) {
34
- // Extract version and currency from pool config
35
- (uint8 version, address currency_) = abi.decode(poolConfig_, (uint8, address));
36
-
37
- // Store the currency, defaulting to WETH if address(0)
38
- currency = currency_ == address(0) ? uniswapV3Config.weth : currency_;
39
-
40
- // Sort token addresses for Uniswap V3 pool creation
41
- bool isCoinToken0 = _sortTokens(coin, currency);
42
- address token0 = isCoinToken0 ? coin : currency;
43
- address token1 = isCoinToken0 ? currency : coin;
44
-
45
- // Configure the pool with appropriate version
46
- uint160 sqrtPriceX96;
47
- (sqrtPriceX96, poolConfiguration) = setupPoolWithVersion(version, poolConfig_, isCoinToken0, uniswapV3Config.weth);
48
-
49
- // Create the pool
50
- poolAddress = _createPool(token0, token1, sqrtPriceX96, uniswapV3Config.v3Factory);
51
- }
52
-
53
27
  /// @dev Deploys the Uniswap V3 pool and mints initial liquidity based on the pool configuration
54
- function deployLiquidity(address coin, address currency, PoolConfiguration memory poolConfiguration, address poolAddress) internal {
28
+ function deployLiquidity(LpPosition[] memory positions, address poolAddress) internal {
55
29
  // Calculate and mint positions
56
- LpPosition[] memory positions = calculatePositions(coin, currency, poolConfiguration);
57
30
  _mintPositions(positions, poolAddress);
58
31
  }
59
32
 
60
- // Helper function to sort tokens and determine if coin is token0
61
- function _sortTokens(address coin, address currency) private pure returns (bool isCoinToken0) {
62
- return coin < currency;
63
- }
64
-
65
- function setupPoolWithVersion(
66
- uint8 version,
67
- bytes memory poolConfig_,
68
- bool isCoinToken0,
69
- address weth
70
- ) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
71
- if (version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
72
- (sqrtPriceX96, poolConfiguration) = CoinLegacy.setupPool(isCoinToken0, poolConfig_, weth);
73
- } else if (version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
74
- (sqrtPriceX96, poolConfiguration) = CoinDopplerUniV3.setupPool(isCoinToken0, poolConfig_);
75
- } else {
76
- revert InvalidPoolVersion();
77
- }
78
- }
79
-
80
- function calculatePositions(
81
- address coin,
82
- address currency,
83
- PoolConfiguration memory poolConfiguration
84
- ) internal pure returns (LpPosition[] memory positions) {
85
- // Create the pool
86
- bool isCoinToken0 = _sortTokens(coin, currency);
87
- if (poolConfiguration.version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
88
- positions = CoinLegacy.calculatePositions(isCoinToken0, poolConfiguration.tickLower, poolConfiguration.tickUpper);
89
- } else if (poolConfiguration.version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
90
- positions = CoinDopplerUniV3.calculatePositions(isCoinToken0, poolConfiguration);
91
- } else {
92
- revert InvalidPoolVersion();
93
- }
94
- }
95
-
96
33
  /// @dev Mints the calculated liquidity positions into the Uniswap V3 pool
97
34
  function _mintPositions(LpPosition[] memory lbpPositions, address poolAddress) internal {
98
35
  for (uint256 i; i < lbpPositions.length; i++) {
@@ -101,7 +38,9 @@ library CoinSetupV3 {
101
38
  }
102
39
 
103
40
  /// @dev Creates the Uniswap V3 pool for the coin/currency pair
104
- function _createPool(address token0, address token1, uint160 sqrtPriceX96, address v3Factory) internal returns (address pool) {
41
+ function createV3Pool(address coin, address currency, bool isCoinToken0, uint160 sqrtPriceX96, address v3Factory) internal returns (address pool) {
42
+ address token0 = isCoinToken0 ? coin : currency;
43
+ address token1 = isCoinToken0 ? currency : coin;
105
44
  pool = IUniswapV3Factory(v3Factory).createPool(token0, token1, MarketConstants.LP_FEE);
106
45
 
107
46
  // This pool should be new, if it has already been initialized
@@ -0,0 +1,156 @@
1
+ // SPDX-License-Identifier: BUSL-1.1
2
+ pragma solidity ^0.8.24;
3
+
4
+ import {IDopplerErrors} from "../interfaces/IDopplerErrors.sol";
5
+ import {TickMath} from "../utils/uniswap/TickMath.sol";
6
+ import {FullMath} from "../utils/uniswap/FullMath.sol";
7
+ import {SqrtPriceMath} from "../utils/uniswap/SqrtPriceMath.sol";
8
+ import {LiquidityAmounts} from "../utils/uniswap/LiquidityAmounts.sol";
9
+ import {LpPosition} from "../types/LpPosition.sol";
10
+ import {MarketConstants} from "./MarketConstants.sol";
11
+
12
+ /// @author Whetstone Research
13
+ /// @notice Calculates liquidity provisioning with Uniswap v3
14
+ library DopplerMath {
15
+ /// @notice Calculates the distribution of liquidity positions across tick ranges.
16
+ /// @dev For example, with 1000 tokens and 10 bins starting at tick 0:
17
+ /// - Creates positions: [0,10], [1,10], [2,10], ..., [9,10]
18
+ /// - Each position gets an equal share of tokens (100 tokens each)
19
+ /// This creates a linear distribution of liquidity across the tick range
20
+ /// @dev Changed from UniswapV3Initializer:
21
+ /// - Added `LpPosition[] memory newPositions` as an input parameter, removing the internal allocation (`new LpPosition[](totalPositions + 1)`).
22
+ /// - Added `uint256 positionOffset` as an input parameter to specify the starting write index within the `newPositions` array.
23
+ /// - Removed the calculation and accumulation of the `reserves` variable entirely.
24
+ /// - Return value changed from `(LpPosition[] memory, uint256)` (positions, reserves) to `(LpPosition[] memory, uint256)` (positions, totalAssetsSold).
25
+ /// @param tickLower The lower tick of the LP range set
26
+ /// @param tickUpper The upper tick of the LP range set
27
+ /// @param tickSpacing The tick spacing of the LP range set
28
+ /// @param isToken0 Whether the base asset is the token0 of the pair
29
+ /// @param discoverySupply The total supply of the base asset to be sold
30
+ /// @param totalPositions The total number of positions in the LP range set
31
+ /// @param newPositions The array of new positions to be created
32
+ /// @param positionOffset The starting index to update `newPositions`
33
+ /// @return newPositions The array of new positions to be created
34
+ /// @return totalAssetsSold The total assets used in the LP range set
35
+ function calculateLogNormalDistribution(
36
+ int24 tickLower,
37
+ int24 tickUpper,
38
+ int24 tickSpacing,
39
+ bool isToken0,
40
+ uint256 discoverySupply,
41
+ uint16 totalPositions,
42
+ LpPosition[] memory newPositions,
43
+ uint256 positionOffset
44
+ ) internal pure returns (LpPosition[] memory, uint256) {
45
+ int24 farTick = isToken0 ? tickUpper : tickLower;
46
+ int24 closeTick = isToken0 ? tickLower : tickUpper;
47
+
48
+ int24 spread = tickUpper - tickLower;
49
+
50
+ uint160 farSqrtPriceX96 = TickMath.getSqrtPriceAtTick(farTick);
51
+ uint256 amountPerPosition = FullMath.mulDiv(discoverySupply, MarketConstants.WAD, totalPositions * MarketConstants.WAD);
52
+ uint256 totalAssetsSold;
53
+
54
+ for (uint256 i; i < totalPositions; i++) {
55
+ // calculate the ticks position * 1/n to optimize the division
56
+ int24 startingTick = isToken0
57
+ ? closeTick + int24(uint24(FullMath.mulDiv(i, uint256(uint24(spread)), totalPositions)))
58
+ : closeTick - int24(uint24(FullMath.mulDiv(i, uint256(uint24(spread)), totalPositions)));
59
+
60
+ // round the tick to the nearest bin
61
+ startingTick = alignTickToTickSpacing(isToken0, startingTick, tickSpacing);
62
+
63
+ if (startingTick != farTick) {
64
+ uint160 startingSqrtPriceX96 = TickMath.getSqrtPriceAtTick(startingTick);
65
+
66
+ // if discoverySupply is 0, we skip the liquidity calculation as we are burning max liquidity
67
+ // in each position
68
+ uint128 liquidity;
69
+ if (discoverySupply != 0) {
70
+ liquidity = isToken0
71
+ ? LiquidityAmounts.getLiquidityForAmount0(startingSqrtPriceX96, farSqrtPriceX96, amountPerPosition)
72
+ : LiquidityAmounts.getLiquidityForAmount1(farSqrtPriceX96, startingSqrtPriceX96, amountPerPosition);
73
+
74
+ totalAssetsSold += (
75
+ isToken0
76
+ ? SqrtPriceMath.getAmount0Delta(startingSqrtPriceX96, farSqrtPriceX96, liquidity, true)
77
+ : SqrtPriceMath.getAmount1Delta(farSqrtPriceX96, startingSqrtPriceX96, liquidity, true)
78
+ );
79
+ }
80
+
81
+ int24 posFinalTickLower;
82
+ int24 posFinalTickUpper;
83
+ if (farSqrtPriceX96 < startingSqrtPriceX96) {
84
+ posFinalTickLower = farTick;
85
+ posFinalTickUpper = startingTick;
86
+ } else {
87
+ posFinalTickLower = startingTick;
88
+ posFinalTickUpper = farTick;
89
+ }
90
+
91
+ newPositions[positionOffset + i] = LpPosition({tickLower: posFinalTickLower, tickUpper: posFinalTickUpper, liquidity: liquidity});
92
+ }
93
+ }
94
+
95
+ require(totalAssetsSold <= discoverySupply, IDopplerErrors.CannotMintZeroLiquidity());
96
+
97
+ return (newPositions, totalAssetsSold);
98
+ }
99
+
100
+ /// @notice Calculates the final LP position that extends from the far tick to the pool's min/max tick
101
+ /// @dev This position ensures price equivalence between Uniswap v2 and v3 pools beyond the LBP range
102
+ /// @dev Changed from UniswapV3Initializer:
103
+ /// - Removed parameters: `id`, `reserves`
104
+ /// - Liquidity calculation is based *solely* on the provided `tailSupply` within the calculated tail tick range using `LiquidityAmounts.getLiquidityForAmount0` or `getLiquidityForAmount1`.
105
+ function calculateLpTail(
106
+ int24 tickLower,
107
+ int24 tickUpper,
108
+ bool isToken0,
109
+ uint256 tailSupply,
110
+ int24 tickSpacing
111
+ ) internal pure returns (LpPosition memory lpTail) {
112
+ int24 posTickLower = isToken0 ? tickUpper : alignTickToTickSpacing(false, TickMath.MIN_TICK, tickSpacing);
113
+ int24 posTickUpper = isToken0 ? alignTickToTickSpacing(true, TickMath.MAX_TICK, tickSpacing) : tickLower;
114
+
115
+ require(posTickLower < posTickUpper, IDopplerErrors.InvalidTickRangeMisordered(posTickLower, posTickUpper));
116
+
117
+ // Calculate the sqrtPrices for the tail range boundaries
118
+ uint160 sqrtPriceA = TickMath.getSqrtPriceAtTick(posTickLower);
119
+ uint160 sqrtPriceB = TickMath.getSqrtPriceAtTick(posTickUpper);
120
+
121
+ // Calculate liquidity only based on the tail range supply
122
+ uint128 lpTailLiquidity = isToken0
123
+ ? LiquidityAmounts.getLiquidityForAmount0(sqrtPriceA, sqrtPriceB, tailSupply)
124
+ : LiquidityAmounts.getLiquidityForAmount1(sqrtPriceA, sqrtPriceB, tailSupply);
125
+
126
+ lpTail = LpPosition({tickLower: posTickLower, tickUpper: posTickUpper, liquidity: lpTailLiquidity});
127
+ }
128
+
129
+ /// @notice Aligns a tick to the nearest tick spacing
130
+ /// @dev The tickSpacing parameter cannot be zero
131
+ /// @param isToken0 Whether the base asset is the token0 of the pair
132
+ /// @param tick The tick to align
133
+ /// @param tickSpacing The tick spacing of the pair
134
+ /// @return alignedTick The aligned tick
135
+ function alignTickToTickSpacing(bool isToken0, int24 tick, int24 tickSpacing) internal pure returns (int24) {
136
+ if (isToken0) {
137
+ // Round down if isToken0
138
+ if (tick < 0) {
139
+ // If the tick is negative, we round up (negatively) the negative result to round down
140
+ return ((tick - tickSpacing + 1) / tickSpacing) * tickSpacing;
141
+ } else {
142
+ // Else if positive, we simply round down
143
+ return (tick / tickSpacing) * tickSpacing;
144
+ }
145
+ } else {
146
+ // Round up if isToken1
147
+ if (tick < 0) {
148
+ // If the tick is negative, we round down the negative result to round up
149
+ return (tick / tickSpacing) * tickSpacing;
150
+ } else {
151
+ // Else if positive, we simply round up
152
+ return ((tick + tickSpacing - 1) / tickSpacing) * tickSpacing;
153
+ }
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,128 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
5
+ import {ZoraV4CoinHook} from "../hooks/ZoraV4CoinHook.sol";
6
+ import {HookMiner} from "@uniswap/v4-periphery/src/utils/HookMiner.sol";
7
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
8
+ import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";
9
+ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
10
+
11
+ // copy of hook miner from v4 periphery
12
+ library HookMinerWithCreationCodeArgs {
13
+ // mask to slice out the bottom 14 bit of the address
14
+ uint160 constant FLAG_MASK = Hooks.ALL_HOOK_MASK; // 0000 ... 0000 0011 1111 1111 1111
15
+
16
+ // Maximum number of iterations to find a salt, avoid infinite loops or MemoryOOG
17
+ // (arbitrarily set)
18
+ uint256 constant MAX_LOOP = 160_444;
19
+
20
+ function deterministicHookAddress(address deployer, bytes32 salt, bytes memory creationCode) internal view returns (address) {
21
+ return Create2.computeAddress(salt, keccak256(creationCode), deployer);
22
+ }
23
+
24
+ /// @notice Find a salt that produces a hook address with the desired `flags`
25
+ /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address
26
+ /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy)
27
+ /// @param flags The desired flags for the hook address. Example `uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | ...)`
28
+ /// @param creationCodeWithArgs The creation code of a hook contract, with encoded constructor arguments appended. Example: `abi.encodePacked(type(Counter).creationCode, abi.encode(constructorArg1, constructorArg2))`
29
+ /// @return (hookAddress, salt) The hook deploys to `hookAddress` when using `salt` with the syntax: `new Hook{salt: salt}(<constructor arguments>)`
30
+ function find(address deployer, uint160 flags, bytes memory creationCodeWithArgs) internal view returns (address, bytes32) {
31
+ flags = flags & FLAG_MASK; // mask for only the bottom 14 bits
32
+
33
+ address hookAddress;
34
+
35
+ bytes32 creationCodeHash = keccak256(creationCodeWithArgs);
36
+ for (uint256 salt; salt < MAX_LOOP; salt++) {
37
+ hookAddress = deterministicHookAddress(deployer, bytes32(salt), creationCodeWithArgs);
38
+
39
+ // if the hook's bottom 14 bits match the desired flags AND the address does not have bytecode, we found a match
40
+ if (uint160(hookAddress) & FLAG_MASK == flags && hookAddress.code.length == 0) {
41
+ return (hookAddress, bytes32(salt));
42
+ }
43
+ }
44
+ revert("HookMiner: could not find salt");
45
+ }
46
+ }
47
+
48
+ library HooksDeployment {
49
+ error HookNotDeployed();
50
+ error InvalidHookAddress(address expected, address actual);
51
+
52
+ function mineForSaltAndDeployHook(address deployer, bytes memory hookCreationCode) internal returns (IHooks hook, bytes32 salt) {
53
+ uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_INITIALIZE_FLAG) ^ (0x4444 << 144);
54
+
55
+ address hookAddress;
56
+ (hookAddress, salt) = HookMinerWithCreationCodeArgs.find(deployer, flags, hookCreationCode);
57
+
58
+ // check if the hook is already deployed
59
+ (bool isDeployed, address existingHookAddress) = hooksIsDeployed(deployer, hookCreationCode, salt);
60
+ if (isDeployed) {
61
+ return (IHooks(existingHookAddress), salt);
62
+ }
63
+
64
+ hook = IHooks(Create2.deploy(0, salt, hookCreationCode));
65
+
66
+ require(address(hook).code.length > 0, HookNotDeployed());
67
+
68
+ require(hookAddress == address(hook), InvalidHookAddress(hookAddress, address(hook)));
69
+ }
70
+
71
+ /// @notice Checks if ZoraV4CoinHook is already deployed for given parameters
72
+ /// @param deployer The address that will deploy the hook
73
+ /// @param hookCreationCode The creation code of the hook
74
+ /// @param existingSalt The salt of the existing hook
75
+ /// @return isDeployed True if hook is already deployed
76
+ /// @return hookAddress The address where the hook would be/is deployed
77
+ function hooksIsDeployed(
78
+ address deployer,
79
+ bytes memory hookCreationCode,
80
+ bytes32 existingSalt
81
+ ) internal view returns (bool isDeployed, address hookAddress) {
82
+ hookAddress = HookMinerWithCreationCodeArgs.deterministicHookAddress(deployer, existingSalt, hookCreationCode);
83
+
84
+ // Check if code exists at the predicted address
85
+ isDeployed = hookAddress.code.length > 0;
86
+ }
87
+
88
+ function deployOrGetHook(address deployer, bytes memory hookCreationCode, bytes32 existingSalt) internal returns (IHooks hook, bytes32 salt) {
89
+ (bool isDeployed, address existingHookAddress) = hooksIsDeployed(deployer, hookCreationCode, existingSalt);
90
+ if (isDeployed) {
91
+ return (IHooks(existingHookAddress), existingSalt);
92
+ }
93
+
94
+ (hook, salt) = mineForSaltAndDeployHook(deployer, hookCreationCode);
95
+ }
96
+
97
+ function zoraV4CoinHookCreationCode(
98
+ address poolManager,
99
+ address coinVersionLookup,
100
+ address[] memory trustedMessageSenders
101
+ ) internal pure returns (bytes memory) {
102
+ return abi.encodePacked(type(ZoraV4CoinHook).creationCode, abi.encode(poolManager, coinVersionLookup, trustedMessageSenders));
103
+ }
104
+
105
+ function deployZoraV4CoinHookFromContract(
106
+ address poolManager,
107
+ address coinVersionLookup,
108
+ address[] memory trustedMessageSenders
109
+ ) internal returns (IHooks hook) {
110
+ bytes memory hookCreationCode = zoraV4CoinHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders);
111
+ (hook, ) = deployOrGetHook(address(this), hookCreationCode, bytes32(0));
112
+ }
113
+
114
+ /// @notice Deploys or returns existing ZoraV4CoinHook using deterministic deployment. Ensures that if a hooks is already
115
+ /// deployed with an existing salt, it will be returned.
116
+ function deployZoraV4CoinHookFromScript(
117
+ address poolManager,
118
+ address coinVersionLookup,
119
+ address[] memory trustedMessageSenders,
120
+ bytes32 existingSalt
121
+ ) internal returns (IHooks hook, bytes32 salt) {
122
+ address deployer = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
123
+
124
+ bytes memory hookCreationCode = zoraV4CoinHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders);
125
+
126
+ (hook, salt) = deployOrGetHook(deployer, hookCreationCode, existingSalt);
127
+ }
128
+ }
@@ -13,6 +13,10 @@ library MarketConstants {
13
13
  /// @dev 10000 basis points = 1%
14
14
  uint24 internal constant LP_FEE = 10000;
15
15
 
16
+ /// @notice The LP fee
17
+ /// @dev 20000 basis points = 2%
18
+ uint24 internal constant LP_FEE_V4 = 20000;
19
+
16
20
  /// @notice The spacing for 1% pools
17
21
  /// @dev 200 ticks
18
22
  int24 internal constant TICK_SPACING = 200;
@@ -0,0 +1,22 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
5
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
6
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
7
+ import {PoolId} from "@uniswap/v4-core/src/types/PoolId.sol";
8
+
9
+ /// @title PoolStateReader
10
+ /// @notice Library for reading state information from Uniswap V4 pools
11
+ /// @dev Provides utility functions to extract specific pool state data without requiring full slot0 information
12
+ library PoolStateReader {
13
+ /// @notice Retrieves the current square root price from a Uniswap V4 pool
14
+ /// @dev Gets the sqrtPriceX96 value from slot0 of the specified pool, discarding other slot0 data
15
+ /// @param key The PoolKey struct identifying the specific pool to query
16
+ /// @param poolManager The IPoolManager contract instance to query pool state from
17
+ /// @return sqrtPriceX96 The current square root price of the pool in X96 fixed-point format
18
+ function getSqrtPriceX96(PoolKey memory key, IPoolManager poolManager) internal view returns (uint160 sqrtPriceX96) {
19
+ PoolId poolId = key.toId();
20
+ (sqrtPriceX96, , , ) = StateLibrary.getSlot0(poolManager, poolId);
21
+ }
22
+ }