diff --git a/Cargo.lock b/Cargo.lock index ede44ce..59ba3d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4445,9 +4445,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tycho-common" -version = "0.66.4" +version = "0.70.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5131fdb21cbd754822b0947fc6c763494531837ba8bb34123f6c7f4f89cb69f7" +checksum = "5237d0e4ab6979a1ca9cdb749a2a97240ca6dc716c0da6f42543960d3141255a" dependencies = [ "anyhow", "async-trait", diff --git a/Cargo.toml b/Cargo.toml index f97e4ff..9061925 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ clap = { version = "4.5.3", features = ["derive"] } alloy = { version = "0.9.2", features = ["providers", "rpc-types-eth", "eip712", "signer-local"], optional = true } alloy-sol-types = { version = "0.8.14", optional = true } alloy-primitives = { version = "0.8.9", optional = true } -tycho-common = "^0.66.4" +tycho-common = ">0.66.4" once_cell = "1.20.2" [dev-dependencies] diff --git a/foundry/src/Dispatcher.sol b/foundry/src/Dispatcher.sol index dcbd1b4..6e5c582 100644 --- a/foundry/src/Dispatcher.sol +++ b/foundry/src/Dispatcher.sol @@ -10,7 +10,6 @@ error Dispatcher__InvalidDataLength(); /** * @title Dispatcher - Dispatch execution to external contracts - * @author PropellerHeads Devs * @dev Provides the ability to delegate execution of swaps to external * contracts. This allows dynamically adding new supported protocols * without needing to upgrade any contracts. External contracts will diff --git a/foundry/src/RestrictTransferFrom.sol b/foundry/src/RestrictTransferFrom.sol new file mode 100644 index 0000000..db9e250 --- /dev/null +++ b/foundry/src/RestrictTransferFrom.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.26; + +import "@interfaces/IExecutor.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "@permit2/src/interfaces/IAllowanceTransfer.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; + +error RestrictTransferFrom__AddressZero(); +error RestrictTransferFrom__ExceededTransferFromAllowance( + uint256 allowedAmount, uint256 amountAttempted +); +error RestrictTransferFrom__UnknownTransferType(); + +/** + * @title RestrictTransferFrom - Restrict transferFrom upto allowed amount of token + * @dev Restricts to one `transferFrom` (using `permit2` or regular `transferFrom`) + * per swap, while ensuring that the `transferFrom` is only performed on the input + * token upto input amount, from the msg.sender's wallet that calls the main swap + * method. Reverts if `transferFrom`s are attempted above this allowed amount. + */ +contract RestrictTransferFrom { + using SafeERC20 for IERC20; + + IAllowanceTransfer public immutable permit2; + // keccak256("Dispatcher#TOKEN_IN_SLOT") + uint256 private constant _TOKEN_IN_SLOT = + 0x66f353cfe8e3cbe0d03292348fbf0fca32e6e07fa0c2a52b4aac22193ac3b894; + // keccak256("Dispatcher#AMOUNT_ALLOWED_SLOT") + uint256 private constant _AMOUNT_ALLOWED_SLOT = + 0xc76591aca92830b1554f3dcc7893e7519ec7c57bd4e64fec0c546d9078033291; + // keccak256("Dispatcher#IS_PERMIT2_SLOT") + uint256 private constant _IS_PERMIT2_SLOT = + 0x3162c9d1175ca0ca7441f87984fdac41bbfdb13246f42c8bb4414d345da39e2a; + // keccak256("Dispatcher#SENDER_SLOT") + uint256 private constant _SENDER_SLOT = + 0x5dcc7974be5cb30f183f878073999aaa6620995b9e052ab5a713071ff60ae9b5; + // keccak256("Dispatcher#AMOUNT_SPENT_SLOT") + uint256 private constant _AMOUNT_SPENT_SLOT = + 0x56044a5eb3aa5bd3ad908b7f15d1e8cb830836bb4ad178a0bf08955c94c40d30; + + constructor(address _permit2) { + if (_permit2 == address(0)) { + revert RestrictTransferFrom__AddressZero(); + } + permit2 = IAllowanceTransfer(_permit2); + } + + enum TransferType { + TransferFrom, + Transfer, + None + } + + /** + * @dev This function is used to store the transfer information in the + * contract's storage. This is done as the first step in the swap process in TychoRouter. + */ + // slither-disable-next-line assembly + function _tstoreTransferFromInfo( + address tokenIn, + uint256 amountIn, + bool isPermit2, + bool transferFromNeeded + ) internal { + uint256 amountAllowed = amountIn; + if (!transferFromNeeded) { + amountAllowed = 0; + } + assembly { + tstore(_TOKEN_IN_SLOT, tokenIn) + tstore(_AMOUNT_ALLOWED_SLOT, amountAllowed) + tstore(_IS_PERMIT2_SLOT, isPermit2) + tstore(_SENDER_SLOT, caller()) + tstore(_AMOUNT_SPENT_SLOT, 0) + } + } + + /** + * @dev This function is used to transfer the tokens from the sender to the receiver. + * This function is called within the Executor contracts. + * If the TransferType is TransferFrom, it will check if the amount is within the allowed amount and transfer those funds from the user. + * If the TransferType is Transfer, it will transfer the funds from the TychoRouter to the receiver. + * If the TransferType is None, it will do nothing. + */ + // slither-disable-next-line assembly + function _transfer( + address receiver, + TransferType transferType, + address tokenIn, + uint256 amount + ) internal { + if (transferType == TransferType.TransferFrom) { + bool isPermit2; + address sender; + uint256 amountSpent; + uint256 amountAllowed; + assembly { + tokenIn := tload(_TOKEN_IN_SLOT) + amountAllowed := tload(_AMOUNT_ALLOWED_SLOT) + isPermit2 := tload(_IS_PERMIT2_SLOT) + sender := tload(_SENDER_SLOT) + amountSpent := tload(_AMOUNT_SPENT_SLOT) + } + uint256 amountAttempted = amountSpent + amount; + if (amountAttempted > amountAllowed) { + revert RestrictTransferFrom__ExceededTransferFromAllowance( + amountAllowed, amountAttempted + ); + } + assembly { + tstore(_AMOUNT_SPENT_SLOT, amountAttempted) + } + if (isPermit2) { + // Permit2.permit is already called from the TychoRouter + permit2.transferFrom(sender, receiver, uint160(amount), tokenIn); + } else { + // slither-disable-next-line arbitrary-send-erc20 + IERC20(tokenIn).safeTransferFrom(sender, receiver, amount); + } + } else if (transferType == TransferType.Transfer) { + if (tokenIn == address(0)) { + Address.sendValue(payable(receiver), amount); + } else { + IERC20(tokenIn).safeTransfer(receiver, amount); + } + } else if (transferType == TransferType.None) { + return; + } else { + revert RestrictTransferFrom__UnknownTransferType(); + } + } +} diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 5c41e9f..503f09b 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -14,6 +14,7 @@ import "@permit2/src/interfaces/IAllowanceTransfer.sol"; import "./Dispatcher.sol"; import {LibSwap} from "../lib/LibSwap.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; +import {RestrictTransferFrom} from "./RestrictTransferFrom.sol"; // ✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷ // ✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷✷ @@ -65,8 +66,13 @@ error TychoRouter__MessageValueMismatch(uint256 value, uint256 amount); error TychoRouter__InvalidDataLength(); error TychoRouter__UndefinedMinAmountOut(); -contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { - IAllowanceTransfer public immutable permit2; +contract TychoRouter is + AccessControl, + Dispatcher, + Pausable, + ReentrancyGuard, + RestrictTransferFrom +{ IWETH private immutable _weth; using SafeERC20 for IERC20; @@ -87,7 +93,9 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address indexed token, uint256 amount, address indexed receiver ); - constructor(address _permit2, address weth) { + constructor(address _permit2, address weth) + RestrictTransferFrom(_permit2) + { if (_permit2 == address(0) || weth == address(0)) { revert TychoRouter__AddressZero(); } @@ -115,6 +123,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { * @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver. * @param nTokens The total number of tokens involved in the swap graph (used to initialize arrays for internal calculations). * @param receiver The address to receive the output tokens. + * @param transferFromNeeded If false, the contract will assume that the input token is already transferred to the contract and don't allow any transferFroms * @param swaps Encoded swap graph data containing details of each swap. * * @return amountOut The total amount of the output token received by the receiver. @@ -128,13 +137,18 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bool unwrapEth, uint256 nTokens, address receiver, + bool transferFromNeeded, bytes calldata swaps ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { + uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); + _tstoreTransferFromInfo(tokenIn, amountIn, false, transferFromNeeded); + return _splitSwapChecked( amountIn, tokenIn, tokenOut, minAmountOut, + initialBalanceTokenOut, wrapEth, unwrapEth, nTokens, @@ -182,16 +196,19 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bytes calldata signature, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { + uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); // For native ETH, assume funds already in our router. Else, handle approval. if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); } + _tstoreTransferFromInfo(tokenIn, amountIn, true, true); return _splitSwapChecked( amountIn, tokenIn, tokenOut, minAmountOut, + initialBalanceTokenOut, wrapEth, unwrapEth, nTokens, @@ -218,6 +235,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { * @param wrapEth If true, wraps the input token (native ETH) into WETH. * @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver. * @param receiver The address to receive the output tokens. + * @param transferFromNeeded If false, the contract will assume that the input token is already transferred to the contract and don't allow any transferFroms * @param swaps Encoded swap graph data containing details of each swap. * * @return amountOut The total amount of the output token received by the receiver. @@ -230,13 +248,18 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bool wrapEth, bool unwrapEth, address receiver, + bool transferFromNeeded, bytes calldata swaps ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { + uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); + _tstoreTransferFromInfo(tokenIn, amountIn, false, transferFromNeeded); + return _sequentialSwapChecked( amountIn, tokenIn, tokenOut, minAmountOut, + initialBalanceTokenOut, wrapEth, unwrapEth, receiver, @@ -280,16 +303,20 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bytes calldata signature, bytes calldata swaps ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { + uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); // For native ETH, assume funds already in our router. Else, handle approval. if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); } + _tstoreTransferFromInfo(tokenIn, amountIn, true, true); + return _sequentialSwapChecked( amountIn, tokenIn, tokenOut, minAmountOut, + initialBalanceTokenOut, wrapEth, unwrapEth, receiver, @@ -313,6 +340,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { * @param wrapEth If true, wraps the input token (native ETH) into WETH. * @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver. * @param receiver The address to receive the output tokens. + * @param transferFromNeeded If false, the contract will assume that the input token is already transferred to the contract and don't allow any transferFroms * @param swapData Encoded swap details. * * @return amountOut The total amount of the output token received by the receiver. @@ -325,13 +353,18 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bool wrapEth, bool unwrapEth, address receiver, + bool transferFromNeeded, bytes calldata swapData ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { + uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); + _tstoreTransferFromInfo(tokenIn, amountIn, false, transferFromNeeded); + return _singleSwap( amountIn, tokenIn, tokenOut, minAmountOut, + initialBalanceTokenOut, wrapEth, unwrapEth, receiver, @@ -375,16 +408,19 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { bytes calldata signature, bytes calldata swapData ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { + uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); // For native ETH, assume funds already in our router. Else, handle approval. if (tokenIn != address(0)) { permit2.permit(msg.sender, permitSingle, signature); } + _tstoreTransferFromInfo(tokenIn, amountIn, true, true); return _singleSwap( amountIn, tokenIn, tokenOut, minAmountOut, + initialBalanceTokenOut, wrapEth, unwrapEth, receiver, @@ -405,6 +441,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address tokenIn, address tokenOut, uint256 minAmountOut, + uint256 initialBalanceTokenOut, bool wrapEth, bool unwrapEth, uint256 nTokens, @@ -424,7 +461,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { tokenIn = address(_weth); } - uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); amountOut = _splitSwap(amountIn, nTokens, swaps); if (amountOut < minAmountOut) { @@ -459,6 +495,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address tokenIn, address tokenOut, uint256 minAmountOut, + uint256 initialBalanceTokenOut, bool wrapEth, bool unwrapEth, address receiver, @@ -480,7 +517,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { (address executor, bytes calldata protocolData) = swap_.decodeSingleSwap(); - uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); amountOut = _callSwapOnExecutor(executor, amountIn, protocolData); if (amountOut < minAmountOut) { @@ -515,6 +551,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { address tokenIn, address tokenOut, uint256 minAmountOut, + uint256 initialBalanceTokenOut, bool wrapEth, bool unwrapEth, address receiver, @@ -533,7 +570,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { tokenIn = address(_weth); } - uint256 initialBalanceTokenOut = _balanceOf(tokenOut, receiver); amountOut = _sequentialSwap(amountIn, swaps); if (amountOut < minAmountOut) { diff --git a/foundry/src/executors/BalancerV2Executor.sol b/foundry/src/executors/BalancerV2Executor.sol index 00b772a..43cc7d5 100644 --- a/foundry/src/executors/BalancerV2Executor.sol +++ b/foundry/src/executors/BalancerV2Executor.sol @@ -10,16 +10,16 @@ import { import {IAsset} from "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol"; // slither-disable-next-line solc-version import {IVault} from "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; -import {TokenTransfer} from "./TokenTransfer.sol"; +import {RestrictTransferFrom} from "../RestrictTransferFrom.sol"; error BalancerV2Executor__InvalidDataLength(); -contract BalancerV2Executor is IExecutor, TokenTransfer { +contract BalancerV2Executor is IExecutor, RestrictTransferFrom { using SafeERC20 for IERC20; address private constant VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; - constructor(address _permit2) TokenTransfer(_permit2) {} + constructor(address _permit2) RestrictTransferFrom(_permit2) {} // slither-disable-next-line locked-ether function swap(uint256 givenAmount, bytes calldata data) @@ -32,21 +32,13 @@ contract BalancerV2Executor is IExecutor, TokenTransfer { IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval, + bool approvalNeeded, TransferType transferType ) = _decodeData(data); - _transfer( - address(tokenIn), - msg.sender, - // Receiver can never be the pool, since the pool expects funds in the router contract - // Thus, this call will only ever be used to transfer funds from the user into the router. - address(this), - givenAmount, - transferType - ); + _transfer(address(this), transferType, address(tokenIn), givenAmount); - if (needsApproval) { + if (approvalNeeded) { // slither-disable-next-line unused-return tokenIn.forceApprove(VAULT, type(uint256).max); } @@ -81,7 +73,7 @@ contract BalancerV2Executor is IExecutor, TokenTransfer { IERC20 tokenOut, bytes32 poolId, address receiver, - bool needsApproval, + bool approvalNeeded, TransferType transferType ) { @@ -93,7 +85,7 @@ contract BalancerV2Executor is IExecutor, TokenTransfer { tokenOut = IERC20(address(bytes20(data[20:40]))); poolId = bytes32(data[40:72]); receiver = address(bytes20(data[72:92])); - needsApproval = uint8(data[92]) > 0; + approvalNeeded = data[92] != 0; transferType = TransferType(uint8(data[93])); } } diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index e10a213..517db9b 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -3,8 +3,8 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./TokenTransfer.sol"; import "@openzeppelin/contracts/utils/Address.sol"; +import {RestrictTransferFrom} from "../RestrictTransferFrom.sol"; error CurveExecutor__AddressZero(); error CurveExecutor__InvalidDataLength(); @@ -35,13 +35,13 @@ interface CryptoPoolETH { // slither-disable-end naming-convention } -contract CurveExecutor is IExecutor, TokenTransfer { +contract CurveExecutor is IExecutor, RestrictTransferFrom { using SafeERC20 for IERC20; address public immutable nativeToken; constructor(address _nativeToken, address _permit2) - TokenTransfer(_permit2) + RestrictTransferFrom(_permit2) { if (_nativeToken == address(0)) { revert CurveExecutor__AddressZero(); @@ -64,25 +64,16 @@ contract CurveExecutor is IExecutor, TokenTransfer { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded, + bool approvalNeeded, TransferType transferType, address receiver ) = _decodeData(data); - _transfer( - tokenIn, - msg.sender, - // Receiver can never be the pool, since the pool expects funds in the router contract - // Thus, this call will only ever be used to transfer funds from the user into the router. - address(this), - amountIn, - transferType - ); - - if (tokenApprovalNeeded && tokenIn != nativeToken) { + if (approvalNeeded && tokenIn != nativeToken) { // slither-disable-next-line unused-return IERC20(tokenIn).forceApprove(address(pool), type(uint256).max); } + _transfer(address(this), transferType, tokenIn, amountIn); /// Inspired by Curve's router contract: https://github.com/curvefi/curve-router-ng/blob/9ab006ca848fc7f1995b6fbbecfecc1e0eb29e2a/contracts/Router.vy#L44 uint256 balanceBefore = _balanceOf(tokenOut); @@ -133,7 +124,7 @@ contract CurveExecutor is IExecutor, TokenTransfer { uint8 poolType, int128 i, int128 j, - bool tokenApprovalNeeded, + bool approvalNeeded, TransferType transferType, address receiver ) @@ -144,7 +135,7 @@ contract CurveExecutor is IExecutor, TokenTransfer { poolType = uint8(data[60]); i = int128(uint128(uint8(data[61]))); j = int128(uint128(uint8(data[62]))); - tokenApprovalNeeded = data[63] != 0; + approvalNeeded = data[63] != 0; transferType = TransferType(uint8(data[64])); receiver = address(bytes20(data[65:85])); } diff --git a/foundry/src/executors/EkuboExecutor.sol b/foundry/src/executors/EkuboExecutor.sol index 25d40ab..63a57b3 100644 --- a/foundry/src/executors/EkuboExecutor.sol +++ b/foundry/src/executors/EkuboExecutor.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IExecutor} from "@interfaces/IExecutor.sol"; import {ICallback} from "@interfaces/ICallback.sol"; import {ICore} from "@ekubo/interfaces/ICore.sol"; @@ -11,14 +11,15 @@ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol"; import {LibBytes} from "@solady/utils/LibBytes.sol"; import {Config, EkuboPoolKey} from "@ekubo/types/poolKey.sol"; import {MAX_SQRT_RATIO, MIN_SQRT_RATIO} from "@ekubo/types/sqrtRatio.sol"; -import {TokenTransfer} from "./TokenTransfer.sol"; +import {RestrictTransferFrom} from "../RestrictTransferFrom.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback, - TokenTransfer + RestrictTransferFrom { error EkuboExecutor__InvalidDataLength(); error EkuboExecutor__CoreOnly(); @@ -26,13 +27,17 @@ contract EkuboExecutor is ICore immutable core; - uint256 constant POOL_DATA_OFFSET = 77; + uint256 constant POOL_DATA_OFFSET = 57; uint256 constant HOP_BYTE_LEN = 52; bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256) bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address) - constructor(address _core, address _permit2) TokenTransfer(_permit2) { + using SafeERC20 for IERC20; + + constructor(address _core, address _permit2) + RestrictTransferFrom(_permit2) + { core = ICore(_core); } @@ -41,16 +46,11 @@ contract EkuboExecutor is payable returns (uint256 calculatedAmount) { - if (data.length < 93) revert EkuboExecutor__InvalidDataLength(); + if (data.length < 92) revert EkuboExecutor__InvalidDataLength(); // amountIn must be at most type(int128).MAX - calculatedAmount = uint256( - _lock( - bytes.concat( - bytes16(uint128(amountIn)), bytes20(msg.sender), data - ) - ) - ); + calculatedAmount = + uint256(_lock(bytes.concat(bytes16(uint128(amountIn)), data))); } function handleCallback(bytes calldata raw) @@ -125,11 +125,9 @@ contract EkuboExecutor is function _locked(bytes calldata swapData) internal returns (int128) { int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16]))); uint128 tokenInDebtAmount = uint128(nextAmountIn); - address sender = address(bytes20(swapData[16:36])); - uint8 transferType = uint8(swapData[36]); - - address receiver = address(bytes20(swapData[37:57])); - address tokenIn = address(bytes20(swapData[57:77])); + TransferType transferType = TransferType(uint8(swapData[16])); + address receiver = address(bytes20(swapData[17:37])); + address tokenIn = address(bytes20(swapData[37:57])); address nextTokenIn = tokenIn; @@ -163,17 +161,14 @@ contract EkuboExecutor is offset += HOP_BYTE_LEN; } - _pay(tokenIn, tokenInDebtAmount, sender, transferType); + _pay(tokenIn, tokenInDebtAmount, transferType); core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn)); return nextAmountIn; } - function _pay( - address token, - uint128 amount, - address sender, - uint8 transferType - ) internal { + function _pay(address token, uint128 amount, TransferType transferType) + internal + { address target = address(core); if (token == NATIVE_TOKEN_ADDRESS) { @@ -186,11 +181,10 @@ contract EkuboExecutor is mstore(free, shl(224, 0x0c11dedd)) mstore(add(free, 4), token) mstore(add(free, 36), shl(128, amount)) - mstore(add(free, 52), shl(96, sender)) - mstore(add(free, 72), shl(248, transferType)) + mstore(add(free, 52), shl(248, transferType)) - // 4 (selector) + 32 (token) + 16 (amount) + 20 (sender) + 1 (transferType) = 73 - if iszero(call(gas(), target, 0, free, 73, 0, 0)) { + // 4 (selector) + 32 (token) + 16 (amount) + 1 (transferType) = 53 + if iszero(call(gas(), target, 0, free, 53, 0, 0)) { returndatacopy(0, 0, returndatasize()) revert(0, returndatasize()) } @@ -201,9 +195,8 @@ contract EkuboExecutor is function _payCallback(bytes calldata payData) internal { address token = address(bytes20(payData[12:32])); // This arg is abi-encoded uint128 amount = uint128(bytes16(payData[32:48])); - address sender = address(bytes20(payData[48:68])); - TransferType transferType = TransferType(uint8(payData[68])); - _transfer(token, sender, address(core), amount, transferType); + TransferType transferType = TransferType(uint8(payData[48])); + _transfer(address(core), transferType, token, amount); } // To receive withdrawals from Core diff --git a/foundry/src/executors/MaverickV2Executor.sol b/foundry/src/executors/MaverickV2Executor.sol index caf8ff0..bded021 100644 --- a/foundry/src/executors/MaverickV2Executor.sol +++ b/foundry/src/executors/MaverickV2Executor.sol @@ -3,18 +3,21 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "./TokenTransfer.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; +import {RestrictTransferFrom} from "../RestrictTransferFrom.sol"; error MaverickV2Executor__InvalidDataLength(); error MaverickV2Executor__InvalidTarget(); error MaverickV2Executor__InvalidFactory(); -contract MaverickV2Executor is IExecutor, TokenTransfer { +contract MaverickV2Executor is IExecutor, RestrictTransferFrom { using SafeERC20 for IERC20; address public immutable factory; - constructor(address _factory, address _permit2) TokenTransfer(_permit2) { + constructor(address _factory, address _permit2) + RestrictTransferFrom(_permit2) + { if (_factory == address(0)) { revert MaverickV2Executor__InvalidFactory(); } @@ -47,9 +50,8 @@ contract MaverickV2Executor is IExecutor, TokenTransfer { tickLimit: tickLimit }); - _transfer( - address(tokenIn), msg.sender, target, givenAmount, transferType - ); + _transfer(target, transferType, address(tokenIn), givenAmount); + // slither-disable-next-line unused-return (, calculatedAmount) = pool.swap(receiver, swapParams, ""); } diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol deleted file mode 100644 index 6eac1c0..0000000 --- a/foundry/src/executors/TokenTransfer.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.26; - -import "@interfaces/IExecutor.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import "@permit2/src/interfaces/IAllowanceTransfer.sol"; - -error TokenTransfer__AddressZero(); -error TokenTransfer__InvalidTransferType(); - -contract TokenTransfer { - using SafeERC20 for IERC20; - - IAllowanceTransfer public immutable permit2; - - enum TransferType { - // Assume funds are in the TychoRouter - transfer into the pool - TRANSFER_TO_PROTOCOL, - // Assume funds are in msg.sender's wallet - transferFrom into the pool - TRANSFER_FROM_TO_PROTOCOL, - // Assume funds are in msg.sender's wallet - permit2TransferFrom into the pool - TRANSFER_PERMIT2_TO_PROTOCOL, - // Assume funds are in msg.sender's wallet - but the pool requires it to be - // in the router contract when calling swap - transferFrom into the router - // contract - TRANSFER_FROM_TO_ROUTER, - // Assume funds are in msg.sender's wallet - but the pool requires it to be - // in the router contract when calling swap - transferFrom into the router - // contract using permit2 - TRANSFER_PERMIT2_TO_ROUTER, - // Assume funds have already been transferred into the pool. Do nothing. - NONE - } - - constructor(address _permit2) { - if (_permit2 == address(0)) { - revert TokenTransfer__AddressZero(); - } - permit2 = IAllowanceTransfer(_permit2); - } - - function _transfer( - address tokenIn, - address sender, - address receiver, - uint256 amount, - TransferType transferType - ) internal { - if (transferType == TransferType.NONE) { - return; - } else if (transferType == TransferType.TRANSFER_TO_PROTOCOL) { - if (tokenIn == address(0)) { - payable(receiver).transfer(amount); - } else { - IERC20(tokenIn).safeTransfer(receiver, amount); - } - } else if (transferType == TransferType.TRANSFER_FROM_TO_PROTOCOL) { - // slither-disable-next-line arbitrary-send-erc20 - IERC20(tokenIn).safeTransferFrom(sender, receiver, amount); - } else if (transferType == TransferType.TRANSFER_PERMIT2_TO_PROTOCOL) { - // Permit2.permit is already called from the TychoRouter - permit2.transferFrom(sender, receiver, uint160(amount), tokenIn); - } else if (transferType == TransferType.TRANSFER_FROM_TO_ROUTER) { - // slither-disable-next-line arbitrary-send-erc20 - IERC20(tokenIn).safeTransferFrom(sender, address(this), amount); - } else if (transferType == TransferType.TRANSFER_PERMIT2_TO_ROUTER) { - // Permit2.permit is already called from the TychoRouter - permit2.transferFrom( - sender, address(this), uint160(amount), tokenIn - ); - } else { - revert TokenTransfer__InvalidTransferType(); - } - } -} diff --git a/foundry/src/executors/UniswapV2Executor.sol b/foundry/src/executors/UniswapV2Executor.sol index dd04dd1..50e39a4 100644 --- a/foundry/src/executors/UniswapV2Executor.sol +++ b/foundry/src/executors/UniswapV2Executor.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap-v2/contracts/interfaces/IUniswapV2Pair.sol"; -import "./TokenTransfer.sol"; +import {RestrictTransferFrom} from "../RestrictTransferFrom.sol"; error UniswapV2Executor__InvalidDataLength(); error UniswapV2Executor__InvalidTarget(); @@ -12,7 +12,7 @@ error UniswapV2Executor__InvalidFactory(); error UniswapV2Executor__InvalidInitCode(); error UniswapV2Executor__InvalidFee(); -contract UniswapV2Executor is IExecutor, TokenTransfer { +contract UniswapV2Executor is IExecutor, RestrictTransferFrom { using SafeERC20 for IERC20; address public immutable factory; @@ -25,7 +25,7 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { bytes32 _initCode, address _permit2, uint256 _feeBps - ) TokenTransfer(_permit2) { + ) RestrictTransferFrom(_permit2) { if (_factory == address(0)) { revert UniswapV2Executor__InvalidFactory(); } @@ -59,9 +59,8 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { _verifyPairAddress(target); calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne); - _transfer( - address(tokenIn), msg.sender, target, givenAmount, transferType - ); + + _transfer(target, transferType, address(tokenIn), givenAmount); IUniswapV2Pair pool = IUniswapV2Pair(target); if (zeroForOne) { @@ -88,7 +87,7 @@ contract UniswapV2Executor is IExecutor, TokenTransfer { inToken = IERC20(address(bytes20(data[0:20]))); target = address(bytes20(data[20:40])); receiver = address(bytes20(data[40:60])); - zeroForOne = uint8(data[60]) > 0; + zeroForOne = data[60] != 0; transferType = TransferType(uint8(data[61])); } diff --git a/foundry/src/executors/UniswapV3Executor.sol b/foundry/src/executors/UniswapV3Executor.sol index c1d570d..b35619e 100644 --- a/foundry/src/executors/UniswapV3Executor.sol +++ b/foundry/src/executors/UniswapV3Executor.sol @@ -5,15 +5,15 @@ import "@interfaces/IExecutor.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import "@interfaces/ICallback.sol"; -import {TokenTransfer} from "./TokenTransfer.sol"; +import {RestrictTransferFrom} from "../RestrictTransferFrom.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; error UniswapV3Executor__InvalidDataLength(); error UniswapV3Executor__InvalidFactory(); error UniswapV3Executor__InvalidTarget(); error UniswapV3Executor__InvalidInitCode(); -error UniswapV3Executor__InvalidTransferType(uint8 transferType); -contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { +contract UniswapV3Executor is IExecutor, ICallback, RestrictTransferFrom { using SafeERC20 for IERC20; uint160 private constant MIN_SQRT_RATIO = 4295128739; @@ -25,7 +25,7 @@ contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { address private immutable self; constructor(address _factory, bytes32 _initCode, address _permit2) - TokenTransfer(_permit2) + RestrictTransferFrom(_permit2) { if (_factory == address(0)) { revert UniswapV3Executor__InvalidFactory(); @@ -97,21 +97,14 @@ contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { abi.decode(msgData[4:68], (int256, int256)); address tokenIn = address(bytes20(msgData[132:152])); - - // Transfer type does not exist - if (uint8(msgData[175]) > uint8(TransferType.NONE)) { - revert UniswapV3Executor__InvalidTransferType(uint8(msgData[175])); - } - TransferType transferType = TransferType(uint8(msgData[175])); - address sender = address(bytes20(msgData[176:196])); verifyCallback(msgData[132:]); uint256 amountOwed = amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); - _transfer(tokenIn, sender, msg.sender, amountOwed, transferType); + _transfer(msg.sender, transferType, tokenIn, amountOwed); return abi.encode(amountOwed, tokenIn); } @@ -162,10 +155,8 @@ contract UniswapV3Executor is IExecutor, ICallback, TokenTransfer { address tokenOut, uint24 fee, TransferType transferType - ) internal view returns (bytes memory) { - return abi.encodePacked( - tokenIn, tokenOut, fee, uint8(transferType), msg.sender - ); + ) internal pure returns (bytes memory) { + return abi.encodePacked(tokenIn, tokenOut, fee, uint8(transferType)); } function _verifyPairAddress( diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index 9b878ad..b8fd87d 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.26; import "@interfaces/IExecutor.sol"; import {ICallback} from "@interfaces/ICallback.sol"; -import {TokenTransfer} from "./TokenTransfer.sol"; import { IERC20, SafeERC20 @@ -23,6 +22,8 @@ import {IUnlockCallback} from import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; +import "../RestrictTransferFrom.sol"; +import "@openzeppelin/contracts/utils/Address.sol"; error UniswapV4Executor__InvalidDataLength(); error UniswapV4Executor__NotPoolManager(); @@ -37,7 +38,7 @@ contract UniswapV4Executor is IExecutor, IUnlockCallback, ICallback, - TokenTransfer + RestrictTransferFrom { using SafeERC20 for IERC20; using CurrencyLibrary for Currency; @@ -47,8 +48,8 @@ contract UniswapV4Executor is IPoolManager public immutable poolManager; address private immutable _self; - bytes4 constant SWAP_EXACT_INPUT_SINGLE_SELECTOR = 0x8bc6d0d7; - bytes4 constant SWAP_EXACT_INPUT_SELECTOR = 0xaf90aeb1; + bytes4 constant SWAP_EXACT_INPUT_SINGLE_SELECTOR = 0x6022fbcd; + bytes4 constant SWAP_EXACT_INPUT_SELECTOR = 0x044f0d3d; struct UniswapV4Pool { address intermediaryToken; @@ -57,7 +58,7 @@ contract UniswapV4Executor is } constructor(IPoolManager _poolManager, address _permit2) - TokenTransfer(_permit2) + RestrictTransferFrom(_permit2) { poolManager = _poolManager; _self = address(this); @@ -100,7 +101,6 @@ contract UniswapV4Executor is key, zeroForOne, amountIn, - msg.sender, transferType, receiver, bytes("") @@ -123,7 +123,6 @@ contract UniswapV4Executor is currencyIn, path, amountIn, - msg.sender, transferType, receiver ); @@ -153,7 +152,7 @@ contract UniswapV4Executor is tokenIn = address(bytes20(data[0:20])); tokenOut = address(bytes20(data[20:40])); - zeroForOne = (data[40] != 0); + zeroForOne = data[40] != 0; transferType = TransferType(uint8(data[41])); receiver = address(bytes20(data[42:62])); @@ -239,8 +238,7 @@ contract UniswapV4Executor is * @param poolKey The key of the pool to swap in. * @param zeroForOne Whether the swap is from token0 to token1 (true) or vice versa (false). * @param amountIn The amount of tokens to swap in. - * @param sender The address of the sender. - * @param transferType The type of transfer in to use. + * @param transferType The type of action necessary to pay back the pool. * @param receiver The address of the receiver. * @param hookData Additional data for hook contracts. */ @@ -248,7 +246,6 @@ contract UniswapV4Executor is PoolKey memory poolKey, bool zeroForOne, uint128 amountIn, - address sender, TransferType transferType, address receiver, bytes calldata hookData @@ -262,7 +259,7 @@ contract UniswapV4Executor is if (amount > amountIn) { revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount); } - _settle(currencyIn, amount, sender, transferType); + _settle(currencyIn, amount, transferType); Currency currencyOut = zeroForOne ? poolKey.currency1 : poolKey.currency0; @@ -275,15 +272,13 @@ contract UniswapV4Executor is * @param currencyIn The currency of the input token. * @param path The path to swap along. * @param amountIn The amount of tokens to swap in. - * @param sender The address of the sender. - * @param transferType The type of transfer in to use. + * @param transferType The type of action necessary to pay back the pool. * @param receiver The address of the receiver. */ function swapExactInput( Currency currencyIn, PathKey[] calldata path, uint128 amountIn, - address sender, TransferType transferType, address receiver ) external returns (uint128) { @@ -315,7 +310,7 @@ contract UniswapV4Executor is if (amount > amountIn) { revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount); } - _settle(currencyIn, amount, sender, transferType); + _settle(currencyIn, amount, transferType); _take( swapCurrencyIn, // at the end of the loop this is actually currency out @@ -387,14 +382,12 @@ contract UniswapV4Executor is * @dev The implementing contract must ensure that the `payer` is a secure address. * @param currency The currency to settle. * @param amount The amount to send. - * @param sender The address of the payer. - * @param transferType The type of transfer to use. + * @param transferType The type of action necessary to pay back the pool. * @dev Returns early if the amount is 0. */ function _settle( Currency currency, uint256 amount, - address sender, TransferType transferType ) internal { if (amount == 0) return; @@ -404,11 +397,10 @@ contract UniswapV4Executor is poolManager.settle{value: amount}(); } else { _transfer( - Currency.unwrap(currency), - sender, address(poolManager), - amount, - transferType + transferType, + Currency.unwrap(currency), + amount ); // slither-disable-next-line unused-return poolManager.settle(); diff --git a/foundry/test/TychoRouterProtocolIntegration.t.sol b/foundry/test/TychoRouterProtocolIntegration.t.sol index 04af1f0..7157310 100644 --- a/foundry/test/TychoRouterProtocolIntegration.t.sol +++ b/foundry/test/TychoRouterProtocolIntegration.t.sol @@ -26,7 +26,7 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { USDE_ADDR, USDT_ADDR, true, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL, + RestrictTransferFrom.TransferType.TransferFrom, ALICE, pools ); @@ -55,7 +55,7 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { // This test has two uniswap v4 hops that will be executed inside of the V4 pool manager // USDE -> USDT -> WBTC uint256 amountIn = 100 ether; - deal(USDE_ADDR, tychoRouterAddr, amountIn); + deal(USDE_ADDR, ALICE, amountIn); UniswapV4Executor.UniswapV4Pool[] memory pools = new UniswapV4Executor.UniswapV4Pool[](2); @@ -74,7 +74,7 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { USDE_ADDR, WBTC_ADDR, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, + RestrictTransferFrom.TransferType.TransferFrom, ALICE, pools ); @@ -82,8 +82,18 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { bytes memory swap = encodeSingleSwap(address(usv4Executor), protocolData); + vm.startPrank(ALICE); + IERC20(USDE_ADDR).approve(tychoRouterAddr, amountIn); tychoRouter.singleSwap( - amountIn, USDE_ADDR, WBTC_ADDR, 118280, false, false, ALICE, swap + amountIn, + USDE_ADDR, + WBTC_ADDR, + 118280, + false, + false, + ALICE, + true, + swap ); assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281); @@ -262,7 +272,7 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup { ALICE, DAI_WETH_USV3, zeroForOne, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + RestrictTransferFrom.TransferType.TransferFrom ); bytes memory swap = encodeSingleSwap(address(usv3Executor), protocolData); diff --git a/foundry/test/TychoRouterSequentialSwap.t.sol b/foundry/test/TychoRouterSequentialSwap.t.sol index 1d44076..9c5d710 100644 --- a/foundry/test/TychoRouterSequentialSwap.t.sol +++ b/foundry/test/TychoRouterSequentialSwap.t.sol @@ -8,25 +8,21 @@ import "./executors/UniswapV4Utils.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { - function _getSequentialSwaps(bool permit2) - internal - view - returns (bytes[] memory) - { + function _getSequentialSwaps() internal view returns (bytes[] memory) { // Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2 // 1 WETH -> DAI -> USDC // (univ2) (univ2) - TokenTransfer.TransferType transferType = permit2 - ? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL - : TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; - bytes[] memory swaps = new bytes[](2); // WETH -> DAI swaps[0] = encodeSequentialSwap( address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, transferType + WETH_ADDR, + WETH_DAI_POOL, + DAI_USDC_POOL, // receiver (direct to next pool) + false, + RestrictTransferFrom.TransferType.TransferFrom // transfer to protocol from router ) ); @@ -38,7 +34,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, ALICE, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.None // funds already sent to pool ) ); return swaps; @@ -55,7 +51,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(true); + bytes[] memory swaps = _getSequentialSwaps(); tychoRouter.sequentialSwapPermit2( amountIn, WETH_ADDR, @@ -82,7 +78,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(false); + bytes[] memory swaps = _getSequentialSwaps(); tychoRouter.sequentialSwap( amountIn, WETH_ADDR, @@ -91,6 +87,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, pleEncode(swaps) ); @@ -107,7 +104,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(false); + bytes[] memory swaps = _getSequentialSwaps(); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); tychoRouter.sequentialSwap( amountIn, @@ -117,6 +114,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, pleEncode(swaps) ); } @@ -129,7 +127,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn - 1); - bytes[] memory swaps = _getSequentialSwaps(false); + bytes[] memory swaps = _getSequentialSwaps(); vm.expectRevert(); tychoRouter.sequentialSwap( amountIn, @@ -139,6 +137,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, pleEncode(swaps) ); } @@ -154,7 +153,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { bytes memory signature ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSequentialSwaps(true); + bytes[] memory swaps = _getSequentialSwaps(); uint256 minAmountOut = 3000 * 1e18; @@ -204,9 +203,9 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { encodeUniswapV2Swap( WETH_ADDR, WETH_DAI_POOL, - tychoRouterAddr, + DAI_USDC_POOL, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ) ); @@ -218,7 +217,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, ALICE, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.None ) ); @@ -266,7 +265,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + RestrictTransferFrom.TransferType.TransferFrom ) ); @@ -278,7 +277,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ) ); @@ -315,7 +314,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( @@ -324,7 +323,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes[] memory swaps = new bytes[](2); diff --git a/foundry/test/TychoRouterSingleSwap.t.sol b/foundry/test/TychoRouterSingleSwap.t.sol index bf49fbd..ec74865 100644 --- a/foundry/test/TychoRouterSingleSwap.t.sol +++ b/foundry/test/TychoRouterSingleSwap.t.sol @@ -26,7 +26,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + RestrictTransferFrom.TransferType.TransferFrom ); bytes memory swap = @@ -67,7 +67,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + RestrictTransferFrom.TransferType.TransferFrom ); bytes memory swap = @@ -82,6 +82,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, swap ); @@ -108,7 +109,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + RestrictTransferFrom.TransferType.None ); bytes memory swap = @@ -116,7 +117,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); tychoRouter.singleSwap( - amountIn, WETH_ADDR, DAI_ADDR, 0, false, false, ALICE, swap + amountIn, WETH_ADDR, DAI_ADDR, 0, false, false, ALICE, true, swap ); } @@ -134,7 +135,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + RestrictTransferFrom.TransferType.TransferFrom ); bytes memory swap = @@ -150,6 +151,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, swap ); } @@ -169,7 +171,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + RestrictTransferFrom.TransferType.TransferFrom ); bytes memory swap = @@ -192,6 +194,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { false, false, ALICE, + true, swap ); } @@ -218,7 +221,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer // ETH has already been transferred to router ); bytes memory swap = @@ -261,7 +264,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + RestrictTransferFrom.TransferType.TransferFrom ); bytes memory swap = @@ -287,6 +290,49 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup { vm.stopPrank(); } + function testSingleSwapNoTransferNeededIllegalTransfer() public { + // Tokens are already in the router, there is no need to transfer them. + // Failure because there will be an attempt on an illegal transfer. + uint256 amountIn = 1 ether; + + deal(WETH_ADDR, address(tychoRouter), amountIn); + vm.startPrank(ALICE); + // Approve the tokenIn to be transferred to the router + IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); + + bytes memory protocolData = encodeUniswapV2Swap( + WETH_ADDR, + WETH_DAI_POOL, + ALICE, + false, + RestrictTransferFrom.TransferType.TransferFrom + ); + + bytes memory swap = + encodeSingleSwap(address(usv2Executor), protocolData); + + vm.expectRevert( + abi.encodeWithSelector( + RestrictTransferFrom__ExceededTransferFromAllowance.selector, + 0, // allowed amount + amountIn // attempted amount + ) + ); + uint256 amountOut = tychoRouter.singleSwap( + amountIn, + WETH_ADDR, + DAI_ADDR, + 2000 * 1e18, + false, + false, + ALICE, + false, + swap + ); + + vm.stopPrank(); + } + function testSingleSwapIntegration() public { // Tests swapping WETH -> DAI on a USV2 pool with regular approvals deal(WETH_ADDR, ALICE, 1 ether); diff --git a/foundry/test/TychoRouterSplitSwap.t.sol b/foundry/test/TychoRouterSplitSwap.t.sol index 417b350..480d3e7 100644 --- a/foundry/test/TychoRouterSplitSwap.t.sol +++ b/foundry/test/TychoRouterSplitSwap.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.26; import "@src/executors/UniswapV4Executor.sol"; -import {TychoRouter} from "@src/TychoRouter.sol"; +import {TychoRouter, RestrictTransferFrom} from "@src/TychoRouter.sol"; import "./TychoRouterTestSetup.sol"; import "./executors/UniswapV4Utils.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; contract TychoRouterSplitSwapTest is TychoRouterTestSetup { - function _getSplitSwaps(bool permit2) + function _getSplitSwaps(bool transferFrom) private view returns (bytes[] memory) @@ -19,10 +19,9 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { // -> WBTC -> // (univ2) (univ2) bytes[] memory swaps = new bytes[](4); - - TokenTransfer.TransferType inTransferType = permit2 - ? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL - : TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; + RestrictTransferFrom.TransferType transferType = transferFrom + ? RestrictTransferFrom.TransferType.TransferFrom + : RestrictTransferFrom.TransferType.Transfer; // WETH -> WBTC (60%) swaps[0] = encodeSplitSwap( @@ -31,11 +30,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { (0xffffff * 60) / 100, // 60% address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, - WETH_WBTC_POOL, - tychoRouterAddr, - false, - inTransferType + WETH_ADDR, WETH_WBTC_POOL, tychoRouterAddr, false, transferType ) ); // WBTC -> USDC @@ -49,7 +44,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_WBTC_POOL, ALICE, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ) ); // WETH -> DAI @@ -59,7 +54,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { uint24(0), address(usv2Executor), encodeUniswapV2Swap( - WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, inTransferType + WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, transferType ) ); @@ -74,7 +69,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { DAI_USDC_POOL, ALICE, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ) ); @@ -85,9 +80,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { // Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info uint256 amountIn = 1 ether; - deal(WETH_ADDR, ALICE, amountIn); + deal(WETH_ADDR, address(tychoRouterAddr), amountIn); vm.startPrank(ALICE); - IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); bytes[] memory swaps = _getSplitSwaps(false); tychoRouter.exposedSplitSwap(amountIn, 4, pleEncode(swaps)); vm.stopPrank(); @@ -138,7 +132,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); - bytes[] memory swaps = _getSplitSwaps(false); + bytes[] memory swaps = _getSplitSwaps(true); tychoRouter.splitSwap( amountIn, @@ -149,6 +143,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { false, 4, ALICE, + true, pleEncode(swaps) ); @@ -165,7 +160,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); - bytes[] memory swaps = _getSplitSwaps(false); + bytes[] memory swaps = _getSplitSwaps(true); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); tychoRouter.splitSwap( @@ -177,6 +172,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { false, 4, ALICE, + true, pleEncode(swaps) ); vm.stopPrank(); @@ -190,7 +186,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { vm.startPrank(ALICE); // Approve less than the amountIn IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1); - bytes[] memory swaps = _getSplitSwaps(false); + bytes[] memory swaps = _getSplitSwaps(true); vm.expectRevert(); tychoRouter.splitSwap( @@ -202,6 +198,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { false, 2, ALICE, + true, pleEncode(swaps) ); @@ -270,7 +267,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, ALICE, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes memory swap = encodeSplitSwap( @@ -319,7 +316,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { WETH_DAI_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL + RestrictTransferFrom.TransferType.TransferFrom ); bytes memory swap = encodeSplitSwap( @@ -365,10 +362,10 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { // │ │ // └─ (USV3, 40% split) ──> WETH ─┘ uint256 amountIn = 100 * 10 ** 6; - deal(USDC_ADDR, ALICE, amountIn); + + // Assume funds have already been transferred to tychoRouter + deal(USDC_ADDR, tychoRouterAddr, amountIn); vm.startPrank(ALICE); - // Approve the TychoRouter to spend USDC - IERC20(USDC_ADDR).approve(tychoRouterAddr, amountIn); bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap( USDC_ADDR, @@ -376,7 +373,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, true, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap( @@ -385,7 +382,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, true, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes memory wethUsdcV2OneZeroData = encodeUniswapV2Swap( @@ -393,7 +390,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_WETH_USV2, tychoRouterAddr, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes[] memory swaps = new bytes[](3); @@ -426,6 +423,74 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { assertEq(IERC20(USDC_ADDR).balanceOf(tychoRouterAddr), 99654537); } + function testSplitInputIllegalTransfers() public { + // This test attempts to perform multiple `transferFrom`s - which is not + // permitted by the TychoRouter. + // + // The flow is: + // ┌─ (USV3, 60% split) ───┐ + // │ │ + // USDC ──────┤ ├────> WETH + // │ │ + // └─ (USV3, 40% split) ───┘ + uint256 amountIn = 100 * 10 ** 6; + + // Assume funds have already been transferred to tychoRouter + deal(USDC_ADDR, ALICE, amountIn); + vm.startPrank(ALICE); + IERC20(USDC_ADDR).approve(tychoRouterAddr, amountIn); + + bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap( + USDC_ADDR, + WETH_ADDR, + tychoRouterAddr, + USDC_WETH_USV3, + true, + RestrictTransferFrom.TransferType.Transfer + ); + + bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap( + USDC_ADDR, + WETH_ADDR, + tychoRouterAddr, + USDC_WETH_USV3_2, + true, + RestrictTransferFrom.TransferType.Transfer + ); + + bytes[] memory swaps = new bytes[](2); + // USDC -> WETH (60% split) + swaps[0] = encodeSplitSwap( + uint8(0), + uint8(1), + (0xffffff * 60) / 100, // 60% + address(usv3Executor), + usdcWethV3Pool1ZeroOneData + ); + // USDC -> WETH (40% remainder) + swaps[1] = encodeSplitSwap( + uint8(0), + uint8(1), + uint24(0), + address(usv3Executor), + usdcWethV3Pool2ZeroOneData + ); + vm.expectRevert(); + tychoRouter.splitSwap( + amountIn, + USDC_ADDR, + WETH_ADDR, + 1, + false, + false, + 2, + ALICE, + true, + pleEncode(swaps) + ); + vm.stopPrank(); + } + function testSplitOutputCyclicSwapInternalMethod() public { // This test has start and end tokens that are the same // The flow is: @@ -443,7 +508,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_WETH_USV2, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes memory usdcWethV3Pool1OneZeroData = encodeUniswapV3Swap( @@ -452,7 +517,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( @@ -461,7 +526,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { tychoRouterAddr, USDC_WETH_USV3_2, false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes[] memory swaps = new bytes[](3); @@ -504,7 +569,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup { USDC_MAG7_POOL, tychoRouterAddr, true, - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); bytes memory swap = encodeSplitSwap( diff --git a/foundry/test/TychoRouterTestSetup.sol b/foundry/test/TychoRouterTestSetup.sol index 540027c..d8747e4 100644 --- a/foundry/test/TychoRouterTestSetup.sol +++ b/foundry/test/TychoRouterTestSetup.sol @@ -28,6 +28,17 @@ contract TychoRouterExposed is TychoRouter { return _unwrapETH(amount); } + function tstoreExposed( + address tokenIn, + uint256 amountIn, + bool isPermit2, + bool transferFromNeeded + ) external { + _tstoreTransferFromInfo( + tokenIn, amountIn, isPermit2, transferFromNeeded + ); + } + function exposedSplitSwap( uint256 amountIn, uint256 nTokens, @@ -185,7 +196,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { address target, address receiver, bool zero2one, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) internal pure returns (bytes memory) { return abi.encodePacked(tokenIn, target, receiver, zero2one, transferType); @@ -197,7 +208,7 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils { address receiver, address target, bool zero2one, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( diff --git a/foundry/test/assets/calldata.txt b/foundry/test/assets/calldata.txt index ceeca1c..28bd7c1 100644 --- a/foundry/test/assets/calldata.txt +++ b/foundry/test/assets/calldata.txt @@ -1,28 +1,29 @@ -test_uniswap_v3_uniswap_v2:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000bf00692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb8004375dff511095cc5a197a54140a24efef3a416cbcdf9626bc03e24f779434178a73a0b4bad62ed000100525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010500 -test_single_encoding_strategy_ekubo:20144a070000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000071a0cb889707d426a7a386870a03bc70d1b069759805cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000000000000000000000000000000000 -test_uniswap_v3_uniswap_v3:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000100692e234dae75c793f67a35089c9d99245e1c58470b2260fac5e5542a773aa44fbcfedf7c193bc2c599a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc299ac8ca7087fa4a2a1fb6357269965a2014abc35010000000000000000000000 -test_balancer_v2_uniswap_v2:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000c80072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e004375dff511095cc5a197a54140a24efef3a416010300525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000 -test_sequential_swap_strategy_encoder_no_permit2:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000100525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000 -test_single_encoding_strategy_usv4_grouped_swap:30ace1b1000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000006852b03300000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3b00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041558653365a24c992e17bbf18a802b91eb3cf936eeb1f214d3e5e6cad70ba57070c7e92db5c3f0399d7da505798950313ce1d0c399e54cca0049868ee0f0005501b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330002cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000000000000000 -test_single_encoding_strategy_usv4_eth_out:7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006852b03300000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3b000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c34556c17e3de4636f371033dc793b59f217facb4643ee3113acf72d3c2d7a3d22cf66c34d8d9c7bc119ac2b0fb2781a2097aa743fd77718f2a4e5ef5f92acbf1c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000002cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000 -test_sequential_swap_strategy_encoder:51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041c55b2e21396823d694e6da247eee51ef542d57d3c5901911b85b3c195d89a56d03e85c23251af0d3981b0e07ca05caaaca029ae5efba9d58178be2f38d08cced1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000200525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20105000000000000000000000000000000000000000000000000 -test_single_swap_strategy_encoder_no_permit2:20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000058e7926ee858a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200010000000000000000000000000000 -test_single_swap_strategy_encoder_no_transfer_in:20144a070000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000058e7926ee858a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000 -test_single_encoding_strategy_usv4_eth_in:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000007e0a55d4322a6e93c2379c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006852b03300000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3b00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004160a5490c6e80921cf7b1ad8e7a59a02febd09780fa88c17070663c5a78635116227c299592fac934afa013d95f8711a70fdeb54fc07a2c22352f9cf263924d931c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006cf62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000 -test_sequential_strategy_cyclic_swap:51bcc7b60000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ec8f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041ff87cdaae49fca40cccacadeab4710cdbd41d4901ba28cdabf36dbe07b198887506a6d4960e95f586b5d39011f74d4d57d61a7585ca7b8bdd86f550a9973e5571b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f5640010200692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000000000 -test_single_encoding_strategy_curve_st_eth:20144a070000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe84000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f670220100010005cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000 -test_single_swap_strategy_encoder:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000006b56051582a970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c00000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041c55b2e21396823d694e6da247eee51ef542d57d3c5901911b85b3c195d89a56d03e85c23251af0d3981b0e07ca05caaaca029ae5efba9d58178be2f38d08cced1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200020000000000000000000000000000 -test_single_encoding_strategy_curve:20144a070000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e710201000103cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000 -test_single_swap_strategy_encoder_unwrap:30ace1b10000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be00000000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000410e67daacc0a64ee7e7a57211b347b4af6d4d8865acbb9f549395fe42cf5ea87e1ac51293305eef8aebba46d510052e6fdea42e2caf1c34624260108f32ea6a641b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501020000000000000000000000000000 -test_single_swap_strategy_encoder_wrap:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000059fb7d3830e6fc064b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c00000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000417b597f22dfb861c6ac1ec78eecab17b5e8b8e389494a9d3c2f27efbc35dbd73846d4dc5fef1b9530c4d3b8d7065fbc4d190821c1b5efbfd41ce388163e3812911c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000 -test_split_output_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005e703f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041ff87cdaae49fca40cccacadeab4710cdbd41d4901ba28cdabf36dbe07b198887506a6d4960e95f586b5d39011f74d4d57d61a7585ca7b8bdd86f550a9973e5571b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950102006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56400000006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000000000000000000 -test_split_input_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041ff87cdaae49fca40cccacadeab4710cdbd41d4901ba28cdabf36dbe07b198887506a6d4960e95f586b5d39011f74d4d57d61a7585ca7b8bdd86f550a9973e5571b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400102006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80102005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dccd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000 -test_split_swap_strategy_encoder:7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c55b2e21396823d694e6da247eee51ef542d57d3c5901911b85b3c195d89a56d03e85c23251af0d3981b0e07ca05caaaca029ae5efba9d58178be2f38d08cced1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950002005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950002005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d5cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20100005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010000000000000000000000000000000000000000000000000000000000 -test_uniswap_v3_curve:e8a980d70000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000100691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae460301000105cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000 -test_multi_protocol:51bcc7b600000000000000000000000000000000000000000000005150ae84a8cdf000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a2958f36da71a9200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000005150ae84a8cdf00000000000000000000000000000000000000000000000000000000000006852b03400000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b2a3c00000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004153ce45df3a0b2c8061d920d172d594ee02cf37b1967001935d3a6055700a300f79f68cda34bd3c6f64bfd154a113a43aafd15e6443dcc402e5b6ac9263e6ff031c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021400525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501020072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e3ede3eca2a72b3aecc820e955b36f38437d01395010500691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae4603010001053ede3eca2a72b3aecc820e955b36f38437d013950071a0cb889707d426a7a386870a03bc70d1b0697598003ede3eca2a72b3aecc820e955b36f38437d01395dac17f958d2ee523a2206206994597c13d831ec7a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000001a36e2eb1c43200000032006cf62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c000000000000000000000000 -test_encode_balancer_v2:c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0105 -test_ekubo_encode_swap_multi:00ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032 -test_encode_uniswap_v4_sequential_swap:4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c5990100cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c -test_encode_uniswap_v4_simple_swap:4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec70100cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec7000064000001 -test_single_encoding_strategy_maverick:20144a070000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000051a4ad4f68d0b91cfd19687c881e50f3a00242828c40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f14cf6d2fe3e1b326114b07d22a6f6bb59e346c67cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc201000000000000000000000000000000 -test_encode_maverick_v2:40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f14cf6d2fe3e1b326114b07d22a6f6bb59e346c671d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e00 +test_uniswap_v3_uniswap_v2:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000bf00692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb8004375dff511095cc5a197a54140a24efef3a416cbcdf9626bc03e24f779434178a73a0b4bad62ed000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010200 +test_single_encoding_strategy_ekubo:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000071a0cb889707d426a7a386870a03bc70d1b069759802cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000000000000000000000000000000000 +test_uniswap_v3_uniswap_v3:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000000692e234dae75c793f67a35089c9d99245e1c58470b2260fac5e5542a773aa44fbcfedf7c193bc2c599a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc299ac8ca7087fa4a2a1fb6357269965a2014abc35010100000000000000000000 +test_balancer_v2_uniswap_v2:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000c80072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e004375dff511095cc5a197a54140a24efef3a416010000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20102000000000000000000000000000000000000000000000000 +test_sequential_swap_strategy_encoder_no_permit2:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20102000000000000000000000000000000000000000000000000 +test_single_encoding_strategy_usv4_grouped_swap:30ace1b1000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000068529cc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c800000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041b70914df20e4b2675adc5a61d26b959e0fd21e7cce6503b884d5ba9e1db2c82164112e4dc362b9a3ef794a03fcfebe0710700028b79abe8b7de525288ac904991c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d23119330000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000000000000000 +test_single_encoding_strategy_usv4_eth_out:7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e000000000000000000000000000000000000000000000000000000000068529cc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c8000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c33e9da2d2a5bea1087e27f42b9255fcaea06efa76558843f6192d571b15c09c5818e1a4b3a111bb6accc4cb8b583c318d2386e0e94cc87b9fcc4065db0d65611c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007300710001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c00000000000000000000000000 +test_sequential_swap_strategy_encoder:51bcc7b60000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000410c38cb809e33a4b6239b5aab3ac879ec39ba3a6979404f6cfc882cc15b81e92f02fef7985c8edd3985b39224f2738ca6bbcfe7acf0cd44d8bab89636dcce3bfb1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a800525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d940004375dff511095cc5a197a54140a24efef3a416000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20102000000000000000000000000000000000000000000000000 +test_single_swap_strategy_encoder_no_permit2:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000058e7926ee858a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000 +test_single_swap_strategy_encoder_no_transfer_in:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000058e7926ee858a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200010000000000000000000000000000 +test_single_encoding_strategy_usv4_eth_in:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000007e0a55d4322a6e93c2379c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c800000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041f59744bfe3e154be7c72429eb93db7ef2ec00344feeb1376195a2e3f133437f7533c3df955de146e229e4a37fda22acbe11d9393215d14cf1e19334aebbf35e61b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006cf62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330102cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc26982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000 +test_sequential_strategy_cyclic_swap:51bcc7b60000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ec8f6e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000415eb0e4c29f56696a0a8c0445740b653ae67c0f52fe364f52adc19c84fd6fdc837bc0df41516f33500ebe7f05f653bc0e53ec3ec27c223484d29e4567bf5a0f561c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f5640010000692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000100000000000000000000 +test_single_encoding_strategy_curve_st_eth:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ae7ab96520de3a18e5e111b5eaab095312d7fe84000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff211eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeae7ab96520de3a18e5e111b5eaab095312d7fe84dc24316b9ae028f1497c275eb9192a3ea0f670220100010002cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000 +test_single_swap_strategy_encoder:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000006b56051582a970000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000026000000000000000000000000000000000000000000000000000000000000000410c38cb809e33a4b6239b5aab3ac879ec39ba3a6979404f6cfc882cc15b81e92f02fef7985c8edd3985b39224f2738ca6bbcfe7acf0cd44d8bab89636dcce3bfb1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000 +test_single_encoding_strategy_curve:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000055c08ca52497e2f1534b59e2917bf524d4765257000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000691d1499e622d69689cdf9004d05ec547d650ff21155c08ca52497e2f1534b59e2917bf524d4765257c02aaa39b223fe8d0a0e5c4f27ead9083c756cc277146b0a1d08b6844376df6d9da99ba7f1b19e710201000100cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000 +test_single_swap_strategy_encoder_unwrap:30ace1b10000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f0000000000000000000000000000000000000000000000a2a15d09519be000000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041e5679a7262a283109d2e312e4ffbec563f501f347efc00418f3a6701492635977583d4e2ff29904a33496b64f1bc2ac73796a546a495ab110cca86e6de1655b61c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501000000000000000000000000000000 +test_single_swap_strategy_encoder_wrap:30ace1b10000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f000000000000000000000000000000000000000000000059fb7d3830e6fc064b00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000260000000000000000000000000000000000000000000000000000000000000004160aa554efc922689a7f426a984f9fc278aa948b9c78f4e7cc0a5f177e5fb6859632b92d3326f710603d43d3a8ea5753c58a61316fd1bff8e0b388f74c98993b91b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000525615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb11cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200010000000000000000000000000000 +test_split_output_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005e703f4000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c90000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415eb0e4c29f56696a0a8c0445740b653ae67c0f52fe364f52adc19c84fd6fdc837bc0df41516f33500ebe7f05f653bc0e53ec3ec27c223484d29e4567bf5a0f561c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48b4e16d0168e52d35cacd2c6185b44281ec28c9dc3ede3eca2a72b3aecc820e955b36f38437d013950100006e01009999992e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480001f4cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc288e6a0c2ddd26feeb64f039a2c41296fcb3f56400001006e01000000002e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000bb8cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc28ad599c3a0ff1de082011efddc58f1908eb6e6d8000100000000000000 +test_split_input_cyclic_swap:7c5538460000000000000000000000000000000000000000000000000000000005f5e100000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005ef619b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000005f5e1000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c90000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000415eb0e4c29f56696a0a8c0445740b653ae67c0f52fe364f52adc19c84fd6fdc837bc0df41516f33500ebe7f05f653bc0e53ec3ec27c223484d29e4567bf5a0f561c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000139006e00019999992e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20001f43ede3eca2a72b3aecc820e955b36f38437d0139588e6a0c2ddd26feeb64f039a2c41296fcb3f56400100006e00010000002e234dae75c793f67a35089c9d99245e1c58470ba0b86991c6218b36c1d19d4a2e9eb0ce3606eb48c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000bb83ede3eca2a72b3aecc820e955b36f38437d013958ad599c3a0ff1de082011efddc58f1908eb6e6d80100005701000000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2b4e16d0168e52d35cacd2c6185b44281ec28c9dccd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000100000000000000 +test_split_swap_strategy_encoder:7c5538460000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000018f61ec000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c90000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000410c38cb809e33a4b6239b5aab3ac879ec39ba3a6979404f6cfc882cc15b81e92f02fef7985c8edd3985b39224f2738ca6bbcfe7acf0cd44d8bab89636dcce3bfb1c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000164005700028000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d013950000005700010000005615deb798bb3e4dfa0139dfa1b3d433cc23b72fc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2bb2b8038a1640196fbe3e38816f3e67cba72d9403ede3eca2a72b3aecc820e955b36f38437d013950000005702030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fae461ca67b15dc8dc81ce7615e0320da1a9ab8d5cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20101005701030000005615deb798bb3e4dfa0139dfa1b3d433cc23b72f2260fac5e5542a773aa44fbcfedf7c193bc2c599004375dff511095cc5a197a54140a24efef3a416cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2010100000000000000000000000000000000000000000000000000000000 +test_uniswap_v3_curve:e21dd0d30000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000018f61ec00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000d600692e234dae75c793f67a35089c9d99245e1c58470bc02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599000bb83ede3eca2a72b3aecc820e955b36f38437d01395cbcdf9626bc03e24f779434178a73a0b4bad62ed000000691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae460301000102cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000 +test_multi_protocol:51bcc7b600000000000000000000000000000000000000000000005150ae84a8cdf000000000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a2958f36da71a9200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000006b175474e89094c44da98b954eedeac495271d0f00000000000000000000000000000000000000000000005150ae84a8cdf000000000000000000000000000000000000000000000000000000000000068529cc100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000000000000000000000000000000000000682b16c900000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002600000000000000000000000000000000000000000000000000000000000000041a81d713c715dd501edf102a3c3a755a931e3c72e7322d66b620ece81d7c98bae3d46479041445649eeefdb6ccc09c2b933cd37eda3ae8c99034e84f5570eb2c31c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000021400525615deb798bb3e4dfa0139dfa1b3d433cc23b72f6b175474e89094c44da98b954eedeac495271d0fa478c2975ab1ea89e8196811f51a7b7ade33eb113ede3eca2a72b3aecc820e955b36f38437d0139501000072c7183455a4c133ae270771860664b6b7ec320bb1c02aaa39b223fe8d0a0e5c4f27ead9083c756cc22260fac5e5542a773aa44fbcfedf7c193bc2c599a6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e3ede3eca2a72b3aecc820e955b36f38437d01395010200691d1499e622d69689cdf9004d05ec547d650ff2112260fac5e5542a773aa44fbcfedf7c193bc2c599dac17f958d2ee523a2206206994597c13d831ec7d51a44d3fae010294c616388b506acda1bfaae4603010001023ede3eca2a72b3aecc820e955b36f38437d013950071a0cb889707d426a7a386870a03bc70d1b0697598013ede3eca2a72b3aecc820e955b36f38437d01395dac17f958d2ee523a2206206994597c13d831ec7a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000001a36e2eb1c43200000032006cf62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000001cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc20000000000000000000000000000000000000000000bb800003c000000000000000000000000 +test_encode_balancer_v2:c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2ba100000625a3754423978a60c9317c58a424e3d5c6ee304399dbdb9c8ef030ab642b10820db8f560002000000000000000000141d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0102 +test_ekubo_encode_swap_multi:01ca4f73fe97d0b987a0d12b39bbd562c779bab6f60000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4851d02a5948496a67827242eabc5725531342527c000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000001a36e2eb1c43200000032 +test_encode_uniswap_v4_sequential_swap:4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c5990101cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c +test_encode_uniswap_v4_simple_swap:4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec70101cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2dac17f958d2ee523a2206206994597c13d831ec7000064000001 +test_single_encoding_strategy_maverick:5c4b639c0000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000040d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000051a4ad4f68d0b91cfd19687c881e50f3a00242828c40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f14cf6d2fe3e1b326114b07d22a6f6bb59e346c67cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000 +test_encode_maverick_v2:40d16fc0246ad3160ccc09b8d0d3a2cd28ae6c2f14cf6d2fe3e1b326114b07d22a6f6bb59e346c671d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e01 +test_encode_uniswap_v2:c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0001 diff --git a/foundry/test/executors/BalancerV2Executor.t.sol b/foundry/test/executors/BalancerV2Executor.t.sol index 1ad790e..6b099f3 100644 --- a/foundry/test/executors/BalancerV2Executor.t.sol +++ b/foundry/test/executors/BalancerV2Executor.t.sol @@ -46,7 +46,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { WETH_BAL_POOL_ID, address(2), true, - TokenTransfer.TransferType.NONE + RestrictTransferFrom.TransferType.None ); ( @@ -55,7 +55,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { bytes32 poolId, address receiver, bool needsApproval, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) = balancerV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); @@ -63,7 +63,9 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { assertEq(poolId, WETH_BAL_POOL_ID); assertEq(receiver, address(2)); assertEq(needsApproval, true); - assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); + assertEq( + uint8(transferType), uint8(RestrictTransferFrom.TransferType.None) + ); } function testDecodeParamsInvalidDataLength() public { @@ -82,7 +84,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { WETH_BAL_POOL_ID, BOB, true, - TokenTransfer.TransferType.NONE + RestrictTransferFrom.TransferType.Transfer ); deal(WETH_ADDR, address(balancerV2Exposed), amountIn); @@ -104,7 +106,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { bytes32 poolId, address receiver, bool needsApproval, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) = balancerV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); @@ -112,7 +114,9 @@ contract BalancerV2ExecutorTest is Constants, TestUtils { assertEq(poolId, WETH_BAL_POOL_ID); assertEq(receiver, BOB); assertEq(needsApproval, true); - assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); + assertEq( + uint8(transferType), uint8(RestrictTransferFrom.TransferType.None) + ); } function testSwapIntegration() public { diff --git a/foundry/test/executors/CurveExecutor.t.sol b/foundry/test/executors/CurveExecutor.t.sol index 629ce79..d8dab35 100644 --- a/foundry/test/executors/CurveExecutor.t.sol +++ b/foundry/test/executors/CurveExecutor.t.sol @@ -37,7 +37,7 @@ contract CurveExecutorExposed is CurveExecutor { int128 i, int128 j, bool tokenApprovalNeeded, - TokenTransfer.TransferType transferType, + RestrictTransferFrom.TransferType transferType, address receiver ) { @@ -68,7 +68,7 @@ contract CurveExecutorTest is Test, Constants { uint8(2), uint8(0), true, - TokenTransfer.TransferType.NONE, + RestrictTransferFrom.TransferType.None, ALICE ); @@ -80,7 +80,7 @@ contract CurveExecutorTest is Test, Constants { int128 i, int128 j, bool tokenApprovalNeeded, - TokenTransfer.TransferType transferType, + RestrictTransferFrom.TransferType transferType, address receiver ) = curveExecutorExposed.decodeData(data); @@ -91,7 +91,9 @@ contract CurveExecutorTest is Test, Constants { assertEq(i, 2); assertEq(j, 0); assertEq(tokenApprovalNeeded, true); - assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE)); + assertEq( + uint8(transferType), uint8(RestrictTransferFrom.TransferType.None) + ); assertEq(receiver, ALICE); } @@ -100,7 +102,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; deal(DAI_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = _getData(DAI_ADDR, USDC_ADDR, TRIPOOL, 1, ALICE); + bytes memory data = _getData( + DAI_ADDR, + USDC_ADDR, + TRIPOOL, + 1, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -113,8 +122,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; deal(address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(ETH_ADDR_FOR_CURVE, STETH_ADDR, STETH_POOL, 1, ALICE); + bytes memory data = _getData( + ETH_ADDR_FOR_CURVE, + STETH_ADDR, + STETH_POOL, + 1, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -130,8 +145,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(WETH_ADDR, WBTC_ADDR, TRICRYPTO2_POOL, 3, ALICE); + bytes memory data = _getData( + WETH_ADDR, + WBTC_ADDR, + TRICRYPTO2_POOL, + 3, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -144,7 +165,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 100 * 10 ** 6; deal(USDC_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = _getData(USDC_ADDR, SUSD_ADDR, SUSD_POOL, 1, ALICE); + bytes memory data = _getData( + USDC_ADDR, + SUSD_ADDR, + SUSD_POOL, + 1, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -157,8 +185,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; deal(FRAX_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(FRAX_ADDR, USDC_ADDR, FRAX_USDC_POOL, 1, ALICE); + bytes memory data = _getData( + FRAX_ADDR, + USDC_ADDR, + FRAX_USDC_POOL, + 1, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -171,8 +205,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 100 * 10 ** 6; deal(USDC_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(USDC_ADDR, USDE_ADDR, USDE_USDC_POOL, 1, ALICE); + bytes memory data = _getData( + USDC_ADDR, + USDE_ADDR, + USDE_USDC_POOL, + 1, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -185,8 +225,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 100 * 10 ** 6; deal(DOLA_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(DOLA_ADDR, FRAXPYUSD_POOL, DOLA_FRAXPYUSD_POOL, 1, ALICE); + bytes memory data = _getData( + DOLA_ADDR, + FRAXPYUSD_POOL, + DOLA_FRAXPYUSD_POOL, + 1, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -200,8 +246,14 @@ contract CurveExecutorTest is Test, Constants { uint256 initialBalance = address(ALICE).balance; // this address already has some ETH assigned to it deal(XYO_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(XYO_ADDR, ETH_ADDR_FOR_CURVE, ETH_XYO_POOL, 2, ALICE); + bytes memory data = _getData( + XYO_ADDR, + ETH_ADDR_FOR_CURVE, + ETH_XYO_POOL, + 2, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -214,8 +266,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1000 ether; deal(BSGG_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(BSGG_ADDR, USDT_ADDR, BSGG_USDT_POOL, 2, ALICE); + bytes memory data = _getData( + BSGG_ADDR, + USDT_ADDR, + BSGG_USDT_POOL, + 2, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -228,8 +286,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; deal(WETH_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(WETH_ADDR, USDC_ADDR, TRICRYPTO_POOL, 2, ALICE); + bytes memory data = _getData( + WETH_ADDR, + USDC_ADDR, + TRICRYPTO_POOL, + 2, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -242,8 +306,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; deal(UWU_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(UWU_ADDR, WETH_ADDR, UWU_WETH_POOL, 2, ALICE); + bytes memory data = _getData( + UWU_ADDR, + WETH_ADDR, + UWU_WETH_POOL, + 2, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -256,8 +326,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 1 ether; deal(USDT_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(USDT_ADDR, CRVUSD_ADDR, CRVUSD_USDT_POOL, 1, ALICE); + bytes memory data = _getData( + USDT_ADDR, + CRVUSD_ADDR, + CRVUSD_USDT_POOL, + 1, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -270,8 +346,14 @@ contract CurveExecutorTest is Test, Constants { uint256 amountIn = 100 * 10 ** 9; // 9 decimals deal(WTAO_ADDR, address(curveExecutorExposed), amountIn); - bytes memory data = - _getData(WTAO_ADDR, WSTTAO_ADDR, WSTTAO_WTAO_POOL, 1, ALICE); + bytes memory data = _getData( + WTAO_ADDR, + WSTTAO_ADDR, + WSTTAO_WTAO_POOL, + 1, + ALICE, + RestrictTransferFrom.TransferType.None + ); uint256 amountOut = curveExecutorExposed.swap(amountIn, data); @@ -284,7 +366,8 @@ contract CurveExecutorTest is Test, Constants { address tokenOut, address pool, uint8 poolType, - address receiver + address receiver, + RestrictTransferFrom.TransferType transferType ) internal view returns (bytes memory data) { (int128 i, int128 j) = _getIndexes(tokenIn, tokenOut, pool); data = abi.encodePacked( @@ -295,7 +378,7 @@ contract CurveExecutorTest is Test, Constants { uint8(uint256(uint128(i))), uint8(uint256(uint128(j))), true, - TokenTransfer.TransferType.NONE, + transferType, receiver ); } diff --git a/foundry/test/executors/EkuboExecutor.t.sol b/foundry/test/executors/EkuboExecutor.t.sol index 23996f9..7fa3eb1 100644 --- a/foundry/test/executors/EkuboExecutor.t.sol +++ b/foundry/test/executors/EkuboExecutor.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.26; import "../TestUtils.sol"; import {Constants} from "../Constants.sol"; -import {EkuboExecutor, TokenTransfer} from "@src/executors/EkuboExecutor.sol"; +import "@src/executors/EkuboExecutor.sol"; import {ICore} from "@ekubo/interfaces/ICore.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol"; @@ -45,7 +45,7 @@ contract EkuboExecutorTest is Constants, TestUtils { uint256 usdcBalanceBeforeExecutor = USDC.balanceOf(address(executor)); bytes memory data = abi.encodePacked( - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core) + uint8(RestrictTransferFrom.TransferType.Transfer), // transfer type (transfer from executor to core) address(executor), // receiver NATIVE_TOKEN_ADDRESS, // tokenIn USDC_ADDR, // tokenOut @@ -82,7 +82,7 @@ contract EkuboExecutorTest is Constants, TestUtils { uint256 ethBalanceBeforeExecutor = address(executor).balance; bytes memory data = abi.encodePacked( - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core) + uint8(RestrictTransferFrom.TransferType.Transfer), // transferNeeded (transfer from executor to core) address(executor), // receiver USDC_ADDR, // tokenIn NATIVE_TOKEN_ADDRESS, // tokenOut @@ -140,7 +140,7 @@ contract EkuboExecutorTest is Constants, TestUtils { // Same test case as in swap_encoder::tests::ekubo::test_encode_swap_multi function testMultiHopSwap() public { bytes memory data = abi.encodePacked( - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType + uint8(RestrictTransferFrom.TransferType.Transfer), // transferNeeded (transfer from executor to core) address(executor), // receiver NATIVE_TOKEN_ADDRESS, // tokenIn USDC_ADDR, // tokenOut of 1st swap diff --git a/foundry/test/executors/MaverickV2Executor.t.sol b/foundry/test/executors/MaverickV2Executor.t.sol index 755570d..b9eef8d 100644 --- a/foundry/test/executors/MaverickV2Executor.t.sol +++ b/foundry/test/executors/MaverickV2Executor.t.sol @@ -17,7 +17,7 @@ contract MaverickV2ExecutorExposed is MaverickV2Executor { IERC20 tokenIn, address target, address receiver, - TransferType transferType + RestrictTransferFrom.TransferType transferType ) { return _decodeData(data); @@ -43,14 +43,14 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { GHO_ADDR, GHO_USDC_POOL, address(2), - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); ( IERC20 tokenIn, address target, address receiver, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) = maverickV2Exposed.decodeParams(params); assertEq(address(tokenIn), GHO_ADDR); @@ -58,7 +58,7 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { assertEq(receiver, address(2)); assertEq( uint8(transferType), - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) + uint8(RestrictTransferFrom.TransferType.Transfer) ); } @@ -76,7 +76,7 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { GHO_ADDR, GHO_USDC_POOL, BOB, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); deal(GHO_ADDR, address(maverickV2Exposed), amountIn); @@ -98,7 +98,7 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { IERC20 tokenIn, address pool, address receiver, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) = maverickV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), GHO_ADDR); @@ -106,7 +106,7 @@ contract MaverickV2ExecutorTest is TestUtils, Constants { assertEq(receiver, BOB); assertEq( uint8(transferType), - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) + uint8(RestrictTransferFrom.TransferType.Transfer) ); } diff --git a/foundry/test/executors/UniswapV2Executor.t.sol b/foundry/test/executors/UniswapV2Executor.t.sol index 5f81427..a509877 100644 --- a/foundry/test/executors/UniswapV2Executor.t.sol +++ b/foundry/test/executors/UniswapV2Executor.t.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.26; +import "../TestUtils.sol"; import "@src/executors/UniswapV2Executor.sol"; -import "@src/executors/TokenTransfer.sol"; -import {Test} from "../../lib/forge-std/src/Test.sol"; import {Constants} from "../Constants.sol"; import {Permit2TestHelper} from "../Permit2TestHelper.sol"; +import {Test} from "../../lib/forge-std/src/Test.sol"; contract UniswapV2ExecutorExposed is UniswapV2Executor { constructor( @@ -23,7 +23,7 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor { address target, address receiver, bool zeroForOne, - TransferType transferType + RestrictTransferFrom.TransferType transferType ) { return _decodeData(data); @@ -52,7 +52,7 @@ contract FakeUniswapV2Pool { } } -contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { +contract UniswapV2ExecutorTest is Constants, Permit2TestHelper, TestUtils { using SafeERC20 for IERC20; UniswapV2ExecutorExposed uniswapV2Exposed; @@ -60,7 +60,6 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { UniswapV2ExecutorExposed pancakeswapV2Exposed; IERC20 WETH = IERC20(WETH_ADDR); IERC20 DAI = IERC20(DAI_ADDR); - IAllowanceTransfer permit2; function setUp() public { uint256 forkBlock = 17323404; @@ -80,7 +79,6 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { PERMIT2_ADDRESS, 25 ); - permit2 = IAllowanceTransfer(PERMIT2_ADDRESS); } function testDecodeParams() public view { @@ -89,7 +87,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { address(2), address(3), false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); ( @@ -97,7 +95,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { address target, address receiver, bool zeroForOne, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) = uniswapV2Exposed.decodeParams(params); assertEq(address(tokenIn), WETH_ADDR); @@ -105,8 +103,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(receiver, address(3)); assertEq(zeroForOne, false); assertEq( - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), - uint8(transferType) + uint8(transferType), + uint8(RestrictTransferFrom.TransferType.Transfer) ); } @@ -163,7 +161,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) + RestrictTransferFrom.TransferType.Transfer ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -173,59 +171,6 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { assertGe(finalBalance, amountOut); } - function testSwapWithTransferFrom() public { - uint256 amountIn = 10 ** 18; - uint256 amountOut = 1847751195973566072891; - bool zeroForOne = false; - bytes memory protocolData = abi.encodePacked( - WETH_ADDR, - WETH_DAI_POOL, - BOB, - zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL) - ); - - deal(WETH_ADDR, address(this), amountIn); - IERC20(WETH_ADDR).approve(address(uniswapV2Exposed), amountIn); - - uniswapV2Exposed.swap(amountIn, protocolData); - - uint256 finalBalance = DAI.balanceOf(BOB); - assertGe(finalBalance, amountOut); - } - - function testSwapWithPermit2TransferFrom() public { - uint256 amountIn = 10 ** 18; - uint256 amountOut = 1847751195973566072891; - bool zeroForOne = false; - bytes memory protocolData = abi.encodePacked( - WETH_ADDR, - WETH_DAI_POOL, - ALICE, - zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL) - ); - - deal(WETH_ADDR, ALICE, amountIn); - vm.startPrank(ALICE); - ( - IAllowanceTransfer.PermitSingle memory permitSingle, - bytes memory signature - ) = handlePermit2Approval( - WETH_ADDR, address(uniswapV2Exposed), amountIn - ); - - // Assume the permit2.approve method will be called from the TychoRouter - // Replicate this scenario in this test. - permit2.permit(ALICE, permitSingle, signature); - - uniswapV2Exposed.swap(amountIn, protocolData); - vm.stopPrank(); - - uint256 finalBalance = DAI.balanceOf(ALICE); - assertGe(finalBalance, amountOut); - } - function testSwapNoTransfer() public { uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; @@ -235,7 +180,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { WETH_DAI_POOL, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.NONE) + RestrictTransferFrom.TransferType.None ); deal(WETH_ADDR, address(this), amountIn); @@ -248,27 +193,29 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { function testDecodeIntegration() public view { bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010000"; + hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc288e6a0c2ddd26feeb64f039a2c41296fcb3f564000000000000000000000000000000000000000010001"; ( IERC20 tokenIn, address target, address receiver, bool zeroForOne, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) = uniswapV2Exposed.decodeParams(protocolData); assertEq(address(tokenIn), WETH_ADDR); assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640); assertEq(receiver, 0x0000000000000000000000000000000000000001); assertEq(zeroForOne, false); - // TRANSFER = 0 - assertEq(0, uint8(transferType)); + assertEq( + uint8(transferType), + uint8(RestrictTransferFrom.TransferType.Transfer) + ); } function testSwapIntegration() public { bytes memory protocolData = - hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; + loadCallDataFromFile("test_encode_uniswap_v2"); uint256 amountIn = 10 ** 18; uint256 amountOut = 1847751195973566072891; deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -287,7 +234,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { fakePool, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) + RestrictTransferFrom.TransferType.Transfer ); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); @@ -307,7 +254,7 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper { USDC_MAG7_POOL, BOB, zeroForOne, - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) + RestrictTransferFrom.TransferType.Transfer ); deal(BASE_USDC, address(uniswapV2Exposed), amountIn); diff --git a/foundry/test/executors/UniswapV3Executor.t.sol b/foundry/test/executors/UniswapV3Executor.t.sol index e9d95e6..566f4c8 100644 --- a/foundry/test/executors/UniswapV3Executor.t.sol +++ b/foundry/test/executors/UniswapV3Executor.t.sol @@ -22,7 +22,7 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor { address receiver, address target, bool zeroForOne, - TransferType transferType + RestrictTransferFrom.TransferType transferType ) { return _decodeData(data); @@ -71,7 +71,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(2), address(3), false, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); ( @@ -81,7 +81,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address receiver, address target, bool zeroForOne, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) = uniswapV3Exposed.decodeData(data); assertEq(tokenIn, WETH_ADDR); @@ -92,7 +92,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { assertEq(zeroForOne, false); assertEq( uint8(transferType), - uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL) + uint8(RestrictTransferFrom.TransferType.Transfer) ); } @@ -109,7 +109,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), DAI_WETH_USV3, zeroForOne, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); @@ -150,7 +150,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { WETH_ADDR, DAI_ADDR, poolFee, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, + RestrictTransferFrom.TransferType.Transfer, address(uniswapV3Exposed) ); uint256 dataOffset = 3; // some offset @@ -184,7 +184,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address(this), fakePool, zeroForOne, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL + RestrictTransferFrom.TransferType.Transfer ); vm.expectRevert(UniswapV3Executor__InvalidTarget.selector); @@ -197,7 +197,7 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper { address receiver, address target, bool zero2one, - TokenTransfer.TransferType transferType + RestrictTransferFrom.TransferType transferType ) internal view returns (bytes memory) { IUniswapV3Pool pool = IUniswapV3Pool(target); return abi.encodePacked( diff --git a/foundry/test/executors/UniswapV4Executor.t.sol b/foundry/test/executors/UniswapV4Executor.t.sol index 8f2c292..21c39cc 100644 --- a/foundry/test/executors/UniswapV4Executor.t.sol +++ b/foundry/test/executors/UniswapV4Executor.t.sol @@ -4,7 +4,6 @@ pragma solidity ^0.8.26; import "../../src/executors/UniswapV4Executor.sol"; import "../TestUtils.sol"; import "./UniswapV4Utils.sol"; -import "@src/executors/TokenTransfer.sol"; import "@src/executors/UniswapV4Executor.sol"; import {Constants} from "../Constants.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; @@ -22,7 +21,7 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor { address tokenIn, address tokenOut, bool zeroForOne, - TokenTransfer.TransferType transferType, + RestrictTransferFrom.TransferType transferType, address receiver, UniswapV4Pool[] memory pools ) @@ -53,8 +52,6 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { int24 tickSpacing1 = 60; uint24 pool2Fee = 1000; int24 tickSpacing2 = -10; - TokenTransfer.TransferType transferType = - TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; UniswapV4Executor.UniswapV4Pool[] memory pools = new UniswapV4Executor.UniswapV4Pool[](2); @@ -70,14 +67,19 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { }); bytes memory data = UniswapV4Utils.encodeExactInput( - USDE_ADDR, USDT_ADDR, zeroForOne, transferType, ALICE, pools + USDE_ADDR, + USDT_ADDR, + zeroForOne, + RestrictTransferFrom.TransferType.Transfer, + ALICE, + pools ); ( address tokenIn, address tokenOut, bool zeroForOneDecoded, - TokenTransfer.TransferType transferTypeDecoded, + RestrictTransferFrom.TransferType transferType, address receiver, UniswapV4Executor.UniswapV4Pool[] memory decodedPools ) = uniswapV4Exposed.decodeData(data); @@ -85,7 +87,10 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { assertEq(tokenIn, USDE_ADDR); assertEq(tokenOut, USDT_ADDR); assertEq(zeroForOneDecoded, zeroForOne); - assertEq(uint8(transferTypeDecoded), uint8(transferType)); + assertEq( + uint8(transferType), + uint8(RestrictTransferFrom.TransferType.Transfer) + ); assertEq(receiver, ALICE); assertEq(decodedPools.length, 2); assertEq(decodedPools[0].intermediaryToken, USDT_ADDR); @@ -115,7 +120,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { USDE_ADDR, USDT_ADDR, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, + RestrictTransferFrom.TransferType.Transfer, ALICE, pools ); @@ -172,7 +177,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils { USDE_ADDR, WBTC_ADDR, true, - TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, + RestrictTransferFrom.TransferType.Transfer, ALICE, pools ); diff --git a/foundry/test/executors/UniswapV4Utils.sol b/foundry/test/executors/UniswapV4Utils.sol index c96465c..c280fcb 100644 --- a/foundry/test/executors/UniswapV4Utils.sol +++ b/foundry/test/executors/UniswapV4Utils.sol @@ -8,7 +8,7 @@ library UniswapV4Utils { address tokenIn, address tokenOut, bool zeroForOne, - UniswapV4Executor.TransferType transferType, + RestrictTransferFrom.TransferType transferType, address receiver, UniswapV4Executor.UniswapV4Pool[] memory pools ) public pure returns (bytes memory) { diff --git a/src/encoding/evm/constants.rs b/src/encoding/evm/constants.rs index 2777040..1a51fdf 100644 --- a/src/encoding/evm/constants.rs +++ b/src/encoding/evm/constants.rs @@ -34,11 +34,12 @@ pub static IN_TRANSFER_REQUIRED_PROTOCOLS: LazyLock> = Laz set }); -// The protocols here are a subset of the ones defined in IN_TRANSFER_REQUIRED_PROTOCOLS. The tokens -// can not be sent directly from the previous pool into a pool of this protocol. The tokens need to -// be sent to the router and only then transferred into the pool. This is the case for uniswap v3 -// because of the callback logic. The only way for this to work it would be to call the second swap -// during the callback of the first swap. This is currently not supported. +// The protocols here are a subset of the ones defined in IN_TRANSFER_REQUIRED_PROTOCOLS. The in +// transfer needs to be performed inside the callback logic. This means, the tokens can not be sent +// directly from the previous pool into a pool of this protocol. The tokens need to be sent to the +// router and only then transferred into the pool. This is the case for uniswap v3 because of the +// callback logic. The only way for this to work it would be to call the second swap during the +// callback of the first swap. This is currently not supported. pub static CALLBACK_CONSTRAINED_PROTOCOLS: LazyLock> = LazyLock::new(|| { let mut set = HashSet::new(); set.insert("uniswap_v3"); diff --git a/src/encoding/evm/strategy_encoder/strategy_encoders.rs b/src/encoding/evm/strategy_encoder/strategy_encoders.rs index 4c1f2ee..df9719e 100644 --- a/src/encoding/evm/strategy_encoder/strategy_encoders.rs +++ b/src/encoding/evm/strategy_encoder/strategy_encoders.rs @@ -33,6 +33,7 @@ use crate::encoding::{ /// * `selector`: String, the selector for the swap function in the router contract /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `token_in_already_in_router`: bool, whether the token in is already in the router #[derive(Clone)] pub struct SingleSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -40,6 +41,7 @@ pub struct SingleSwapStrategyEncoder { selector: String, router_address: Bytes, transfer_optimization: TransferOptimization, + token_in_already_in_router: bool, } impl SingleSwapStrategyEncoder { @@ -55,10 +57,10 @@ impl SingleSwapStrategyEncoder { } else { ( None, - "singleSwap(uint256,address,address,uint256,bool,bool,address,bytes)".to_string(), + "singleSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" + .to_string(), ) }; - let permit2_is_active = permit2.is_some(); Ok(Self { permit2, selector, @@ -67,10 +69,10 @@ impl SingleSwapStrategyEncoder { transfer_optimization: TransferOptimization::new( chain.native_token()?, chain.wrapped_token()?, - permit2_is_active, token_in_already_in_router, router_address, ), + token_in_already_in_router, }) } @@ -125,16 +127,16 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { let swap_receiver = if !unwrap { solution.receiver.clone() } else { self.router_address.clone() }; - let transfer_type = self + let transfer = self .transfer_optimization - .get_transfer_type(grouped_swap.clone(), solution.given_token.clone(), wrap, false); + .get_transfers(grouped_swap.clone(), solution.given_token.clone(), wrap, false); let encoding_context = EncodingContext { receiver: swap_receiver.clone(), exact_out: solution.exact_out, router_address: Some(self.router_address.clone()), group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), - transfer_type: transfer_type.clone(), + transfer_type: transfer, }; let mut grouped_protocol_data: Vec = vec![]; @@ -178,6 +180,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { wrap, unwrap, bytes_to_address(&solution.receiver)?, + !self.token_in_already_in_router, swap_data, ) .abi_encode() @@ -210,6 +213,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder { /// * `sequential_swap_validator`: SequentialSwapValidator, responsible for checking validity of /// sequential swap solutions /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `token_in_already_in_router`: bool, whether the token in is already in the router #[derive(Clone)] pub struct SequentialSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -220,6 +224,7 @@ pub struct SequentialSwapStrategyEncoder { wrapped_address: Bytes, sequential_swap_validator: SequentialSwapValidator, transfer_optimization: TransferOptimization, + token_in_already_in_router: bool, } impl SequentialSwapStrategyEncoder { @@ -235,11 +240,10 @@ impl SequentialSwapStrategyEncoder { } else { ( None, - "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bytes)" + "sequentialSwap(uint256,address,address,uint256,bool,bool,address,bool,bytes)" .to_string(), ) }; - let permit2_is_active = permit2.is_some(); Ok(Self { permit2, selector, @@ -251,10 +255,10 @@ impl SequentialSwapStrategyEncoder { transfer_optimization: TransferOptimization::new( chain.native_token()?, chain.wrapped_token()?, - permit2_is_active, token_in_already_in_router, router_address, ), + token_in_already_in_router, }) } @@ -311,9 +315,10 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { .transfer_optimization .get_receiver(solution.receiver.clone(), next_swap)?; next_in_between_swap_optimization_allowed = next_swap_optimization; - let transfer_type = self + + let transfer = self .transfer_optimization - .get_transfer_type( + .get_transfers( grouped_swap.clone(), solution.given_token.clone(), wrap, @@ -325,7 +330,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { router_address: Some(self.router_address.clone()), group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), - transfer_type: transfer_type.clone(), + transfer_type: transfer, }; let mut grouped_protocol_data: Vec = vec![]; @@ -374,6 +379,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { wrap, unwrap, bytes_to_address(&solution.receiver)?, + !self.token_in_already_in_router, encoded_swaps, ) .abi_encode() @@ -406,6 +412,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder { /// solutions /// * `router_address`: Address of the router to be used to execute swaps /// * `transfer_optimization`: TransferOptimization, responsible for optimizing the token transfers +/// * `token_in_already_in_router`: bool, whether the token in is already in the router #[derive(Clone)] pub struct SplitSwapStrategyEncoder { swap_encoder_registry: SwapEncoderRegistry, @@ -416,6 +423,7 @@ pub struct SplitSwapStrategyEncoder { split_swap_validator: SplitSwapValidator, router_address: Bytes, transfer_optimization: TransferOptimization, + token_in_already_in_router: bool, } impl SplitSwapStrategyEncoder { @@ -431,11 +439,10 @@ impl SplitSwapStrategyEncoder { } else { ( None, - "splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bytes)" + "splitSwap(uint256,address,address,uint256,bool,bool,uint256,address,bool,bytes)" .to_string(), ) }; - let permit2_is_active = permit2.is_some(); Ok(Self { permit2, selector, @@ -447,10 +454,10 @@ impl SplitSwapStrategyEncoder { transfer_optimization: TransferOptimization::new( chain.native_token()?, chain.wrapped_token()?, - permit2_is_active, token_in_already_in_router, router_address, ), + token_in_already_in_router, }) } @@ -553,16 +560,16 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { } else { self.router_address.clone() }; - let transfer_type = self + let transfer = self .transfer_optimization - .get_transfer_type(grouped_swap.clone(), solution.given_token.clone(), wrap, false); + .get_transfers(grouped_swap.clone(), solution.given_token.clone(), wrap, false); let encoding_context = EncodingContext { receiver: swap_receiver.clone(), exact_out: solution.exact_out, router_address: Some(self.router_address.clone()), group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), - transfer_type: transfer_type.clone(), + transfer_type: transfer, }; let mut grouped_protocol_data: Vec = vec![]; @@ -621,6 +628,7 @@ impl StrategyEncoder for SplitSwapStrategyEncoder { unwrap, U256::from(tokens_len), bytes_to_address(&solution.receiver)?, + !self.token_in_already_in_router, encoded_swaps, ) .abi_encode() @@ -750,7 +758,7 @@ mod tests { let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ "30ace1b1", // Function selector - "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount out + "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out &expected_min_amount_encoded, // min amount out @@ -772,7 +780,7 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "00", // zero2one - "02", // transfer type + "00", // transfer type TransferFrom "0000000000000000000000000000", // padding )); let hex_calldata = encode(&calldata); @@ -839,7 +847,7 @@ mod tests { .unwrap(); let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ - "20144a07", // Function selector + "5c4b639c", // Function selector "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out @@ -847,7 +855,8 @@ mod tests { "0000000000000000000000000000000000000000000000000000000000000000", // wrap "0000000000000000000000000000000000000000000000000000000000000000", // unwrap "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes + "0000000000000000000000000000000000000000000000000000000000000001", // transfer from needed + "0000000000000000000000000000000000000000000000000000000000000120", // offset of swap bytes "0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding // Swap data @@ -856,8 +865,8 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "00", // zero2one - "01", // transfer type - "0000000000000000000000000000", // padding + "00", // transfer type TransferFrom + "0000000000000000000000000000", // padding ] .join(""); @@ -921,7 +930,7 @@ mod tests { .unwrap(); let expected_min_amount_encoded = hex::encode(U256::abi_encode(&expected_min_amount)); let expected_input = [ - "20144a07", // Function selector + "5c4b639c", // Function selector "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in "0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out @@ -929,7 +938,8 @@ mod tests { "0000000000000000000000000000000000000000000000000000000000000000", // wrap "0000000000000000000000000000000000000000000000000000000000000000", // unwrap "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "0000000000000000000000000000000000000000000000000000000000000100", // offset of swap bytes + "0000000000000000000000000000000000000000000000000000000000000000", // transfer from not needed + "0000000000000000000000000000000000000000000000000000000000000120", // offset of swap bytes "0000000000000000000000000000000000000000000000000000000000000052", // length of swap bytes without padding // Swap data @@ -938,7 +948,7 @@ mod tests { "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "00", // zero2one - "00", // transfer type + "01", // transfer type Transfer "0000000000000000000000000000", // padding ] .join(""); @@ -1191,7 +1201,7 @@ mod tests { let hex_calldata = encode(&calldata); let expected = String::from(concat!( - "e8a980d7", /* function selector */ + "e21dd0d3", /* function selector */ "0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in "000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in "000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token ou @@ -1199,7 +1209,8 @@ mod tests { "0000000000000000000000000000000000000000000000000000000000000000", // wrap "0000000000000000000000000000000000000000000000000000000000000000", // unwrap "000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver - "0000000000000000000000000000000000000000000000000000000000000100", /* length ple + "0000000000000000000000000000000000000000000000000000000000000001", /* transfer from needed */ + "0000000000000000000000000000000000000000000000000000000000000120", /* length ple * encode */ "00000000000000000000000000000000000000000000000000000000000000a8", // swap 1 @@ -1209,7 +1220,7 @@ mod tests { "bb2b8038a1640196fbe3e38816f3e67cba72d940", // component id "004375dff511095cc5a197a54140a24efef3a416", // receiver (next pool) "00", // zero to one - "01", // transfer type + "00", // transfer type TransferFrom // swap 2 "0052", // swap length "5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address @@ -1217,7 +1228,7 @@ mod tests { "004375dff511095cc5a197a54140a24efef3a416", // component id "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver (final user) "01", // zero to one - "05", // transfer type - None + "02", // transfer type None "000000000000000000000000000000000000000000000000", // padding )); @@ -1336,7 +1347,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "02", // transfer type + "00", // transfer type TransferFrom "0069", // ple encoded swaps "2e234dae75c793f67a35089c9d99245e1c58470b", // executor address "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in @@ -1345,7 +1356,7 @@ mod tests { "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "00", // zero2one - "00", // transfer type + "01", // transfer type Transfer "00000000000000000000", // padding ] .join(""); @@ -2062,7 +2073,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "01", // zero2one - "02", // transfer type + "00", // transfer type TransferFrom "006e", // ple encoded swaps "00", // token in index "01", // token out index @@ -2074,7 +2085,7 @@ mod tests { "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "01", // zero2one - "02", // transfer type + "00", // transfer type TransferFrom "0057", // ple encoded swaps "01", // token in index "00", // token out index @@ -2084,7 +2095,7 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id, "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "00", // zero2one - "00", // transfer type + "01", // transfer type Transfer "00000000000000" // padding ] .join(""); @@ -2224,7 +2235,7 @@ mod tests { "b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id "3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver "01", // zero2one - "02", // transfer type + "00", // transfer type TransferFrom "006e", // ple encoded swaps "01", // token in index "00", // token out index @@ -2236,7 +2247,7 @@ mod tests { "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id "00", // zero2one - "00", // transfer type + "01", // transfer type Transfer "006e", // ple encoded swaps "01", // token in index "00", // token out index @@ -2248,7 +2259,7 @@ mod tests { "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver "8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id "00", // zero2one - "00", // transfer type + "01", // transfer type Transfer "00000000000000" // padding ] .join(""); @@ -2625,7 +2636,7 @@ mod tests { "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in "6982508145454ce325ddbe47a25d4ec3d2311933", // group token in "00", // zero2one - "02", // transfer type (transfer to router) + "00", // transfer type TransferFrom "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver // First pool params "0000000000000000000000000000000000000000", // intermediary token (ETH) diff --git a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs index 62bfca1..c13c028 100644 --- a/src/encoding/evm/strategy_encoder/transfer_optimizations.rs +++ b/src/encoding/evm/strategy_encoder/transfer_optimizations.rs @@ -16,7 +16,6 @@ use crate::encoding::{ pub struct TransferOptimization { native_token: Bytes, wrapped_token: Bytes, - permit2: bool, token_in_already_in_router: bool, router_address: Bytes, } @@ -25,69 +24,58 @@ impl TransferOptimization { pub fn new( native_token: Bytes, wrapped_token: Bytes, - permit2: bool, token_in_already_in_router: bool, router_address: Bytes, ) -> Self { TransferOptimization { native_token, wrapped_token, - permit2, token_in_already_in_router, router_address, } } - /// Returns the transfer method that should be used for the given swap and solution. - pub fn get_transfer_type( + /// Returns the transfer type that should be used for the current transfer. + pub fn get_transfers( &self, swap: SwapGroup, given_token: Bytes, wrap: bool, in_between_swap_optimization: bool, ) -> TransferType { + let is_first_swap = swap.token_in == given_token; let in_transfer_required: bool = IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.protocol_system.as_str()); - let is_first_swap = swap.token_in == given_token; - if swap.token_in == self.native_token { // Funds are already in router. All protocols currently take care of native transfers. TransferType::None } else if (swap.token_in == self.wrapped_token) && wrap { - // Wrapping already happened in the router so we can just use a normal transfer. - TransferType::TransferToProtocol + // Wrapping already happened in the router so, we just do a normal transfer. + TransferType::Transfer } else if is_first_swap { if in_transfer_required { if self.token_in_already_in_router { // Transfer from router to pool. - TransferType::TransferToProtocol - } else if self.permit2 { - // Transfer from swapper to pool using permit2. - TransferType::TransferPermit2ToProtocol + TransferType::Transfer } else { - // Transfer from swapper to pool. - TransferType::TransferFromToProtocol + // Transfer from swapper to pool + TransferType::TransferFrom } - // in transfer is not necessary for these protocols. Only make a transfer if the - // tokens are not already in the router + // in transfer is not necessary for these protocols. Only make a transfer from the + // swapper to the router if the tokens are not already in the router } else if !self.token_in_already_in_router { - if self.permit2 { - // Transfer from swapper to router using permit2. - TransferType::TransferPermit2ToRouter - } else { - // Transfer from swapper to router. - TransferType::TransferFromToRouter - } + // Transfer from swapper to router using. + TransferType::TransferFrom } else { TransferType::None } - // all other swaps + // all other swaps that not the first one } else if !in_transfer_required || in_between_swap_optimization { // funds should already be in the router or in the next pool TransferType::None } else { - TransferType::TransferToProtocol + TransferType::Transfer } } @@ -155,146 +143,63 @@ mod tests { Bytes::from("0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f") } - #[test] - fn test_first_swap_transfer_from_permit2() { + #[rstest] + // First swap tests + // WETH -(univ2)-> DAI we expect a transfer from the user to the protocol + #[case(weth(), weth(), "uniswap_v2".to_string(), false, false,false, TransferType::TransferFrom)] + // Native token swap. No transfer is needed + #[case(eth(), eth(), "uniswap_v2".to_string(),false, false,false, TransferType::None)] + // ETH -(wrap)-> WETH -(univ2)-> DAI. Only a transfer from the router into the protocol is + // needed + #[case(eth(), weth(), "uniswap_v2".to_string(),true, false,false,TransferType::Transfer)] + // USDC -(univ2)-> DAI and the tokens are already in the router. Only a transfer from the router + // to the protocol is needed + #[case(usdc(), usdc(), "uniswap_v2".to_string(),false, true,false, TransferType::Transfer)] + // USDC -(curve)-> DAI and the tokens are already in the router. No transfer is needed + #[case(usdc(), usdc(), "vm:curve".to_string(),false, true, false,TransferType::None)] + // other swaps tests + // tokens need to be transferred into the pool + #[case(weth(), usdc(), "uniswap_v2".to_string(), false, false,false, TransferType::Transfer)] + // tokens are already in the pool (optimization) + #[case(weth(), usdc(), "uniswap_v2".to_string(), false, false, true, TransferType::None)] + // tokens are already in the router and don't need a transfer + #[case(weth(), usdc(), "vm:curve".to_string(), false, false, false, TransferType::None)] + fn test_get_transfers( + #[case] given_token: Bytes, + #[case] swap_token_in: Bytes, + #[case] protocol: String, + #[case] wrap: bool, + #[case] token_in_already_in_router: bool, + #[case] in_between_swap_optimization: bool, + #[case] expected_transfer: TransferType, + ) { // The swap token is the same as the given token, which is not the native token - let swap = SwapGroup { - protocol_system: "uniswap_v2".to_string(), - token_in: weth(), + let swaps = vec![Swap { + component: ProtocolComponent { + protocol_system: "uniswap_v2".to_string(), + id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(), + ..Default::default() + }, + token_in: swap_token_in.clone(), token_out: dai(), split: 0f64, - swaps: vec![], - }; - let optimization = TransferOptimization::new(eth(), weth(), true, false, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); - assert_eq!(transfer_method, TransferType::TransferPermit2ToProtocol); - } - - #[test] - fn test_first_swap_transfer_from() { - // The swap token is the same as the given token, which is not the native token + }]; let swap = SwapGroup { - protocol_system: "uniswap_v2".to_string(), - token_in: weth(), + protocol_system: protocol, + token_in: swap_token_in, token_out: dai(), split: 0f64, - swaps: vec![], + swaps, }; - let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); - assert_eq!(transfer_method, TransferType::TransferFromToProtocol); - } - - #[test] - fn test_first_swap_native() { - // The swap token is the same as the given token, and it's the native token. - // No transfer action is needed. - let swap = SwapGroup { - protocol_system: "uniswap_v2".to_string(), - token_in: eth(), - token_out: dai(), - split: 0f64, - swaps: vec![], - }; - let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), false, false); - assert_eq!(transfer_method, TransferType::None); - } - - #[test] - fn test_first_swap_wrapped() { - // The swap token is NOT the same as the given token, but we are wrapping. - // Since the swap's token in is the wrapped token - this is the first swap. - let swap = SwapGroup { - protocol_system: "uniswap_v2".to_string(), - token_in: weth(), - token_out: dai(), - split: 0f64, - swaps: vec![], - }; - let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), eth(), true, false); - assert_eq!(transfer_method, TransferType::TransferToProtocol); - } - - #[test] - fn test_not_first_swap() { - // The swap token is NOT the same as the given token, and we are NOT wrapping. - // Thus, this is not the first swap. - let swap = SwapGroup { - protocol_system: "uniswap_v2".to_string(), - token_in: usdc(), - token_out: dai(), - split: 0f64, - swaps: vec![], - }; - let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); - assert_eq!(transfer_method, TransferType::TransferToProtocol); - } - - #[test] - fn test_not_first_swap_funds_in_router() { - // Not the first swap and the protocol requires the funds to be in the router (which they - // already are, so the transfer type is None) - let swap = SwapGroup { - protocol_system: "vm:curve".to_string(), - token_in: usdc(), - token_out: dai(), - split: 0f64, - swaps: vec![], - }; - let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, false); - assert_eq!(transfer_method, TransferType::None); - } - - #[test] - fn test_not_first_swap_in_between_swap_optimization() { - // Not the first swap and the in between swaps are optimized. The funds should already be in - // the next pool or in the router - let swap = SwapGroup { - protocol_system: "uniswap_v2".to_string(), - token_in: usdc(), - token_out: dai(), - split: 0f64, - swaps: vec![], - }; - let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), weth(), false, true); - assert_eq!(transfer_method, TransferType::None); - } - - #[test] - fn test_first_swap_tokens_already_in_router_optimization() { - // It is the first swap, tokens are already in the router and the protocol requires the - // transfer in - let swap = SwapGroup { - protocol_system: "uniswap_v2".to_string(), - token_in: usdc(), - token_out: dai(), - split: 0f64, - swaps: vec![], - }; - let optimization = TransferOptimization::new(eth(), weth(), false, true, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), usdc(), false, false); - assert_eq!(transfer_method, TransferType::TransferToProtocol); - } - - #[test] - fn test_first_swap_tokens_already_in_router_no_transfer_needed_optimization() { - // It is the first swap, tokens are already in the router and the protocol does not require - // the transfer in - let swap = SwapGroup { - protocol_system: "vm:curve".to_string(), - token_in: usdc(), - token_out: dai(), - split: 0f64, - swaps: vec![], - }; - let optimization = TransferOptimization::new(eth(), weth(), false, true, router_address()); - let transfer_method = optimization.get_transfer_type(swap.clone(), usdc(), false, false); - assert_eq!(transfer_method, TransferType::None); + let optimization = + TransferOptimization::new(eth(), weth(), token_in_already_in_router, router_address()); + let transfer = optimization.get_transfers( + swap.clone(), + given_token, + wrap, + in_between_swap_optimization, + ); + assert_eq!(transfer, expected_transfer); } fn receiver() -> Bytes { @@ -319,7 +224,7 @@ mod tests { #[case] expected_receiver: Bytes, #[case] expected_optimization: bool, ) { - let optimization = TransferOptimization::new(eth(), weth(), false, false, router_address()); + let optimization = TransferOptimization::new(eth(), weth(), false, router_address()); let next_swap = if protocol.is_none() { None diff --git a/src/encoding/evm/swap_encoder/swap_encoders.rs b/src/encoding/evm/swap_encoder/swap_encoders.rs index 9924418..2c123ef 100644 --- a/src/encoding/evm/swap_encoder/swap_encoders.rs +++ b/src/encoding/evm/swap_encoder/swap_encoders.rs @@ -592,7 +592,7 @@ mod tests { #[test] fn test_encode_uniswap_v2() { let usv2_pool = ProtocolComponent { - id: String::from("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), + id: String::from("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11"), ..Default::default() }; @@ -605,12 +605,12 @@ mod tests { split: 0f64, }; let encoding_context = EncodingContext { - receiver: Bytes::from("0x0000000000000000000000000000000000000001"), + receiver: Bytes::from("0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e"), // BOB exact_out: false, router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, + transfer_type: TransferType::Transfer, }; let encoder = UniswapV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -628,15 +628,16 @@ mod tests { // in token "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // component id - "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", + "a478c2975ab1ea89e8196811f51a7b7ade33eb11", // receiver - "0000000000000000000000000000000000000001", + "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // zero for one "00", - // transfer type (transfer) - "00", + // transfer type Transfer + "01", )) ); + write_calldata_to_file("test_encode_uniswap_v2", hex_swap.as_str()); } } @@ -668,7 +669,7 @@ mod tests { router_address: Some(Bytes::zero(20)), group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, + transfer_type: TransferType::Transfer, }; let encoder = UniswapV3SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -695,8 +696,8 @@ mod tests { "88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // zero for one "00", - // transfer type (transfer) - "00", + // transfer type Transfer + "01", )) ); } @@ -759,8 +760,8 @@ mod tests { "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // approval needed "01", - // transfer type - "05" + // transfer type None + "02" )) ); write_calldata_to_file("test_encode_balancer_v2", hex_swap.as_str()); @@ -804,7 +805,7 @@ mod tests { group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, + transfer_type: TransferType::Transfer, }; let encoder = UniswapV4SwapEncoder::new( String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"), @@ -826,8 +827,8 @@ mod tests { "dac17f958d2ee523a2206206994597c13d831ec7", // zero for one "01", - // transfer type - "00", + // transfer type Transfer + "01", // receiver "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // pool params: @@ -875,7 +876,7 @@ mod tests { group_token_in: group_token_in.clone(), // Token out is the same as the group token out group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, + transfer_type: TransferType::Transfer, }; let encoder = UniswapV4SwapEncoder::new( @@ -918,7 +919,7 @@ mod tests { router_address: Some(router_address.clone()), group_token_in: usde_address.clone(), group_token_out: wbtc_address.clone(), - transfer_type: TransferType::TransferToProtocol, + transfer_type: TransferType::Transfer, }; // Setup - First sequence: USDE -> USDT @@ -996,8 +997,8 @@ mod tests { "2260fac5e5542a773aa44fbcfedf7c193bc2c599", // zero for one "01", - // transfer type - "00", + // transfer type Transfer + "01", // receiver "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // pool params: @@ -1053,7 +1054,7 @@ mod tests { group_token_out: token_out.clone(), exact_out: false, router_address: Some(Bytes::default()), - transfer_type: TransferType::TransferToProtocol, + transfer_type: TransferType::Transfer, }; let encoder = @@ -1069,8 +1070,8 @@ mod tests { assert_eq!( hex_swap, concat!( - // transfer type - "00", + // transfer type Transfer + "01", // receiver "ca4f73fe97d0b987a0d12b39bbd562c779bab6f6", // group token in @@ -1099,7 +1100,7 @@ mod tests { group_token_out: group_token_out.clone(), exact_out: false, router_address: Some(Bytes::default()), - transfer_type: TransferType::TransferToProtocol, + transfer_type: TransferType::Transfer, }; let first_swap = Swap { @@ -1149,8 +1150,8 @@ mod tests { combined_hex, // transfer type concat!( - // transfer type - "00", + // transfer type Transfer + "01", // receiver "ca4f73fe97d0b987a0d12b39bbd562c779bab6f6", // group token in @@ -1332,10 +1333,10 @@ mod tests { "01", // approval needed "01", - // transfer type - "05", + // transfer type None + "02", // receiver, - "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e" + "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", )) ); } @@ -1403,10 +1404,10 @@ mod tests { "00", // approval needed "01", - // transfer type - "05", + // transfer type None + "02", // receiver - "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e" + "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", )) ); } @@ -1484,10 +1485,10 @@ mod tests { "01", // approval needed "01", - // transfer type - "05", + // transfer type None + "02", // receiver - "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e" + "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", )) ); } @@ -1516,7 +1517,7 @@ mod tests { router_address: Some(Bytes::default()), group_token_in: token_in.clone(), group_token_out: token_out.clone(), - transfer_type: TransferType::TransferToProtocol, + transfer_type: TransferType::Transfer, }; let encoder = MaverickV2SwapEncoder::new( String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"), @@ -1539,8 +1540,8 @@ mod tests { "14Cf6D2Fe3E1B326114b07d22A6F6bb59e346c67", // receiver "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", - // transfer from router to protocol - "00", + // transfer true + "01", )) .to_lowercase() ); diff --git a/src/encoding/evm/tycho_encoders.rs b/src/encoding/evm/tycho_encoders.rs index 1eb3390..51d27f5 100644 --- a/src/encoding/evm/tycho_encoders.rs +++ b/src/encoding/evm/tycho_encoders.rs @@ -6,7 +6,7 @@ use tycho_common::Bytes; use crate::encoding::{ errors::EncodingError, evm::{ - constants::GROUPABLE_PROTOCOLS, + constants::{GROUPABLE_PROTOCOLS, IN_TRANSFER_REQUIRED_PROTOCOLS}, group_swaps::group_swaps, strategy_encoder::strategy_encoders::{ SequentialSwapStrategyEncoder, SingleSwapStrategyEncoder, SplitSwapStrategyEncoder, @@ -273,13 +273,20 @@ impl TychoExecutorEncoder { let mut grouped_protocol_data: Vec = vec![]; for swap in grouped_swap.swaps.iter() { + let transfer = if IN_TRANSFER_REQUIRED_PROTOCOLS + .contains(&swap.component.protocol_system.as_str()) + { + TransferType::Transfer + } else { + TransferType::None + }; let encoding_context = EncodingContext { receiver: receiver.clone(), exact_out: solution.exact_out, router_address: None, group_token_in: grouped_swap.token_in.clone(), group_token_out: grouped_swap.token_out.clone(), - transfer_type: TransferType::TransferToProtocol, + transfer_type: transfer, }; let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?; grouped_protocol_data.extend(protocol_data); @@ -461,7 +468,7 @@ mod tests { Bytes::from_str("0x3ede3eca2a72b3aecc820e955b36f38437d01395").unwrap() ); // single swap selector - assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "20144a07"); + assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "5c4b639c"); } #[test] @@ -486,7 +493,7 @@ mod tests { let transactions = transactions.unwrap(); assert_eq!(transactions.len(), 1); // single swap selector - assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "20144a07"); + assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "5c4b639c"); } #[test] @@ -533,7 +540,7 @@ mod tests { assert_eq!(transactions.len(), 1); assert_eq!(transactions[0].value, eth_amount_in); // sequential swap selector - assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e8a980d7"); + assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e21dd0d3"); } #[test] @@ -1059,8 +1066,8 @@ mod tests { "1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e", // zero for one "00", - // transfer type - "00", + // transfer true + "01", )) ); } @@ -1147,8 +1154,8 @@ mod tests { "6982508145454ce325ddbe47a25d4ec3d2311933", // zero for one "00", - // transfer type - "00", + // transfer type Transfer + "01", // receiver "cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // first pool intermediary token (ETH) diff --git a/src/encoding/models.rs b/src/encoding/models.rs index 052df36..ef8ae1b 100644 --- a/src/encoding/models.rs +++ b/src/encoding/models.rs @@ -96,27 +96,6 @@ pub struct Transaction { pub data: Vec, } -/// Represents the type of transfer to be performed into the pool. -/// -/// # Fields -/// -/// * `TransferToProtocol`: Transfer the token from the router into the protocol. -/// * `TransferFromToProtocol`: Transfer the token from the sender to the protocol. -/// * `TransferPermit2ToProtocol`: Transfer the token from the sender to the protocol using Permit2. -/// * `TransferFromToRouter`: Transfer the token from the sender to the router. -/// * `TransferPermit2ToRouter`: Transfer the token from the sender to the router using Permit2. -/// * `None`: No transfer is needed. Tokens are already in the pool. -#[repr(u8)] -#[derive(Clone, Debug, PartialEq)] -pub enum TransferType { - TransferToProtocol = 0, - TransferFromToProtocol = 1, - TransferPermit2ToProtocol = 2, - TransferFromToRouter = 3, - TransferPermit2ToRouter = 4, - None = 5, -} - /// Represents necessary attributes for encoding an order. /// /// # Fields @@ -127,6 +106,7 @@ pub enum TransferType { /// solution does not require router address. /// * `group_token_in`: Token to be used as the input for the group swap. /// * `group_token_out`: Token to be used as the output for the group swap. +/// * `transfer`: Type of transfer to be performed. See `TransferType` for more details. #[derive(Clone, Debug)] pub struct EncodingContext { pub receiver: Bytes, @@ -137,6 +117,21 @@ pub struct EncodingContext { pub transfer_type: TransferType, } +/// Represents the type of transfer to be performed into the pool. +/// +/// # Fields +/// +/// * `TransferFrom`: Transfer the token from the sender to the protocol/router. +/// * `Transfer`: Transfer the token from the router into the protocol. +/// * `None`: No transfer is needed. Tokens are already in the pool. +#[repr(u8)] +#[derive(Clone, Debug, PartialEq)] +pub enum TransferType { + TransferFrom = 0, + Transfer = 1, + None = 2, +} + #[derive(Clone, PartialEq, Eq, Hash)] pub struct Chain { pub id: u64,