refactor: create Permit2TestHelper

- To avoid duplicating permit2 setup code for TychoRouter and executor tests.
This commit is contained in:
TAMARA LIPOWSKI
2025-04-08 11:31:35 -04:00
committed by Diana Carvalho
parent ca1d474f08
commit 30557e7e54
7 changed files with 106 additions and 178 deletions

View File

@@ -4,7 +4,6 @@ pragma solidity ^0.8.26;
import "@interfaces/IExecutor.sol"; import "@interfaces/IExecutor.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@permit2/src/interfaces/IAllowanceTransfer.sol"; import "@permit2/src/interfaces/IAllowanceTransfer.sol";
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
error ExecutorTransferMethods__InvalidPermit2(); error ExecutorTransferMethods__InvalidPermit2();
@@ -45,10 +44,7 @@ contract ExecutorTransferMethods {
} else if (method == TransferMethod.TRANSFERPERMIT2) { } else if (method == TransferMethod.TRANSFERPERMIT2) {
// Permit2.permit is already called from the TychoRouter // Permit2.permit is already called from the TychoRouter
permit2.transferFrom( permit2.transferFrom(
sender, sender, receiver, uint160(amount), address(tokenIn)
receiver,
uint160(amount),
address(tokenIn)
); );
} else { } else {
// Funds are likely already in pool. Do nothing. // Funds are likely already in pool. Do nothing.

View File

@@ -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);
}
}

View File

@@ -51,7 +51,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSequentialSwaps(); bytes[] memory swaps = _getSequentialSwaps();
tychoRouter.sequentialSwapPermit2( tychoRouter.sequentialSwapPermit2(
@@ -150,7 +150,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSequentialSwaps(); bytes[] memory swaps = _getSequentialSwaps();
@@ -229,7 +229,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(USDC_ADDR, amountIn); ) = handlePermit2Approval(USDC_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = new bytes[](2); bytes[] memory swaps = new bytes[](2);

View File

@@ -19,7 +19,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData = encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
@@ -230,7 +230,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(DAI_ADDR, amountIn); ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData = bytes memory protocolData =
encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true);

View File

@@ -81,7 +81,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSplitSwaps(); bytes[] memory swaps = _getSplitSwaps();
@@ -191,7 +191,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSplitSwaps(); bytes[] memory swaps = _getSplitSwaps();
@@ -282,7 +282,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(DAI_ADDR, amountIn); ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData = bytes memory protocolData =
encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true); encodeUniswapV2Swap(DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true);
@@ -325,7 +325,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
bool zeroForOne = false; bool zeroForOne = false;
@@ -377,7 +377,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData = encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false
@@ -426,7 +426,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(USDE_ADDR, amountIn); ) = handlePermit2Approval(USDE_ADDR, tychoRouterAddr, amountIn);
UniswapV4Executor.UniswapV4Pool[] memory pools = UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](1); new UniswapV4Executor.UniswapV4Pool[](1);

View File

@@ -13,6 +13,7 @@ import "@src/TychoRouter.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol"; import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol";
import {Permit2TestHelper} from "./Permit2TestHelper.sol";
contract TychoRouterExposed is TychoRouter { contract TychoRouterExposed is TychoRouter {
constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {} 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; TychoRouterExposed tychoRouter;
address tychoRouterAddr; address tychoRouterAddr;
UniswapV2Executor public usv2Executor; 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) function pleEncode(bytes[] memory data)
public public
pure pure

View File

@@ -5,6 +5,7 @@ import "@src/executors/UniswapV2Executor.sol";
import "@src/executors/ExecutorTransferMethods.sol"; import "@src/executors/ExecutorTransferMethods.sol";
import {Test} from "../../lib/forge-std/src/Test.sol"; import {Test} from "../../lib/forge-std/src/Test.sol";
import {Constants} from "../Constants.sol"; import {Constants} from "../Constants.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
contract UniswapV2ExecutorExposed is UniswapV2Executor { contract UniswapV2ExecutorExposed is UniswapV2Executor {
constructor(address _factory, bytes32 _initCode, address _permit2) 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; using SafeERC20 for IERC20;
UniswapV2ExecutorExposed uniswapV2Exposed; UniswapV2ExecutorExposed uniswapV2Exposed;
@@ -188,86 +189,6 @@ contract UniswapV2ExecutorTest is Test, Constants {
assertGe(finalBalance, amountOut); 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 { function testSwapWithPermit2TransferFrom() public {
uint256 amountIn = 10 ** 18; uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891; uint256 amountOut = 1847751195973566072891;
@@ -280,13 +201,14 @@ contract UniswapV2ExecutorTest is Test, Constants {
uint8(ExecutorTransferMethods.TransferMethod.TRANSFERPERMIT2) uint8(ExecutorTransferMethods.TransferMethod.TRANSFERPERMIT2)
); );
deal(WETH_ADDR, ALICE, amountIn); deal(WETH_ADDR, ALICE, amountIn);
vm.startPrank(ALICE); vm.startPrank(ALICE);
( (
IAllowanceTransfer.PermitSingle memory permitSingle, IAllowanceTransfer.PermitSingle memory permitSingle,
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, amountIn); ) = handlePermit2Approval(
WETH_ADDR, address(uniswapV2Exposed), amountIn
);
// Assume the permit2.approve method will be called from the TychoRouter // Assume the permit2.approve method will be called from the TychoRouter
// Replicate this secnario in this test. // Replicate this secnario in this test.