Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mixed route v4 tests #374

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
301 changes: 294 additions & 7 deletions test/integration-tests/UniswapMixed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@ import type { Contract } from '@ethersproject/contracts'
import { Pair } from '@uniswap/v2-sdk'
import { expect } from './shared/expect'
import { BigNumber } from 'ethers'
import { IPermit2, UniversalRouter } from '../../typechain'
import { IPermit2, PoolManager, PositionManager, UniversalRouter } from '../../typechain'
import { abi as TOKEN_ABI } from '../../artifacts/solmate/src/tokens/ERC20.sol/ERC20.json'
import { resetFork, WETH, DAI, USDC, USDT, PERMIT2 } from './shared/mainnetForkHelpers'
import {
ADDRESS_THIS,
ALICE_ADDRESS,
CONTRACT_BALANCE,
DEADLINE,
ETH_ADDRESS,
MAX_UINT,
MAX_UINT160,
MSG_SENDER,
OPEN_DELTA,
SOURCE_MSG_SENDER,
SOURCE_ROUTER,
} from './shared/constants'
Expand All @@ -24,9 +26,19 @@ import hre from 'hardhat'
import { getPermitBatchSignature } from './shared/protocolHelpers/permit2'
import { encodePathExactInput, encodePathExactOutput } from './shared/swapRouter02Helpers'
import { executeRouter } from './shared/executeRouter'
import { Actions, V4Planner } from './shared/v4Planner'
import {
addLiquidityToV4Pool,
DAI_USDC,
deployV4PoolManager,
encodeMultihopExactInPath,
ETH_USDC,
initializeV4Pool,
USDC_WETH,
} from './shared/v4Helpers'
const { ethers } = hre

describe('Uniswap V2 and V3 Tests:', () => {
describe('Uniswap V2, V3, and V4 Tests:', () => {
let alice: SignerWithAddress
let bob: SignerWithAddress
let router: UniversalRouter
Expand All @@ -35,6 +47,9 @@ describe('Uniswap V2 and V3 Tests:', () => {
let wethContract: Contract
let usdcContract: Contract
let planner: RoutePlanner
let v4Planner: V4Planner
let v4PoolManager: PoolManager
let v4PositionManager: PositionManager

beforeEach(async () => {
await resetFork()
Expand All @@ -48,13 +63,20 @@ describe('Uniswap V2 and V3 Tests:', () => {
wethContract = new ethers.Contract(WETH.address, TOKEN_ABI, bob)
usdcContract = new ethers.Contract(USDC.address, TOKEN_ABI, bob)
permit2 = PERMIT2.connect(bob) as IPermit2
router = (await deployUniversalRouter()) as UniversalRouter

v4PoolManager = (await deployV4PoolManager()).connect(bob) as PoolManager
router = (await deployUniversalRouter(v4PoolManager.address)).connect(bob) as UniversalRouter

v4PositionManager = (await ethers.getContractAt('PositionManager', await router.V4_POSITION_MANAGER())).connect(
bob
) as PositionManager
planner = new RoutePlanner()
v4Planner = new V4Planner()

// alice gives bob some tokens
await daiContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(100000))
await wethContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(100))
await usdcContract.connect(alice).transfer(bob.address, expandTo6DecimalsBN(100000))
await daiContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(1000000))
await wethContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(1000))
await usdcContract.connect(alice).transfer(bob.address, expandTo6DecimalsBN(50000000))

// Bob max-approves the permit2 contract to access his DAI and WETH
await daiContract.connect(bob).approve(permit2.address, MAX_UINT)
Expand All @@ -65,9 +87,28 @@ describe('Uniswap V2 and V3 Tests:', () => {
await permit2.approve(DAI.address, router.address, MAX_UINT160, DEADLINE)
await permit2.approve(WETH.address, router.address, MAX_UINT160, DEADLINE)
await permit2.approve(USDC.address, router.address, MAX_UINT160, DEADLINE)

// for setting up pools, bob gives position manager approval on permit2
await permit2.approve(DAI.address, v4PositionManager.address, MAX_UINT160, DEADLINE)
await permit2.approve(WETH.address, v4PositionManager.address, MAX_UINT160, DEADLINE)
await permit2.approve(USDC.address, v4PositionManager.address, MAX_UINT160, DEADLINE)

// bob initializes 3 v4 pools
await initializeV4Pool(v4PoolManager, USDC_WETH.poolKey, USDC_WETH.price)
await initializeV4Pool(v4PoolManager, DAI_USDC.poolKey, DAI_USDC.price)
await initializeV4Pool(v4PoolManager, ETH_USDC.poolKey, ETH_USDC.price)

// bob adds liquidity to the pools
await addLiquidityToV4Pool(v4PositionManager, USDC_WETH, expandTo18DecimalsBN(2).toString(), bob)
await addLiquidityToV4Pool(v4PositionManager, DAI_USDC, expandTo18DecimalsBN(400).toString(), bob)
await addLiquidityToV4Pool(v4PositionManager, ETH_USDC, expandTo18DecimalsBN(0.1).toString(), bob)
})

describe('Interleaving routes', () => {
describe.only('Interleaving routes', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove .only

// current market ETH price at block
const USD_ETH_PRICE = 3820
const ONE_PERCENT = 38

it('V3, then V2', async () => {
const v3Tokens = [DAI.address, USDC.address]
const v2Tokens = [USDC.address, WETH.address]
Expand Down Expand Up @@ -130,6 +171,252 @@ describe('Uniswap V2 and V3 Tests:', () => {
const { amount1: wethTraded } = v3SwapEventArgs!
expect(wethBalanceAfter.sub(wethBalanceBefore)).to.eq(wethTraded.mul(-1))
})

it('V3, then V4', async () => {
// DAI -v3-> USDC -v4-> WETH
const v3Tokens = [DAI.address, USDC.address]
const v3AmountIn: BigNumber = expandTo18DecimalsBN(1234)
const v3AmountOutMin = 0
const v4AmountOutMin = expandTo18DecimalsBN(1234 / (USD_ETH_PRICE + ONE_PERCENT))

planner.addCommand(CommandType.V3_SWAP_EXACT_IN, [
ADDRESS_THIS, // the router is the recipient of the v3 trade
v3AmountIn,
v3AmountOutMin,
encodePathExactInput(v3Tokens),
SOURCE_MSG_SENDER, // the user pays for the input of the v3 trade
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in v3 you input the payer?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its just a boolean (like in v4) called payerIsUser. So SOURCE_MSG_SENDER == true and SOURCE_ROUTER == false

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesnt let you pull from any address

])

// prep the v4 swap
// settle USDC that are in the router, into v4, to get a positive delta
v4Planner.addAction(Actions.SETTLE, [USDC.address, CONTRACT_BALANCE, false])
v4Planner.addAction(Actions.SWAP_EXACT_IN, [
{
currencyIn: USDC.address,
path: encodeMultihopExactInPath([USDC_WETH.poolKey], USDC.address),
amountIn: OPEN_DELTA,
amountOutMinimum: v4AmountOutMin,
},
])
v4Planner.addAction(Actions.TAKE_ALL, [WETH.address, 0])

// add the v4 commands to the UR calldata
planner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params])

const { daiBalanceBefore, daiBalanceAfter, wethBalanceBefore, wethBalanceAfter } = await executeRouter(
planner,
bob,
router,
wethContract,
daiContract,
usdcContract
)

expect(wethBalanceAfter.sub(wethBalanceBefore)).to.be.gte(v4AmountOutMin)
expect(daiBalanceBefore.sub(daiBalanceAfter)).to.be.eq(v3AmountIn)
expect(await usdcContract.balanceOf(router.address)).to.be.eq(0)
})

it('V2, then V4', async () => {
// DAI -v2-> USDC -v4-> WETH
const v2Tokens = [DAI.address, USDC.address]
const v2AmountIn: BigNumber = expandTo18DecimalsBN(1234)
const v2AmountOutMin = 0
const v4AmountOutMin = expandTo18DecimalsBN(1234 / (USD_ETH_PRICE + ONE_PERCENT))

planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [
ADDRESS_THIS, // the router is the recipient of the v3 trade
v2AmountIn,
v2AmountOutMin,
v2Tokens,
SOURCE_MSG_SENDER, // the user pays for the input of the v3 trade
])

// prep the v4 swap
// settle USDC that are in the router, into v4, to get a positive delta
v4Planner.addAction(Actions.SETTLE, [USDC.address, CONTRACT_BALANCE, false])
v4Planner.addAction(Actions.SWAP_EXACT_IN, [
{
currencyIn: USDC.address,
path: encodeMultihopExactInPath([USDC_WETH.poolKey], USDC.address),
amountIn: OPEN_DELTA,
amountOutMinimum: v4AmountOutMin,
},
])
v4Planner.addAction(Actions.TAKE_ALL, [WETH.address, 0])

// add the v4 commands to the UR calldata
planner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params])

const { daiBalanceBefore, daiBalanceAfter, wethBalanceBefore, wethBalanceAfter } = await executeRouter(
planner,
bob,
router,
wethContract,
daiContract,
usdcContract
)

expect(wethBalanceAfter.sub(wethBalanceBefore)).to.be.gte(v4AmountOutMin)
expect(daiBalanceBefore.sub(daiBalanceAfter)).to.be.eq(v2AmountIn)
expect(await usdcContract.balanceOf(router.address)).to.be.eq(0)
})

it('V4, then V3', async () => {
// DAI -v4-> USDC -v3-> WETH
const v4AmountIn: BigNumber = expandTo18DecimalsBN(1234)
const v4AmountOutMin = 0
const v3Tokens = [USDC.address, WETH.address]
const v3AmountOutMin = expandTo18DecimalsBN(1234 / (USD_ETH_PRICE + ONE_PERCENT))

v4Planner.addAction(Actions.SWAP_EXACT_IN_SINGLE, [
{
poolKey: DAI_USDC.poolKey,
zeroForOne: true,
amountIn: v4AmountIn,
amountOutMinimum: v4AmountOutMin,
sqrtPriceLimitX96: 0,
hookData: '0x',
},
])
v4Planner.addAction(Actions.SETTLE_ALL, [daiContract.address, MAX_UINT160])
// take all of the available tokens (open_delta), with the router (address(this)) as the recipient
v4Planner.addAction(Actions.TAKE, [usdcContract.address, ADDRESS_THIS, OPEN_DELTA])
planner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params])

planner.addCommand(CommandType.V3_SWAP_EXACT_IN, [
MSG_SENDER, // the recipient of the output is the caller
CONTRACT_BALANCE, // the router's balance is the input amount
v3AmountOutMin,
encodePathExactInput(v3Tokens),
SOURCE_ROUTER, // the USDC is in the router
])

const { daiBalanceBefore, daiBalanceAfter, wethBalanceBefore, wethBalanceAfter, v3SwapEventArgs } =
await executeRouter(planner, bob, router, wethContract, daiContract, usdcContract)
const { amount1: wethTraded } = v3SwapEventArgs!

expect(wethBalanceAfter.sub(wethBalanceBefore)).to.eq(wethTraded.mul(-1))
expect(wethTraded.mul(-1)).to.be.gte(v4AmountOutMin)
expect(daiBalanceBefore.sub(daiBalanceAfter)).to.be.eq(v4AmountIn)
expect(await usdcContract.balanceOf(router.address)).to.be.eq(0)
})

it('ETH into V4, then V3', async () => {
// ETH -v4-> USDC -v3-> DAI
const v4AmountIn: BigNumber = expandTo18DecimalsBN(1.2)
const v4AmountOutMin = 0
const v3Tokens = [USDC.address, DAI.address]
const v3AmountOutMin = expandTo18DecimalsBN(1.2 * USD_ETH_PRICE - ONE_PERCENT)

v4Planner.addAction(Actions.SWAP_EXACT_IN_SINGLE, [
{
poolKey: ETH_USDC.poolKey,
zeroForOne: true,
amountIn: v4AmountIn,
amountOutMinimum: v4AmountOutMin,
sqrtPriceLimitX96: 0,
hookData: '0x',
},
])
v4Planner.addAction(Actions.SETTLE_ALL, [ETH_ADDRESS, MAX_UINT160])
// take all of the available tokens (open_delta), with the router (address(this)) as the recipient
v4Planner.addAction(Actions.TAKE, [usdcContract.address, ADDRESS_THIS, OPEN_DELTA])
planner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params])

planner.addCommand(CommandType.V3_SWAP_EXACT_IN, [
MSG_SENDER, // the recipient of the output is the caller
CONTRACT_BALANCE, // the router's balance is the input amount
v3AmountOutMin,
encodePathExactInput(v3Tokens),
SOURCE_ROUTER, // the USDC is in the router
])

const { daiBalanceBefore, daiBalanceAfter, ethBalanceBefore, ethBalanceAfter, gasSpent, v3SwapEventArgs } =
await executeRouter(planner, bob, router, wethContract, daiContract, usdcContract, v4AmountIn)
const { amount0: daiTraded } = v3SwapEventArgs!

expect(ethBalanceBefore.sub(ethBalanceAfter)).to.eq(v4AmountIn.add(gasSpent))
expect(daiBalanceAfter.sub(daiBalanceBefore)).to.be.eq(daiTraded.mul(-1))
expect(daiTraded.mul(-1)).to.be.gte(v3AmountOutMin)
})

it('ETH wrap, WETH into V4, then V3, ', async () => {
// ETH -> WETH -v4-> USDC -v3-> DAI
const v4AmountIn: BigNumber = expandTo18DecimalsBN(1.2)
const v4AmountOutMin = 0
const v3Tokens = [USDC.address, DAI.address]
const v3AmountOutMin = expandTo18DecimalsBN(1.2 * USD_ETH_PRICE - ONE_PERCENT)

// wrap ETH into WETH
planner.addCommand(CommandType.WRAP_ETH, [ADDRESS_THIS, v4AmountIn])
// swap WETH to USDC
v4Planner.addAction(Actions.SWAP_EXACT_IN_SINGLE, [
{
poolKey: USDC_WETH.poolKey,
zeroForOne: false,
amountIn: v4AmountIn,
amountOutMinimum: v4AmountOutMin,
sqrtPriceLimitX96: 0,
hookData: '0x',
},
])
// settle the whole open WETH delta, where the payer is the router (payerIsUser = false)
v4Planner.addAction(Actions.SETTLE, [WETH.address, OPEN_DELTA, false])
// take all of the available tokens (open_delta), with the router (address(this)) as the recipient
v4Planner.addAction(Actions.TAKE, [usdcContract.address, ADDRESS_THIS, OPEN_DELTA])
planner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params])

planner.addCommand(CommandType.V3_SWAP_EXACT_IN, [
MSG_SENDER, // the recipient of the output is the caller
CONTRACT_BALANCE, // the router's balance is the input amount
v3AmountOutMin,
encodePathExactInput(v3Tokens),
SOURCE_ROUTER, // the USDC is in the router
])

const { daiBalanceBefore, daiBalanceAfter, ethBalanceBefore, ethBalanceAfter, gasSpent, v3SwapEventArgs } =
await executeRouter(planner, bob, router, wethContract, daiContract, usdcContract, v4AmountIn)
const { amount0: daiTraded } = v3SwapEventArgs!

expect(ethBalanceBefore.sub(ethBalanceAfter)).to.eq(v4AmountIn.add(gasSpent))
expect(daiBalanceAfter.sub(daiBalanceBefore)).to.be.eq(daiTraded.mul(-1))
expect(daiTraded.mul(-1)).to.be.gte(v3AmountOutMin)
})

it('V4, then V2', async () => {
// DAI -v4-> USDC -v2-> WETH
const v4AmountIn: BigNumber = expandTo18DecimalsBN(1234)
const v4AmountOutMin = 0
const v2Tokens = [USDC.address, WETH.address]
const v2AmountOutMin = expandTo18DecimalsBN(1234 / (USD_ETH_PRICE + ONE_PERCENT))

v4Planner.addAction(Actions.SWAP_EXACT_IN_SINGLE, [
{
poolKey: DAI_USDC.poolKey,
zeroForOne: true,
amountIn: v4AmountIn,
amountOutMinimum: v4AmountOutMin,
sqrtPriceLimitX96: 0,
hookData: '0x',
},
])
v4Planner.addAction(Actions.SETTLE_ALL, [daiContract.address, MAX_UINT160])
// take all of the available tokens (open_delta), with the v2 pair as the recipient
v4Planner.addAction(Actions.TAKE, [usdcContract.address, Pair.getAddress(USDC, WETH), OPEN_DELTA])
planner.addCommand(CommandType.V4_SWAP, [v4Planner.actions, v4Planner.params])

planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [MSG_SENDER, 0, v2AmountOutMin, v2Tokens, SOURCE_MSG_SENDER])

const { daiBalanceBefore, daiBalanceAfter, wethBalanceBefore, wethBalanceAfter, v2SwapEventArgs } =
await executeRouter(planner, bob, router, wethContract, daiContract, usdcContract)
const { amount1Out: wethTraded } = v2SwapEventArgs!

expect(wethBalanceAfter.sub(wethBalanceBefore)).to.eq(wethTraded)
expect(wethTraded).to.be.gte(v4AmountOutMin)
expect(daiBalanceBefore.sub(daiBalanceAfter)).to.be.eq(v4AmountIn)
expect(await usdcContract.balanceOf(router.address)).to.be.eq(0)
})
})

describe('Split routes', () => {
Expand Down
Loading
Loading