From b4fdf37619eb3f8fbd4a79cb8065de316cf593f2 Mon Sep 17 00:00:00 2001 From: Charles Jhong Date: Sun, 10 Sep 2023 22:02:29 +0800 Subject: [PATCH] add tests for SwapRouter02 --- contracts/interfaces/IUniswapSwapRouter02.sol | 50 +++++++++++++ test/forkMainnet/UniAgent/SwapRouter02.t.sol | 73 +++++++++++++++++++ 2 files changed, 123 insertions(+) create mode 100644 contracts/interfaces/IUniswapSwapRouter02.sol create mode 100644 test/forkMainnet/UniAgent/SwapRouter02.t.sol diff --git a/contracts/interfaces/IUniswapSwapRouter02.sol b/contracts/interfaces/IUniswapSwapRouter02.sol new file mode 100644 index 00000000..5a2c21b7 --- /dev/null +++ b/contracts/interfaces/IUniswapSwapRouter02.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +interface ISwapRouter02 { + function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] calldata path, address to) external payable returns (uint256 amountOut); + + function swapTokensForExactTokens(uint256 amountOut, uint256 amountInMax, address[] calldata path, address to) external payable returns (uint256 amountIn); + + struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; + } + + function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); + + struct ExactInputParams { + bytes path; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + } + + function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut); + + struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; + } + + function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn); + + struct ExactOutputParams { + bytes path; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + } + + function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn); +} diff --git a/test/forkMainnet/UniAgent/SwapRouter02.t.sol b/test/forkMainnet/UniAgent/SwapRouter02.t.sol new file mode 100644 index 00000000..764dbd4f --- /dev/null +++ b/test/forkMainnet/UniAgent/SwapRouter02.t.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import { ISwapRouter02 } from "contracts/interfaces/IUniswapSwapRouter02.sol"; +import { IUniswapRouterV2 } from "contracts/interfaces/IUniswapRouterV2.sol"; +import { IUniswapV3Quoter } from "contracts/interfaces/IUniswapV3Quoter.sol"; +import { IUniAgent } from "contracts/interfaces/IUniAgent.sol"; +import { UniswapV3 } from "contracts/libraries/UniswapV3.sol"; +import { BalanceSnapshot, Snapshot } from "test/utils/BalanceSnapshot.sol"; +import { UniAgentTest } from "test/forkMainnet/UniAgent/Setup.t.sol"; + +contract SwapRouter02Test is UniAgentTest { + using BalanceSnapshot for Snapshot; + + IUniswapRouterV2 v2Router = IUniswapRouterV2(UNISWAP_V2_ADDRESS); + IUniswapV3Quoter v3Quoter = IUniswapV3Quoter(UNISWAP_V3_QUOTER_ADDRESS); + uint256 defaultOutputAmount; + uint24 defaultFee = 3000; + uint24[] v3Fees = [defaultFee]; + + function setUp() public override { + super.setUp(); + } + + function testV2SwapExactTokensForTokens() public { + // USDT -> CRV + Snapshot memory userInputToken = BalanceSnapshot.take({ owner: user, token: defaultInputToken }); + Snapshot memory recvOutputToken = BalanceSnapshot.take({ owner: recipient, token: defaultOutputToken }); + + uint256[] memory amounts = v2Router.getAmountsOut(defaultInputAmount, defaultPath); + uint256 outputAmount = amounts[amounts.length - 1]; + uint256 minOutputAmount = (defaultOutputAmount * 95) / 100; // default 5% slippage tolerance + bytes memory payload = abi.encodeCall(ISwapRouter02.swapExactTokensForTokens, (defaultInputAmount, minOutputAmount, defaultPath, recipient)); + + vm.prank(user); + uniAgent.swap(IUniAgent.RouterType.SwapRouter02, defaultInputToken, defaultInputAmount, payload, defaultUserPermit); + + userInputToken.assertChange(-int256(defaultInputAmount)); + // recipient should receive exact amount of quote from Uniswap + recvOutputToken.assertChange(int256(outputAmount)); + } + + function testV3ExactInputSingle() public { + // USDT -> CRV + Snapshot memory userInputToken = BalanceSnapshot.take({ owner: user, token: defaultInputToken }); + Snapshot memory recvOutputToken = BalanceSnapshot.take({ owner: recipient, token: defaultOutputToken }); + + bytes memory encodedPath = UniswapV3.encodePath(defaultPath, v3Fees); + defaultOutputAmount = v3Quoter.quoteExactInput(encodedPath, defaultInputAmount); + uint256 minOutputAmount = (defaultOutputAmount * 95) / 100; // default 5% slippage tolerance + bytes memory payload = abi.encodeCall( + ISwapRouter02.exactInputSingle, + ( + ISwapRouter02.ExactInputSingleParams({ + tokenIn: defaultInputToken, + tokenOut: defaultOutputToken, + fee: defaultFee, + recipient: recipient, + amountIn: defaultInputAmount, + amountOutMinimum: minOutputAmount, + sqrtPriceLimitX96: 0 + }) + ) + ); + + vm.prank(user); + uniAgent.swap(IUniAgent.RouterType.SwapRouter02, defaultInputToken, defaultInputAmount, payload, defaultUserPermit); + + userInputToken.assertChange(-int256(defaultInputAmount)); + // recipient should receive exact amount of quote from Uniswap + recvOutputToken.assertChange(int256(defaultOutputAmount)); + } +}