pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import {ClientFeeParams} from "@src/TychoRouter.sol";
import {Constants} from "./Constants.sol";
contract ClientFeeTestHelper is Test, Constants {
bytes32 private constant _CLIENT_FEE_TYPEHASH = keccak256(
"ClientFee(uint16 clientFeeBps,address clientFeeReceiver,"
"uint256 maxClientContribution,uint256 deadline,"
"uint256 amountIn,address tokenIn,address tokenOut,"
"uint256 minAmountOut,address receiver,bytes swaps)"
);
/**
* @dev Signs a ClientFeeParams struct with the given private key,
* producing the EIP-712 signature expected by TychoRouter.
*/
function signClientFee(
ClientFeeParams memory params,
uint256 amountIn,
address tokenIn,
address tokenOut,
uint256 minAmountOut,
address receiver,
bytes memory swapData,
address routerAddress,
uint256 privateKey
) internal view returns (bytes memory signature) {
return signClientFeeForChain(
params,
amountIn,
tokenIn,
tokenOut,
minAmountOut,
receiver,
swapData,
routerAddress,
block.chainid,
privateKey
);
}
/**
* @dev Signs a ClientFeeParams struct for a specific chain ID.
* Used to test that signatures from a different chain are rejected.
*/
function signClientFeeForChain(
ClientFeeParams memory params,
uint256 amountIn,
address tokenIn,
address tokenOut,
uint256 minAmountOut,
address receiver,
bytes memory swapData,
address routerAddress,
uint256 chainId,
uint256 privateKey
) internal view returns (bytes memory signature) {
bytes32 domainSeparator = keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,"
"uint256 chainId,address verifyingContract)"
),
keccak256("TychoRouter"),
keccak256("1"),
chainId,
routerAddress
)
);
bytes32 structHash = keccak256(
abi.encode(
_CLIENT_FEE_TYPEHASH,
params.clientFeeBps,
params.clientFeeReceiver,
params.maxClientContribution,
params.deadline,
amountIn,
tokenIn,
tokenOut,
minAmountOut,
receiver,
keccak256(swapData)
)
);
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", domainSeparator, structHash)
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
return abi.encodePacked(r, s, v);
}
/**
* @dev Returns an empty ClientFeeParams for calls that do not use client fees.
*/
function noClientFee()
internal
pure
returns (ClientFeeParams memory params)
{
params = ClientFeeParams({
clientFeeBps: 0,
clientFeeReceiver: address(0),
maxClientContribution: 0,
deadline: 0,
clientSignature: new bytes(0)
});
}
/**
* @dev Builds and signs a ClientFeeParams struct using the given private key.
* The signer address is derived from the private key and used as clientFeeReceiver.
*/
function makeClientFeeParams(
uint16 clientFeeBps,
uint256 maxClientContribution,
uint256 amountIn,
address tokenIn,
address tokenOut,
uint256 minAmountOut,
address receiver,
bytes memory swapData,
address routerAddress,
uint256 privateKey
) internal view returns (ClientFeeParams memory params) {
address feeReceiver = vm.addr(privateKey);
params = ClientFeeParams({
clientFeeBps: clientFeeBps,
clientFeeReceiver: feeReceiver,
maxClientContribution: maxClientContribution,
deadline: block.timestamp + 1 hours,
clientSignature: new bytes(0)
});
params.clientSignature = signClientFee(
params,
amountIn,
tokenIn,
tokenOut,
minAmountOut,
receiver,
swapData,
routerAddress,
privateKey
);
}
}