pragma solidity 0.8.26; //import "@forge-std/console2.sol"; import "../lib_uniswap/v3-core/contracts/libraries/TickMath.sol"; import {IUniswapV3Pool} from "../lib_uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import {MockERC20} from "../src/more/MockERC20.sol"; import "../lib_uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol"; import "../lib_uniswap/v3-periphery/contracts/interfaces/INonfungiblePositionManager.sol"; import "../src/core/Util.sol"; import "../src/core/UniswapV3.sol"; library MockUtil { // 200 is the largest tick spacing. We move our edges inward to prevent violating the extremum when rounding int24 constant public FAR_LOWER_TICK = TickMath.MIN_TICK + 200; int24 constant public FAR_UPPER_TICK = TickMath.MAX_TICK - 200; function swap(IUniswapV3Pool pool, MockERC20 inToken, MockERC20 outToken, uint256 amountIn) internal returns (uint256 amountOut) { return swap(UniswapV3Arbitrum.swapRouter, pool, inToken, outToken, amountIn); } function swap(ISwapRouter swapper, IUniswapV3Pool pool, MockERC20 inToken, MockERC20 outToken, uint256 amountIn) internal returns (uint256 amountOut) { uint160 limit = address(inToken) == pool.token0() ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1; return swap(swapper, pool, inToken, outToken, amountIn, limit); } function swap(IUniswapV3Pool pool, MockERC20 inToken, MockERC20 outToken, uint256 amountIn, uint160 sqrtPriceLimitX96) internal returns (uint256 amountOut) { return swap(UniswapV3Arbitrum.swapRouter, pool, inToken, outToken, amountIn, sqrtPriceLimitX96); } function swap(ISwapRouter swapper, IUniswapV3Pool pool, MockERC20 inToken, MockERC20 outToken, uint256 amountIn, uint160 sqrtPriceLimitX96) internal returns (uint256 amountOut) { inToken.approve(address(swapper), amountIn); // struct ExactInputSingleParams { // address tokenIn; // address tokenOut; // uint24 fee; // address recipient; // uint256 deadline; // uint256 amountIn; // uint256 amountOutMinimum; // uint160 sqrtPriceLimitX96; // } ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams( address(inToken), address(outToken), pool.fee(), msg.sender, block.timestamp, amountIn, 0, sqrtPriceLimitX96 ); return swapper.exactInputSingle(params); } function price(IUniswapV3Pool pool) internal view returns (uint160 sqrtPriceX96) { (sqrtPriceX96,,,,,,) = pool.slot0(); } function swapToPrice(IUniswapV3Pool pool, uint160 sqrtPriceLimitX96) internal { return swapToPrice(UniswapV3Arbitrum.swapRouter, pool, sqrtPriceLimitX96); } function swapToPrice(ISwapRouter swapper, IUniswapV3Pool pool, uint160 sqrtPriceLimitX96) internal { // console2.log('swapToPrice'); // console2.log(sqrtPriceLimitX96); uint160 curPrice = price(pool); // console2.log(curPrice); if( curPrice == sqrtPriceLimitX96 ) { // console2.log('no swap needed'); return; } MockERC20 token0 = MockERC20(pool.token0()); MockERC20 token1 = MockERC20(pool.token1()); MockERC20 inToken = curPrice > sqrtPriceLimitX96 ? MockERC20(token0) : MockERC20(token1); MockERC20 outToken = curPrice < sqrtPriceLimitX96 ? MockERC20(token0) : MockERC20(token1); // instead of calculating how much we need, we just mint an absurd amount uint256 aLot = 2**100; inToken.mint(address(this), aLot); swap(swapper, pool, inToken, outToken, aLot, sqrtPriceLimitX96); } function stakeWide(IUniswapV3Pool pool, uint256 amount) internal returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { return stake(UniswapV3Arbitrum.nfpm, pool, amount/2, amount/2, FAR_LOWER_TICK, FAR_UPPER_TICK); } function stakeWide(INonfungiblePositionManager nfpm, IUniswapV3Pool pool, uint256 amount0, uint256 amount1) internal returns (uint256 tokenId, uint128 liquidity, uint256 stakedAmount0, uint256 stakedAmount1) { return stake(nfpm, pool, amount0, amount1, FAR_LOWER_TICK, FAR_UPPER_TICK); } function stake(IUniswapV3Pool pool, uint256 amount, int24 width) internal returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { return stake(UniswapV3Arbitrum.nfpm, pool, amount, width); } function stake(INonfungiblePositionManager nfpm, IUniswapV3Pool pool, uint256 amount, int24 width) internal returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { require(width>0); (, int24 tick, , , , ,) = pool.slot0(); return stake(nfpm, pool, amount/2, amount/2, tick-width, tick+width); } function stake(IUniswapV3Pool pool, uint256 token0Amount, uint256 token1Amount, int24 lower, int24 upper) internal returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { return stake(UniswapV3Arbitrum.nfpm, pool, token0Amount, token1Amount, lower, upper); } function stake(INonfungiblePositionManager nfpm, IUniswapV3Pool pool, uint256 token0Amount, uint256 token1Amount, int24 lower, int24 upper) internal returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { return _stake(nfpm, pool, token0Amount, token1Amount, lower, upper); } function _stake(INonfungiblePositionManager nfpm, IUniswapV3Pool pool, uint256 token0Amount, uint256 token1Amount, int24 lower, int24 upper) private returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1) { // console2.log('stake amounts'); // console2.log(token0Amount); // console2.log(token1Amount); MockERC20 token0 = MockERC20(pool.token0()); MockERC20 token1 = MockERC20(pool.token1()); token0.mint(address(this), token0Amount); token0.approve(address(nfpm), token0Amount); // console2.log('token0 minted'); token1.mint(address(this), token1Amount); token1.approve(address(nfpm), token1Amount); // console2.log('token1 minted'); // struct MintParams { // address token0; // address token1; // uint24 fee; // int24 tickLower; // int24 tickUpper; // uint256 amount0Desired; // uint256 amount1Desired; // uint256 amount0Min; // uint256 amount1Min; // address recipient; // uint256 deadline; // } int24 ts = pool.tickSpacing(); lower = Util.roundTick(lower, ts); upper = Util.roundTick(upper, ts); // console2.log('lower / upper'); // console2.log(lower); // console2.log(upper); address recipient = msg.sender; if (recipient == address(0) ) // anvil will set msg.sender=0x0 this if there is no specific account and this breaks the NFT mint, so we assign the position to ourselves instead recipient = address(this); INonfungiblePositionManager.MintParams memory params = INonfungiblePositionManager.MintParams( address(token0), address(token1), pool.fee(), lower, upper, token0Amount, token1Amount, 0, 0, recipient, block.timestamp ); (tokenId, liquidity, amount0, amount1) = nfpm.mint(params); // console2.log('minted liquidity'); // console2.log(liquidity); // console2.log(amount0); // console2.log(amount1); } }