UNPKG

@uniswap/universal-router

Version:

Smart contracts for Universal Router

154 lines (130 loc) 5.75 kB
import { UniversalRouter, ERC20, IWETH9, IPermit2 } from '../../typechain' import { Pair } from '@uniswap/v2-sdk' import { expect } from './shared/expect' import { abi as ROUTER_ABI } from '../../artifacts/contracts/UniversalRouter.sol/UniversalRouter.json' import { abi as TOKEN_ABI } from '../../artifacts/solmate/src/tokens/ERC20.sol/ERC20.json' import { abi as WETH_ABI } from '../../artifacts/@uniswap/v4-periphery/src/interfaces/external/IWETH9.sol/IWETH9.json' import deployUniversalRouter from './shared/deployUniversalRouter' import { ADDRESS_THIS, ALICE_ADDRESS, DEADLINE, SOURCE_MSG_SENDER, MAX_UINT160, MAX_UINT, ETH_ADDRESS, } from './shared/constants' import { resetFork, WETH, DAI, PERMIT2 } from './shared/mainnetForkHelpers' import { CommandType, RoutePlanner } from './shared/planner' import { makePair } from './shared/swapRouter02Helpers' import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' import { expandTo18DecimalsBN } from './shared/helpers' import hre from 'hardhat' const { ethers } = hre const routerInterface = new ethers.utils.Interface(ROUTER_ABI) describe('UniversalRouter', () => { let alice: SignerWithAddress let router: UniversalRouter let permit2: IPermit2 let daiContract: ERC20 let wethContract: IWETH9 let pair_DAI_WETH: Pair beforeEach(async () => { await resetFork() alice = await ethers.getSigner(ALICE_ADDRESS) await hre.network.provider.request({ method: 'hardhat_impersonateAccount', params: [ALICE_ADDRESS], }) daiContract = new ethers.Contract(DAI.address, TOKEN_ABI, alice) as ERC20 wethContract = new ethers.Contract(WETH.address, WETH_ABI, alice) as IWETH9 pair_DAI_WETH = await makePair(alice, DAI, WETH) permit2 = PERMIT2.connect(alice) as IPermit2 router = (await deployUniversalRouter(alice.address)).connect(alice) as UniversalRouter }) describe('#execute', () => { let planner: RoutePlanner const invalidCommand: string = '0x3f' beforeEach(async () => { planner = new RoutePlanner() await daiContract.approve(permit2.address, MAX_UINT) await wethContract.approve(permit2.address, MAX_UINT) await permit2.approve(DAI.address, router.address, MAX_UINT160, DEADLINE) await permit2.approve(WETH.address, router.address, MAX_UINT160, DEADLINE) }) it('reverts if block.timestamp exceeds the deadline', async () => { planner.addCommand(CommandType.V2_SWAP_EXACT_IN, [ alice.address, 1, 1, [DAI.address, WETH.address], SOURCE_MSG_SENDER, ]) const invalidDeadline = 10 const { commands, inputs } = planner await expect( router['execute(bytes,bytes[],uint256)'](commands, inputs, invalidDeadline) ).to.be.revertedWithCustomError(router, 'TransactionDeadlinePassed') }) it('reverts for an invalid command at index 0', async () => { const inputs: string[] = ['0x12341234'] await expect(router['execute(bytes,bytes[],uint256)'](invalidCommand, inputs, DEADLINE)) .to.be.revertedWithCustomError(router, 'InvalidCommandType') .withArgs(parseInt(invalidCommand)) }) it('reverts for an invalid command at index 1', async () => { planner.addCommand(CommandType.PERMIT2_TRANSFER_FROM, [ DAI.address, pair_DAI_WETH.liquidityToken.address, expandTo18DecimalsBN(1), ]) let commands = planner.commands let inputs = planner.inputs commands = commands.concat(invalidCommand.slice(2)) inputs.push('0x21341234') await expect(router['execute(bytes,bytes[],uint256)'](commands, inputs, DEADLINE)) .to.be.revertedWithCustomError(router, 'InvalidCommandType') .withArgs(parseInt(invalidCommand)) }) it('reverts if paying a portion over 100% of contract balance', async () => { await daiContract.transfer(router.address, expandTo18DecimalsBN(1)) planner.addCommand(CommandType.PAY_PORTION, [WETH.address, alice.address, 11_000]) planner.addCommand(CommandType.SWEEP, [WETH.address, alice.address, 1]) const { commands, inputs } = planner await expect(router['execute(bytes,bytes[])'](commands, inputs)).to.be.revertedWithCustomError( router, 'InvalidBips' ) }) it('reverts if a malicious contract tries to reenter', async () => { // create malicious calldata to sweep ETH out of the router planner.addCommand(CommandType.SWEEP, [ETH_ADDRESS, alice.address, 0]) let { commands, inputs } = planner const sweepCalldata = routerInterface.encodeFunctionData('execute(bytes,bytes[])', [commands, inputs]) const reentrantWETH = await (await ethers.getContractFactory('ReenteringWETH')).deploy() router = (await deployUniversalRouter(alice.address, undefined, reentrantWETH.address)).connect( alice ) as UniversalRouter await reentrantWETH.setParameters(router.address, sweepCalldata) planner = new RoutePlanner() const value = expandTo18DecimalsBN(1) planner.addCommand(CommandType.WRAP_ETH, [ADDRESS_THIS, value]) ;({ commands, inputs } = planner) await expect(router['execute(bytes,bytes[])'](commands, inputs, { value: value })).to.be.revertedWithCustomError( reentrantWETH, 'NotAllowedReenter' ) }) }) }) describe('UniversalRouter', () => { describe('partial fills', async () => { let planner: RoutePlanner beforeEach(() => { planner = new RoutePlanner() }) // TODO need to rewrite these tests for non-NFT commands it('reverts if no commands are allowed to revert') it('does not revert if failed command allowed to fail') }) })