@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.
- package/.turbo/turbo-build.log +179 -114
- package/CHANGELOG.md +46 -0
- package/abis/BaseCoin.json +26 -118
- package/abis/BaseTest.json +47 -0
- package/abis/BuySupplyWithSwapRouterHook.json +40 -0
- package/abis/Coin.json +171 -63
- package/abis/CoinDopplerMultiCurve.json +38 -0
- package/abis/CoinRewardsV4.json +54 -0
- package/abis/CoinTest.json +53 -20
- package/abis/CoinUniV4Test.json +1091 -0
- package/abis/CoinV4.json +234 -211
- package/abis/DeployScript.json +47 -0
- package/abis/DeployedCoinVersionLookup.json +21 -0
- package/abis/DeployedCoinVersionLookupTest.json +716 -0
- package/abis/DifferentNamespaceVersionLookup.json +39 -0
- package/abis/DopplerUniswapV3Test.json +49 -93
- package/abis/ERC20.json +310 -0
- package/abis/FactoryTest.json +85 -7
- package/abis/FeeEstimatorHook.json +1515 -0
- package/abis/HooksDeployment.json +23 -0
- package/abis/HooksTest.json +60 -0
- package/abis/ICoin.json +40 -71
- package/abis/ICoinV3.json +879 -0
- package/abis/ICoinV4.json +915 -0
- package/abis/IDeployedCoinVersionLookup.json +21 -0
- package/abis/IERC721.json +36 -36
- package/abis/IHasPoolKey.json +42 -0
- package/abis/IHasRewardsRecipients.json +54 -0
- package/abis/IHasSwapPath.json +60 -0
- package/abis/IMsgSender.json +15 -0
- package/abis/IPoolConfigEncoding.json +46 -0
- package/abis/ISwapPathRouter.json +92 -0
- package/abis/IUniversalRouter.json +61 -0
- package/abis/IUnlockCallback.json +21 -0
- package/abis/IV4Quoter.json +310 -0
- package/abis/IZoraFactory.json +210 -11
- package/abis/IZoraV4CoinHook.json +348 -4
- package/abis/MockERC20.json +21 -0
- package/abis/MultiOwnableTest.json +47 -0
- package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
- package/abis/PrintUpgradeCommand.json +9 -0
- package/abis/ProxyShim.json +24 -0
- package/abis/StateLibrary.json +80 -0
- package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
- package/abis/TestV4Swap.json +9 -0
- package/abis/UpgradeCoinImpl.json +47 -0
- package/abis/UpgradesTest.json +81 -0
- package/abis/Vm.json +1482 -111
- package/abis/VmSafe.json +856 -32
- package/abis/ZoraFactoryImpl.json +339 -1
- package/abis/ZoraV4CoinHook.json +442 -5
- package/addresses/8453.json +7 -4
- package/addresses/84532.json +8 -5
- package/addresses/dev/8453.json +10 -0
- package/dist/index.cjs +1932 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1928 -167
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +2606 -160
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/foundry.toml +1 -0
- package/package/wagmiGenerated.ts +1941 -164
- package/package.json +8 -3
- package/remappings.txt +6 -1
- package/script/Deploy.s.sol +1 -1
- package/script/DeployDevFactory.s.sol +21 -0
- package/script/DeployHooks.s.sol +1 -1
- package/script/PrintUpgradeCommand.s.sol +13 -0
- package/script/Simulate.s.sol +1 -10
- package/script/TestBackingCoinSwap.s.sol +147 -0
- package/script/TestV4Swap.s.sol +136 -0
- package/script/UpgradeCoinImpl.sol +2 -2
- package/script/UpgradeFactoryImpl.s.sol +2 -2
- package/src/BaseCoin.sol +190 -0
- package/src/Coin.sol +87 -202
- package/src/CoinV4.sol +121 -0
- package/src/ZoraFactoryImpl.sol +208 -36
- package/{script → src/deployment}/CoinsDeployerBase.sol +111 -17
- package/src/hooks/ZoraV4CoinHook.sol +212 -0
- package/src/hooks/{BaseCoinDeployHook.sol → deployment/BaseCoinDeployHook.sol} +3 -3
- package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +140 -0
- package/src/interfaces/ICoin.sol +31 -39
- package/src/interfaces/ICoinV3.sol +71 -0
- package/src/interfaces/ICoinV4.sol +69 -0
- package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
- package/src/interfaces/IMsgSender.sol +9 -0
- package/src/interfaces/IPoolConfigEncoding.sol +14 -0
- package/src/interfaces/ISwapPathRouter.sol +14 -0
- package/src/interfaces/IZoraFactory.sol +67 -28
- package/src/interfaces/IZoraV4CoinHook.sol +116 -0
- package/src/libs/CoinCommon.sol +15 -0
- package/src/libs/CoinConfigurationVersions.sol +116 -1
- package/src/libs/CoinConstants.sol +5 -0
- package/src/libs/CoinDopplerMultiCurve.sol +134 -0
- package/src/libs/CoinDopplerUniV3.sol +19 -171
- package/src/libs/CoinRewards.sol +195 -0
- package/src/libs/CoinRewardsV4.sol +179 -0
- package/src/libs/CoinSetup.sol +57 -0
- package/src/libs/CoinSetupV3.sol +6 -67
- package/src/libs/DopplerMath.sol +156 -0
- package/src/libs/HooksDeployment.sol +128 -0
- package/src/libs/MarketConstants.sol +4 -0
- package/src/libs/PoolStateReader.sol +22 -0
- package/src/libs/UniV3BuySell.sol +74 -292
- package/src/libs/UniV4SwapHelper.sol +65 -0
- package/src/libs/UniV4SwapToCurrency.sol +109 -0
- package/src/libs/V4Liquidity.sol +122 -0
- package/src/types/PoolConfiguration.sol +15 -0
- package/src/utils/DeployedCoinVersionLookup.sol +52 -0
- package/src/version/ContractVersionBase.sol +1 -1
- package/test/Coin.t.sol +78 -88
- package/test/CoinDopplerUniV3.t.sol +32 -171
- package/test/CoinUniV4.t.sol +777 -0
- package/test/{Hooks.t.sol → DeploymentHooks.t.sol} +53 -16
- package/test/Factory.t.sol +80 -47
- package/test/MultiOwnable.t.sol +6 -3
- package/test/Upgrades.t.sol +97 -5
- package/test/mocks/MockERC20.sol +12 -0
- package/test/utils/BaseTest.sol +162 -57
- package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
- package/test/utils/FeeEstimatorHook.sol +84 -0
- package/test/utils/ProxyShim.sol +17 -0
- package/wagmi.config.ts +4 -0
- package/.env +0 -1
- package/.turbo/turbo-update-contract-version.log +0 -22
- package/abis/CoinSetupV3.json +0 -7
- package/abis/HookDeployer.json +0 -68
- package/abis/IHookDeployer.json +0 -42
- package/src/hooks/BuySupplyWithSwapRouterHook.sol +0 -78
- package/src/libs/CoinLegacy.sol +0 -48
- package/src/libs/CoinLegacyMarket.sol +0 -182
package/src/libs/CoinSetupV3.sol
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
+
}
|