test: add GasTest to compare with Universal Router gas usage
This commit is contained in:
25
foundry/interfaces/IUniversalRouter.sol
Normal file
25
foundry/interfaces/IUniversalRouter.sol
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
pragma solidity ^0.8.24;
|
||||||
|
|
||||||
|
interface IUniversalRouter {
|
||||||
|
/// @notice Thrown when a required command has failed
|
||||||
|
error ExecutionFailed(uint256 commandIndex, bytes message);
|
||||||
|
|
||||||
|
/// @notice Thrown when attempting to send ETH directly to the contract
|
||||||
|
error ETHNotAccepted();
|
||||||
|
|
||||||
|
/// @notice Thrown when executing commands with an expired deadline
|
||||||
|
error TransactionDeadlinePassed();
|
||||||
|
|
||||||
|
/// @notice Thrown when attempting to execute commands and an incorrect number of inputs are provided
|
||||||
|
error LengthMismatch();
|
||||||
|
|
||||||
|
// @notice Thrown when an address that isn't WETH tries to send ETH to the router without calldata
|
||||||
|
error InvalidEthSender();
|
||||||
|
|
||||||
|
/// @notice Executes encoded commands along with provided inputs. Reverts if deadline has expired.
|
||||||
|
/// @param commands A set of concatenated commands, each 1 byte in length
|
||||||
|
/// @param inputs An array of byte strings containing abi encoded inputs for each command
|
||||||
|
/// @param deadline The deadline by which the transaction must be executed
|
||||||
|
function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable;
|
||||||
|
}
|
||||||
@@ -50,6 +50,12 @@ contract Constants is Test, BaseConstants {
|
|||||||
address USV3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
|
address USV3_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
|
||||||
address DAI_WETH_USV3 = 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8;
|
address DAI_WETH_USV3 = 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8;
|
||||||
|
|
||||||
|
// universal router
|
||||||
|
address UNIVERSAL_ROUTER = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af;
|
||||||
|
|
||||||
|
// permit2
|
||||||
|
address PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev Deploys a dummy contract with non-empty bytecode
|
* @dev Deploys a dummy contract with non-empty bytecode
|
||||||
*/
|
*/
|
||||||
|
|||||||
117
foundry/test/GasTest.t.sol
Normal file
117
foundry/test/GasTest.t.sol
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
pragma solidity ^0.8.24;
|
||||||
|
|
||||||
|
import {IUniversalRouter} from "../interfaces/IUniversalRouter.sol";
|
||||||
|
import {IPermit2} from "../lib/permit2/src/interfaces/IPermit2.sol";
|
||||||
|
import {Constants} from "./Constants.sol";
|
||||||
|
import {Actions} from "../lib/v4-periphery/src/libraries/Actions.sol";
|
||||||
|
import {PoolKey} from "../lib/v4-core/src/types/PoolKey.sol";
|
||||||
|
import {IV4Router} from "../lib/v4-periphery/src/interfaces/IV4Router.sol";
|
||||||
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import {Currency} from "../lib/v4-core/src/types/Currency.sol";
|
||||||
|
import {IHooks} from "../lib/v4-core/src/interfaces/IHooks.sol";
|
||||||
|
import "forge-std/Test.sol";
|
||||||
|
|
||||||
|
contract Commands {
|
||||||
|
uint256 constant V2_SWAP_EXACT_IN = 0x08;
|
||||||
|
uint256 constant V3_SWAP_EXACT_IN = 0x00;
|
||||||
|
uint256 constant V4_SWAP = 0x10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A gas test to compare the gas usage of the UniversalRouter with the TychoRouter
|
||||||
|
// The gas usage quoted from the TychoRouter is without any asserts after the swap
|
||||||
|
// The path executed on TychoRouter is the same as the path executed on UniversalRouter
|
||||||
|
contract GasTest is Commands, Test, Constants {
|
||||||
|
IUniversalRouter universalRouter = IUniversalRouter(UNIVERSAL_ROUTER);
|
||||||
|
IPermit2 permit2 = IPermit2(PERMIT2_ADDRESS);
|
||||||
|
|
||||||
|
function setUp() public {
|
||||||
|
uint256 forkBlock = 21817316;
|
||||||
|
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gas usage: 248717
|
||||||
|
// TychoRouter:testSwapSimple costs 255123
|
||||||
|
function testUniversalRouterUniswapV2() public {
|
||||||
|
uint256 amountIn = 10 ** 18;
|
||||||
|
|
||||||
|
bytes memory commands =
|
||||||
|
abi.encodePacked(uint8(Commands.V2_SWAP_EXACT_IN));
|
||||||
|
|
||||||
|
address[] memory path = new address[](2);
|
||||||
|
path[0] = WETH_ADDR;
|
||||||
|
path[1] = DAI_ADDR;
|
||||||
|
|
||||||
|
bytes[] memory inputs = new bytes[](1);
|
||||||
|
inputs[0] = abi.encode(BOB, amountIn, uint256(0), path, false);
|
||||||
|
|
||||||
|
deal(WETH_ADDR, address(universalRouter), amountIn);
|
||||||
|
universalRouter.execute(commands, inputs, block.timestamp + 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gas usage: 251900
|
||||||
|
// TychoRouter:testSwapSingleUSV3 costs 264195
|
||||||
|
function testUniversalRouterUniswapV3() public {
|
||||||
|
uint256 amountIn = 10 ** 18;
|
||||||
|
|
||||||
|
bytes memory commands =
|
||||||
|
abi.encodePacked(uint8(Commands.V3_SWAP_EXACT_IN));
|
||||||
|
|
||||||
|
uint24 poolFee = 3000;
|
||||||
|
bytes memory path = abi.encodePacked(WETH_ADDR, poolFee, DAI_ADDR);
|
||||||
|
|
||||||
|
bytes[] memory inputs = new bytes[](1);
|
||||||
|
inputs[0] = abi.encode(BOB, amountIn, uint256(0), path, false);
|
||||||
|
|
||||||
|
deal(WETH_ADDR, address(universalRouter), amountIn);
|
||||||
|
universalRouter.execute(commands, inputs, block.timestamp + 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gas usage: 299427
|
||||||
|
// TychoRouter:testSwapSingleUSV4Callback costs 286025
|
||||||
|
function testUniversalRouterUniswapV4() public {
|
||||||
|
uint128 amountIn = uint128(100 ether);
|
||||||
|
uint128 amountOutMinimum = uint128(0);
|
||||||
|
uint256 deadline = block.timestamp + 1000;
|
||||||
|
|
||||||
|
bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
|
||||||
|
|
||||||
|
bytes memory actions = abi.encodePacked(
|
||||||
|
uint8(Actions.SWAP_EXACT_IN_SINGLE),
|
||||||
|
uint8(Actions.SETTLE_ALL),
|
||||||
|
uint8(Actions.TAKE_ALL)
|
||||||
|
);
|
||||||
|
|
||||||
|
PoolKey memory key = PoolKey({
|
||||||
|
currency0: Currency.wrap(USDE_ADDR),
|
||||||
|
currency1: Currency.wrap(USDT_ADDR),
|
||||||
|
fee: 100,
|
||||||
|
tickSpacing: int24(1),
|
||||||
|
hooks: IHooks(address(0))
|
||||||
|
});
|
||||||
|
|
||||||
|
bytes[] memory params = new bytes[](3);
|
||||||
|
params[0] = abi.encode(
|
||||||
|
IV4Router.ExactInputSingleParams({
|
||||||
|
poolKey: key,
|
||||||
|
zeroForOne: true,
|
||||||
|
amountIn: amountIn,
|
||||||
|
amountOutMinimum: amountOutMinimum,
|
||||||
|
hookData: bytes("")
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
params[1] = abi.encode(key.currency0, amountIn);
|
||||||
|
params[2] = abi.encode(key.currency1, amountOutMinimum);
|
||||||
|
|
||||||
|
bytes[] memory inputs = new bytes[](1);
|
||||||
|
inputs[0] = abi.encode(actions, params);
|
||||||
|
|
||||||
|
deal(USDE_ADDR, address(this), amountIn);
|
||||||
|
IERC20(USDE_ADDR).approve(PERMIT2_ADDRESS, amountIn);
|
||||||
|
permit2.approve(
|
||||||
|
USDE_ADDR, address(universalRouter), amountIn, uint48(deadline)
|
||||||
|
);
|
||||||
|
universalRouter.execute(commands, inputs, deadline);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -650,7 +650,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
|
|
||||||
// Approve permit2
|
// Approve permit2
|
||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
IERC20(WETH_ADDR).approve(address(permit2Address), type(uint256).max);
|
IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||||
// Encoded solution generated using `test_split_swap_strategy_encoder_simple`
|
// Encoded solution generated using `test_split_swap_strategy_encoder_simple`
|
||||||
// but manually replacing the executor address
|
// but manually replacing the executor address
|
||||||
// `5c2f5a71f67c01775180adc06909288b4c329308` with the one in this test
|
// `5c2f5a71f67c01775180adc06909288b4c329308` with the one in this test
|
||||||
@@ -688,7 +688,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
assertEq(balancerAfter - balancerBefore, 2659881924818443699787);
|
assertEq(balancerAfter - balancerBefore, 2659881924818443699787);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testUSV4Integration4() public {
|
function testUSV4Integration() public {
|
||||||
// Test created with calldata from our router encoder.
|
// Test created with calldata from our router encoder.
|
||||||
|
|
||||||
// Performs a sequential swap from USDC to PEPE though ETH using two
|
// Performs a sequential swap from USDC to PEPE though ETH using two
|
||||||
@@ -701,7 +701,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
|
|
||||||
// Approve permit2
|
// Approve permit2
|
||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
IERC20(USDC_ADDR).approve(address(permit2Address), type(uint256).max);
|
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||||
// Encoded solution generated using `test_split_encoding_strategy_usv4`
|
// Encoded solution generated using `test_split_encoding_strategy_usv4`
|
||||||
// and ensuring that the encoded executor address is the one in this test
|
// and ensuring that the encoded executor address is the one in this test
|
||||||
// `f62849f9a0b5bf2913b396098f7c7019b51a820a`
|
// `f62849f9a0b5bf2913b396098f7c7019b51a820a`
|
||||||
@@ -754,7 +754,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
|
|
||||||
// Approve permit2
|
// Approve permit2
|
||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
IERC20(USDC_ADDR).approve(address(permit2Address), type(uint256).max);
|
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||||
|
|
||||||
// Encoded solution generated using `test_split_encoding_strategy_usv4_eth_out`
|
// Encoded solution generated using `test_split_encoding_strategy_usv4_eth_out`
|
||||||
// and ensuring that the encoded executor address is the one in this test
|
// and ensuring that the encoded executor address is the one in this test
|
||||||
@@ -783,7 +783,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
|
|
||||||
// Approve permit2
|
// Approve permit2
|
||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
// IERC20(WETH_ADDR).approve(address(permit2Address), type(uint256).max);
|
// IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||||
// Encoded solution generated using
|
// Encoded solution generated using
|
||||||
// `test_split_swap_strategy_encoder_simple_route_wrap`
|
// `test_split_swap_strategy_encoder_simple_route_wrap`
|
||||||
// but manually replacing the executor address
|
// but manually replacing the executor address
|
||||||
@@ -812,7 +812,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
|
|
||||||
// Approve permit2
|
// Approve permit2
|
||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
IERC20(DAI_ADDR).approve(address(permit2Address), type(uint256).max);
|
IERC20(DAI_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||||
// Encoded solution generated using
|
// Encoded solution generated using
|
||||||
// `test_split_swap_strategy_encoder_simple_route_unwrap`
|
// `test_split_swap_strategy_encoder_simple_route_unwrap`
|
||||||
// but manually replacing the executor address
|
// but manually replacing the executor address
|
||||||
@@ -844,7 +844,7 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
|
|
||||||
// Approve permit2
|
// Approve permit2
|
||||||
vm.startPrank(ALICE);
|
vm.startPrank(ALICE);
|
||||||
IERC20(WETH_ADDR).approve(address(permit2Address), type(uint256).max);
|
IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||||
// Encoded solution generated using `test_split_swap_strategy_encoder_complex`
|
// Encoded solution generated using `test_split_swap_strategy_encoder_complex`
|
||||||
// but manually replacing the executor address
|
// but manually replacing the executor address
|
||||||
// `5c2f5a71f67c01775180adc06909288b4c329308` with the one in this test
|
// `5c2f5a71f67c01775180adc06909288b4c329308` with the one in this test
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ contract TychoRouterExposed is TychoRouter {
|
|||||||
contract TychoRouterTestSetup is Test, Constants {
|
contract TychoRouterTestSetup is Test, Constants {
|
||||||
TychoRouterExposed tychoRouter;
|
TychoRouterExposed tychoRouter;
|
||||||
address tychoRouterAddr;
|
address tychoRouterAddr;
|
||||||
address permit2Address = address(0x000000000022D473030F116dDEE9F6B43aC78BA3);
|
|
||||||
UniswapV2Executor public usv2Executor;
|
UniswapV2Executor public usv2Executor;
|
||||||
UniswapV3Executor public usv3Executor;
|
UniswapV3Executor public usv3Executor;
|
||||||
UniswapV4Executor public usv4Executor;
|
UniswapV4Executor public usv4Executor;
|
||||||
@@ -48,7 +47,7 @@ contract TychoRouterTestSetup is Test, Constants {
|
|||||||
address factoryV3 = USV3_FACTORY;
|
address factoryV3 = USV3_FACTORY;
|
||||||
address poolManagerAddress = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
address poolManagerAddress = 0x000000000004444c5dc75cB358380D2e3dE08A90;
|
||||||
IPoolManager poolManager = IPoolManager(poolManagerAddress);
|
IPoolManager poolManager = IPoolManager(poolManagerAddress);
|
||||||
tychoRouter = new TychoRouterExposed(permit2Address, WETH_ADDR);
|
tychoRouter = new TychoRouterExposed(PERMIT2_ADDRESS, WETH_ADDR);
|
||||||
tychoRouterAddr = address(tychoRouter);
|
tychoRouterAddr = address(tychoRouter);
|
||||||
tychoRouter.grantRole(keccak256("FUND_RESCUER_ROLE"), FUND_RESCUER);
|
tychoRouter.grantRole(keccak256("FUND_RESCUER_ROLE"), FUND_RESCUER);
|
||||||
tychoRouter.grantRole(keccak256("FEE_SETTER_ROLE"), FEE_SETTER);
|
tychoRouter.grantRole(keccak256("FEE_SETTER_ROLE"), FEE_SETTER);
|
||||||
@@ -107,7 +106,7 @@ contract TychoRouterTestSetup is Test, Constants {
|
|||||||
internal
|
internal
|
||||||
returns (IAllowanceTransfer.PermitSingle memory, bytes memory)
|
returns (IAllowanceTransfer.PermitSingle memory, bytes memory)
|
||||||
{
|
{
|
||||||
IERC20(tokenIn).approve(permit2Address, amount_in);
|
IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in);
|
||||||
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer
|
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer
|
||||||
.PermitSingle({
|
.PermitSingle({
|
||||||
details: IAllowanceTransfer.PermitDetails({
|
details: IAllowanceTransfer.PermitDetails({
|
||||||
@@ -147,7 +146,7 @@ contract TychoRouterTestSetup is Test, Constants {
|
|||||||
),
|
),
|
||||||
keccak256("Permit2"),
|
keccak256("Permit2"),
|
||||||
block.chainid,
|
block.chainid,
|
||||||
permit2Address
|
PERMIT2_ADDRESS
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
bytes32 detailsHash =
|
bytes32 detailsHash =
|
||||||
|
|||||||
Reference in New Issue
Block a user