UNPKG

@airdao/astra-universal-router

Version:

Smart contracts for Universal Router

1,268 lines (1,101 loc) 49.3 kB
import { CurrencyAmount, Amber, Percent, Token, TradeType } from '@airdao/astra-sdk-core' import { Route as ClassicRouteSDK, Pair } from '@airdao/astra-classic-sdk' import { Route as CLRouteSDK, FeeAmount } from '@airdao/astra-cl-sdk' import { SwapRouter, Trade } from '@airdao/astra-router-sdk' import snapshotGasCost from '@uniswap/snapshot-gas-cost' import deployUniversalRouter, { deployPermit2 } from '../shared/deployUniversalRouter' import { getPermitBatchSignature } from '../shared/protocolHelpers/permit2' import { makePair, expandTo18Decimals, encodePath, pool_BOND_SAMB, pool_BOND_USDC, pool_USDC_SAMB, pool_USDC_KOS, pool_SAMB_KOS, } from '../shared/swapRouter02Helpers' import { BigNumber, BigNumberish } from 'ethers' import { UniversalRouter, Permit2, ISAMB, ERC20, ISAMB__factory, ERC20__factory } from '../../../typechain' import { approveAndExecuteSwapRouter02, resetFork, SAMB, BOND, USDC, KOS } from '../shared/testnetForkHelpers' import { ADDRESS_THIS, ALICE_ADDRESS, CONTRACT_BALANCE, DEADLINE, AMB_ADDRESS, MAX_UINT, MAX_UINT160, MSG_SENDER, ONE_PERCENT_BIPS, SOURCE_MSG_SENDER, SOURCE_ROUTER, } from '../shared/constants' import { expandTo18DecimalsBN, expandTo6DecimalsBN } from '../shared/helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import hre from 'hardhat' import { RoutePlanner, CommandType } from '../shared/planner' const { ethers } = hre function encodePathExactInput(tokens: string[]) { return encodePath(tokens, new Array(tokens.length - 1).fill(FeeAmount.MEDIUM)) } function encodePathExactOutput(tokens: string[]) { return encodePath(tokens.slice().reverse(), new Array(tokens.length - 1).fill(FeeAmount.MEDIUM)) } describe('Astra Gas Tests', () => { let alice: SignerWithAddress let bob: SignerWithAddress let router: UniversalRouter let permit2: Permit2 let sambContract: ISAMB let bondContract: ERC20 let usdcContract: ERC20 let planner: RoutePlanner // 6 pairs for gas tests with high numbers of trades let pair_BOND_SAMB: Pair let pair_BOND_USDC: Pair let pair_USDC_SAMB: Pair beforeEach(async () => { await resetFork() await hre.network.provider.request({ method: 'hardhat_impersonateAccount', params: [ALICE_ADDRESS], }) alice = await ethers.getSigner(ALICE_ADDRESS) await hre.network.provider.request({ method: 'hardhat_setBalance', params: [ALICE_ADDRESS, '0x10000000000000000000000'], }) bob = (await ethers.getSigners())[1] await (await ISAMB__factory.connect(SAMB.address, alice).deposit({ value: expandTo18DecimalsBN(1000) })).wait() bondContract = ERC20__factory.connect(BOND.address, bob) usdcContract = ERC20__factory.connect(USDC.address, bob) sambContract = ISAMB__factory.connect(SAMB.address, bob) permit2 = (await deployPermit2()).connect(bob) as Permit2 router = (await deployUniversalRouter(permit2)).connect(bob) as UniversalRouter pair_BOND_SAMB = await makePair(bob, BOND, SAMB) pair_BOND_USDC = await makePair(bob, BOND, USDC) pair_USDC_SAMB = await makePair(bob, USDC, SAMB) // alice gives bob some tokens await bondContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(100000)) await sambContract.connect(alice).transfer(bob.address, expandTo18DecimalsBN(100)) // Bob max-approves the permit2 contract to access his BOND and SAMB await bondContract.connect(bob).approve(permit2.address, MAX_UINT) await sambContract.connect(bob).approve(permit2.address, MAX_UINT) }) describe('Trade on AstraClassic', () => { describe('with Router02.', () => { const slippageTolerance = new Percent(10, 100) const recipient = '0x0000000000000000000000000000000000000003' let amountInBOND: CurrencyAmount<Token> let amountInAMB: CurrencyAmount<Amber> let amountOut: CurrencyAmount<Token> let classicTradeExactIn: Trade<Token, Token, TradeType.EXACT_INPUT> let classicTradeExactOut: Trade<Token, Token, TradeType.EXACT_OUTPUT> beforeEach(async () => { amountInBOND = CurrencyAmount.fromRawAmount(BOND, expandTo18Decimals(5)) amountInAMB = CurrencyAmount.fromRawAmount(Amber.onChain(22040), expandTo18Decimals(5)) amountOut = CurrencyAmount.fromRawAmount(BOND, expandTo18Decimals(5)) }) it('gas: ERC20 --> ERC20 exactIn, one trade, one hop', async () => { classicTradeExactIn = await Trade.fromRoute( new ClassicRouteSDK([pair_BOND_SAMB], BOND, SAMB), amountInBOND, TradeType.EXACT_INPUT ) const { calldata } = SwapRouter.swapCallParameters(classicTradeExactIn, { slippageTolerance, recipient, deadlineOrPreviousBlockhash: DEADLINE, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, BOND, SAMB, bob)) }) it('gas: ERC20 --> ERC20 exactIn, one trade, two hops', async () => { classicTradeExactIn = await Trade.fromRoute( new ClassicRouteSDK([pair_BOND_USDC, pair_USDC_SAMB], BOND, SAMB), amountInBOND, TradeType.EXACT_INPUT ) const { calldata } = SwapRouter.swapCallParameters(classicTradeExactIn, { slippageTolerance, recipient, deadlineOrPreviousBlockhash: DEADLINE, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, BOND, SAMB, bob)) }) it('gas: AMB --> ERC20 exactIn, one trade, one hop', async () => { const trade = await Trade.fromRoute( new ClassicRouteSDK([pair_BOND_SAMB], Amber.onChain(22040), BOND), amountInAMB, TradeType.EXACT_INPUT ) const { calldata, value } = SwapRouter.swapCallParameters(trade, { slippageTolerance, recipient, deadlineOrPreviousBlockhash: DEADLINE, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value, calldata }, BOND, SAMB, bob)) }) it('gas: ERC20 --> ERC20 exactOut, one trade, one hop', async () => { classicTradeExactOut = await Trade.fromRoute( new ClassicRouteSDK([pair_BOND_SAMB], SAMB, BOND), amountOut, TradeType.EXACT_OUTPUT ) const { calldata } = SwapRouter.swapCallParameters(classicTradeExactOut, { slippageTolerance, recipient, deadlineOrPreviousBlockhash: DEADLINE, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, SAMB, BOND, bob)) }) it('gas: ERC20 --> AMB exactOut AMB, one trade, one hop', async () => { const amountOutAMB = CurrencyAmount.fromRawAmount(Amber.onChain(22040), expandTo18Decimals(5)) const trade = await Trade.fromRoute( new ClassicRouteSDK([pair_BOND_SAMB], BOND, Amber.onChain(22040)), amountOutAMB, TradeType.EXACT_OUTPUT ) const { calldata, value } = SwapRouter.swapCallParameters(trade, { slippageTolerance, recipient, deadlineOrPreviousBlockhash: DEADLINE, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value, calldata }, BOND, SAMB, bob)) }) }) describe('with Universal Router.', () => { const amountIn: BigNumber = expandTo18DecimalsBN(5) let planner: RoutePlanner beforeEach(async () => { planner = new RoutePlanner() // for these tests Bob gives the router max approval on permit2 await permit2.approve(BOND.address, router.address, MAX_UINT160, DEADLINE) await permit2.approve(SAMB.address, router.address, MAX_UINT160, DEADLINE) }) describe('ERC20 --> ERC20', () => { const minAmountOut = expandTo18DecimalsBN(0.0001) it('gas: exactIn, one trade, one hop', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, amountIn, minAmountOut, [BOND.address, SAMB.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactIn, one trade, two hops', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, amountIn, minAmountOut, [BOND.address, USDC.address, SAMB.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactIn, one trade, two hops, MSG_SENDER flag', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, amountIn, minAmountOut, [BOND.address, USDC.address, SAMB.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactIn, one trade, three hops', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, amountIn, minAmountOut, [BOND.address, USDC.address, KOS.address, SAMB.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactIn, one trade, three hops, no deadline', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, amountIn, 1, [BOND.address, USDC.address, KOS.address, SAMB.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[])'](commands, inputs)) }) it('gas: exactIn trade, where an output fee is taken', async () => { // back to the router so someone can take a fee planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ router.address, amountIn, 1, [BOND.address, SAMB.address], SOURCE_MSG_SENDER, ]) planner.addCommand(CommandType.PAY_PORTION, [SAMB.address, alice.address, ONE_PERCENT_BIPS]) planner.addCommand(CommandType.SWEEP, [SAMB.address, MSG_SENDER, 1]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut, one trade, one hop', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_OUT, [ MSG_SENDER, expandTo18DecimalsBN(5), expandTo18DecimalsBN(100), [SAMB.address, BOND.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut, one trade, two hops', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_OUT, [ MSG_SENDER, expandTo18DecimalsBN(5), expandTo18DecimalsBN(100), [SAMB.address, USDC.address, BOND.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut, one trade, three hops', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_OUT, [ MSG_SENDER, expandTo18DecimalsBN(5), expandTo18DecimalsBN(100), [SAMB.address, KOS.address, USDC.address, BOND.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) }) describe('ERC20 --> AMB', () => { it('gas: exactIn, one trade, one hop', async () => { planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ router.address, amountIn, 1, [BOND.address, SAMB.address], SOURCE_MSG_SENDER, ]) planner.addCommand(CommandType.UNWRAP_SAMB, [MSG_SENDER, 0]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut, one trade, one hop', async () => { const amountOut = expandTo18DecimalsBN(1) planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_OUT, [ router.address, amountOut, expandTo18DecimalsBN(10000), [BOND.address, SAMB.address], SOURCE_MSG_SENDER, ]) planner.addCommand(CommandType.UNWRAP_SAMB, [MSG_SENDER, amountOut]) planner.addCommand(CommandType.SWEEP, [BOND.address, MSG_SENDER, 0]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut, with AMB fee', async () => { const amountOut = expandTo18DecimalsBN(1) planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_OUT, [ router.address, amountOut, expandTo18DecimalsBN(10000), [BOND.address, SAMB.address], SOURCE_MSG_SENDER, ]) planner.addCommand(CommandType.UNWRAP_SAMB, [ADDRESS_THIS, amountOut]) planner.addCommand(CommandType.PAY_PORTION, [AMB_ADDRESS, MSG_SENDER, 50]) planner.addCommand(CommandType.SWEEP, [AMB_ADDRESS, alice.address, 0]) const { commands, inputs } = planner await snapshotGasCost( router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE, { value: amountIn }) ) }) }) describe('AMB --> ERC20', () => { it('gas: exactIn, one trade, one hop', async () => { const minAmountOut = expandTo18DecimalsBN(0.001) const pairAddress = Pair.getAddress(BOND, SAMB) planner.addCommand(CommandType.WRAP_AMB, [pairAddress, amountIn]) // the money is already in the pair, so amountIn is 0 planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, 0, minAmountOut, [SAMB.address, BOND.address], SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost( router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE, { value: amountIn }) ) }) it('gas: exactOut, one trade, one hop', async () => { const amountOut = expandTo18DecimalsBN(100) const value = expandTo18DecimalsBN(11) planner.addCommand(CommandType.WRAP_AMB, [ADDRESS_THIS, value]) planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_OUT, [ MSG_SENDER, amountOut, expandTo18DecimalsBN(11), [SAMB.address, BOND.address], SOURCE_ROUTER, ]) planner.addCommand(CommandType.UNWRAP_SAMB, [MSG_SENDER, 0]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE, { value })) }) }) }) }) describe('Trade on AstraCL', () => { describe('with Router02.', () => { const amountIn = CurrencyAmount.fromRawAmount(BOND, expandTo18Decimals(5)) const amountOut = CurrencyAmount.fromRawAmount(SAMB, expandTo18Decimals(1)) const slippageTolerance = new Percent(10, 100) let clExactIn: Trade<Token, Token, TradeType.EXACT_INPUT> let clExactInMultihop: Trade<Token, Token, TradeType.EXACT_INPUT> let clExactOut: Trade<Token, Token, TradeType.EXACT_OUTPUT> let clExactOutMultihop: Trade<Token, Token, TradeType.EXACT_OUTPUT> beforeEach(async () => { clExactIn = await Trade.fromRoute(new CLRouteSDK([pool_BOND_SAMB], BOND, SAMB), amountIn, TradeType.EXACT_INPUT) clExactOut = await Trade.fromRoute( new CLRouteSDK([pool_BOND_SAMB], BOND, SAMB), amountOut, TradeType.EXACT_OUTPUT ) }) it('gas: ERC20 --> ERC20 exactIn, one trade, one hop', async () => { const { calldata } = SwapRouter.swapCallParameters(clExactIn, { slippageTolerance, recipient: MSG_SENDER, deadlineOrPreviousBlockhash: 2000000000, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, BOND, SAMB, bob)) }) it('gas: ERC20 --> ERC20 exactIn, one trade, two hops', async () => { clExactInMultihop = await Trade.fromRoute( new CLRouteSDK([pool_BOND_USDC, pool_USDC_SAMB], BOND, SAMB), amountIn, TradeType.EXACT_INPUT ) const { calldata } = SwapRouter.swapCallParameters(clExactInMultihop, { slippageTolerance, recipient: MSG_SENDER, deadlineOrPreviousBlockhash: 2000000000, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, BOND, SAMB, bob)) }) it('gas: ERC20 --> ERC20 exactIn, one trade, three hops', async () => { clExactInMultihop = await Trade.fromRoute( new CLRouteSDK([pool_BOND_USDC, pool_USDC_KOS, pool_SAMB_KOS], BOND, SAMB), amountIn, TradeType.EXACT_INPUT ) const { calldata } = SwapRouter.swapCallParameters(clExactInMultihop, { slippageTolerance, recipient: MSG_SENDER, deadlineOrPreviousBlockhash: 2000000000, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, BOND, SAMB, bob)) }) it('gas: ERC20 --> ERC20 exactOut, one trade, one hop', async () => { const { calldata } = SwapRouter.swapCallParameters(clExactOut, { slippageTolerance, recipient: MSG_SENDER, deadlineOrPreviousBlockhash: 2000000000, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, BOND, SAMB, bob)) }) it('gas: ERC20 --> ERC20 exactOut, one trade, two hops', async () => { clExactOutMultihop = await Trade.fromRoute( new CLRouteSDK([pool_BOND_USDC, pool_USDC_SAMB], BOND, SAMB), amountOut, TradeType.EXACT_OUTPUT ) const { calldata } = SwapRouter.swapCallParameters(clExactOutMultihop, { slippageTolerance, recipient: MSG_SENDER, deadlineOrPreviousBlockhash: 2000000000, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, BOND, SAMB, bob)) }) it('gas: ERC20 --> ERC20 exactOut, one trade, three hops', async () => { clExactOutMultihop = await Trade.fromRoute( new CLRouteSDK([pool_BOND_USDC, pool_USDC_KOS, pool_SAMB_KOS], BOND, SAMB), amountOut, TradeType.EXACT_OUTPUT ) const { calldata } = SwapRouter.swapCallParameters(clExactOutMultihop, { slippageTolerance, recipient: MSG_SENDER, deadlineOrPreviousBlockhash: 2000000000, }) await snapshotGasCost(approveAndExecuteSwapRouter02({ value: '0', calldata }, BOND, SAMB, bob)) }) }) describe('with Universal Router.', () => { const amountIn: BigNumber = expandTo18DecimalsBN(500) const amountInMax: BigNumber = expandTo18DecimalsBN(2000) const amountOut: BigNumber = expandTo18DecimalsBN(1) const addCLExactInTrades = ( planner: RoutePlanner, numTrades: BigNumberish, amountOutMin: BigNumberish, recipient?: string, tokens: string[] = [BOND.address, SAMB.address], sourceOfTokens: boolean = SOURCE_MSG_SENDER ) => { const path = encodePathExactInput(tokens) for (let i = 0; i < Number(numTrades); i++) { planner.addCommand(CommandType.CL_SWAP_EXACT_IN, [ recipient ?? MSG_SENDER, amountIn, amountOutMin, path, sourceOfTokens, ]) } } beforeEach(async () => { planner = new RoutePlanner() // for these tests Bob gives the router max approval on permit2 await permit2.approve(BOND.address, router.address, MAX_UINT160, DEADLINE) await permit2.approve(SAMB.address, router.address, MAX_UINT160, DEADLINE) }) describe('ERC20 --> ERC20', () => { it('gas: exactIn, one trade, one hop', async () => { const amountOutMin: number = 0.0005 * 10 ** 18 addCLExactInTrades(planner, 1, amountOutMin) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactIn, one trade, two hops', async () => { const amountOutMin: number = 3 * 10 ** 6 addCLExactInTrades(planner, 1, amountOutMin, MSG_SENDER, [BOND.address, SAMB.address, USDC.address]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactIn, one trade, three hops', async () => { const amountOutMin: number = 3 * 10 ** 6 addCLExactInTrades(planner, 1, amountOutMin, MSG_SENDER, [ BOND.address, SAMB.address, KOS.address, USDC.address, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut, one trade, one hop', async () => { const tokens = [BOND.address, SAMB.address] const path = encodePathExactOutput(tokens) planner.addCommand(CommandType.CL_SWAP_EXACT_OUT, [ MSG_SENDER, amountOut, amountInMax, path, SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut, one trade, two hops', async () => { // trade BOND in for SAMB out const tokens = [BOND.address, USDC.address, SAMB.address] const path = encodePathExactOutput(tokens) planner.addCommand(CommandType.CL_SWAP_EXACT_OUT, [ MSG_SENDER, amountOut, amountInMax, path, SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut, one trade, three hops', async () => { // trade BOND in for SAMB out const tokens = [BOND.address, USDC.address, KOS.address, SAMB.address] const path = encodePathExactOutput(tokens) planner.addCommand(CommandType.CL_SWAP_EXACT_OUT, [ MSG_SENDER, amountOut, amountInMax, path, SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) }) describe('ERC20 --> AMB', () => { it('gas: exactIn swap', async () => { const amountOutMin: BigNumber = expandTo18DecimalsBN(0.0005) addCLExactInTrades(planner, 1, amountOutMin, router.address) planner.addCommand(CommandType.UNWRAP_SAMB, [MSG_SENDER, 0]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: exactOut swap', async () => { // trade BOND in for SAMB out const tokens = [BOND.address, SAMB.address] const path = encodePathExactOutput(tokens) planner.addCommand(CommandType.CL_SWAP_EXACT_OUT, [ router.address, amountOut, amountInMax, path, SOURCE_MSG_SENDER, ]) planner.addCommand(CommandType.UNWRAP_SAMB, [MSG_SENDER, amountOut]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) }) describe('AMB --> ERC20', () => { it('gas: exactIn swap', async () => { const tokens = [SAMB.address, BOND.address] const amountOutMin: BigNumber = expandTo18DecimalsBN(0.0005) planner.addCommand(CommandType.WRAP_AMB, [ADDRESS_THIS, amountIn]) addCLExactInTrades(planner, 1, amountOutMin, MSG_SENDER, tokens, SOURCE_ROUTER) const { commands, inputs } = planner await snapshotGasCost( router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE, { value: amountIn }) ) }) it('gas: exactOut swap', async () => { const tokens = [SAMB.address, BOND.address] const path = encodePathExactOutput(tokens) planner.addCommand(CommandType.WRAP_AMB, [ADDRESS_THIS, amountInMax]) planner.addCommand(CommandType.CL_SWAP_EXACT_OUT, [MSG_SENDER, amountOut, amountInMax, path, SOURCE_ROUTER]) planner.addCommand(CommandType.UNWRAP_SAMB, [MSG_SENDER, 0]) const { commands, inputs } = planner await snapshotGasCost( router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE, { value: amountInMax }) ) }) }) }) }) describe('Mixing Classic and CL', () => { describe('with Universal Router.', () => { beforeEach(async () => { planner = new RoutePlanner() // Bob max-approves the permit2 contract to access his BOND and SAMB await permit2.approve(BOND.address, router.address, MAX_UINT160, DEADLINE) await permit2.approve(SAMB.address, router.address, MAX_UINT160, DEADLINE) }) describe('Interleaving routes', () => { it('gas: CL, then Classic', async () => { const clTokens = [BOND.address, USDC.address] const classicTokens = [USDC.address, SAMB.address] const clAmountIn: BigNumber = expandTo18DecimalsBN(5) const clAmountOutMin = 0 const classicAmountOutMin = expandTo18DecimalsBN(0.0005) planner.addCommand(CommandType.CL_SWAP_EXACT_IN, [ Pair.getAddress(USDC, SAMB), clAmountIn, clAmountOutMin, encodePathExactInput(clTokens), SOURCE_MSG_SENDER, ]) // the tokens are already int he classic pair, so amountIn is 0 planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, 0, classicAmountOutMin, classicTokens, SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: Classic, then CL', async () => { const classicTokens = [BOND.address, USDC.address] const clTokens = [USDC.address, SAMB.address] const classicAmountIn: BigNumber = expandTo18DecimalsBN(5) const classicAmountOutMin = 0 // doesnt matter how much USDC it is, what matters is the end of the trade const clAmountOutMin = expandTo18DecimalsBN(0.0005) planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ router.address, classicAmountIn, classicAmountOutMin, classicTokens, SOURCE_MSG_SENDER, ]) planner.addCommand(CommandType.CL_SWAP_EXACT_IN, [ MSG_SENDER, CONTRACT_BALANCE, clAmountOutMin, encodePathExactInput(clTokens), SOURCE_ROUTER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) }) describe('Split routes', () => { it('gas: ERC20 --> ERC20 split Classic and Classic different routes, each two hop, with explicit permit', async () => { const route1 = [BOND.address, USDC.address, SAMB.address] const route2 = [BOND.address, KOS.address, SAMB.address] const classicAmountIn1: BigNumber = expandTo18DecimalsBN(20) const classicAmountIn2: BigNumber = expandTo18DecimalsBN(30) const minAmountOut1 = expandTo18DecimalsBN(0.005) const minAmountOut2 = expandTo18DecimalsBN(0.0075) // 1) transfer funds into BOND-USDC and BOND-KOS pairs to trade planner.addCommand(CommandType.PERMIT2_TRANSFER_FROM, [ BOND.address, Pair.getAddress(BOND, USDC), classicAmountIn1, ]) planner.addCommand(CommandType.PERMIT2_TRANSFER_FROM, [ BOND.address, Pair.getAddress(BOND, KOS), classicAmountIn2, ]) // 2) trade route1 and return tokens to bob planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, 0, minAmountOut1, route1, SOURCE_MSG_SENDER, ]) // 3) trade route2 and return tokens to bob planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, 0, minAmountOut2, route2, SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: ERC20 --> ERC20 split Classic and Classic different routes, each two hop, with explicit permit transfer from batch', async () => { const route1 = [BOND.address, USDC.address, SAMB.address] const route2 = [BOND.address, KOS.address, SAMB.address] const classicAmountIn1: BigNumber = expandTo18DecimalsBN(20) const classicAmountIn2: BigNumber = expandTo18DecimalsBN(30) const minAmountOut1 = expandTo18DecimalsBN(0.005) const minAmountOut2 = expandTo18DecimalsBN(0.0075) const BATCH_TRANSFER = [ { from: bob.address, to: Pair.getAddress(BOND, USDC), amount: classicAmountIn1, token: BOND.address, }, { from: bob.address, to: Pair.getAddress(BOND, KOS), amount: classicAmountIn2, token: BOND.address, }, ] // 1) transfer funds into BOND-USDC and BOND-KOS pairs to trade planner.addCommand(CommandType.PERMIT2_TRANSFER_FROM_BATCH, [BATCH_TRANSFER]) // 2) trade route1 and return tokens to bob planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, 0, minAmountOut1, route1, SOURCE_MSG_SENDER, ]) // 3) trade route2 and return tokens to bob planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, 0, minAmountOut2, route2, SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: ERC20 --> ERC20 split Classic and Classic different routes, each two hop, without explicit permit', async () => { // this test is the same as the above test, but instead of a permit permit, separate permits within the 2 trades const route1 = [BOND.address, USDC.address, SAMB.address] const route2 = [BOND.address, KOS.address, SAMB.address] const classicAmountIn1: BigNumber = expandTo18DecimalsBN(20) const classicAmountIn2: BigNumber = expandTo18DecimalsBN(30) const minAmountOut1 = expandTo18DecimalsBN(0.005) const minAmountOut2 = expandTo18DecimalsBN(0.0075) // 1) trade route1 and return tokens to bob planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, classicAmountIn1, minAmountOut1, route1, SOURCE_MSG_SENDER, ]) // 2) trade route2 and return tokens to bob planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, classicAmountIn2, minAmountOut2, route2, SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: ERC20 --> ERC20 split Classic and Classic different routes, different input tokens, each two hop, with batch permit', async () => { const route1 = [BOND.address, SAMB.address, USDC.address] const route2 = [SAMB.address, BOND.address, USDC.address] const classicAmountIn1: BigNumber = expandTo18DecimalsBN(20) const classicAmountIn2: BigNumber = expandTo18DecimalsBN(5) const minAmountOut1 = BigNumber.from(0.005 * 10 ** 6) const minAmountOut2 = BigNumber.from(0.0075 * 10 ** 6) const BATCH_PERMIT = { details: [ { token: BOND.address, amount: classicAmountIn1, expiration: 0, // expiration of 0 is block.timestamp nonce: 0, // this is his first trade }, { token: SAMB.address, amount: classicAmountIn2, expiration: 0, // expiration of 0 is block.timestamp nonce: 0, // this is his first trade }, ], spender: router.address, sigDeadline: DEADLINE, } const sig = await getPermitBatchSignature(BATCH_PERMIT, bob, permit2) // 1) transfer funds into BOND-USDC and BOND-KOS pairs to trade planner.addCommand(CommandType.PERMIT2_PERMIT_BATCH, [BATCH_PERMIT, sig]) // 2) trade route1 and return tokens to bob planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, classicAmountIn1, minAmountOut1, route1, SOURCE_MSG_SENDER, ]) // 3) trade route2 and return tokens to bob planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ MSG_SENDER, classicAmountIn2, minAmountOut2, route2, SOURCE_MSG_SENDER, ]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: ERC20 --> ERC20 split Classic and CL, one hop', async () => { const tokens = [BOND.address, SAMB.address] const classicAmountIn: BigNumber = expandTo18DecimalsBN(2) const clAmountIn: BigNumber = expandTo18DecimalsBN(3) // Classic trades BOND for USDC, sending the tokens back to the router for cl trade planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ router.address, classicAmountIn, 0, tokens, SOURCE_MSG_SENDER, ]) // CL trades USDC for SAMB, trading the whole balance, with a recipient of Alice planner.addCommand(CommandType.CL_SWAP_EXACT_IN, [ router.address, clAmountIn, 0, encodePathExactInput(tokens), SOURCE_MSG_SENDER, ]) // aggregate slippate check planner.addCommand(CommandType.SWEEP, [SAMB.address, MSG_SENDER, expandTo18DecimalsBN(0.0005)]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: ERC20 --> ERC20 split Classic and CL, one hop, ADDRESS_THIS flag', async () => { const tokens = [BOND.address, SAMB.address] const classicAmountIn: BigNumber = expandTo18DecimalsBN(2) const clAmountIn: BigNumber = expandTo18DecimalsBN(3) // Classic trades BOND for USDC, sending the tokens back to the router for cl trade planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ ADDRESS_THIS, classicAmountIn, 0, tokens, SOURCE_MSG_SENDER, ]) // CL trades USDC for SAMB, trading the whole balance, with a recipient of Alice planner.addCommand(CommandType.CL_SWAP_EXACT_IN, [ router.address, clAmountIn, 0, encodePathExactInput(tokens), SOURCE_MSG_SENDER, ]) // aggregate slippate check planner.addCommand(CommandType.SWEEP, [SAMB.address, MSG_SENDER, expandTo18DecimalsBN(0.0005)]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: AMB --> ERC20 split Classic and CL, one hop', async () => { const tokens = [SAMB.address, USDC.address] const classicAmountIn: BigNumber = expandTo18DecimalsBN(2) const clAmountIn: BigNumber = expandTo18DecimalsBN(3) const value = classicAmountIn.add(clAmountIn) planner.addCommand(CommandType.WRAP_AMB, [ADDRESS_THIS, value]) planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ router.address, classicAmountIn, 0, tokens, SOURCE_ROUTER, ]) planner.addCommand(CommandType.CL_SWAP_EXACT_IN, [ router.address, clAmountIn, 0, encodePathExactInput(tokens), SOURCE_ROUTER, ]) // aggregate slippate check planner.addCommand(CommandType.SWEEP, [USDC.address, MSG_SENDER, 0.0005 * 10 ** 6]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE, { value })) }) it('gas: ERC20 --> AMB split Classic and CL, one hop', async () => { const tokens = [BOND.address, SAMB.address] const classicAmountIn: BigNumber = expandTo18DecimalsBN(20) const clAmountIn: BigNumber = expandTo18DecimalsBN(30) planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ router.address, classicAmountIn, 0, tokens, SOURCE_MSG_SENDER, ]) planner.addCommand(CommandType.CL_SWAP_EXACT_IN, [ router.address, clAmountIn, 0, encodePathExactInput(tokens), SOURCE_MSG_SENDER, ]) // aggregate slippate check planner.addCommand(CommandType.UNWRAP_SAMB, [MSG_SENDER, expandTo18DecimalsBN(0.0005)]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: ERC20 --> AMB split Classic and CL, exactOut, one hop', async () => { const tokens = [BOND.address, SAMB.address] const classicAmountOut: BigNumber = expandTo18DecimalsBN(0.5) const clAmountOut: BigNumber = expandTo18DecimalsBN(1) const path = encodePathExactOutput(tokens) const maxAmountIn = expandTo18DecimalsBN(4000) const fullAmountOut = classicAmountOut.add(clAmountOut) planner.addCommand(CommandType.CLASSIC_SWAP_EXACT_OUT, [ router.address, classicAmountOut, maxAmountIn, [BOND.address, SAMB.address], SOURCE_MSG_SENDER, ]) planner.addCommand(CommandType.CL_SWAP_EXACT_OUT, [ router.address, clAmountOut, maxAmountIn, path, SOURCE_MSG_SENDER, ]) // aggregate slippate check planner.addCommand(CommandType.UNWRAP_SAMB, [MSG_SENDER, fullAmountOut]) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) }) describe('Batch reverts', () => { let subplan: RoutePlanner const planOneTokens = [BOND.address, SAMB.address] const planTwoTokens = [USDC.address, SAMB.address] const planOneClassicAmountIn: BigNumber = expandTo18DecimalsBN(2) const planOneCLAmountIn: BigNumber = expandTo18DecimalsBN(3) const planTwoCLAmountIn = expandTo6DecimalsBN(5) beforeEach(async () => { subplan = new RoutePlanner() }) it('gas: 2 sub-plans, neither fails', async () => { // first split route sub-plan. BOND->SAMB, 2 routes on Classic and CL. const planOneWethMinOut = expandTo18DecimalsBN(0.0005) // Classic trades BOND for USDC, sending the tokens back to the router for cl trade subplan.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ ADDRESS_THIS, planOneClassicAmountIn, 0, planOneTokens, SOURCE_MSG_SENDER, ]) // CL trades USDC for SAMB, trading the whole balance, with a recipient of Alice subplan.addCommand(CommandType.CL_SWAP_EXACT_IN, [ ADDRESS_THIS, planOneCLAmountIn, 0, encodePathExactInput(planOneTokens), SOURCE_MSG_SENDER, ]) // aggregate slippage check subplan.addCommand(CommandType.SWEEP, [SAMB.address, MSG_SENDER, planOneWethMinOut]) // add the subplan to the main planner planner.addSubPlan(subplan) subplan = new RoutePlanner() // second split route sub-plan. USDC->SAMB, 1 route on CL const sambMinAmountOut2 = expandTo18DecimalsBN(0.0005) // Add the trade to the sub-plan subplan.addCommand(CommandType.CL_SWAP_EXACT_IN, [ MSG_SENDER, planTwoCLAmountIn, sambMinAmountOut2, encodePathExactInput(planTwoTokens), SOURCE_MSG_SENDER, ]) // add the second subplan to the main planner planner.addSubPlan(subplan) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: 2 sub-plans, the first fails', async () => { // first split route sub-plan. BOND->SAMB, 2 routes on Classic and CL. // FAIL: large samb amount out to cause a failure const planOneWethMinOut = expandTo18DecimalsBN(1) // Classic trades BOND for USDC, sending the tokens back to the router for cl trade subplan.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ ADDRESS_THIS, planOneClassicAmountIn, 0, planOneTokens, SOURCE_MSG_SENDER, ]) // CL trades USDC for SAMB, trading the whole balance, with a recipient of Alice subplan.addCommand(CommandType.CL_SWAP_EXACT_IN, [ ADDRESS_THIS, planOneCLAmountIn, 0, encodePathExactInput(planOneTokens), SOURCE_MSG_SENDER, ]) // aggregate slippage check subplan.addCommand(CommandType.SWEEP, [SAMB.address, MSG_SENDER, planOneWethMinOut]) // add the subplan to the main planner planner.addSubPlan(subplan) subplan = new RoutePlanner() // second split route sub-plan. USDC->SAMB, 1 route on CL const sambMinAmountOut2 = expandTo18DecimalsBN(0.0005) // Add the trade to the sub-plan subplan.addCommand(CommandType.CL_SWAP_EXACT_IN, [ MSG_SENDER, planTwoCLAmountIn, sambMinAmountOut2, encodePathExactInput(planTwoTokens), SOURCE_MSG_SENDER, ]) // add the second subplan to the main planner planner.addSubPlan(subplan) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: 2 sub-plans, both fail but the transaction succeeds', async () => { // first split route sub-plan. BOND->SAMB, 2 routes on Classic and CL. // FAIL: large amount out to cause the swap to revert const planOneWethMinOut = expandTo18DecimalsBN(1) // Classic trades BOND for USDC, sending the tokens back to the router for cl trade subplan.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ ADDRESS_THIS, planOneClassicAmountIn, 0, planOneTokens, SOURCE_MSG_SENDER, ]) // CL trades USDC for SAMB, trading the whole balance, with a recipient of Alice subplan.addCommand(CommandType.CL_SWAP_EXACT_IN, [ ADDRESS_THIS, planOneCLAmountIn, 0, encodePathExactInput(planOneTokens), SOURCE_MSG_SENDER, ]) // aggregate slippage check subplan.addCommand(CommandType.SWEEP, [SAMB.address, MSG_SENDER, planOneWethMinOut]) // add the subplan to the main planner planner.addSubPlan(subplan) subplan = new RoutePlanner() // second split route sub-plan. USDC->SAMB, 1 route on CL // FAIL: large amount out to cause the swap to revert const sambMinAmountOut2 = expandTo18DecimalsBN(1) // Add the trade to the sub-plan subplan.addCommand(CommandType.CL_SWAP_EXACT_IN, [ MSG_SENDER, planTwoCLAmountIn, sambMinAmountOut2, encodePathExactInput(planTwoTokens), SOURCE_MSG_SENDER, ]) // add the second subplan to the main planner planner.addSubPlan(subplan) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) it('gas: 2 sub-plans, second sub plan fails', async () => { // first split route sub-plan. BOND->SAMB, 2 routes on Classic and CL. const planOneWethMinOut = expandTo18DecimalsBN(0.0005) // Classic trades BOND for USDC, sending the tokens back to the router for cl trade subplan.addCommand(CommandType.CLASSIC_SWAP_EXACT_IN, [ ADDRESS_THIS, planOneClassicAmountIn, 0, planOneTokens, SOURCE_MSG_SENDER, ]) // CL trades USDC for SAMB, trading the whole balance, with a recipient of Alice subplan.addCommand(CommandType.CL_SWAP_EXACT_IN, [ ADDRESS_THIS, planOneCLAmountIn, 0, encodePathExactInput(planOneTokens), SOURCE_MSG_SENDER, ]) // aggregate slippage check subplan.addCommand(CommandType.SWEEP, [SAMB.address, MSG_SENDER, planOneWethMinOut]) // add the subplan to the main planner planner.addSubPlan(subplan) subplan = new RoutePlanner() // second split route sub-plan. USDC->SAMB, 1 route on CL // FAIL: large amount out to cause the swap to revert const sambMinAmountOut2 = expandTo18DecimalsBN(1) // Add the trade to the sub-plan subplan.addCommand(CommandType.CL_SWAP_EXACT_IN, [ MSG_SENDER, planTwoCLAmountIn, sambMinAmountOut2, encodePathExactInput(planTwoTokens), SOURCE_MSG_SENDER, ]) // add the second subplan to the main planner planner.addSubPlan(subplan) const { commands, inputs } = planner await snapshotGasCost(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) }) }) }) }) })