@zoralabs/coins 0.7.1 → 1.0.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.
- package/.turbo/turbo-build.log +106 -84
- package/CHANGELOG.md +68 -0
- package/abis/BadImpl.json +15 -0
- package/abis/BalanceDeltaLibrary.json +15 -0
- package/abis/BaseCoin.json +1350 -0
- package/abis/BaseCoinDeployHook.json +78 -0
- package/abis/BaseHook.json +897 -0
- package/abis/BaseTest.json +60 -91
- package/abis/BeforeSwapDeltaLibrary.json +15 -0
- package/abis/BuySupplyWithSwapRouterHook.json +126 -0
- package/abis/Coin.json +214 -150
- package/abis/CoinConstants.json +65 -0
- package/abis/CoinDopplerMultiCurve.json +38 -0
- package/abis/CoinRewardsV4.json +54 -0
- package/abis/CoinTest.json +66 -111
- package/abis/CoinUniV4Test.json +1053 -0
- package/abis/CoinV4.json +1687 -0
- package/abis/CurrencyLibrary.json +25 -0
- package/abis/DeployHooks.json +9 -0
- 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 +62 -184
- package/abis/ERC20.json +310 -0
- package/abis/FactoryTest.json +98 -98
- package/abis/FakeHookNoInterface.json +21 -0
- package/abis/FeeEstimatorHook.json +1528 -0
- package/abis/Hooks.json +28 -0
- package/abis/HooksDeployment.json +23 -0
- package/abis/HooksTest.json +698 -0
- package/abis/IAllowanceTransfer.json +486 -0
- package/abis/ICoin.json +62 -69
- package/abis/ICoinDeployHook.json +31 -0
- package/abis/ICoinV3.json +879 -0
- package/abis/ICoinV4.json +915 -0
- package/abis/IContractMetadata.json +28 -0
- package/abis/IDeployedCoinVersionLookup.json +21 -0
- package/abis/IEIP712.json +15 -0
- package/abis/IEIP712_v4.json +15 -0
- package/abis/IERC20Minimal.json +172 -0
- package/abis/IERC6909Claims.json +288 -0
- package/abis/IERC721.json +36 -36
- package/abis/IERC721Permit_v4.json +88 -0
- package/abis/IExtsload.json +64 -0
- package/abis/IExttload.json +40 -0
- package/abis/IHasAfterCoinDeploy.json +31 -0
- package/abis/IHasContractName.json +15 -0
- package/abis/IHasPoolKey.json +42 -0
- package/abis/IHasRewardsRecipients.json +54 -0
- package/abis/IHasSwapPath.json +60 -0
- package/abis/IHooks.json +789 -0
- package/abis/IImmutableState.json +15 -0
- package/abis/IMsgSender.json +15 -0
- package/abis/IMulticall_v4.json +21 -0
- package/abis/INotifier.json +187 -0
- package/abis/IPermit2.json +865 -0
- package/abis/IPermit2Forwarder.json +138 -0
- package/abis/IPoolConfigEncoding.json +46 -0
- package/abis/IPoolInitializer_v4.json +53 -0
- package/abis/IPoolManager.json +1286 -0
- package/abis/IPositionManager.json +712 -0
- package/abis/IProtocolFees.json +174 -0
- package/abis/ISignatureTransfer.json +394 -0
- package/abis/ISubscriber.json +89 -0
- package/abis/ISwapPathRouter.json +92 -0
- package/abis/ISwapRouter.json +82 -0
- package/abis/IUniversalRouter.json +61 -0
- package/abis/IUnlockCallback.json +21 -0
- package/abis/IUnorderedNonce.json +44 -0
- package/abis/IV4Quoter.json +310 -0
- package/abis/IV4Router.json +47 -0
- package/abis/IZoraFactory.json +328 -4
- package/abis/IZoraV4CoinHook.json +427 -0
- package/abis/ImmutableState.json +36 -0
- package/abis/LPFeeLibrary.json +65 -0
- package/abis/MockERC20.json +21 -0
- package/abis/MultiOwnableTest.json +60 -91
- package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
- package/abis/PrintUpgradeCommand.json +9 -0
- package/abis/ProxyShim.json +24 -0
- package/abis/Simulate.json +0 -91
- package/abis/StateLibrary.json +80 -0
- package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
- package/abis/TestV4Swap.json +9 -0
- package/abis/{CoinSetup.json → UniV3BuySell.json} +5 -0
- package/abis/UniV3Errors.json +32 -0
- package/abis/UpgradeCoinImpl.json +47 -0
- package/abis/UpgradeFactoryImpl.json +9 -0
- package/abis/UpgradesTest.json +671 -0
- package/abis/Vm.json +1482 -111
- package/abis/VmSafe.json +856 -32
- package/abis/ZoraFactoryImpl.json +450 -1
- package/abis/ZoraV4CoinHook.json +1439 -0
- package/addresses/8453.json +8 -3
- package/addresses/84532.json +8 -3
- package/dist/index.cjs +1998 -184
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1989 -178
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +2852 -688
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/package/wagmiGenerated.ts +1992 -173
- package/package.json +7 -2
- package/remappings.txt +6 -1
- package/script/CoinsDeployerBase.sol +105 -10
- package/script/DeployDevFactory.s.sol +21 -0
- package/script/DeployHooks.s.sol +22 -0
- package/script/PrintUpgradeCommand.s.sol +13 -0
- package/script/Simulate.s.sol +4 -12
- package/script/TestBackingCoinSwap.s.sol +146 -0
- package/script/TestV4Swap.s.sol +136 -0
- package/script/UpgradeCoinImpl.sol +2 -2
- package/script/UpgradeFactoryImpl.s.sol +23 -0
- package/src/BaseCoin.sol +176 -0
- package/src/Coin.sol +93 -515
- package/src/CoinV4.sol +121 -0
- package/src/ZoraFactoryImpl.sol +257 -57
- package/src/hooks/ZoraV4CoinHook.sol +195 -0
- package/src/hooks/deployment/BaseCoinDeployHook.sol +62 -0
- package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +80 -0
- package/src/interfaces/ICoin.sol +35 -39
- package/src/interfaces/ICoinDeployHook.sol +8 -0
- 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/ISwapRouter.sol +1 -35
- package/src/interfaces/IZoraFactory.sol +97 -7
- package/src/interfaces/IZoraV4CoinHook.sol +116 -0
- package/src/libs/CoinCommon.sol +15 -0
- package/src/libs/CoinConfigurationVersions.sol +116 -1
- package/src/{utils → libs}/CoinConstants.sol +11 -6
- 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 +180 -0
- package/src/libs/CoinSetup.sol +40 -20
- package/src/libs/CoinSetupV3.sol +50 -0
- package/src/libs/DopplerMath.sol +156 -0
- package/src/libs/HooksDeployment.sol +84 -0
- package/src/libs/MarketConstants.sol +4 -0
- package/src/libs/PoolStateReader.sol +22 -0
- package/src/libs/UniV3BuySell.sol +231 -0
- package/src/libs/UniV3Errors.sol +11 -0
- package/src/libs/UniV4SwapHelper.sol +65 -0
- package/src/libs/UniV4SwapToCurrency.sol +109 -0
- package/src/libs/V4Liquidity.sol +129 -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 +94 -101
- package/test/CoinDopplerUniV3.t.sol +35 -184
- package/test/CoinUniV4.t.sol +752 -0
- package/test/DeploymentHooks.t.sol +270 -0
- package/test/Factory.t.sol +84 -50
- package/test/MultiOwnable.t.sol +6 -3
- package/test/Upgrades.t.sol +68 -0
- package/test/mocks/MockERC20.sol +12 -0
- package/test/utils/BaseTest.sol +124 -59
- 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 +10 -9
- package/src/libs/CoinLegacy.sol +0 -48
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {PoolConfiguration} from "../interfaces/ICoin.sol";
|
|
5
|
+
import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
|
|
6
|
+
import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
|
|
7
|
+
import {LpPosition} from "../types/LpPosition.sol";
|
|
8
|
+
import {IUniswapV3Factory} from "../interfaces/IUniswapV3Factory.sol";
|
|
9
|
+
import {MarketConstants} from "./MarketConstants.sol";
|
|
10
|
+
import {IUniswapV3Pool} from "../interfaces/IUniswapV3Pool.sol";
|
|
11
|
+
import {ICoin} from "../interfaces/ICoin.sol";
|
|
12
|
+
import {CoinCommon} from "./CoinCommon.sol";
|
|
13
|
+
struct UniV3Config {
|
|
14
|
+
address weth;
|
|
15
|
+
address v3Factory;
|
|
16
|
+
address airlock;
|
|
17
|
+
address swapRouter;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
struct CoinV3Config {
|
|
21
|
+
address currency;
|
|
22
|
+
PoolConfiguration poolConfiguration;
|
|
23
|
+
address poolAddress;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
library CoinSetupV3 {
|
|
27
|
+
/// @dev Deploys the Uniswap V3 pool and mints initial liquidity based on the pool configuration
|
|
28
|
+
function deployLiquidity(LpPosition[] memory positions, address poolAddress) internal {
|
|
29
|
+
// Calculate and mint positions
|
|
30
|
+
_mintPositions(positions, poolAddress);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// @dev Mints the calculated liquidity positions into the Uniswap V3 pool
|
|
34
|
+
function _mintPositions(LpPosition[] memory lbpPositions, address poolAddress) internal {
|
|
35
|
+
for (uint256 i; i < lbpPositions.length; i++) {
|
|
36
|
+
IUniswapV3Pool(poolAddress).mint(address(this), lbpPositions[i].tickLower, lbpPositions[i].tickUpper, lbpPositions[i].liquidity, "");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// @dev Creates the Uniswap V3 pool for the coin/currency pair
|
|
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;
|
|
44
|
+
pool = IUniswapV3Factory(v3Factory).createPool(token0, token1, MarketConstants.LP_FEE);
|
|
45
|
+
|
|
46
|
+
// This pool should be new, if it has already been initialized
|
|
47
|
+
// then we will fail the creation step prompting the user to try again.
|
|
48
|
+
IUniswapV3Pool(pool).initialize(sqrtPriceX96);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -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,84 @@
|
|
|
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
|
+
/// @notice Find a salt that produces a hook address with the desired `flags`
|
|
21
|
+
/// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address
|
|
22
|
+
/// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy)
|
|
23
|
+
/// @param flags The desired flags for the hook address. Example `uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | ...)`
|
|
24
|
+
/// @param creationCodeWithArgs The creation code of a hook contract, with encoded constructor arguments appended. Example: `abi.encodePacked(type(Counter).creationCode, abi.encode(constructorArg1, constructorArg2))`
|
|
25
|
+
/// @return (hookAddress, salt) The hook deploys to `hookAddress` when using `salt` with the syntax: `new Hook{salt: salt}(<constructor arguments>)`
|
|
26
|
+
function find(address deployer, uint160 flags, bytes memory creationCodeWithArgs) internal view returns (address, bytes32) {
|
|
27
|
+
flags = flags & FLAG_MASK; // mask for only the bottom 14 bits
|
|
28
|
+
|
|
29
|
+
address hookAddress;
|
|
30
|
+
|
|
31
|
+
bytes32 creationCodeHash = keccak256(creationCodeWithArgs);
|
|
32
|
+
for (uint256 salt; salt < MAX_LOOP; salt++) {
|
|
33
|
+
hookAddress = Create2.computeAddress(bytes32(salt), creationCodeHash, deployer);
|
|
34
|
+
|
|
35
|
+
// if the hook's bottom 14 bits match the desired flags AND the address does not have bytecode, we found a match
|
|
36
|
+
if (uint160(hookAddress) & FLAG_MASK == flags && hookAddress.code.length == 0) {
|
|
37
|
+
return (hookAddress, bytes32(salt));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
revert("HookMiner: could not find salt");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
library HooksDeployment {
|
|
45
|
+
error HookNotDeployed();
|
|
46
|
+
error InvalidHookAddress(address expected, address actual);
|
|
47
|
+
|
|
48
|
+
function deployZoraV4CoinHook(address deployer, bytes memory hookCreationCode) internal returns (IHooks hook) {
|
|
49
|
+
uint160 flags = uint160(Hooks.AFTER_SWAP_FLAG | Hooks.AFTER_INITIALIZE_FLAG) ^ (0x4444 << 144);
|
|
50
|
+
|
|
51
|
+
(address hookAddress, bytes32 salt) = HookMinerWithCreationCodeArgs.find(deployer, flags, hookCreationCode);
|
|
52
|
+
|
|
53
|
+
hook = IHooks(Create2.deploy(0, salt, hookCreationCode));
|
|
54
|
+
|
|
55
|
+
require(address(hook).code.length > 0, HookNotDeployed());
|
|
56
|
+
|
|
57
|
+
require(hookAddress == address(hook), InvalidHookAddress(hookAddress, address(hook)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function zoraV4CoinHookCreationCode(
|
|
61
|
+
address poolManager,
|
|
62
|
+
address coinVersionLookup,
|
|
63
|
+
address[] memory trustedMessageSenders
|
|
64
|
+
) internal pure returns (bytes memory) {
|
|
65
|
+
return abi.encodePacked(type(ZoraV4CoinHook).creationCode, abi.encode(poolManager, coinVersionLookup, trustedMessageSenders));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function deployZoraV4CoinHookFromContract(
|
|
69
|
+
address poolManager,
|
|
70
|
+
address coinVersionLookup,
|
|
71
|
+
address[] memory trustedMessageSenders
|
|
72
|
+
) internal returns (IHooks hook) {
|
|
73
|
+
return deployZoraV4CoinHook(address(this), zoraV4CoinHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function deployZoraV4CoinHookFromScript(
|
|
77
|
+
address poolManager,
|
|
78
|
+
address coinVersionLookup,
|
|
79
|
+
address[] memory trustedMessageSenders
|
|
80
|
+
) internal returns (IHooks hook) {
|
|
81
|
+
address deployer = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
|
|
82
|
+
return deployZoraV4CoinHook(deployer, zoraV4CoinHookCreationCode(poolManager, coinVersionLookup, trustedMessageSenders));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
5
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
6
|
+
import {ISwapRouter} from "../interfaces/ISwapRouter.sol";
|
|
7
|
+
import {IWETH} from "../interfaces/IWETH.sol";
|
|
8
|
+
import {MarketConstants} from "./MarketConstants.sol";
|
|
9
|
+
import {CoinConstants} from "./CoinConstants.sol";
|
|
10
|
+
import {ICoin} from "../interfaces/ICoin.sol";
|
|
11
|
+
import {IProtocolRewards} from "../interfaces/IProtocolRewards.sol";
|
|
12
|
+
import {LpPosition} from "../types/LpPosition.sol";
|
|
13
|
+
import {PoolConfiguration} from "../interfaces/ICoin.sol";
|
|
14
|
+
import {IUniswapV3Pool} from "../interfaces/IUniswapV3Pool.sol";
|
|
15
|
+
import {IAirlock} from "../interfaces/IAirlock.sol";
|
|
16
|
+
import {CoinV3Config} from "./CoinSetupV3.sol";
|
|
17
|
+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
|
|
18
|
+
import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
|
|
19
|
+
import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
|
|
20
|
+
import {CoinRewards, CoinConfig} from "./CoinRewards.sol";
|
|
21
|
+
struct SellResult {
|
|
22
|
+
uint256 payoutSize;
|
|
23
|
+
uint256 tradeReward;
|
|
24
|
+
uint256 trueOrderSize;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
library UniV3BuySell {
|
|
28
|
+
using SafeERC20 for IERC20;
|
|
29
|
+
|
|
30
|
+
error AddressZero();
|
|
31
|
+
error InvalidPoolVersion();
|
|
32
|
+
|
|
33
|
+
function handleBuy(
|
|
34
|
+
address recipient,
|
|
35
|
+
uint256 orderSize,
|
|
36
|
+
uint256 minAmountOut,
|
|
37
|
+
uint160 sqrtPriceLimitX96,
|
|
38
|
+
address tradeReferrer,
|
|
39
|
+
CoinConfig memory coinConfig,
|
|
40
|
+
address currency,
|
|
41
|
+
ISwapRouter swapRouter,
|
|
42
|
+
IWETH weth
|
|
43
|
+
) internal returns (uint256 amountOut, uint256 tradeReward, uint256 trueOrderSize) {
|
|
44
|
+
if (recipient == address(0)) {
|
|
45
|
+
revert AddressZero();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Calculate the trade reward
|
|
49
|
+
tradeReward = _calculateReward(orderSize, CoinConstants.TOTAL_FEE_BPS);
|
|
50
|
+
|
|
51
|
+
// Calculate the remaining size
|
|
52
|
+
trueOrderSize = orderSize - tradeReward;
|
|
53
|
+
|
|
54
|
+
// Handle incoming currency
|
|
55
|
+
_handleIncomingCurrency(orderSize, trueOrderSize, currency, swapRouter, weth);
|
|
56
|
+
|
|
57
|
+
// Set up the swap parameters
|
|
58
|
+
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
59
|
+
tokenIn: currency,
|
|
60
|
+
tokenOut: address(this),
|
|
61
|
+
fee: MarketConstants.LP_FEE,
|
|
62
|
+
recipient: recipient,
|
|
63
|
+
amountIn: trueOrderSize,
|
|
64
|
+
amountOutMinimum: minAmountOut,
|
|
65
|
+
sqrtPriceLimitX96: sqrtPriceLimitX96
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Execute the swap
|
|
69
|
+
amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
|
|
70
|
+
|
|
71
|
+
CoinRewards.handleTradeRewards(tradeReward, tradeReferrer, coinConfig, currency, weth);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _executeSwap(
|
|
75
|
+
uint256 orderSize,
|
|
76
|
+
uint256 minAmountOut,
|
|
77
|
+
uint160 sqrtPriceLimitX96,
|
|
78
|
+
address currency,
|
|
79
|
+
ISwapRouter swapRouter
|
|
80
|
+
) internal returns (uint256 amountOut) {
|
|
81
|
+
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
|
|
82
|
+
tokenIn: address(this),
|
|
83
|
+
tokenOut: currency,
|
|
84
|
+
fee: MarketConstants.LP_FEE,
|
|
85
|
+
recipient: address(this),
|
|
86
|
+
amountIn: orderSize,
|
|
87
|
+
amountOutMinimum: minAmountOut,
|
|
88
|
+
sqrtPriceLimitX96: sqrtPriceLimitX96
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
amountOut = swapRouter.exactInputSingle(params);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function _handleRefund(uint256 beforeCoinBalance, uint256 orderSize, address recipient) internal returns (uint256 trueOrderSize) {
|
|
95
|
+
uint256 afterCoinBalance = IERC20(address(this)).balanceOf(address(this));
|
|
96
|
+
trueOrderSize = orderSize;
|
|
97
|
+
|
|
98
|
+
if (afterCoinBalance > beforeCoinBalance) {
|
|
99
|
+
uint256 coinRefund = afterCoinBalance - beforeCoinBalance;
|
|
100
|
+
trueOrderSize -= coinRefund;
|
|
101
|
+
IERC20(address(this)).safeTransfer(recipient, coinRefund);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function _handlePayoutAndRewards(
|
|
106
|
+
uint256 amountOut,
|
|
107
|
+
address recipient,
|
|
108
|
+
address tradeReferrer,
|
|
109
|
+
CoinConfig memory coinConfig,
|
|
110
|
+
address currency,
|
|
111
|
+
IWETH weth
|
|
112
|
+
) internal returns (uint256 payoutSize, uint256 tradeReward) {
|
|
113
|
+
if (currency == address(weth)) {
|
|
114
|
+
weth.withdraw(amountOut);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
tradeReward = _calculateReward(amountOut, CoinConstants.TOTAL_FEE_BPS);
|
|
118
|
+
payoutSize = amountOut - tradeReward;
|
|
119
|
+
|
|
120
|
+
_handlePayout(payoutSize, recipient, currency, weth);
|
|
121
|
+
|
|
122
|
+
CoinRewards.handleTradeRewards(tradeReward, tradeReferrer, coinConfig, currency, weth);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function handleSell(
|
|
126
|
+
address recipient,
|
|
127
|
+
uint256 beforeCoinBalance,
|
|
128
|
+
uint256 orderSize,
|
|
129
|
+
uint256 minAmountOut,
|
|
130
|
+
uint160 sqrtPriceLimitX96,
|
|
131
|
+
address tradeReferrer,
|
|
132
|
+
CoinConfig memory coinConfig,
|
|
133
|
+
address currency,
|
|
134
|
+
ISwapRouter swapRouter,
|
|
135
|
+
IWETH weth
|
|
136
|
+
) internal returns (SellResult memory result) {
|
|
137
|
+
if (recipient == address(0)) {
|
|
138
|
+
revert AddressZero();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
uint256 amountOut = _executeSwap(orderSize, minAmountOut, sqrtPriceLimitX96, currency, swapRouter);
|
|
142
|
+
result.trueOrderSize = _handleRefund(beforeCoinBalance, orderSize, recipient);
|
|
143
|
+
(result.payoutSize, result.tradeReward) = _handlePayoutAndRewards(amountOut, recipient, tradeReferrer, coinConfig, currency, weth);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// @dev Handles incoming currency transfers for buy orders; if WETH is the currency the caller has the option to send native-ETH
|
|
147
|
+
/// @param orderSize The total size of the order in the currency
|
|
148
|
+
/// @param trueOrderSize The actual amount being used for the swap after fees
|
|
149
|
+
function _handleIncomingCurrency(uint256 orderSize, uint256 trueOrderSize, address currency, ISwapRouter swapRouter, IWETH weth) internal {
|
|
150
|
+
if (currency == address(weth) && msg.value > 0) {
|
|
151
|
+
if (msg.value != orderSize) {
|
|
152
|
+
revert ICoin.EthAmountMismatch();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (msg.value < CoinConstants.MIN_ORDER_SIZE) {
|
|
156
|
+
revert ICoin.EthAmountTooSmall();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
IWETH(weth).deposit{value: trueOrderSize}();
|
|
160
|
+
IWETH(weth).approve(address(swapRouter), trueOrderSize);
|
|
161
|
+
} else {
|
|
162
|
+
// Ensure ETH is not sent with a non-ETH pair
|
|
163
|
+
if (msg.value != 0) {
|
|
164
|
+
revert ICoin.EthTransferInvalid();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
uint256 beforeBalance = IERC20(currency).balanceOf(address(this));
|
|
168
|
+
IERC20(currency).safeTransferFrom(msg.sender, address(this), orderSize);
|
|
169
|
+
uint256 afterBalance = IERC20(currency).balanceOf(address(this));
|
|
170
|
+
|
|
171
|
+
if ((afterBalance - beforeBalance) != orderSize) {
|
|
172
|
+
revert ICoin.ERC20TransferAmountMismatch();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
IERC20(currency).approve(address(swapRouter), trueOrderSize);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/// @dev Handles sending ETH and ERC20 payouts and refunds to recipients
|
|
180
|
+
/// @param orderPayout The amount of currency to pay out
|
|
181
|
+
/// @param recipient The address to receive the payout
|
|
182
|
+
function _handlePayout(uint256 orderPayout, address recipient, address currency, IWETH weth) internal {
|
|
183
|
+
if (currency == address(weth)) {
|
|
184
|
+
Address.sendValue(payable(recipient), orderPayout);
|
|
185
|
+
} else {
|
|
186
|
+
IERC20(currency).safeTransfer(recipient, orderPayout);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function _collectFees(LpPosition[] storage positions, address poolAddress) internal returns (uint256 totalAmountToken0, uint256 totalAmountToken1) {
|
|
191
|
+
for (uint256 i; i < positions.length; i++) {
|
|
192
|
+
// Must burn to update the collect mapping on the pool
|
|
193
|
+
IUniswapV3Pool(poolAddress).burn(positions[i].tickLower, positions[i].tickUpper, 0);
|
|
194
|
+
|
|
195
|
+
(uint256 amount0, uint256 amount1) = IUniswapV3Pool(poolAddress).collect(
|
|
196
|
+
address(this),
|
|
197
|
+
positions[i].tickLower,
|
|
198
|
+
positions[i].tickUpper,
|
|
199
|
+
type(uint128).max,
|
|
200
|
+
type(uint128).max
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
totalAmountToken0 += amount0;
|
|
204
|
+
totalAmountToken1 += amount1;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/// @dev Collects and distributes accrued fees from all LP positions
|
|
209
|
+
function handleMarketRewards(
|
|
210
|
+
CoinConfig memory coinConfig,
|
|
211
|
+
address currency,
|
|
212
|
+
address poolAddress,
|
|
213
|
+
LpPosition[] storage positions,
|
|
214
|
+
IWETH weth,
|
|
215
|
+
address doppler
|
|
216
|
+
) internal returns (ICoin.MarketRewards memory rewards) {
|
|
217
|
+
address coin = address(this);
|
|
218
|
+
(uint256 totalAmountToken0, uint256 totalAmountToken1) = _collectFees(positions, poolAddress);
|
|
219
|
+
|
|
220
|
+
address token0 = currency < coin ? currency : coin;
|
|
221
|
+
address token1 = currency < coin ? coin : currency;
|
|
222
|
+
|
|
223
|
+
rewards = CoinRewards.transferBothRewards(token0, totalAmountToken0, token1, totalAmountToken1, coin, coinConfig, currency, weth, doppler);
|
|
224
|
+
|
|
225
|
+
emit ICoin.CoinMarketRewards(coinConfig.payoutRecipient, coinConfig.platformReferrer, coinConfig.protocolRewardRecipient, currency, rewards);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function _calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
|
|
229
|
+
return CoinRewards.calculateReward(amount, bps);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
library UniV3Errors {
|
|
5
|
+
error InvalidPoolAddress();
|
|
6
|
+
error InvalidCurrency();
|
|
7
|
+
error InvalidWeth();
|
|
8
|
+
error InvalidTickLower();
|
|
9
|
+
error InvalidTickUpper();
|
|
10
|
+
error InvalidUniswapV3Factory();
|
|
11
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.23;
|
|
3
|
+
|
|
4
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
5
|
+
import {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";
|
|
6
|
+
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
|
|
7
|
+
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
|
|
8
|
+
import {CoinCommon} from "./CoinCommon.sol";
|
|
9
|
+
import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
|
|
10
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
11
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
12
|
+
import {ISwapPathRouter} from "../interfaces/ISwapPathRouter.sol";
|
|
13
|
+
import {IHasPoolKey} from "../interfaces/ICoinV4.sol";
|
|
14
|
+
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
15
|
+
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
16
|
+
import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
|
|
17
|
+
|
|
18
|
+
library UniV4SwapHelper {
|
|
19
|
+
function buildExactInputSingleSwapCommand(
|
|
20
|
+
address currencyIn,
|
|
21
|
+
uint128 amountIn,
|
|
22
|
+
address currencyOut,
|
|
23
|
+
uint128 minAmountOut,
|
|
24
|
+
PoolKey memory key,
|
|
25
|
+
bytes memory hookData
|
|
26
|
+
) internal pure returns (bytes memory commands, bytes[] memory inputs) {
|
|
27
|
+
bool zeroForOne = Currency.unwrap(key.currency0) == currencyIn;
|
|
28
|
+
|
|
29
|
+
// now buy some coin for usdc
|
|
30
|
+
commands = abi.encodePacked(uint8(Commands.V4_SWAP));
|
|
31
|
+
|
|
32
|
+
bytes memory actions = abi.encodePacked(uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), uint8(Actions.TAKE_ALL));
|
|
33
|
+
|
|
34
|
+
bytes[] memory params = new bytes[](3);
|
|
35
|
+
|
|
36
|
+
// First parameter: swap configuration
|
|
37
|
+
params[0] = abi.encode(
|
|
38
|
+
IV4Router.ExactInputSingleParams({
|
|
39
|
+
poolKey: key,
|
|
40
|
+
zeroForOne: zeroForOne,
|
|
41
|
+
amountIn: amountIn, // amount of tokens we're swapping
|
|
42
|
+
amountOutMinimum: minAmountOut, // minimum amount we expect to receive
|
|
43
|
+
hookData: hookData
|
|
44
|
+
})
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Second parameter: specify input tokens for the swap
|
|
48
|
+
// encode SETTLE_ALL parameters
|
|
49
|
+
params[1] = abi.encode(currencyIn, amountIn);
|
|
50
|
+
|
|
51
|
+
// Third parameter: specify output tokens from the swap
|
|
52
|
+
// encode TAKE_ALL parameters
|
|
53
|
+
params[2] = abi.encode(currencyOut, minAmountOut);
|
|
54
|
+
|
|
55
|
+
inputs = new bytes[](1);
|
|
56
|
+
|
|
57
|
+
// Combine actions and params into inputs
|
|
58
|
+
inputs[0] = abi.encode(actions, params);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function approveTokenWithPermit2(IPermit2 permit2, address router, address token, uint160 amount, uint48 expiration) internal {
|
|
62
|
+
IERC20(token).approve(address(permit2), type(uint256).max);
|
|
63
|
+
permit2.approve(token, router, amount, expiration);
|
|
64
|
+
}
|
|
65
|
+
}
|