infrastructure-upgrade/lib/v3-core/test/shared/utilities.ts
T-Hax 735546619e
init
Signed-off-by: T-Hax <>
2023-04-08 18:46:18 +00:00

259 lines
8.1 KiB
TypeScript

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<ContractTransaction>
export type SwapToPriceFunction = (sqrtPriceX96: BigNumberish, to: Wallet | string) => Promise<ContractTransaction>
export type FlashFunction = (
amount0: BigNumberish,
amount1: BigNumberish,
to: Wallet | string,
pay0?: BigNumberish,
pay1?: BigNumberish
) => Promise<ContractTransaction>
export type MintFunction = (
recipient: string,
tickLower: BigNumberish,
tickUpper: BigNumberish,
liquidity: BigNumberish
) => Promise<ContractTransaction>
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<ContractTransaction> {
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<ContractTransaction> {
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<ContractTransaction> {
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<ContractTransaction> {
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,
}
}