feat: Support new transfer logic in all executors

TODO:
- Fix failing tests
- Remove permit2 from initialization of contracts
This commit is contained in:
TAMARA LIPOWSKI
2025-05-14 20:42:19 -04:00
parent 0f9af65846
commit 27dfde3118
22 changed files with 378 additions and 462 deletions

View File

@@ -27,7 +27,7 @@ contract OneTransferFromOnly {
function tstoreTransferFromInfo( function tstoreTransferFromInfo(
address tokenIn, address tokenIn,
address amountIn, uint256 amountIn,
bool isPermit2, bool isPermit2,
address sender address sender
) internal { ) internal {
@@ -40,10 +40,7 @@ contract OneTransferFromOnly {
} }
} }
function _transfer(address receiver) function _transfer(address receiver) internal {
// we could pass the amount and address too and compare to what is in the slots?
internal
{
address tokenIn; address tokenIn;
uint256 amount; uint256 amount;
bool isPermit2; bool isPermit2;

View File

@@ -122,6 +122,8 @@ contract TychoRouter is
* @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver. * @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 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 receiver The address to receive the output tokens.
* @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router.
* @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true.
* @param swaps Encoded swap graph data containing details of each swap. * @param swaps Encoded swap graph data containing details of each swap.
* *
* @return amountOut The total amount of the output token received by the receiver. * @return amountOut The total amount of the output token received by the receiver.
@@ -135,9 +137,14 @@ contract TychoRouter is
bool unwrapEth, bool unwrapEth,
uint256 nTokens, uint256 nTokens,
address receiver, address receiver,
bool transferFromRequired,
address tokenInReceiver,
bytes calldata swaps bytes calldata swaps
) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) {
tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender); tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender);
if (transferFromRequired) {
_transfer(tokenInReceiver);
}
return _splitSwapChecked( return _splitSwapChecked(
amountIn, amountIn,
tokenIn, tokenIn,
@@ -173,6 +180,8 @@ contract TychoRouter is
* @param receiver The address to receive the output tokens. * @param receiver The address to receive the output tokens.
* @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true. * @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true.
* @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true. * @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true.
* @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router.
* @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true.
* @param swaps Encoded swap graph data containing details of each swap. * @param swaps Encoded swap graph data containing details of each swap.
* *
* @return amountOut The total amount of the output token received by the receiver. * @return amountOut The total amount of the output token received by the receiver.
@@ -188,14 +197,18 @@ contract TychoRouter is
address receiver, address receiver,
IAllowanceTransfer.PermitSingle calldata permitSingle, IAllowanceTransfer.PermitSingle calldata permitSingle,
bytes calldata signature, bytes calldata signature,
bool transferFromRequired,
address tokenInReceiver,
bytes calldata swaps bytes calldata swaps
) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) {
// For native ETH, assume funds already in our router. Else, handle approval. // For native ETH, assume funds already in our router. Else, handle approval.
if (tokenIn != address(0)) { if (tokenIn != address(0)) {
permit2.permit(msg.sender, permitSingle, signature); permit2.permit(msg.sender, permitSingle, signature);
} }
tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender); tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender);
if (transferFromRequired) {
_transfer(tokenInReceiver);
}
return _splitSwapChecked( return _splitSwapChecked(
amountIn, amountIn,
@@ -228,6 +241,8 @@ contract TychoRouter is
* @param wrapEth If true, wraps the input token (native ETH) into WETH. * @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 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 receiver The address to receive the output tokens.
* @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router.
* @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true.
* @param swaps Encoded swap graph data containing details of each swap. * @param swaps Encoded swap graph data containing details of each swap.
* *
* @return amountOut The total amount of the output token received by the receiver. * @return amountOut The total amount of the output token received by the receiver.
@@ -240,9 +255,14 @@ contract TychoRouter is
bool wrapEth, bool wrapEth,
bool unwrapEth, bool unwrapEth,
address receiver, address receiver,
bool transferFromRequired,
address tokenInReceiver,
bytes calldata swaps bytes calldata swaps
) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) {
tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender); tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender);
if (transferFromRequired) {
_transfer(tokenInReceiver);
}
return _sequentialSwapChecked( return _sequentialSwapChecked(
amountIn, amountIn,
tokenIn, tokenIn,
@@ -275,6 +295,8 @@ contract TychoRouter is
* @param receiver The address to receive the output tokens. * @param receiver The address to receive the output tokens.
* @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true. * @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true.
* @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true. * @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true.
* @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router.
* @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true.
* @param swaps Encoded swap graph data containing details of each swap. * @param swaps Encoded swap graph data containing details of each swap.
* *
* @return amountOut The total amount of the output token received by the receiver. * @return amountOut The total amount of the output token received by the receiver.
@@ -289,6 +311,8 @@ contract TychoRouter is
address receiver, address receiver,
IAllowanceTransfer.PermitSingle calldata permitSingle, IAllowanceTransfer.PermitSingle calldata permitSingle,
bytes calldata signature, bytes calldata signature,
bool transferFromRequired,
address tokenInReceiver,
bytes calldata swaps bytes calldata swaps
) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) {
// For native ETH, assume funds already in our router. Else, handle approval. // For native ETH, assume funds already in our router. Else, handle approval.
@@ -297,6 +321,9 @@ contract TychoRouter is
} }
tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender); tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender);
if (transferFromRequired) {
_transfer(tokenInReceiver);
}
return _sequentialSwapChecked( return _sequentialSwapChecked(
amountIn, amountIn,
tokenIn, tokenIn,
@@ -325,6 +352,8 @@ contract TychoRouter is
* @param wrapEth If true, wraps the input token (native ETH) into WETH. * @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 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 receiver The address to receive the output tokens.
* @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router.
* @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true.
* @param swapData Encoded swap details. * @param swapData Encoded swap details.
* *
* @return amountOut The total amount of the output token received by the receiver. * @return amountOut The total amount of the output token received by the receiver.
@@ -337,13 +366,13 @@ contract TychoRouter is
bool wrapEth, bool wrapEth,
bool unwrapEth, bool unwrapEth,
address receiver, address receiver,
bool inTransferNeeded, bool transferFromRequired,
address fundsReceiver, address tokenInReceiver,
bytes calldata swapData bytes calldata swapData
) public payable whenNotPaused nonReentrant returns (uint256 amountOut) { ) public payable whenNotPaused nonReentrant returns (uint256 amountOut) {
tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender); tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender);
if (inTransferNeeded) { if (transferFromRequired) {
_transfer(fundsReceiver); _transfer(tokenInReceiver);
} }
return _singleSwap( return _singleSwap(
amountIn, amountIn,
@@ -377,6 +406,8 @@ contract TychoRouter is
* @param receiver The address to receive the output tokens. * @param receiver The address to receive the output tokens.
* @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true. * @param permitSingle A Permit2 structure containing token approval details for the input token. Ignored if `wrapEth` is true.
* @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true. * @param signature A valid signature authorizing the Permit2 approval. Ignored if `wrapEth` is true.
* @param transferFromRequired If true, the contract will transfer the input token from the caller to the receiver. Otherwise, assume funds are already in router.
* @param tokenInReceiver The address to receive the input tokens. This is used when `transferFromRequired` is true.
* @param swapData Encoded swap details. * @param swapData Encoded swap details.
* *
* @return amountOut The total amount of the output token received by the receiver. * @return amountOut The total amount of the output token received by the receiver.
@@ -391,6 +422,8 @@ contract TychoRouter is
address receiver, address receiver,
IAllowanceTransfer.PermitSingle calldata permitSingle, IAllowanceTransfer.PermitSingle calldata permitSingle,
bytes calldata signature, bytes calldata signature,
bool transferFromRequired,
address tokenInReceiver,
bytes calldata swapData bytes calldata swapData
) external payable whenNotPaused nonReentrant returns (uint256 amountOut) { ) external payable whenNotPaused nonReentrant returns (uint256 amountOut) {
// For native ETH, assume funds already in our router. Else, handle approval. // For native ETH, assume funds already in our router. Else, handle approval.
@@ -398,6 +431,9 @@ contract TychoRouter is
permit2.permit(msg.sender, permitSingle, signature); permit2.permit(msg.sender, permitSingle, signature);
} }
tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender); tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender);
if (transferFromRequired) {
_transfer(tokenInReceiver);
}
return _singleSwap( return _singleSwap(
amountIn, amountIn,
tokenIn, tokenIn,

View File

@@ -10,16 +10,15 @@ import {
import {IAsset} from "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol"; import {IAsset} from "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol";
// slither-disable-next-line solc-version // slither-disable-next-line solc-version
import {IVault} from "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; import {IVault} from "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol";
import {TokenTransfer} from "./TokenTransfer.sol";
error BalancerV2Executor__InvalidDataLength(); error BalancerV2Executor__InvalidDataLength();
contract BalancerV2Executor is IExecutor, TokenTransfer { contract BalancerV2Executor is IExecutor {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
address private constant VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; address private constant VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
constructor(address _permit2) TokenTransfer(_permit2) {} constructor(address _permit2) {}
// slither-disable-next-line locked-ether // slither-disable-next-line locked-ether
function swap(uint256 givenAmount, bytes calldata data) function swap(uint256 givenAmount, bytes calldata data)
@@ -33,19 +32,9 @@ contract BalancerV2Executor is IExecutor, TokenTransfer {
bytes32 poolId, bytes32 poolId,
address receiver, address receiver,
bool needsApproval, bool needsApproval,
TransferType transferType bool transferNeeded
) = _decodeData(data); ) = _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
);
if (needsApproval) { if (needsApproval) {
// slither-disable-next-line unused-return // slither-disable-next-line unused-return
tokenIn.forceApprove(VAULT, type(uint256).max); tokenIn.forceApprove(VAULT, type(uint256).max);
@@ -82,7 +71,7 @@ contract BalancerV2Executor is IExecutor, TokenTransfer {
bytes32 poolId, bytes32 poolId,
address receiver, address receiver,
bool needsApproval, bool needsApproval,
TransferType transferType bool transferNeeded
) )
{ {
if (data.length != 94) { if (data.length != 94) {
@@ -94,6 +83,6 @@ contract BalancerV2Executor is IExecutor, TokenTransfer {
poolId = bytes32(data[40:72]); poolId = bytes32(data[40:72]);
receiver = address(bytes20(data[72:92])); receiver = address(bytes20(data[72:92]));
needsApproval = uint8(data[92]) > 0; needsApproval = uint8(data[92]) > 0;
transferType = TransferType(uint8(data[93])); transferNeeded = uint8(data[93]) > 0;
} }
} }

View File

@@ -3,7 +3,6 @@ pragma solidity ^0.8.26;
import "@interfaces/IExecutor.sol"; import "@interfaces/IExecutor.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./TokenTransfer.sol";
import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts/utils/Address.sol";
error CurveExecutor__AddressZero(); error CurveExecutor__AddressZero();
@@ -35,14 +34,12 @@ interface CryptoPoolETH {
// slither-disable-end naming-convention // slither-disable-end naming-convention
} }
contract CurveExecutor is IExecutor, TokenTransfer { contract CurveExecutor is IExecutor {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
address public immutable nativeToken; address public immutable nativeToken;
constructor(address _nativeToken, address _permit2) constructor(address _nativeToken, address _permit2) {
TokenTransfer(_permit2)
{
if (_nativeToken == address(0)) { if (_nativeToken == address(0)) {
revert CurveExecutor__AddressZero(); revert CurveExecutor__AddressZero();
} }
@@ -65,20 +62,10 @@ contract CurveExecutor is IExecutor, TokenTransfer {
int128 i, int128 i,
int128 j, int128 j,
bool tokenApprovalNeeded, bool tokenApprovalNeeded,
TransferType transferType, bool transferNeeded, // TODO remove this with the encoding
address receiver address receiver
) = _decodeData(data); ) = _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 (tokenApprovalNeeded && tokenIn != nativeToken) {
// slither-disable-next-line unused-return // slither-disable-next-line unused-return
IERC20(tokenIn).forceApprove(address(pool), type(uint256).max); IERC20(tokenIn).forceApprove(address(pool), type(uint256).max);
@@ -134,7 +121,7 @@ contract CurveExecutor is IExecutor, TokenTransfer {
int128 i, int128 i,
int128 j, int128 j,
bool tokenApprovalNeeded, bool tokenApprovalNeeded,
TransferType transferType, bool transferNeeded,
address receiver address receiver
) )
{ {
@@ -145,7 +132,7 @@ contract CurveExecutor is IExecutor, TokenTransfer {
i = int128(uint128(uint8(data[61]))); i = int128(uint128(uint8(data[61])));
j = int128(uint128(uint8(data[62]))); j = int128(uint128(uint8(data[62])));
tokenApprovalNeeded = data[63] != 0; tokenApprovalNeeded = data[63] != 0;
transferType = TransferType(uint8(data[64])); transferNeeded = data[64] != 0;
receiver = address(bytes20(data[65:85])); receiver = address(bytes20(data[65:85]));
} }

View File

@@ -11,14 +11,14 @@ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
import {LibBytes} from "@solady/utils/LibBytes.sol"; import {LibBytes} from "@solady/utils/LibBytes.sol";
import {Config, EkuboPoolKey} from "@ekubo/types/poolKey.sol"; import {Config, EkuboPoolKey} from "@ekubo/types/poolKey.sol";
import {MAX_SQRT_RATIO, MIN_SQRT_RATIO} from "@ekubo/types/sqrtRatio.sol"; import {MAX_SQRT_RATIO, MIN_SQRT_RATIO} from "@ekubo/types/sqrtRatio.sol";
import {TokenTransfer} from "./TokenTransfer.sol"; import "../OneTransferFromOnly.sol";
contract EkuboExecutor is contract EkuboExecutor is
IExecutor, IExecutor,
ILocker, ILocker,
IPayer, IPayer,
ICallback, ICallback,
TokenTransfer OneTransferFromOnly
{ {
error EkuboExecutor__InvalidDataLength(); error EkuboExecutor__InvalidDataLength();
error EkuboExecutor__CoreOnly(); error EkuboExecutor__CoreOnly();
@@ -32,7 +32,9 @@ contract EkuboExecutor is
bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256) bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256)
bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address) bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address)
constructor(address _core, address _permit2) TokenTransfer(_permit2) { constructor(address _core, address _permit2)
OneTransferFromOnly(_permit2)
{
core = ICore(_core); core = ICore(_core);
} }
@@ -44,13 +46,8 @@ contract EkuboExecutor is
if (data.length < 93) revert EkuboExecutor__InvalidDataLength(); if (data.length < 93) revert EkuboExecutor__InvalidDataLength();
// amountIn must be at most type(int128).MAX // amountIn must be at most type(int128).MAX
calculatedAmount = uint256( calculatedAmount =
_lock( uint256(_lock(bytes.concat(bytes16(uint128(amountIn)), data)));
bytes.concat(
bytes16(uint128(amountIn)), bytes20(msg.sender), data
)
)
);
} }
function handleCallback(bytes calldata raw) function handleCallback(bytes calldata raw)
@@ -126,10 +123,11 @@ contract EkuboExecutor is
int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16]))); int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16])));
uint128 tokenInDebtAmount = uint128(nextAmountIn); uint128 tokenInDebtAmount = uint128(nextAmountIn);
address sender = address(bytes20(swapData[16:36])); address sender = address(bytes20(swapData[16:36]));
uint8 transferType = uint8(swapData[36]); bool transferFromNeeded = (swapData[36] != 0);
bool transferNeeded = (swapData[37] != 0);
address receiver = address(bytes20(swapData[37:57])); address receiver = address(bytes20(swapData[38:58]));
address tokenIn = address(bytes20(swapData[57:77])); address tokenIn = address(bytes20(swapData[58:78]));
address nextTokenIn = tokenIn; address nextTokenIn = tokenIn;
@@ -163,7 +161,7 @@ contract EkuboExecutor is
offset += HOP_BYTE_LEN; offset += HOP_BYTE_LEN;
} }
_pay(tokenIn, tokenInDebtAmount, sender, transferType); _pay(tokenIn, tokenInDebtAmount, transferFromNeeded, transferNeeded);
core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn)); core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn));
return nextAmountIn; return nextAmountIn;
} }
@@ -171,8 +169,8 @@ contract EkuboExecutor is
function _pay( function _pay(
address token, address token,
uint128 amount, uint128 amount,
address sender, bool transferFromNeeded,
uint8 transferType bool transferNeeded
) internal { ) internal {
address target = address(core); address target = address(core);
@@ -186,11 +184,11 @@ contract EkuboExecutor is
mstore(free, shl(224, 0x0c11dedd)) mstore(free, shl(224, 0x0c11dedd))
mstore(add(free, 4), token) mstore(add(free, 4), token)
mstore(add(free, 36), shl(128, amount)) mstore(add(free, 36), shl(128, amount))
mstore(add(free, 52), shl(96, sender)) mstore(add(free, 52), shl(248, transferFromNeeded))
mstore(add(free, 72), shl(248, transferType)) mstore(add(free, 53), shl(248, transferNeeded))
// 4 (selector) + 32 (token) + 16 (amount) + 20 (sender) + 1 (transferType) = 73 // 4 (selector) + 32 (token) + 16 (amount) + 1 (transferFromNeeded) + 1 (transferNeeded) = 54
if iszero(call(gas(), target, 0, free, 73, 0, 0)) { if iszero(call(gas(), target, 0, free, 54, 0, 0)) {
returndatacopy(0, 0, returndatasize()) returndatacopy(0, 0, returndatasize())
revert(0, returndatasize()) revert(0, returndatasize())
} }
@@ -201,9 +199,17 @@ contract EkuboExecutor is
function _payCallback(bytes calldata payData) internal { function _payCallback(bytes calldata payData) internal {
address token = address(bytes20(payData[12:32])); // This arg is abi-encoded address token = address(bytes20(payData[12:32])); // This arg is abi-encoded
uint128 amount = uint128(bytes16(payData[32:48])); uint128 amount = uint128(bytes16(payData[32:48]));
address sender = address(bytes20(payData[48:68])); bool transferFromNeeded = (payData[48] != 0);
TransferType transferType = TransferType(uint8(payData[68])); bool transferNeeded = (payData[49] != 0);
_transfer(token, sender, address(core), amount, transferType); if (transferFromNeeded) {
_transfer(msg.sender);
} else if (transferNeeded) {
if (token == address(0)) {
payable(msg.sender).transfer(amount);
} else {
IERC20(token).transfer(msg.sender, amount);
}
}
} }
// To receive withdrawals from Core // To receive withdrawals from Core

View File

@@ -9,12 +9,12 @@ error MaverickV2Executor__InvalidDataLength();
error MaverickV2Executor__InvalidTarget(); error MaverickV2Executor__InvalidTarget();
error MaverickV2Executor__InvalidFactory(); error MaverickV2Executor__InvalidFactory();
contract MaverickV2Executor is IExecutor, TokenTransfer { contract MaverickV2Executor is IExecutor {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
address public immutable factory; address public immutable factory;
constructor(address _factory, address _permit2) TokenTransfer(_permit2) { constructor(address _factory, address _permit2) {
if (_factory == address(0)) { if (_factory == address(0)) {
revert MaverickV2Executor__InvalidFactory(); revert MaverickV2Executor__InvalidFactory();
} }
@@ -30,9 +30,9 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
address target; address target;
address receiver; address receiver;
IERC20 tokenIn; IERC20 tokenIn;
TransferType transferType; bool transferNeeded;
(tokenIn, target, receiver, transferType) = _decodeData(data); (tokenIn, target, receiver, transferNeeded) = _decodeData(data);
_verifyPairAddress(target); _verifyPairAddress(target);
IMaverickV2Pool pool = IMaverickV2Pool(target); IMaverickV2Pool pool = IMaverickV2Pool(target);
@@ -47,9 +47,15 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
tickLimit: tickLimit tickLimit: tickLimit
}); });
_transfer( if (transferNeeded) {
address(tokenIn), msg.sender, target, givenAmount, transferType if (address(tokenIn) == address(0)) {
); payable(target).transfer(givenAmount);
} else {
// slither-disable-next-line arbitrary-send-erc20
tokenIn.safeTransferFrom(msg.sender, target, givenAmount);
}
}
// slither-disable-next-line unused-return // slither-disable-next-line unused-return
(, calculatedAmount) = pool.swap(receiver, swapParams, ""); (, calculatedAmount) = pool.swap(receiver, swapParams, "");
} }
@@ -61,7 +67,7 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
IERC20 inToken, IERC20 inToken,
address target, address target,
address receiver, address receiver,
TransferType transferType bool transferNeeded
) )
{ {
if (data.length != 61) { if (data.length != 61) {
@@ -70,7 +76,7 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
inToken = IERC20(address(bytes20(data[0:20]))); inToken = IERC20(address(bytes20(data[0:20])));
target = address(bytes20(data[20:40])); target = address(bytes20(data[20:40]));
receiver = address(bytes20(data[40:60])); receiver = address(bytes20(data[40:60]));
transferType = TransferType(uint8(data[60])); transferNeeded = (data[60] != 0);
} }
function _verifyPairAddress(address target) internal view { function _verifyPairAddress(address target) internal view {

View File

@@ -60,7 +60,7 @@ contract UniswapV2Executor is IExecutor {
calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne); calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne);
if (transferNeeded) { if (transferNeeded) {
if (tokenIn == address(0)) { if (address(tokenIn) == address(0)) {
payable(target).transfer(givenAmount); payable(target).transfer(givenAmount);
} else { } else {
// slither-disable-next-line arbitrary-send-erc20 // slither-disable-next-line arbitrary-send-erc20
@@ -93,8 +93,8 @@ contract UniswapV2Executor is IExecutor {
inToken = IERC20(address(bytes20(data[0:20]))); inToken = IERC20(address(bytes20(data[0:20])));
target = address(bytes20(data[20:40])); target = address(bytes20(data[20:40]));
receiver = address(bytes20(data[40:60])); receiver = address(bytes20(data[40:60]));
zeroForOne = uint8(data[60]) > 0; zeroForOne = data[60] != 0;
transferNeeded = bool(data[61]); transferNeeded = data[61] != 0;
} }
function _getAmountOut(address target, uint256 amountIn, bool zeroForOne) function _getAmountOut(address target, uint256 amountIn, bool zeroForOne)

View File

@@ -51,8 +51,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
address receiver, address receiver,
address target, address target,
bool zeroForOne, bool zeroForOne,
bool inTransferNeeded, bool transferFromNeeded,
bool inBetweenSwapsTransferNeeded bool transferNeeded
) = _decodeData(data); ) = _decodeData(data);
_verifyPairAddress(tokenIn, tokenOut, fee, target); _verifyPairAddress(tokenIn, tokenOut, fee, target);
@@ -62,11 +62,7 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
IUniswapV3Pool pool = IUniswapV3Pool(target); IUniswapV3Pool pool = IUniswapV3Pool(target);
bytes memory callbackData = _makeV3CallbackData( bytes memory callbackData = _makeV3CallbackData(
tokenIn, tokenIn, tokenOut, fee, transferFromNeeded, transferNeeded
tokenOut,
fee,
inTransferNeeded,
inBetweenSwapsTransferNeeded
); );
{ {
@@ -104,8 +100,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
address tokenIn = address(bytes20(msgData[132:152])); address tokenIn = address(bytes20(msgData[132:152]));
bool inTransferNeeded = bool(msgData[175]); bool transferFromNeeded = msgData[175] != 0;
bool inBetweenSwapsTransferNeeded = bool(msgData[176]); bool transferNeeded = msgData[176] != 0;
address sender = address(bytes20(msgData[176:196])); address sender = address(bytes20(msgData[176:196]));
verifyCallback(msgData[132:]); verifyCallback(msgData[132:]);
@@ -113,9 +109,9 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
uint256 amountOwed = uint256 amountOwed =
amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta); amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta);
if (inTransferNeeded) { if (transferFromNeeded) {
_transfer(msg.sender); _transfer(msg.sender);
} else if (inBetweenSwapsTransferNeeded) { } else if (transferNeeded) {
if (tokenIn == address(0)) { if (tokenIn == address(0)) {
payable(msg.sender).transfer(amountOwed); payable(msg.sender).transfer(amountOwed);
} else { } else {
@@ -152,8 +148,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
address receiver, address receiver,
address target, address target,
bool zeroForOne, bool zeroForOne,
bool inTransferNeeded, bool transferFromNeeded,
bool inBetweenSwapsTransferNeeded bool transferNeeded
) )
{ {
if (data.length != 85) { if (data.length != 85) {
@@ -165,23 +161,23 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
receiver = address(bytes20(data[43:63])); receiver = address(bytes20(data[43:63]));
target = address(bytes20(data[63:83])); target = address(bytes20(data[63:83]));
zeroForOne = uint8(data[83]) > 0; zeroForOne = uint8(data[83]) > 0;
inTransferNeeded = bool(data[84]); transferFromNeeded = uint8(data[84]) > 0;
inBetweenSwapsTransferNeeded = bool(data[85]); transferNeeded = uint8(data[85]) > 0;
} }
function _makeV3CallbackData( function _makeV3CallbackData(
address tokenIn, address tokenIn,
address tokenOut, address tokenOut,
uint24 fee, uint24 fee,
bool inTransferNeeded, bool transferFromNeeded,
bool inBetweenSwapsTransferNeeded bool transferNeeded
) internal view returns (bytes memory) { ) internal view returns (bytes memory) {
return abi.encodePacked( return abi.encodePacked(
tokenIn, tokenIn,
tokenOut, tokenOut,
fee, fee,
inTransferNeeded, transferFromNeeded,
inBetweenSwapsTransferNeeded, transferNeeded,
msg.sender msg.sender
); );
} }

View File

@@ -3,7 +3,6 @@ pragma solidity ^0.8.26;
import "@interfaces/IExecutor.sol"; import "@interfaces/IExecutor.sol";
import {ICallback} from "@interfaces/ICallback.sol"; import {ICallback} from "@interfaces/ICallback.sol";
import {TokenTransfer} from "./TokenTransfer.sol";
import { import {
IERC20, IERC20,
SafeERC20 SafeERC20
@@ -23,6 +22,7 @@ import {IUnlockCallback} from
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol"; import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {TransientStateLibrary} from import {TransientStateLibrary} from
"@uniswap/v4-core/src/libraries/TransientStateLibrary.sol"; "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
import "../OneTransferFromOnly.sol";
error UniswapV4Executor__InvalidDataLength(); error UniswapV4Executor__InvalidDataLength();
error UniswapV4Executor__NotPoolManager(); error UniswapV4Executor__NotPoolManager();
@@ -37,7 +37,7 @@ contract UniswapV4Executor is
IExecutor, IExecutor,
IUnlockCallback, IUnlockCallback,
ICallback, ICallback,
TokenTransfer OneTransferFromOnly
{ {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
using CurrencyLibrary for Currency; using CurrencyLibrary for Currency;
@@ -57,7 +57,7 @@ contract UniswapV4Executor is
} }
constructor(IPoolManager _poolManager, address _permit2) constructor(IPoolManager _poolManager, address _permit2)
TokenTransfer(_permit2) OneTransferFromOnly(_permit2)
{ {
poolManager = _poolManager; poolManager = _poolManager;
_self = address(this); _self = address(this);
@@ -82,7 +82,8 @@ contract UniswapV4Executor is
address tokenIn, address tokenIn,
address tokenOut, address tokenOut,
bool zeroForOne, bool zeroForOne,
TransferType transferType, bool transferFromNeeded,
bool transferNeeded,
address receiver, address receiver,
UniswapV4Executor.UniswapV4Pool[] memory pools UniswapV4Executor.UniswapV4Pool[] memory pools
) = _decodeData(data); ) = _decodeData(data);
@@ -100,8 +101,8 @@ contract UniswapV4Executor is
key, key,
zeroForOne, zeroForOne,
amountIn, amountIn,
msg.sender, transferFromNeeded,
transferType, transferNeeded,
receiver, receiver,
bytes("") bytes("")
); );
@@ -123,8 +124,8 @@ contract UniswapV4Executor is
currencyIn, currencyIn,
path, path,
amountIn, amountIn,
msg.sender, transferFromNeeded,
transferType, transferNeeded,
receiver receiver
); );
} }
@@ -142,24 +143,26 @@ contract UniswapV4Executor is
address tokenIn, address tokenIn,
address tokenOut, address tokenOut,
bool zeroForOne, bool zeroForOne,
TransferType transferType, bool transferFromNeeded,
bool transferNeeded,
address receiver, address receiver,
UniswapV4Pool[] memory pools UniswapV4Pool[] memory pools
) )
{ {
if (data.length < 88) { if (data.length < 89) {
revert UniswapV4Executor__InvalidDataLength(); revert UniswapV4Executor__InvalidDataLength();
} }
tokenIn = address(bytes20(data[0:20])); tokenIn = address(bytes20(data[0:20]));
tokenOut = address(bytes20(data[20:40])); tokenOut = address(bytes20(data[20:40]));
zeroForOne = (data[40] != 0); zeroForOne = (data[40] != 0);
transferType = TransferType(uint8(data[41])); transferFromNeeded = (data[41] != 0);
receiver = address(bytes20(data[42:62])); transferNeeded = (data[42] != 0);
receiver = address(bytes20(data[43:63]));
uint256 poolsLength = (data.length - 62) / 26; // 26 bytes per pool object uint256 poolsLength = (data.length - 63) / 26; // 26 bytes per pool object
pools = new UniswapV4Pool[](poolsLength); pools = new UniswapV4Pool[](poolsLength);
bytes memory poolsData = data[62:]; bytes memory poolsData = data[63:];
uint256 offset = 0; uint256 offset = 0;
for (uint256 i = 0; i < poolsLength; i++) { for (uint256 i = 0; i < poolsLength; i++) {
address intermediaryToken; address intermediaryToken;
@@ -239,8 +242,8 @@ contract UniswapV4Executor is
* @param poolKey The key of the pool to swap in. * @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 zeroForOne Whether the swap is from token0 to token1 (true) or vice versa (false).
* @param amountIn The amount of tokens to swap in. * @param amountIn The amount of tokens to swap in.
* @param sender The address of the sender. * @param transferFromNeeded Whether to transferFrom input tokens into the core contract from the swapper's wallet .
* @param transferType The type of transfer in to use. * @param transferNeeded Whether to transfer input tokens into the core contract from the router contract
* @param receiver The address of the receiver. * @param receiver The address of the receiver.
* @param hookData Additional data for hook contracts. * @param hookData Additional data for hook contracts.
*/ */
@@ -248,8 +251,8 @@ contract UniswapV4Executor is
PoolKey memory poolKey, PoolKey memory poolKey,
bool zeroForOne, bool zeroForOne,
uint128 amountIn, uint128 amountIn,
address sender, bool transferFromNeeded,
TransferType transferType, bool transferNeeded,
address receiver, address receiver,
bytes calldata hookData bytes calldata hookData
) external returns (uint128) { ) external returns (uint128) {
@@ -262,7 +265,7 @@ contract UniswapV4Executor is
if (amount > amountIn) { if (amount > amountIn) {
revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount); revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount);
} }
_settle(currencyIn, amount, sender, transferType); _settle(currencyIn, amount, transferFromNeeded, transferNeeded);
Currency currencyOut = Currency currencyOut =
zeroForOne ? poolKey.currency1 : poolKey.currency0; zeroForOne ? poolKey.currency1 : poolKey.currency0;
@@ -275,16 +278,16 @@ contract UniswapV4Executor is
* @param currencyIn The currency of the input token. * @param currencyIn The currency of the input token.
* @param path The path to swap along. * @param path The path to swap along.
* @param amountIn The amount of tokens to swap in. * @param amountIn The amount of tokens to swap in.
* @param sender The address of the sender. * @param transferFromNeeded Whether to transferFrom input tokens into the core contract from the swapper's wallet .
* @param transferType The type of transfer in to use. * @param transferNeeded Whether to transfer input tokens into the core contract from the router contract
* @param receiver The address of the receiver. * @param receiver The address of the receiver.
*/ */
function swapExactInput( function swapExactInput(
Currency currencyIn, Currency currencyIn,
PathKey[] calldata path, PathKey[] calldata path,
uint128 amountIn, uint128 amountIn,
address sender, bool transferFromNeeded,
TransferType transferType, bool transferNeeded,
address receiver address receiver
) external returns (uint128) { ) external returns (uint128) {
uint128 amountOut = 0; uint128 amountOut = 0;
@@ -315,7 +318,7 @@ contract UniswapV4Executor is
if (amount > amountIn) { if (amount > amountIn) {
revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount); revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount);
} }
_settle(currencyIn, amount, sender, transferType); _settle(currencyIn, amount, transferFromNeeded, transferNeeded);
_take( _take(
swapCurrencyIn, // at the end of the loop this is actually currency out swapCurrencyIn, // at the end of the loop this is actually currency out
@@ -387,15 +390,17 @@ contract UniswapV4Executor is
* @dev The implementing contract must ensure that the `payer` is a secure address. * @dev The implementing contract must ensure that the `payer` is a secure address.
* @param currency The currency to settle. * @param currency The currency to settle.
* @param amount The amount to send. * @param amount The amount to send.
* @param sender The address of the payer. * @param transferFromNeeded Whether to manually transferFrom input tokens into the
* @param transferType The type of transfer to use. * core contract from the swapper.
* @param transferNeeded Whether to manually transfer input tokens into the
* core contract from the router.
* @dev Returns early if the amount is 0. * @dev Returns early if the amount is 0.
*/ */
function _settle( function _settle(
Currency currency, Currency currency,
uint256 amount, uint256 amount,
address sender, bool transferFromNeeded,
TransferType transferType bool transferNeeded
) internal { ) internal {
if (amount == 0) return; if (amount == 0) return;
poolManager.sync(currency); poolManager.sync(currency);
@@ -403,13 +408,18 @@ contract UniswapV4Executor is
// slither-disable-next-line unused-return // slither-disable-next-line unused-return
poolManager.settle{value: amount}(); poolManager.settle{value: amount}();
} else { } else {
_transfer( if (transferFromNeeded) {
Currency.unwrap(currency), // transferFrom swapper's wallet into the core contract
sender, _transfer(msg.sender);
address(poolManager), } else if (transferNeeded) {
amount, address tokenIn = Currency.unwrap(currency);
transferType // transfer from router contract into the core contract
); if (tokenIn == address(0)) {
payable(msg.sender).transfer(amount);
} else {
IERC20(tokenIn).safeTransfer(msg.sender, amount);
}
}
// slither-disable-next-line unused-return // slither-disable-next-line unused-return
poolManager.settle(); poolManager.settle();
} }

View File

@@ -26,7 +26,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
USDE_ADDR, USDE_ADDR,
USDT_ADDR, USDT_ADDR,
true, true,
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL, true, // permit2 transferFrom to protocol
false, // transfer to protocol
ALICE, ALICE,
pools pools
); );
@@ -44,6 +45,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
false,
address(0),
swap swap
); );
@@ -74,7 +77,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
USDE_ADDR, USDE_ADDR,
WBTC_ADDR, WBTC_ADDR,
true, true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL, true, // permit2 transferFrom to protocol
false, // transfer to protocol
ALICE, ALICE,
pools pools
); );
@@ -83,7 +87,16 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
encodeSingleSwap(address(usv4Executor), protocolData); encodeSingleSwap(address(usv4Executor), protocolData);
tychoRouter.singleSwap( tychoRouter.singleSwap(
amountIn, USDE_ADDR, WBTC_ADDR, 118280, false, false, ALICE, swap amountIn,
USDE_ADDR,
WBTC_ADDR,
118280,
false,
false,
ALICE,
false,
address(0),
swap
); );
assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281); assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281);
@@ -262,7 +275,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
ALICE, ALICE,
DAI_WETH_USV3, DAI_WETH_USV3,
zeroForOne, zeroForOne,
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL true, // permit2 transferFrom to protocol
false // transfer to protocol
); );
bytes memory swap = bytes memory swap =
encodeSingleSwap(address(usv3Executor), protocolData); encodeSingleSwap(address(usv3Executor), protocolData);
@@ -277,6 +291,8 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
false,
address(0),
swap swap
); );

View File

@@ -8,25 +8,21 @@ import "./executors/UniswapV4Utils.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
contract TychoRouterSequentialSwapTest is TychoRouterTestSetup { contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
function _getSequentialSwaps(bool permit2) function _getSequentialSwaps() internal view returns (bytes[] memory) {
internal
view
returns (bytes[] memory)
{
// Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2 // Trade 1 WETH for USDC through DAI with 2 swaps on Uniswap V2
// 1 WETH -> DAI -> USDC // 1 WETH -> DAI -> USDC
// (univ2) (univ2) // (univ2) (univ2)
TokenTransfer.TransferType transferType = permit2
? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
: TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL;
bytes[] memory swaps = new bytes[](2); bytes[] memory swaps = new bytes[](2);
// WETH -> DAI // WETH -> DAI
swaps[0] = encodeSequentialSwap( swaps[0] = encodeSequentialSwap(
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, transferType WETH_ADDR,
WETH_DAI_POOL,
DAI_USDC_POOL, // receiver (direct to next pool)
false,
false // transfer to protocol from router
) )
); );
@@ -38,7 +34,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
DAI_USDC_POOL, DAI_USDC_POOL,
ALICE, ALICE,
true, true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL false // transfer to protocol from router
) )
); );
return swaps; return swaps;
@@ -55,7 +51,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSequentialSwaps(true); bytes[] memory swaps = _getSequentialSwaps();
tychoRouter.sequentialSwapPermit2( tychoRouter.sequentialSwapPermit2(
amountIn, amountIn,
WETH_ADDR, WETH_ADDR,
@@ -66,6 +62,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
true,
WETH_DAI_POOL,
pleEncode(swaps) pleEncode(swaps)
); );
@@ -82,7 +80,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE); vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSequentialSwaps(false); bytes[] memory swaps = _getSequentialSwaps();
tychoRouter.sequentialSwap( tychoRouter.sequentialSwap(
amountIn, amountIn,
WETH_ADDR, WETH_ADDR,
@@ -91,6 +89,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
false, false,
false, false,
ALICE, ALICE,
true,
WETH_DAI_POOL,
pleEncode(swaps) pleEncode(swaps)
); );
@@ -107,7 +107,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE); vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSequentialSwaps(false); bytes[] memory swaps = _getSequentialSwaps();
vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector);
tychoRouter.sequentialSwap( tychoRouter.sequentialSwap(
amountIn, amountIn,
@@ -117,6 +117,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
false, false,
false, false,
ALICE, ALICE,
true,
WETH_DAI_POOL,
pleEncode(swaps) pleEncode(swaps)
); );
} }
@@ -129,7 +131,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE); vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn - 1); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn - 1);
bytes[] memory swaps = _getSequentialSwaps(false); bytes[] memory swaps = _getSequentialSwaps();
vm.expectRevert(); vm.expectRevert();
tychoRouter.sequentialSwap( tychoRouter.sequentialSwap(
amountIn, amountIn,
@@ -139,6 +141,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
false, false,
false, false,
ALICE, ALICE,
true,
WETH_DAI_POOL,
pleEncode(swaps) pleEncode(swaps)
); );
} }
@@ -154,7 +158,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSequentialSwaps(true); bytes[] memory swaps = _getSequentialSwaps();
uint256 minAmountOut = 3000 * 1e18; uint256 minAmountOut = 3000 * 1e18;
@@ -175,6 +179,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
true,
WETH_DAI_POOL,
pleEncode(swaps) pleEncode(swaps)
); );
vm.stopPrank(); vm.stopPrank();
@@ -202,24 +208,14 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
swaps[0] = encodeSequentialSwap( swaps[0] = encodeSequentialSwap(
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(
WETH_ADDR, WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, true
WETH_DAI_POOL,
tychoRouterAddr,
false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
) )
); );
// DAI -> USDC // DAI -> USDC
swaps[1] = encodeSequentialSwap( swaps[1] = encodeSequentialSwap(
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, ALICE, true, true)
DAI_ADDR,
DAI_USDC_POOL,
ALICE,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
)
); );
uint256 amountOut = tychoRouter.sequentialSwapPermit2{value: amountIn}( uint256 amountOut = tychoRouter.sequentialSwapPermit2{value: amountIn}(
@@ -232,6 +228,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
emptyPermitSingle, emptyPermitSingle,
"", "",
true,
tychoRouterAddr,
pleEncode(swaps) pleEncode(swaps)
); );
uint256 expectedAmount = 2005810530; uint256 expectedAmount = 2005810530;
@@ -262,11 +260,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
swaps[0] = encodeSequentialSwap( swaps[0] = encodeSequentialSwap(
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(
USDC_ADDR, USDC_ADDR, DAI_USDC_POOL, tychoRouterAddr, false, false
DAI_USDC_POOL,
tychoRouterAddr,
false,
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
) )
); );
@@ -274,11 +268,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
swaps[1] = encodeSequentialSwap( swaps[1] = encodeSequentialSwap(
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(
DAI_ADDR, DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true, true
WETH_DAI_POOL,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
) )
); );
@@ -292,6 +282,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
true,
DAI_USDC_POOL,
pleEncode(swaps) pleEncode(swaps)
); );
@@ -315,7 +307,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
tychoRouterAddr, tychoRouterAddr,
USDC_WETH_USV3, USDC_WETH_USV3,
true, true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL false,
true
); );
bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap(
@@ -324,7 +317,8 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
tychoRouterAddr, tychoRouterAddr,
USDC_WETH_USV3_2, USDC_WETH_USV3_2,
false, false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL false, // permit2 transferFrom to protocol
true // transfer to protocol
); );
bytes[] memory swaps = new bytes[](2); bytes[] memory swaps = new bytes[](2);

View File

@@ -26,7 +26,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
WETH_DAI_POOL, WETH_DAI_POOL,
ALICE, ALICE,
false, false,
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL false // funds already in WETH_DAI_POOL, no transfer necessary
); );
bytes memory swap = bytes memory swap =
@@ -42,6 +42,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
true, // transferFrom to WETH_DAI_POOL
WETH_DAI_POOL, // receiver of input tokens
swap swap
); );
@@ -67,7 +69,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
WETH_DAI_POOL, WETH_DAI_POOL,
ALICE, ALICE,
false, false,
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL false // funds already in WETH_DAI_POOL, no transfer necessary
); );
bytes memory swap = bytes memory swap =
@@ -82,6 +84,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
false, false,
false, false,
ALICE, ALICE,
true,
WETH_DAI_POOL,
swap swap
); );
@@ -103,20 +107,24 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE); vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData =
WETH_ADDR, encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false, false);
WETH_DAI_POOL,
ALICE,
false,
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
);
bytes memory swap = bytes memory swap =
encodeSingleSwap(address(usv2Executor), protocolData); encodeSingleSwap(address(usv2Executor), protocolData);
vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector);
tychoRouter.singleSwap( tychoRouter.singleSwap(
amountIn, WETH_ADDR, DAI_ADDR, 0, false, false, ALICE, swap amountIn,
WETH_ADDR,
DAI_ADDR,
0,
false,
false,
ALICE,
true,
WETH_DAI_POOL,
swap
); );
} }
@@ -134,7 +142,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
WETH_DAI_POOL, WETH_DAI_POOL,
ALICE, ALICE,
false, false,
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL false // funds already in WETH_DAI_POOL, no transfer necessary
); );
bytes memory swap = bytes memory swap =
@@ -150,6 +158,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
false, false,
false, false,
ALICE, ALICE,
true,
WETH_DAI_POOL,
swap swap
); );
} }
@@ -169,7 +179,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
WETH_DAI_POOL, WETH_DAI_POOL,
ALICE, ALICE,
false, false,
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL false // funds already in WETH_DAI_POOL, no transfer necessary
); );
bytes memory swap = bytes memory swap =
@@ -192,6 +202,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
false, false,
false, false,
ALICE, ALICE,
true,
WETH_DAI_POOL,
swap swap
); );
} }
@@ -213,13 +225,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
sigDeadline: 0 sigDeadline: 0
}); });
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData =
WETH_ADDR, encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false, true);
WETH_DAI_POOL,
ALICE,
false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
bytes memory swap = bytes memory swap =
encodeSingleSwap(address(usv2Executor), protocolData); encodeSingleSwap(address(usv2Executor), protocolData);
@@ -234,6 +241,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
emptyPermitSingle, emptyPermitSingle,
"", "",
true,
tychoRouterAddr,
swap swap
); );
uint256 expectedAmount = 2018817438608734439722; uint256 expectedAmount = 2018817438608734439722;
@@ -257,11 +266,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData = encodeUniswapV2Swap(
DAI_ADDR, DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true, false
WETH_DAI_POOL,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
); );
bytes memory swap = bytes memory swap =
@@ -277,6 +282,8 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
true, // transferFrom to WETH_DAI_POOL
WETH_DAI_POOL, // receiver of input tokens
swap swap
); );

View File

@@ -8,11 +8,7 @@ import "./executors/UniswapV4Utils.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
contract TychoRouterSplitSwapTest is TychoRouterTestSetup { contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
function _getSplitSwaps(bool permit2) function _getSplitSwaps() private view returns (bytes[] memory) {
private
view
returns (bytes[] memory)
{
// Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2 // Trade 1 WETH for USDC through DAI and WBTC with 4 swaps on Uniswap V2
// -> DAI -> // -> DAI ->
// 1 WETH USDC // 1 WETH USDC
@@ -20,10 +16,6 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
// (univ2) (univ2) // (univ2) (univ2)
bytes[] memory swaps = new bytes[](4); bytes[] memory swaps = new bytes[](4);
TokenTransfer.TransferType inTransferType = permit2
? TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
: TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL;
// WETH -> WBTC (60%) // WETH -> WBTC (60%)
swaps[0] = encodeSplitSwap( swaps[0] = encodeSplitSwap(
uint8(0), uint8(0),
@@ -31,11 +23,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
(0xffffff * 60) / 100, // 60% (0xffffff * 60) / 100, // 60%
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(
WETH_ADDR, WETH_ADDR, WETH_WBTC_POOL, tychoRouterAddr, false, true
WETH_WBTC_POOL,
tychoRouterAddr,
false,
inTransferType
) )
); );
// WBTC -> USDC // WBTC -> USDC
@@ -44,13 +32,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
uint8(2), uint8(2),
uint24(0), uint24(0),
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(WBTC_ADDR, USDC_WBTC_POOL, ALICE, true, true)
WBTC_ADDR,
USDC_WBTC_POOL,
ALICE,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
)
); );
// WETH -> DAI // WETH -> DAI
swaps[2] = encodeSplitSwap( swaps[2] = encodeSplitSwap(
@@ -59,7 +41,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
uint24(0), uint24(0),
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(
WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, inTransferType WETH_ADDR, WETH_DAI_POOL, tychoRouterAddr, false, true
) )
); );
@@ -69,13 +51,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
uint8(2), uint8(2),
uint24(0), uint24(0),
address(usv2Executor), address(usv2Executor),
encodeUniswapV2Swap( encodeUniswapV2Swap(DAI_ADDR, DAI_USDC_POOL, ALICE, true, true)
DAI_ADDR,
DAI_USDC_POOL,
ALICE,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
)
); );
return swaps; return swaps;
@@ -85,10 +61,9 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
// Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info // Trade 1 WETH for USDC through DAI and WBTC - see _getSplitSwaps for more info
uint256 amountIn = 1 ether; uint256 amountIn = 1 ether;
deal(WETH_ADDR, ALICE, amountIn); deal(WETH_ADDR, address(tychoRouterAddr), amountIn);
vm.startPrank(ALICE); vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); bytes[] memory swaps = _getSplitSwaps();
bytes[] memory swaps = _getSplitSwaps(false);
tychoRouter.exposedSplitSwap(amountIn, 4, pleEncode(swaps)); tychoRouter.exposedSplitSwap(amountIn, 4, pleEncode(swaps));
vm.stopPrank(); vm.stopPrank();
@@ -109,7 +84,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSplitSwaps(true); bytes[] memory swaps = _getSplitSwaps();
tychoRouter.splitSwapPermit2( tychoRouter.splitSwapPermit2(
amountIn, amountIn,
@@ -122,6 +97,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
true,
tychoRouterAddr,
pleEncode(swaps) pleEncode(swaps)
); );
@@ -138,7 +115,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE); vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn); IERC20(WETH_ADDR).approve(tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSplitSwaps(false); bytes[] memory swaps = _getSplitSwaps();
tychoRouter.splitSwap( tychoRouter.splitSwap(
amountIn, amountIn,
@@ -149,6 +126,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
false, false,
4, 4,
ALICE, ALICE,
true,
tychoRouterAddr,
pleEncode(swaps) pleEncode(swaps)
); );
@@ -165,7 +144,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE); vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn);
bytes[] memory swaps = _getSplitSwaps(false); bytes[] memory swaps = _getSplitSwaps();
vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector); vm.expectRevert(TychoRouter__UndefinedMinAmountOut.selector);
tychoRouter.splitSwap( tychoRouter.splitSwap(
@@ -177,6 +156,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
false, false,
4, 4,
ALICE, ALICE,
true,
tychoRouterAddr,
pleEncode(swaps) pleEncode(swaps)
); );
vm.stopPrank(); vm.stopPrank();
@@ -190,7 +171,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE); vm.startPrank(ALICE);
// Approve less than the amountIn // Approve less than the amountIn
IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1); IERC20(WETH_ADDR).approve(address(tychoRouterAddr), amountIn - 1);
bytes[] memory swaps = _getSplitSwaps(false); bytes[] memory swaps = _getSplitSwaps();
vm.expectRevert(); vm.expectRevert();
tychoRouter.splitSwap( tychoRouter.splitSwap(
@@ -202,6 +183,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
false, false,
2, 2,
ALICE, ALICE,
true,
tychoRouterAddr,
pleEncode(swaps) pleEncode(swaps)
); );
@@ -219,7 +202,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
bytes memory signature bytes memory signature
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn); ) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
bytes[] memory swaps = _getSplitSwaps(true); bytes[] memory swaps = _getSplitSwaps();
uint256 minAmountOut = 3000 * 1e18; uint256 minAmountOut = 3000 * 1e18;
@@ -241,6 +224,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
true,
tychoRouterAddr,
pleEncode(swaps) pleEncode(swaps)
); );
vm.stopPrank(); vm.stopPrank();
@@ -265,13 +250,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
spender: address(0), spender: address(0),
sigDeadline: 0 sigDeadline: 0
}); });
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData =
WETH_ADDR, encodeUniswapV2Swap(WETH_ADDR, WETH_DAI_POOL, ALICE, false, true);
WETH_DAI_POOL,
ALICE,
false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
bytes memory swap = encodeSplitSwap( bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData uint8(0), uint8(1), uint24(0), address(usv2Executor), protocolData
@@ -290,6 +270,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
emptyPermitSingle, emptyPermitSingle,
"", "",
false,
tychoRouterAddr,
pleEncode(swaps) pleEncode(swaps)
); );
uint256 expectedAmount = 2018817438608734439722; uint256 expectedAmount = 2018817438608734439722;
@@ -315,11 +297,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn); ) = handlePermit2Approval(DAI_ADDR, tychoRouterAddr, amountIn);
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData = encodeUniswapV2Swap(
DAI_ADDR, DAI_ADDR, WETH_DAI_POOL, tychoRouterAddr, true, true
WETH_DAI_POOL,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER_PERMIT2_TO_PROTOCOL
); );
bytes memory swap = encodeSplitSwap( bytes memory swap = encodeSplitSwap(
@@ -339,6 +317,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
ALICE, ALICE,
permitSingle, permitSingle,
signature, signature,
true,
tychoRouterAddr,
pleEncode(swaps) pleEncode(swaps)
); );
@@ -365,10 +345,10 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
// │ │ // │ │
// └─ (USV3, 40% split) ──> WETH ─┘ // └─ (USV3, 40% split) ──> WETH ─┘
uint256 amountIn = 100 * 10 ** 6; 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); vm.startPrank(ALICE);
// Approve the TychoRouter to spend USDC
IERC20(USDC_ADDR).approve(tychoRouterAddr, amountIn);
bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap( bytes memory usdcWethV3Pool1ZeroOneData = encodeUniswapV3Swap(
USDC_ADDR, USDC_ADDR,
@@ -376,7 +356,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tychoRouterAddr, tychoRouterAddr,
USDC_WETH_USV3, USDC_WETH_USV3,
true, true,
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL false, // transferFrom swapper required
true // transfer from tycho router to protocol
); );
bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap( bytes memory usdcWethV3Pool2ZeroOneData = encodeUniswapV3Swap(
@@ -385,7 +366,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tychoRouterAddr, tychoRouterAddr,
USDC_WETH_USV3_2, USDC_WETH_USV3_2,
true, true,
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL false, // transferFrom swapper required
true // transfer from tycho router to protocol
); );
bytes memory wethUsdcV2OneZeroData = encodeUniswapV2Swap( bytes memory wethUsdcV2OneZeroData = encodeUniswapV2Swap(
@@ -393,7 +375,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
USDC_WETH_USV2, USDC_WETH_USV2,
tychoRouterAddr, tychoRouterAddr,
false, false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL true // transfer from tycho router to protocol
); );
bytes[] memory swaps = new bytes[](3); bytes[] memory swaps = new bytes[](3);
@@ -443,7 +425,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
USDC_WETH_USV2, USDC_WETH_USV2,
tychoRouterAddr, tychoRouterAddr,
true, true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL true // transfer required
); );
bytes memory usdcWethV3Pool1OneZeroData = encodeUniswapV3Swap( bytes memory usdcWethV3Pool1OneZeroData = encodeUniswapV3Swap(
@@ -452,7 +434,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tychoRouterAddr, tychoRouterAddr,
USDC_WETH_USV3, USDC_WETH_USV3,
false, false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL false, // transferFrom required
true // transfer required
); );
bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap( bytes memory usdcWethV3Pool2OneZeroData = encodeUniswapV3Swap(
@@ -461,7 +444,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tychoRouterAddr, tychoRouterAddr,
USDC_WETH_USV3_2, USDC_WETH_USV3_2,
false, false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL false, // transferFrom required
true // transfer required
); );
bytes[] memory swaps = new bytes[](3); bytes[] memory swaps = new bytes[](3);
@@ -500,11 +484,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
deal(BASE_USDC, tychoRouterAddr, amountIn); deal(BASE_USDC, tychoRouterAddr, amountIn);
bytes memory protocolData = encodeUniswapV2Swap( bytes memory protocolData = encodeUniswapV2Swap(
BASE_USDC, BASE_USDC, USDC_MAG7_POOL, tychoRouterAddr, true, true
USDC_MAG7_POOL,
tychoRouterAddr,
true,
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL
); );
bytes memory swap = encodeSplitSwap( bytes memory swap = encodeSplitSwap(

View File

@@ -185,10 +185,11 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
address target, address target,
address receiver, address receiver,
bool zero2one, bool zero2one,
TokenTransfer.TransferType transferType bool transferNeeded
) internal pure returns (bytes memory) { ) internal pure returns (bytes memory) {
return return abi.encodePacked(
abi.encodePacked(tokenIn, target, receiver, zero2one, transferType); tokenIn, target, receiver, zero2one, transferNeeded
);
} }
function encodeUniswapV3Swap( function encodeUniswapV3Swap(
@@ -197,7 +198,8 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
address receiver, address receiver,
address target, address target,
bool zero2one, bool zero2one,
TokenTransfer.TransferType transferType bool transferFromNeeded,
bool transferNeeded
) internal view returns (bytes memory) { ) internal view returns (bytes memory) {
IUniswapV3Pool pool = IUniswapV3Pool(target); IUniswapV3Pool pool = IUniswapV3Pool(target);
return abi.encodePacked( return abi.encodePacked(
@@ -207,7 +209,8 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
receiver, receiver,
target, target,
zero2one, zero2one,
transferType transferFromNeeded,
transferNeeded
); );
} }
} }

View File

@@ -17,7 +17,7 @@ contract BalancerV2ExecutorExposed is BalancerV2Executor {
bytes32 poolId, bytes32 poolId,
address receiver, address receiver,
bool needsApproval, bool needsApproval,
TransferType transferType bool transferNeeded
) )
{ {
return _decodeData(data); return _decodeData(data);
@@ -41,12 +41,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
function testDecodeParams() public view { function testDecodeParams() public view {
bytes memory params = abi.encodePacked( bytes memory params = abi.encodePacked(
WETH_ADDR, WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, address(2), true, false
BAL_ADDR,
WETH_BAL_POOL_ID,
address(2),
true,
TokenTransfer.TransferType.NONE
); );
( (
@@ -55,7 +50,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
bytes32 poolId, bytes32 poolId,
address receiver, address receiver,
bool needsApproval, bool needsApproval,
TokenTransfer.TransferType transferType bool transferNeeded
) = balancerV2Exposed.decodeParams(params); ) = balancerV2Exposed.decodeParams(params);
assertEq(address(tokenIn), WETH_ADDR); assertEq(address(tokenIn), WETH_ADDR);
@@ -63,7 +58,6 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
assertEq(poolId, WETH_BAL_POOL_ID); assertEq(poolId, WETH_BAL_POOL_ID);
assertEq(receiver, address(2)); assertEq(receiver, address(2));
assertEq(needsApproval, true); assertEq(needsApproval, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
} }
function testDecodeParamsInvalidDataLength() public { function testDecodeParamsInvalidDataLength() public {
@@ -77,12 +71,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
function testSwap() public { function testSwap() public {
uint256 amountIn = 10 ** 18; uint256 amountIn = 10 ** 18;
bytes memory protocolData = abi.encodePacked( bytes memory protocolData = abi.encodePacked(
WETH_ADDR, WETH_ADDR, BAL_ADDR, WETH_BAL_POOL_ID, BOB, true, false
BAL_ADDR,
WETH_BAL_POOL_ID,
BOB,
true,
TokenTransfer.TransferType.NONE
); );
deal(WETH_ADDR, address(balancerV2Exposed), amountIn); deal(WETH_ADDR, address(balancerV2Exposed), amountIn);
@@ -104,7 +93,7 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
bytes32 poolId, bytes32 poolId,
address receiver, address receiver,
bool needsApproval, bool needsApproval,
TokenTransfer.TransferType transferType bool transferNeeded
) = balancerV2Exposed.decodeParams(protocolData); ) = balancerV2Exposed.decodeParams(protocolData);
assertEq(address(tokenIn), WETH_ADDR); assertEq(address(tokenIn), WETH_ADDR);
@@ -112,7 +101,6 @@ contract BalancerV2ExecutorTest is Constants, TestUtils {
assertEq(poolId, WETH_BAL_POOL_ID); assertEq(poolId, WETH_BAL_POOL_ID);
assertEq(receiver, BOB); assertEq(receiver, BOB);
assertEq(needsApproval, true); assertEq(needsApproval, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
} }
function testSwapIntegration() public { function testSwapIntegration() public {

View File

@@ -37,7 +37,7 @@ contract CurveExecutorExposed is CurveExecutor {
int128 i, int128 i,
int128 j, int128 j,
bool tokenApprovalNeeded, bool tokenApprovalNeeded,
TokenTransfer.TransferType transferType, bool transferNeeded,
address receiver address receiver
) )
{ {
@@ -68,7 +68,7 @@ contract CurveExecutorTest is Test, Constants {
uint8(2), uint8(2),
uint8(0), uint8(0),
true, true,
TokenTransfer.TransferType.NONE, false,
ALICE ALICE
); );
@@ -80,7 +80,7 @@ contract CurveExecutorTest is Test, Constants {
int128 i, int128 i,
int128 j, int128 j,
bool tokenApprovalNeeded, bool tokenApprovalNeeded,
TokenTransfer.TransferType transferType, bool transferNeeded,
address receiver address receiver
) = curveExecutorExposed.decodeData(data); ) = curveExecutorExposed.decodeData(data);
@@ -91,7 +91,6 @@ contract CurveExecutorTest is Test, Constants {
assertEq(i, 2); assertEq(i, 2);
assertEq(j, 0); assertEq(j, 0);
assertEq(tokenApprovalNeeded, true); assertEq(tokenApprovalNeeded, true);
assertEq(uint8(transferType), uint8(TokenTransfer.TransferType.NONE));
assertEq(receiver, ALICE); assertEq(receiver, ALICE);
} }
@@ -295,7 +294,7 @@ contract CurveExecutorTest is Test, Constants {
uint8(uint256(uint128(i))), uint8(uint256(uint128(i))),
uint8(uint256(uint128(j))), uint8(uint256(uint128(j))),
true, true,
TokenTransfer.TransferType.NONE, false,
receiver receiver
); );
} }

View File

@@ -3,7 +3,7 @@ pragma solidity ^0.8.26;
import "../TestUtils.sol"; import "../TestUtils.sol";
import {Constants} from "../Constants.sol"; import {Constants} from "../Constants.sol";
import {EkuboExecutor, TokenTransfer} from "@src/executors/EkuboExecutor.sol"; import {EkuboExecutor} from "@src/executors/EkuboExecutor.sol";
import {ICore} from "@ekubo/interfaces/ICore.sol"; import {ICore} from "@ekubo/interfaces/ICore.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.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)); uint256 usdcBalanceBeforeExecutor = USDC.balanceOf(address(executor));
bytes memory data = abi.encodePacked( bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core) true, // transferNeeded (transfer from executor to core)
address(executor), // receiver address(executor), // receiver
NATIVE_TOKEN_ADDRESS, // tokenIn NATIVE_TOKEN_ADDRESS, // tokenIn
USDC_ADDR, // tokenOut USDC_ADDR, // tokenOut
@@ -82,7 +82,7 @@ contract EkuboExecutorTest is Constants, TestUtils {
uint256 ethBalanceBeforeExecutor = address(executor).balance; uint256 ethBalanceBeforeExecutor = address(executor).balance;
bytes memory data = abi.encodePacked( bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType (transfer from executor to core) true, // transferNeeded (transfer from executor to core)
address(executor), // receiver address(executor), // receiver
USDC_ADDR, // tokenIn USDC_ADDR, // tokenIn
NATIVE_TOKEN_ADDRESS, // tokenOut 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 // Same test case as in swap_encoder::tests::ekubo::test_encode_swap_multi
function testMultiHopSwap() public { function testMultiHopSwap() public {
bytes memory data = abi.encodePacked( bytes memory data = abi.encodePacked(
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL), // transferType true, // transferNeeded (transfer from executor to core)
address(executor), // receiver address(executor), // receiver
NATIVE_TOKEN_ADDRESS, // tokenIn NATIVE_TOKEN_ADDRESS, // tokenIn
USDC_ADDR, // tokenOut of 1st swap USDC_ADDR, // tokenOut of 1st swap

View File

@@ -17,7 +17,7 @@ contract MaverickV2ExecutorExposed is MaverickV2Executor {
IERC20 tokenIn, IERC20 tokenIn,
address target, address target,
address receiver, address receiver,
TransferType transferType bool transferNeeded
) )
{ {
return _decodeData(data); return _decodeData(data);
@@ -39,27 +39,16 @@ contract MaverickV2ExecutorTest is TestUtils, Constants {
} }
function testDecodeParams() public view { function testDecodeParams() public view {
bytes memory params = abi.encodePacked( bytes memory params =
GHO_ADDR, abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, address(2), true);
GHO_USDC_POOL,
address(2),
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
( (IERC20 tokenIn, address target, address receiver, bool transferNeeded)
IERC20 tokenIn, = maverickV2Exposed.decodeParams(params);
address target,
address receiver,
TokenTransfer.TransferType transferType
) = maverickV2Exposed.decodeParams(params);
assertEq(address(tokenIn), GHO_ADDR); assertEq(address(tokenIn), GHO_ADDR);
assertEq(target, GHO_USDC_POOL); assertEq(target, GHO_USDC_POOL);
assertEq(receiver, address(2)); assertEq(receiver, address(2));
assertEq( assertEq(transferNeeded, true);
uint8(transferType),
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
} }
function testDecodeParamsInvalidDataLength() public { function testDecodeParamsInvalidDataLength() public {
@@ -72,12 +61,8 @@ contract MaverickV2ExecutorTest is TestUtils, Constants {
function testSwap() public { function testSwap() public {
uint256 amountIn = 10e18; uint256 amountIn = 10e18;
bytes memory protocolData = abi.encodePacked( bytes memory protocolData =
GHO_ADDR, abi.encodePacked(GHO_ADDR, GHO_USDC_POOL, BOB, true);
GHO_USDC_POOL,
BOB,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
deal(GHO_ADDR, address(maverickV2Exposed), amountIn); deal(GHO_ADDR, address(maverickV2Exposed), amountIn);
uint256 balanceBefore = USDC.balanceOf(BOB); uint256 balanceBefore = USDC.balanceOf(BOB);
@@ -94,20 +79,13 @@ contract MaverickV2ExecutorTest is TestUtils, Constants {
bytes memory protocolData = bytes memory protocolData =
loadCallDataFromFile("test_encode_maverick_v2"); loadCallDataFromFile("test_encode_maverick_v2");
( (IERC20 tokenIn, address pool, address receiver, bool transferNeeded) =
IERC20 tokenIn, maverickV2Exposed.decodeParams(protocolData);
address pool,
address receiver,
TokenTransfer.TransferType transferType
) = maverickV2Exposed.decodeParams(protocolData);
assertEq(address(tokenIn), GHO_ADDR); assertEq(address(tokenIn), GHO_ADDR);
assertEq(pool, GHO_USDC_POOL); assertEq(pool, GHO_USDC_POOL);
assertEq(receiver, BOB); assertEq(receiver, BOB);
assertEq( assertEq(transferNeeded, true);
uint8(transferType),
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
} }
function testSwapIntegration() public { function testSwapIntegration() public {

View File

@@ -2,7 +2,6 @@
pragma solidity ^0.8.26; pragma solidity ^0.8.26;
import "@src/executors/UniswapV2Executor.sol"; import "@src/executors/UniswapV2Executor.sol";
import "@src/executors/TokenTransfer.sol";
import {Test} from "../../lib/forge-std/src/Test.sol"; import {Test} from "../../lib/forge-std/src/Test.sol";
import {Constants} from "../Constants.sol"; import {Constants} from "../Constants.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol"; import {Permit2TestHelper} from "../Permit2TestHelper.sol";
@@ -23,7 +22,7 @@ contract UniswapV2ExecutorExposed is UniswapV2Executor {
address target, address target,
address receiver, address receiver,
bool zeroForOne, bool zeroForOne,
TransferType transferType bool transferNeeded
) )
{ {
return _decodeData(data); return _decodeData(data);
@@ -60,7 +59,6 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
UniswapV2ExecutorExposed pancakeswapV2Exposed; UniswapV2ExecutorExposed pancakeswapV2Exposed;
IERC20 WETH = IERC20(WETH_ADDR); IERC20 WETH = IERC20(WETH_ADDR);
IERC20 DAI = IERC20(DAI_ADDR); IERC20 DAI = IERC20(DAI_ADDR);
IAllowanceTransfer permit2;
function setUp() public { function setUp() public {
uint256 forkBlock = 17323404; uint256 forkBlock = 17323404;
@@ -80,34 +78,25 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
PERMIT2_ADDRESS, PERMIT2_ADDRESS,
25 25
); );
permit2 = IAllowanceTransfer(PERMIT2_ADDRESS);
} }
function testDecodeParams() public view { function testDecodeParams() public view {
bytes memory params = abi.encodePacked( bytes memory params =
WETH_ADDR, abi.encodePacked(WETH_ADDR, address(2), address(3), false, true);
address(2),
address(3),
false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL
);
( (
IERC20 tokenIn, IERC20 tokenIn,
address target, address target,
address receiver, address receiver,
bool zeroForOne, bool zeroForOne,
TokenTransfer.TransferType transferType bool transferNeeded
) = uniswapV2Exposed.decodeParams(params); ) = uniswapV2Exposed.decodeParams(params);
assertEq(address(tokenIn), WETH_ADDR); assertEq(address(tokenIn), WETH_ADDR);
assertEq(target, address(2)); assertEq(target, address(2));
assertEq(receiver, address(3)); assertEq(receiver, address(3));
assertEq(zeroForOne, false); assertEq(zeroForOne, false);
assertEq( assertEq(transferNeeded, true);
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL),
uint8(transferType)
);
} }
function testDecodeParamsInvalidDataLength() public { function testDecodeParamsInvalidDataLength() public {
@@ -158,13 +147,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
uint256 amountIn = 10 ** 18; uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891; uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false; bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked( bytes memory protocolData =
WETH_ADDR, abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne, true);
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
uniswapV2Exposed.swap(amountIn, protocolData); uniswapV2Exposed.swap(amountIn, protocolData);
@@ -173,70 +157,12 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
assertGe(finalBalance, amountOut); 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 { function testSwapNoTransfer() public {
uint256 amountIn = 10 ** 18; uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891; uint256 amountOut = 1847751195973566072891;
bool zeroForOne = false; bool zeroForOne = false;
bytes memory protocolData = abi.encodePacked( bytes memory protocolData =
WETH_ADDR, abi.encodePacked(WETH_ADDR, WETH_DAI_POOL, BOB, zeroForOne, false);
WETH_DAI_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.NONE)
);
deal(WETH_ADDR, address(this), amountIn); deal(WETH_ADDR, address(this), amountIn);
IERC20(WETH_ADDR).transfer(address(WETH_DAI_POOL), amountIn); IERC20(WETH_ADDR).transfer(address(WETH_DAI_POOL), amountIn);
@@ -255,20 +181,19 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
address target, address target,
address receiver, address receiver,
bool zeroForOne, bool zeroForOne,
TokenTransfer.TransferType transferType bool transferNeeded
) = uniswapV2Exposed.decodeParams(protocolData); ) = uniswapV2Exposed.decodeParams(protocolData);
assertEq(address(tokenIn), WETH_ADDR); assertEq(address(tokenIn), WETH_ADDR);
assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640); assertEq(target, 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640);
assertEq(receiver, 0x0000000000000000000000000000000000000001); assertEq(receiver, 0x0000000000000000000000000000000000000001);
assertEq(zeroForOne, false); assertEq(zeroForOne, false);
// TRANSFER = 0 assertEq(transferNeeded, false);
assertEq(0, uint8(transferType));
} }
function testSwapIntegration() public { function testSwapIntegration() public {
bytes memory protocolData = bytes memory protocolData =
hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0000"; hex"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2a478c2975ab1ea89e8196811f51a7b7ade33eb111d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e0001";
uint256 amountIn = 10 ** 18; uint256 amountIn = 10 ** 18;
uint256 amountOut = 1847751195973566072891; uint256 amountOut = 1847751195973566072891;
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
@@ -282,13 +207,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
uint256 amountIn = 10 ** 18; uint256 amountIn = 10 ** 18;
bool zeroForOne = false; bool zeroForOne = false;
address fakePool = address(new FakeUniswapV2Pool(WETH_ADDR, DAI_ADDR)); address fakePool = address(new FakeUniswapV2Pool(WETH_ADDR, DAI_ADDR));
bytes memory protocolData = abi.encodePacked( bytes memory protocolData =
WETH_ADDR, abi.encodePacked(WETH_ADDR, fakePool, BOB, zeroForOne, true);
fakePool,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
deal(WETH_ADDR, address(uniswapV2Exposed), amountIn); deal(WETH_ADDR, address(uniswapV2Exposed), amountIn);
vm.expectRevert(UniswapV2Executor__InvalidTarget.selector); vm.expectRevert(UniswapV2Executor__InvalidTarget.selector);
@@ -302,13 +222,8 @@ contract UniswapV2ExecutorTest is Test, Constants, Permit2TestHelper {
vm.rollFork(26857267); vm.rollFork(26857267);
uint256 amountIn = 10 * 10 ** 6; uint256 amountIn = 10 * 10 ** 6;
bool zeroForOne = true; bool zeroForOne = true;
bytes memory protocolData = abi.encodePacked( bytes memory protocolData =
BASE_USDC, abi.encodePacked(BASE_USDC, USDC_MAG7_POOL, BOB, zeroForOne, true);
USDC_MAG7_POOL,
BOB,
zeroForOne,
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
deal(BASE_USDC, address(uniswapV2Exposed), amountIn); deal(BASE_USDC, address(uniswapV2Exposed), amountIn);

View File

@@ -22,7 +22,8 @@ contract UniswapV3ExecutorExposed is UniswapV3Executor {
address receiver, address receiver,
address target, address target,
bool zeroForOne, bool zeroForOne,
TransferType transferType bool transferFromNeeded,
bool transferNeeded
) )
{ {
return _decodeData(data); return _decodeData(data);
@@ -71,7 +72,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address(2), address(2),
address(3), address(3),
false, false,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL false,
true
); );
( (
@@ -81,7 +83,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address receiver, address receiver,
address target, address target,
bool zeroForOne, bool zeroForOne,
TokenTransfer.TransferType transferType bool transferFromNeeded,
bool transferNeeded
) = uniswapV3Exposed.decodeData(data); ) = uniswapV3Exposed.decodeData(data);
assertEq(tokenIn, WETH_ADDR); assertEq(tokenIn, WETH_ADDR);
@@ -90,10 +93,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
assertEq(receiver, address(2)); assertEq(receiver, address(2));
assertEq(target, address(3)); assertEq(target, address(3));
assertEq(zeroForOne, false); assertEq(zeroForOne, false);
assertEq( assertEq(transferFromNeeded, false);
uint8(transferType), assertEq(transferNeeded, true);
uint8(TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL)
);
} }
function testSwapIntegration() public { function testSwapIntegration() public {
@@ -109,7 +110,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address(this), address(this),
DAI_WETH_USV3, DAI_WETH_USV3,
zeroForOne, zeroForOne,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL false,
true
); );
uint256 amountOut = uniswapV3Exposed.swap(amountIn, data); uint256 amountOut = uniswapV3Exposed.swap(amountIn, data);
@@ -184,7 +186,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address(this), address(this),
fakePool, fakePool,
zeroForOne, zeroForOne,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL false,
true
); );
vm.expectRevert(UniswapV3Executor__InvalidTarget.selector); vm.expectRevert(UniswapV3Executor__InvalidTarget.selector);
@@ -197,7 +200,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
address receiver, address receiver,
address target, address target,
bool zero2one, bool zero2one,
TokenTransfer.TransferType transferType bool transferFromNeeded,
bool transferNeeded
) internal view returns (bytes memory) { ) internal view returns (bytes memory) {
IUniswapV3Pool pool = IUniswapV3Pool(target); IUniswapV3Pool pool = IUniswapV3Pool(target);
return abi.encodePacked( return abi.encodePacked(
@@ -207,7 +211,8 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
receiver, receiver,
target, target,
zero2one, zero2one,
transferType transferFromNeeded,
transferNeeded
); );
} }
} }

View File

@@ -4,7 +4,6 @@ pragma solidity ^0.8.26;
import "../../src/executors/UniswapV4Executor.sol"; import "../../src/executors/UniswapV4Executor.sol";
import "../TestUtils.sol"; import "../TestUtils.sol";
import "./UniswapV4Utils.sol"; import "./UniswapV4Utils.sol";
import "@src/executors/TokenTransfer.sol";
import "@src/executors/UniswapV4Executor.sol"; import "@src/executors/UniswapV4Executor.sol";
import {Constants} from "../Constants.sol"; import {Constants} from "../Constants.sol";
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol"; import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
@@ -22,7 +21,8 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
address tokenIn, address tokenIn,
address tokenOut, address tokenOut,
bool zeroForOne, bool zeroForOne,
TokenTransfer.TransferType transferType, bool transferFromNeeded,
bool transferNeeded,
address receiver, address receiver,
UniswapV4Pool[] memory pools UniswapV4Pool[] memory pools
) )
@@ -53,8 +53,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
int24 tickSpacing1 = 60; int24 tickSpacing1 = 60;
uint24 pool2Fee = 1000; uint24 pool2Fee = 1000;
int24 tickSpacing2 = -10; int24 tickSpacing2 = -10;
TokenTransfer.TransferType transferType = bool transferFromNeeded = false;
TokenTransfer.TransferType.TRANSFER_FROM_TO_PROTOCOL; bool transferNeeded = true;
UniswapV4Executor.UniswapV4Pool[] memory pools = UniswapV4Executor.UniswapV4Pool[] memory pools =
new UniswapV4Executor.UniswapV4Pool[](2); new UniswapV4Executor.UniswapV4Pool[](2);
@@ -70,14 +70,21 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
}); });
bytes memory data = UniswapV4Utils.encodeExactInput( bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDT_ADDR, zeroForOne, transferType, ALICE, pools USDE_ADDR,
USDT_ADDR,
zeroForOne,
transferFromNeeded,
transferNeeded,
ALICE,
pools
); );
( (
address tokenIn, address tokenIn,
address tokenOut, address tokenOut,
bool zeroForOneDecoded, bool zeroForOneDecoded,
TokenTransfer.TransferType transferTypeDecoded, bool transferFromNeededDecoded,
bool transferNeededDecoded,
address receiver, address receiver,
UniswapV4Executor.UniswapV4Pool[] memory decodedPools UniswapV4Executor.UniswapV4Pool[] memory decodedPools
) = uniswapV4Exposed.decodeData(data); ) = uniswapV4Exposed.decodeData(data);
@@ -85,7 +92,8 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
assertEq(tokenIn, USDE_ADDR); assertEq(tokenIn, USDE_ADDR);
assertEq(tokenOut, USDT_ADDR); assertEq(tokenOut, USDT_ADDR);
assertEq(zeroForOneDecoded, zeroForOne); assertEq(zeroForOneDecoded, zeroForOne);
assertEq(uint8(transferTypeDecoded), uint8(transferType)); assertEq(transferFromNeededDecoded, transferFromNeeded);
assertEq(transferNeededDecoded, transferNeeded);
assertEq(receiver, ALICE); assertEq(receiver, ALICE);
assertEq(decodedPools.length, 2); assertEq(decodedPools.length, 2);
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR); assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
@@ -112,12 +120,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
}); });
bytes memory data = UniswapV4Utils.encodeExactInput( bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDE_ADDR, USDT_ADDR, true, false, true, ALICE, pools
USDT_ADDR,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
ALICE,
pools
); );
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
@@ -169,12 +172,7 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
}); });
bytes memory data = UniswapV4Utils.encodeExactInput( bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDE_ADDR, WBTC_ADDR, true, false, true, ALICE, pools
WBTC_ADDR,
true,
TokenTransfer.TransferType.TRANSFER_TO_PROTOCOL,
ALICE,
pools
); );
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data); uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);

View File

@@ -8,7 +8,8 @@ library UniswapV4Utils {
address tokenIn, address tokenIn,
address tokenOut, address tokenOut,
bool zeroForOne, bool zeroForOne,
UniswapV4Executor.TransferType transferType, bool transferFromNeeded,
bool transferNeeded,
address receiver, address receiver,
UniswapV4Executor.UniswapV4Pool[] memory pools UniswapV4Executor.UniswapV4Pool[] memory pools
) public pure returns (bytes memory) { ) public pure returns (bytes memory) {
@@ -24,7 +25,12 @@ library UniswapV4Utils {
} }
return abi.encodePacked( return abi.encodePacked(
tokenIn, tokenOut, zeroForOne, transferType, receiver, encodedPools tokenIn,
tokenOut,
zeroForOne,
transferNeeded,
receiver,
encodedPools
); );
} }
} }