fix: Make all tests pass!

Delete TokenTransfer.sol
Make slither happy

Bugfixes:
- Executors
  - Ekubo:
    - Fix the POOL_DATA_OFFSET value and remove sender from callback data
    - Use SafeERC20
  - Maverick and Univ2: Use safeTransfer and not safeTransferFrom
  - Univ3: update expected data length
  - Univ4: update the selectors (the signature changed)
- Router:
  - For split swap we don't need to pass the tokenInReceiver, it should always be the router address
  - For single and sequential: change order of the parameters (to be before the permit2 specific objects)
- Encoders:
  - Update selector signatures
  - For split swap pass the transfer_from (we might not need to if the token in is ETH)

Took 2 hours 51 minutes
This commit is contained in:
Diana Carvalho
2025-05-15 13:11:34 +01:00
parent 27dfde3118
commit ee687038c5
20 changed files with 126 additions and 197 deletions

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.26;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IExecutor} from "@interfaces/IExecutor.sol";
import {ICallback} from "@interfaces/ICallback.sol";
import {ICore} from "@ekubo/interfaces/ICore.sol";
@@ -26,12 +26,14 @@ contract EkuboExecutor is
ICore immutable core;
uint256 constant POOL_DATA_OFFSET = 77;
uint256 constant POOL_DATA_OFFSET = 58;
uint256 constant HOP_BYTE_LEN = 52;
bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256)
bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address)
using SafeERC20 for IERC20;
constructor(address _core, address _permit2)
OneTransferFromOnly(_permit2)
{
@@ -122,12 +124,10 @@ contract EkuboExecutor is
function _locked(bytes calldata swapData) internal returns (int128) {
int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16])));
uint128 tokenInDebtAmount = uint128(nextAmountIn);
address sender = address(bytes20(swapData[16:36]));
bool transferFromNeeded = (swapData[36] != 0);
bool transferNeeded = (swapData[37] != 0);
address receiver = address(bytes20(swapData[38:58]));
address tokenIn = address(bytes20(swapData[58:78]));
bool transferFromNeeded = (swapData[16] != 0);
bool transferNeeded = (swapData[17] != 0);
address receiver = address(bytes20(swapData[18:38]));
address tokenIn = address(bytes20(swapData[38:58]));
address nextTokenIn = tokenIn;
@@ -207,7 +207,7 @@ contract EkuboExecutor is
if (token == address(0)) {
payable(msg.sender).transfer(amount);
} else {
IERC20(token).transfer(msg.sender, amount);
IERC20(token).safeTransfer(msg.sender, amount);
}
}
}

View File

@@ -3,7 +3,6 @@ pragma solidity ^0.8.26;
import "@interfaces/IExecutor.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "./TokenTransfer.sol";
error MaverickV2Executor__InvalidDataLength();
error MaverickV2Executor__InvalidTarget();
@@ -49,10 +48,11 @@ contract MaverickV2Executor is IExecutor {
if (transferNeeded) {
if (address(tokenIn) == address(0)) {
// slither-disable-next-line arbitrary-send-eth
payable(target).transfer(givenAmount);
} else {
// slither-disable-next-line arbitrary-send-erc20
tokenIn.safeTransferFrom(msg.sender, target, givenAmount);
tokenIn.safeTransfer(target, givenAmount);
}
}

View File

@@ -1,70 +0,0 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "@interfaces/IExecutor.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
error TokenTransfer__AddressZero();
contract TokenTransfer {
using SafeERC20 for IERC20;
IAllowanceTransfer public immutable permit2;
enum TransferType {
// Assume funds are in the TychoRouter - transfer into the pool
TRANSFER_TO_PROTOCOL,
// Assume funds are in msg.sender's wallet - transferFrom into the pool
TRANSFER_FROM_TO_PROTOCOL,
// Assume funds are in msg.sender's wallet - permit2TransferFrom into the pool
TRANSFER_PERMIT2_TO_PROTOCOL,
// Assume funds are in msg.sender's wallet - but the pool requires it to be
// in the router contract when calling swap - transferFrom into the router
// contract
TRANSFER_FROM_TO_ROUTER,
// Assume funds are in msg.sender's wallet - but the pool requires it to be
// in the router contract when calling swap - transferFrom into the router
// contract using permit2
TRANSFER_PERMIT2_TO_ROUTER,
// Assume funds have already been transferred into the pool. Do nothing.
NONE
}
constructor(address _permit2) {
if (_permit2 == address(0)) {
revert TokenTransfer__AddressZero();
}
permit2 = IAllowanceTransfer(_permit2);
}
function _transfer(
address tokenIn,
address sender,
address receiver,
uint256 amount,
TransferType transferType
) internal {
if (transferType == TransferType.TRANSFER_TO_PROTOCOL) {
if (tokenIn == address(0)) {
payable(receiver).transfer(amount);
} else {
IERC20(tokenIn).safeTransfer(receiver, amount);
}
} else if (transferType == TransferType.TRANSFER_FROM_TO_PROTOCOL) {
// slither-disable-next-line arbitrary-send-erc20
IERC20(tokenIn).safeTransferFrom(sender, receiver, amount);
} else if (transferType == TransferType.TRANSFER_PERMIT2_TO_PROTOCOL) {
// Permit2.permit is already called from the TychoRouter
permit2.transferFrom(sender, receiver, uint160(amount), tokenIn);
} else if (transferType == TransferType.TRANSFER_FROM_TO_ROUTER) {
// slither-disable-next-line arbitrary-send-erc20
IERC20(tokenIn).safeTransferFrom(sender, address(this), amount);
} else if (transferType == TransferType.TRANSFER_PERMIT2_TO_ROUTER) {
// Permit2.permit is already called from the TychoRouter
permit2.transferFrom(
sender, address(this), uint160(amount), tokenIn
);
}
}
}

View File

@@ -60,12 +60,8 @@ contract UniswapV2Executor is IExecutor {
calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne);
if (transferNeeded) {
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 arbitrary-send-erc20
tokenIn.safeTransfer(target, givenAmount);
}
IUniswapV2Pair pool = IUniswapV2Pair(target);

View File

@@ -5,7 +5,6 @@ import "@interfaces/IExecutor.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
import "@interfaces/ICallback.sol";
import {TokenTransfer} from "./TokenTransfer.sol";
import {OneTransferFromOnly} from "../OneTransferFromOnly.sol";
error UniswapV3Executor__InvalidDataLength();
@@ -152,7 +151,7 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
bool transferNeeded
)
{
if (data.length != 85) {
if (data.length != 86) {
revert UniswapV3Executor__InvalidDataLength();
}
tokenIn = address(bytes20(data[0:20]));

View File

@@ -47,8 +47,8 @@ contract UniswapV4Executor is
IPoolManager public immutable poolManager;
address private immutable _self;
bytes4 constant SWAP_EXACT_INPUT_SINGLE_SELECTOR = 0x8bc6d0d7;
bytes4 constant SWAP_EXACT_INPUT_SELECTOR = 0xaf90aeb1;
bytes4 constant SWAP_EXACT_INPUT_SINGLE_SELECTOR = 0xbaa46608;
bytes4 constant SWAP_EXACT_INPUT_SELECTOR = 0x653f1785;
struct UniswapV4Pool {
address intermediaryToken;