diff --git a/foundry/test/GasTest.t.sol b/foundry/test/GasTest.t.sol index 33c95a6..2bf9bdd 100644 --- a/foundry/test/GasTest.t.sol +++ b/foundry/test/GasTest.t.sol @@ -11,11 +11,13 @@ 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"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; contract Commands { uint256 constant V2_SWAP_EXACT_IN = 0x08; uint256 constant V3_SWAP_EXACT_IN = 0x00; uint256 constant V4_SWAP = 0x10; + uint256 constant PERMIT2_PERMIT = 0x0a; } // A gas test to compare the gas usage of the UniversalRouter with the TychoRouter @@ -51,24 +53,26 @@ contract GasTest is Commands, Test, Constants { bool isPermit2 = true; uint256 amountIn = 10 ** 18; - bytes memory commands = - abi.encodePacked(uint8(Commands.V2_SWAP_EXACT_IN)); + bytes memory commands = abi.encodePacked( + uint8(Commands.PERMIT2_PERMIT), uint8(Commands.V2_SWAP_EXACT_IN) + ); + + vm.startPrank(ALICE); + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(WETH_ADDR, amountIn); 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, isPermit2); + bytes[] memory inputs = new bytes[](2); + inputs[0] = abi.encode(permitSingle, signature); + inputs[1] = abi.encode(ALICE, amountIn, uint256(0), path, isPermit2); + + deal(WETH_ADDR, ALICE, amountIn); - deal(WETH_ADDR, address(this), amountIn); - IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, amountIn); - permit2.approve( - WETH_ADDR, - address(universalRouter), - uint160(amountIn), - uint48(block.timestamp + 1000) - ); universalRouter.execute(commands, inputs, block.timestamp + 1000); } @@ -93,23 +97,25 @@ contract GasTest is Commands, Test, Constants { bool isPermit2 = true; uint256 amountIn = 10 ** 18; - bytes memory commands = - abi.encodePacked(uint8(Commands.V3_SWAP_EXACT_IN)); + bytes memory commands = abi.encodePacked( + uint8(Commands.PERMIT2_PERMIT), uint8(Commands.V3_SWAP_EXACT_IN) + ); + + vm.startPrank(ALICE); + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(WETH_ADDR, amountIn); 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, isPermit2); + bytes[] memory inputs = new bytes[](2); + inputs[0] = abi.encode(permitSingle, signature); + inputs[1] = abi.encode(ALICE, amountIn, uint256(0), path, isPermit2); + + deal(WETH_ADDR, ALICE, amountIn); - deal(WETH_ADDR, address(this), amountIn); - IERC20(WETH_ADDR).approve(PERMIT2_ADDRESS, amountIn); - permit2.approve( - WETH_ADDR, - address(universalRouter), - uint160(amountIn), - uint48(block.timestamp + 1000) - ); universalRouter.execute(commands, inputs, block.timestamp + 1000); } @@ -118,7 +124,9 @@ contract GasTest is Commands, Test, Constants { uint128 amountOutMinimum = uint128(0); uint256 deadline = block.timestamp + 1000; - bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP)); + bytes memory commands = abi.encodePacked( + uint8(Commands.PERMIT2_PERMIT), uint8(Commands.V4_SWAP) + ); bytes memory actions = abi.encodePacked( uint8(Actions.SWAP_EXACT_IN_SINGLE), @@ -126,6 +134,12 @@ contract GasTest is Commands, Test, Constants { uint8(Actions.TAKE_ALL) ); + vm.startPrank(ALICE); + ( + IAllowanceTransfer.PermitSingle memory permitSingle, + bytes memory signature + ) = handlePermit2Approval(USDE_ADDR, amountIn); + PoolKey memory key = PoolKey({ currency0: Currency.wrap(USDE_ADDR), currency1: Currency.wrap(USDT_ADDR), @@ -148,14 +162,71 @@ contract GasTest is Commands, Test, Constants { 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); + bytes[] memory inputs = new bytes[](2); + inputs[0] = abi.encode(permitSingle, signature); + inputs[1] = abi.encode(actions, params); + + deal(USDE_ADDR, ALICE, amountIn); - 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); } + + function handlePermit2Approval(address tokenIn, uint256 amount_in) + internal + returns (IAllowanceTransfer.PermitSingle memory, bytes memory) + { + IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in); + IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer + .PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: tokenIn, + amount: uint160(amount_in), + expiration: uint48(block.timestamp + 1 days), + nonce: 0 + }), + spender: UNIVERSAL_ROUTER, + sigDeadline: block.timestamp + 1 days + }); + + bytes memory signature = signPermit2(permitSingle, ALICE_PK); + return (permitSingle, signature); + } + + function signPermit2( + IAllowanceTransfer.PermitSingle memory permit, + uint256 privateKey + ) internal view returns (bytes memory) { + bytes32 _PERMIT_DETAILS_TYPEHASH = keccak256( + "PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 _PERMIT_SINGLE_TYPEHASH = keccak256( + "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" + ); + bytes32 domainSeparator = keccak256( + abi.encode( + keccak256( + "EIP712Domain(string name,uint256 chainId,address verifyingContract)" + ), + keccak256("Permit2"), + block.chainid, + PERMIT2_ADDRESS + ) + ); + bytes32 detailsHash = + keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); + bytes32 permitHash = keccak256( + abi.encode( + _PERMIT_SINGLE_TYPEHASH, + detailsHash, + permit.spender, + permit.sigDeadline + ) + ); + + bytes32 digest = + keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest); + + return abi.encodePacked(r, s, v); + } }