-
Notifications
You must be signed in to change notification settings - Fork 219
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
base: dev
Are you sure you want to change the base?
Mixed route v4 tests #374
Changes from 5 commits
48a66b2
76b431a
34f1f9b
82cec45
4f047ea
d0f90ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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' | ||
|
@@ -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 | ||
|
@@ -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() | ||
|
@@ -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) | ||
|
@@ -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', () => { | ||
// 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] | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. in v3 you input the payer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. its just a boolean (like in v4) called There was a problem hiding this comment. Choose a reason for hiding this commentThe 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', () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove .only