diff --git a/foundry/src/executors/ExecutorTransferMethods.sol b/foundry/src/executors/ExecutorTransferMethods.sol index d14d4d3..0e9772e 100644 --- a/foundry/src/executors/ExecutorTransferMethods.sol +++ b/foundry/src/executors/ExecutorTransferMethods.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@permit2/src/interfaces/IAllowanceTransfer.sol"; -import "@permit2/src/interfaces/IAllowanceTransfer.sol"; error ExecutorTransferMethods__InvalidPermit2(); @@ -45,10 +44,7 @@ contract ExecutorTransferMethods { } else if (method == TransferMethod.TRANSFERPERMIT2) { // Permit2.permit is already called from the TychoRouter permit2.transferFrom( - sender, - receiver, - uint160(amount), - address(tokenIn) + sender, receiver, uint160(amount), address(tokenIn) ); } else { // Funds are likely already in pool. Do nothing. diff --git a/foundry/test/Permit2TestHelper.sol b/foundry/test/Permit2TestHelper.sol new file mode 100644 index 0000000..912b007 --- /dev/null +++ b/foundry/test/Permit2TestHelper.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "./Constants.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract Permit2TestHelper is Constants { + /** + * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract + * to spend `amount_in` of `tokenIn` on her behalf. + * + * This function approves the Permit2 contract to transfer the specified token amount + * and constructs a `PermitSingle` struct for the approval. It also generates a valid + * EIP-712 signature for the approval using Alice's private key. + * + * @param tokenIn The address of the token being approved. + * @param amount_in The amount of tokens to approve for transfer. + * @return permitSingle The `PermitSingle` struct containing the approval details. + * @return signature The EIP-712 signature for the approval. + */ + function handlePermit2Approval( + address tokenIn, + address spender, + 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: spender, + sigDeadline: block.timestamp + 1 days + }); + + bytes memory signature = signPermit2(permitSingle, ALICE_PK); + return (permitSingle, signature); + } + + /** + * @dev Signs a Permit2 `PermitSingle` struct with the given private key. + * @param permit The `PermitSingle` struct to sign. + * @param privateKey The private key of the signer. + * @return The signature as a `bytes` array. + */ + 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); + } +} diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index c5db19f..46a9936 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -51,7 +51,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSequentialSwaps(); tychoRouter.sequentialSwapPermit2( @@ -150,7 +150,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSequentialSwaps(); @@ -229,7 +229,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(USDC_ADDR, amountIn); + ) = handlePermit2Approval(USDC_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = new bytes[](2); diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index ea33401..648d9e9 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -19,7 +19,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false @@ -230,7 +230,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(DAI_ADDR, amountIn); + ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 0887500..a9c593a 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -81,7 +81,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSplitSwaps(); @@ -191,7 +191,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes[] memory swaps = _getSplitSwaps(); @@ -282,7 +282,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(DAI_ADDR, amountIn); + ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); @@ -325,7 +325,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI bool zeroForOne = false; @@ -377,7 +377,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); bytes memory protocolData = encodeUniswapV2Swap( WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false @@ -426,7 +426,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(USDE_ADDR, amountIn); + ) = handlePermit2Approval(USDE_ADDR, tychoRouterAddr, amountIn); UniswapV4Executor.UniswapV4Pool[] memory pools = new UniswapV4Executor.UniswapV4Pool[](1); diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index a53614c..e9bc79e 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -13,6 +13,7 @@ import "@src/TychoRouter.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol"; +import {Permit2TestHelper} from "./Permit2TestHelper.sol"; contract TychoRouterExposed is TychoRouter { constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {} @@ -41,7 +42,7 @@ contract TychoRouterExposed is TychoRouter { } } -contract TychoRouterTestSetup is Constants { +contract TychoRouterTestSetup is Constants, Permit2TestHelper { TychoRouterExposed tychoRouter; address tychoRouterAddr; UniswapV2Executor public usv2Executor; @@ -132,84 +133,6 @@ contract TychoRouterTestSetup is Constants { } } - /** - * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract - * to spend `amount_in` of `tokenIn` on her behalf. - * - * This function approves the Permit2 contract to transfer the specified token amount - * and constructs a `PermitSingle` struct for the approval. It also generates a valid - * EIP-712 signature for the approval using Alice's private key. - * - * @param tokenIn The address of the token being approved. - * @param amount_in The amount of tokens to approve for transfer. - * @return permitSingle The `PermitSingle` struct containing the approval details. - * @return signature The EIP-712 signature for the approval. - */ - 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: tychoRouterAddr, - sigDeadline: block.timestamp + 1 days - }); - - bytes memory signature = signPermit2(permitSingle, ALICE_PK); - return (permitSingle, signature); - } - - /** - * @dev Signs a Permit2 `PermitSingle` struct with the given private key. - * @param permit The `PermitSingle` struct to sign. - * @param privateKey The private key of the signer. - * @return The signature as a `bytes` array. - */ - 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); - } - function pleEncode(bytes[] memory data) public pure diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 365d3e7..42abec1 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -5,6 +5,7 @@ import "@src/executors/UniswapV2Executor.sol"; import "@src/executors/ExecutorTransferMethods.sol"; import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; +import {Permit2TestHelper} from "../Permit2TestHelper.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { constructor(address _factory, bytes32 _initCode, address _permit2) @@ -48,7 +49,7 @@ contract FakeUniswapV2Pool { } } -contract UniswapV2ExecutorTest is Test, Constants { +contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { using SafeERC20 for IERC20; UniswapV2ExecutorExposed uniswapV2Exposed; @@ -188,86 +189,6 @@ contract UniswapV2ExecutorTest is Test, Constants { assertGe(finalBalance, amountOut); } - // TODO generalize these next two methods - don't reuse from TychoRouterTestSetup - /** - * @dev Handles the Permit2 approval process for Alice, allowing the TychoRouter contract - * to spend `amount_in` of `tokenIn` on her behalf. - * - * This function approves the Permit2 contract to transfer the specified token amount - * and constructs a `PermitSingle` struct for the approval. It also generates a valid - * EIP-712 signature for the approval using Alice's private key. - * - * @param tokenIn The address of the token being approved. - * @param amount_in The amount of tokens to approve for transfer. - * @return permitSingle The `PermitSingle` struct containing the approval details. - * @return signature The EIP-712 signature for the approval. - */ - 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: address(uniswapV2Exposed), - sigDeadline: block.timestamp + 1 days - }); - - bytes memory signature = signPermit2(permitSingle, ALICE_PK); - return (permitSingle, signature); - } - - /** - * @dev Signs a Permit2 `PermitSingle` struct with the given private key. - * @param permit The `PermitSingle` struct to sign. - * @param privateKey The private key of the signer. - * @return The signature as a `bytes` array. - */ - 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); - } - - function testSwapWithPermit2TransferFrom() public { uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; @@ -280,13 +201,14 @@ contract UniswapV2ExecutorTest is Test, Constants { uint8(ExecutorTransferMethods.TransferMethod.TRANSFERPERMIT2) ); - deal(WETH_ADDR, ALICE, amountIn); vm.startPrank(ALICE); ( IAllowanceTransfer.PermitSingle memory permitSingle, bytes memory signature - ) = handlePermit2Approval(WETH_ADDR, amountIn); + ) = handlePermit2Approval( + WETH_ADDR, address(uniswapV2Exposed), amountIn + ); // Assume the permit2.approve method will be called from the TychoRouter // Replicate this secnario in this test.