import bn from 'bignumber.js' import { BigNumber, BigNumberish, constants, Contract, ContractTransaction, utils, Wallet } from 'ethers' import { TestUniswapV3Callee } from '../../typechain/TestUniswapV3Callee' import { TestUniswapV3Router } from '../../typechain/TestUniswapV3Router' import { MockTimeUniswapV3Pool } from '../../typechain/MockTimeUniswapV3Pool' import { TestERC20 } from '../../typechain/TestERC20' export const MaxUint128 = BigNumber.from(2).pow(128).sub(1) export const getMinTick = (tickSpacing: number) => Math.ceil(-887272 / tickSpacing) * tickSpacing export const getMaxTick = (tickSpacing: number) => Math.floor(887272 / tickSpacing) * tickSpacing export const getMaxLiquidityPerTick = (tickSpacing: number) => BigNumber.from(2) .pow(128) .sub(1) .div((getMaxTick(tickSpacing) - getMinTick(tickSpacing)) / tickSpacing + 1) export const MIN_SQRT_RATIO = BigNumber.from('4295128739') export const MAX_SQRT_RATIO = BigNumber.from('1461446703485210103287273052203988822378723970342') export enum FeeAmount { LOW = 500, MEDIUM = 3000, HIGH = 10000, } export const TICK_SPACINGS: { [amount in FeeAmount]: number } = { [FeeAmount.LOW]: 10, [FeeAmount.MEDIUM]: 60, [FeeAmount.HIGH]: 200, } export function expandTo18Decimals(n: number): BigNumber { return BigNumber.from(n).mul(BigNumber.from(10).pow(18)) } export function getCreate2Address( factoryAddress: string, [tokenA, tokenB]: [string, string], fee: number, bytecode: string ): string { const [token0, token1] = tokenA.toLowerCase() < tokenB.toLowerCase() ? [tokenA, tokenB] : [tokenB, tokenA] const constructorArgumentsEncoded = utils.defaultAbiCoder.encode( ['address', 'address', 'uint24'], [token0, token1, fee] ) const create2Inputs = [ '0xff', factoryAddress, // salt utils.keccak256(constructorArgumentsEncoded), // init code. bytecode + constructor arguments utils.keccak256(bytecode), ] const sanitizedInputs = `0x${create2Inputs.map((i) => i.slice(2)).join('')}` return utils.getAddress(`0x${utils.keccak256(sanitizedInputs).slice(-40)}`) } bn.config({ EXPONENTIAL_AT: 999999, DECIMAL_PLACES: 40 }) // returns the sqrt price as a 64x96 export function encodePriceSqrt(reserve1: BigNumberish, reserve0: BigNumberish): BigNumber { return BigNumber.from( new bn(reserve1.toString()) .div(reserve0.toString()) .sqrt() .multipliedBy(new bn(2).pow(96)) .integerValue(3) .toString() ) } export function getPositionKey(address: string, lowerTick: number, upperTick: number): string { return utils.keccak256(utils.solidityPack(['address', 'int24', 'int24'], [address, lowerTick, upperTick])) } export type SwapFunction = ( amount: BigNumberish, to: Wallet | string, sqrtPriceLimitX96?: BigNumberish ) => Promise export type SwapToPriceFunction = (sqrtPriceX96: BigNumberish, to: Wallet | string) => Promise export type FlashFunction = ( amount0: BigNumberish, amount1: BigNumberish, to: Wallet | string, pay0?: BigNumberish, pay1?: BigNumberish ) => Promise export type MintFunction = ( recipient: string, tickLower: BigNumberish, tickUpper: BigNumberish, liquidity: BigNumberish ) => Promise export interface PoolFunctions { swapToLowerPrice: SwapToPriceFunction swapToHigherPrice: SwapToPriceFunction swapExact0For1: SwapFunction swap0ForExact1: SwapFunction swapExact1For0: SwapFunction swap1ForExact0: SwapFunction flash: FlashFunction mint: MintFunction } export function createPoolFunctions({ swapTarget, token0, token1, pool, }: { swapTarget: TestUniswapV3Callee token0: TestERC20 token1: TestERC20 pool: MockTimeUniswapV3Pool }): PoolFunctions { async function swapToSqrtPrice( inputToken: Contract, targetPrice: BigNumberish, to: Wallet | string ): Promise { const method = inputToken === token0 ? swapTarget.swapToLowerSqrtPrice : swapTarget.swapToHigherSqrtPrice await inputToken.approve(swapTarget.address, constants.MaxUint256) const toAddress = typeof to === 'string' ? to : to.address return method(pool.address, targetPrice, toAddress) } async function swap( inputToken: Contract, [amountIn, amountOut]: [BigNumberish, BigNumberish], to: Wallet | string, sqrtPriceLimitX96?: BigNumberish ): Promise { const exactInput = amountOut === 0 const method = inputToken === token0 ? exactInput ? swapTarget.swapExact0For1 : swapTarget.swap0ForExact1 : exactInput ? swapTarget.swapExact1For0 : swapTarget.swap1ForExact0 if (typeof sqrtPriceLimitX96 === 'undefined') { if (inputToken === token0) { sqrtPriceLimitX96 = MIN_SQRT_RATIO.add(1) } else { sqrtPriceLimitX96 = MAX_SQRT_RATIO.sub(1) } } await inputToken.approve(swapTarget.address, constants.MaxUint256) const toAddress = typeof to === 'string' ? to : to.address return method(pool.address, exactInput ? amountIn : amountOut, toAddress, sqrtPriceLimitX96) } const swapToLowerPrice: SwapToPriceFunction = (sqrtPriceX96, to) => { return swapToSqrtPrice(token0, sqrtPriceX96, to) } const swapToHigherPrice: SwapToPriceFunction = (sqrtPriceX96, to) => { return swapToSqrtPrice(token1, sqrtPriceX96, to) } const swapExact0For1: SwapFunction = (amount, to, sqrtPriceLimitX96) => { return swap(token0, [amount, 0], to, sqrtPriceLimitX96) } const swap0ForExact1: SwapFunction = (amount, to, sqrtPriceLimitX96) => { return swap(token0, [0, amount], to, sqrtPriceLimitX96) } const swapExact1For0: SwapFunction = (amount, to, sqrtPriceLimitX96) => { return swap(token1, [amount, 0], to, sqrtPriceLimitX96) } const swap1ForExact0: SwapFunction = (amount, to, sqrtPriceLimitX96) => { return swap(token1, [0, amount], to, sqrtPriceLimitX96) } const mint: MintFunction = async (recipient, tickLower, tickUpper, liquidity) => { await token0.approve(swapTarget.address, constants.MaxUint256) await token1.approve(swapTarget.address, constants.MaxUint256) return swapTarget.mint(pool.address, recipient, tickLower, tickUpper, liquidity) } const flash: FlashFunction = async (amount0, amount1, to, pay0?: BigNumberish, pay1?: BigNumberish) => { const fee = await pool.fee() if (typeof pay0 === 'undefined') { pay0 = BigNumber.from(amount0) .mul(fee) .add(1e6 - 1) .div(1e6) .add(amount0) } if (typeof pay1 === 'undefined') { pay1 = BigNumber.from(amount1) .mul(fee) .add(1e6 - 1) .div(1e6) .add(amount1) } return swapTarget.flash(pool.address, typeof to === 'string' ? to : to.address, amount0, amount1, pay0, pay1) } return { swapToLowerPrice, swapToHigherPrice, swapExact0For1, swap0ForExact1, swapExact1For0, swap1ForExact0, mint, flash, } } export interface MultiPoolFunctions { swapForExact0Multi: SwapFunction swapForExact1Multi: SwapFunction } export function createMultiPoolFunctions({ inputToken, swapTarget, poolInput, poolOutput, }: { inputToken: TestERC20 swapTarget: TestUniswapV3Router poolInput: MockTimeUniswapV3Pool poolOutput: MockTimeUniswapV3Pool }): MultiPoolFunctions { async function swapForExact0Multi(amountOut: BigNumberish, to: Wallet | string): Promise { const method = swapTarget.swapForExact0Multi await inputToken.approve(swapTarget.address, constants.MaxUint256) const toAddress = typeof to === 'string' ? to : to.address return method(toAddress, poolInput.address, poolOutput.address, amountOut) } async function swapForExact1Multi(amountOut: BigNumberish, to: Wallet | string): Promise { const method = swapTarget.swapForExact1Multi await inputToken.approve(swapTarget.address, constants.MaxUint256) const toAddress = typeof to === 'string' ? to : to.address return method(toAddress, poolInput.address, poolOutput.address, amountOut) } return { swapForExact0Multi, swapForExact1Multi, } }