feat: Support new transfer logic in all executors
TODO: - Fix failing tests - Remove permit2 from initialization of contracts
This commit is contained in:
@@ -27,7 +27,7 @@ contract OneTransferFromOnly {
|
||||
|
||||
function tstoreTransferFromInfo(
|
||||
address tokenIn,
|
||||
address amountIn,
|
||||
uint256 amountIn,
|
||||
bool isPermit2,
|
||||
address sender
|
||||
) internal {
|
||||
@@ -40,10 +40,7 @@ contract OneTransferFromOnly {
|
||||
}
|
||||
}
|
||||
|
||||
function _transfer(address receiver)
|
||||
// we could pass the amount and address too and compare to what is in the slots?
|
||||
internal
|
||||
{
|
||||
function _transfer(address receiver) internal {
|
||||
address tokenIn;
|
||||
uint256 amount;
|
||||
bool isPermit2;
|
||||
|
||||
@@ -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 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 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.
|
||||
*
|
||||
* @return amountOut The total amount of the output token received by the receiver.
|
||||
@@ -135,9 +137,14 @@ contract TychoRouter is
|
||||
bool unwrapEth,
|
||||
uint256 nTokens,
|
||||
address receiver,
|
||||
bool transferFromRequired,
|
||||
address tokenInReceiver,
|
||||
bytes calldata swaps
|
||||
) public payable whenNotPaused nonReentrant returns (uint256 amountOut) {
|
||||
tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender);
|
||||
if (transferFromRequired) {
|
||||
_transfer(tokenInReceiver);
|
||||
}
|
||||
return _splitSwapChecked(
|
||||
amountIn,
|
||||
tokenIn,
|
||||
@@ -173,6 +180,8 @@ contract TychoRouter is
|
||||
* @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 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.
|
||||
*
|
||||
* @return amountOut The total amount of the output token received by the receiver.
|
||||
@@ -188,14 +197,18 @@ contract TychoRouter is
|
||||
address receiver,
|
||||
IAllowanceTransfer.PermitSingle calldata permitSingle,
|
||||
bytes calldata signature,
|
||||
bool transferFromRequired,
|
||||
address tokenInReceiver,
|
||||
bytes calldata swaps
|
||||
) external payable whenNotPaused nonReentrant returns (uint256 amountOut) {
|
||||
// For native ETH, assume funds already in our router. Else, handle approval.
|
||||
if (tokenIn != address(0)) {
|
||||
permit2.permit(msg.sender, permitSingle, signature);
|
||||
}
|
||||
|
||||
tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender);
|
||||
if (transferFromRequired) {
|
||||
_transfer(tokenInReceiver);
|
||||
}
|
||||
|
||||
return _splitSwapChecked(
|
||||
amountIn,
|
||||
@@ -228,6 +241,8 @@ contract TychoRouter is
|
||||
* @param wrapEth If true, wraps the input token (native ETH) into WETH.
|
||||
* @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver.
|
||||
* @param receiver The address to receive the output tokens.
|
||||
* @param 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.
|
||||
*
|
||||
* @return amountOut The total amount of the output token received by the receiver.
|
||||
@@ -240,9 +255,14 @@ contract TychoRouter is
|
||||
bool wrapEth,
|
||||
bool unwrapEth,
|
||||
address receiver,
|
||||
bool transferFromRequired,
|
||||
address tokenInReceiver,
|
||||
bytes calldata swaps
|
||||
) public payable whenNotPaused nonReentrant returns (uint256 amountOut) {
|
||||
tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender);
|
||||
if (transferFromRequired) {
|
||||
_transfer(tokenInReceiver);
|
||||
}
|
||||
return _sequentialSwapChecked(
|
||||
amountIn,
|
||||
tokenIn,
|
||||
@@ -275,6 +295,8 @@ contract TychoRouter is
|
||||
* @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 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.
|
||||
*
|
||||
* @return amountOut The total amount of the output token received by the receiver.
|
||||
@@ -289,6 +311,8 @@ contract TychoRouter is
|
||||
address receiver,
|
||||
IAllowanceTransfer.PermitSingle calldata permitSingle,
|
||||
bytes calldata signature,
|
||||
bool transferFromRequired,
|
||||
address tokenInReceiver,
|
||||
bytes calldata swaps
|
||||
) external payable whenNotPaused nonReentrant returns (uint256 amountOut) {
|
||||
// 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);
|
||||
if (transferFromRequired) {
|
||||
_transfer(tokenInReceiver);
|
||||
}
|
||||
return _sequentialSwapChecked(
|
||||
amountIn,
|
||||
tokenIn,
|
||||
@@ -325,6 +352,8 @@ contract TychoRouter is
|
||||
* @param wrapEth If true, wraps the input token (native ETH) into WETH.
|
||||
* @param unwrapEth If true, unwraps the resulting WETH into native ETH and sends it to the receiver.
|
||||
* @param receiver The address to receive the output tokens.
|
||||
* @param 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.
|
||||
*
|
||||
* @return amountOut The total amount of the output token received by the receiver.
|
||||
@@ -337,13 +366,13 @@ contract TychoRouter is
|
||||
bool wrapEth,
|
||||
bool unwrapEth,
|
||||
address receiver,
|
||||
bool inTransferNeeded,
|
||||
address fundsReceiver,
|
||||
bool transferFromRequired,
|
||||
address tokenInReceiver,
|
||||
bytes calldata swapData
|
||||
) public payable whenNotPaused nonReentrant returns (uint256 amountOut) {
|
||||
tstoreTransferFromInfo(tokenIn, amountIn, false, msg.sender);
|
||||
if (inTransferNeeded) {
|
||||
_transfer(fundsReceiver);
|
||||
if (transferFromRequired) {
|
||||
_transfer(tokenInReceiver);
|
||||
}
|
||||
return _singleSwap(
|
||||
amountIn,
|
||||
@@ -377,6 +406,8 @@ contract TychoRouter is
|
||||
* @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 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.
|
||||
*
|
||||
* @return amountOut The total amount of the output token received by the receiver.
|
||||
@@ -391,6 +422,8 @@ contract TychoRouter is
|
||||
address receiver,
|
||||
IAllowanceTransfer.PermitSingle calldata permitSingle,
|
||||
bytes calldata signature,
|
||||
bool transferFromRequired,
|
||||
address tokenInReceiver,
|
||||
bytes calldata swapData
|
||||
) external payable whenNotPaused nonReentrant returns (uint256 amountOut) {
|
||||
// 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);
|
||||
}
|
||||
tstoreTransferFromInfo(tokenIn, amountIn, true, msg.sender);
|
||||
if (transferFromRequired) {
|
||||
_transfer(tokenInReceiver);
|
||||
}
|
||||
return _singleSwap(
|
||||
amountIn,
|
||||
tokenIn,
|
||||
|
||||
@@ -10,16 +10,15 @@ import {
|
||||
import {IAsset} from "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol";
|
||||
// slither-disable-next-line solc-version
|
||||
import {IVault} from "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol";
|
||||
import {TokenTransfer} from "./TokenTransfer.sol";
|
||||
|
||||
error BalancerV2Executor__InvalidDataLength();
|
||||
|
||||
contract BalancerV2Executor is IExecutor, TokenTransfer {
|
||||
contract BalancerV2Executor is IExecutor {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address private constant VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
|
||||
|
||||
constructor(address _permit2) TokenTransfer(_permit2) {}
|
||||
constructor(address _permit2) {}
|
||||
|
||||
// slither-disable-next-line locked-ether
|
||||
function swap(uint256 givenAmount, bytes calldata data)
|
||||
@@ -33,19 +32,9 @@ contract BalancerV2Executor is IExecutor, TokenTransfer {
|
||||
bytes32 poolId,
|
||||
address receiver,
|
||||
bool needsApproval,
|
||||
TransferType transferType
|
||||
bool transferNeeded
|
||||
) = _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) {
|
||||
// slither-disable-next-line unused-return
|
||||
tokenIn.forceApprove(VAULT, type(uint256).max);
|
||||
@@ -82,7 +71,7 @@ contract BalancerV2Executor is IExecutor, TokenTransfer {
|
||||
bytes32 poolId,
|
||||
address receiver,
|
||||
bool needsApproval,
|
||||
TransferType transferType
|
||||
bool transferNeeded
|
||||
)
|
||||
{
|
||||
if (data.length != 94) {
|
||||
@@ -94,6 +83,6 @@ contract BalancerV2Executor is IExecutor, TokenTransfer {
|
||||
poolId = bytes32(data[40:72]);
|
||||
receiver = address(bytes20(data[72:92]));
|
||||
needsApproval = uint8(data[92]) > 0;
|
||||
transferType = TransferType(uint8(data[93]));
|
||||
transferNeeded = uint8(data[93]) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ pragma solidity ^0.8.26;
|
||||
|
||||
import "@interfaces/IExecutor.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "./TokenTransfer.sol";
|
||||
import "@openzeppelin/contracts/utils/Address.sol";
|
||||
|
||||
error CurveExecutor__AddressZero();
|
||||
@@ -35,14 +34,12 @@ interface CryptoPoolETH {
|
||||
// slither-disable-end naming-convention
|
||||
}
|
||||
|
||||
contract CurveExecutor is IExecutor, TokenTransfer {
|
||||
contract CurveExecutor is IExecutor {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address public immutable nativeToken;
|
||||
|
||||
constructor(address _nativeToken, address _permit2)
|
||||
TokenTransfer(_permit2)
|
||||
{
|
||||
constructor(address _nativeToken, address _permit2) {
|
||||
if (_nativeToken == address(0)) {
|
||||
revert CurveExecutor__AddressZero();
|
||||
}
|
||||
@@ -65,20 +62,10 @@ contract CurveExecutor is IExecutor, TokenTransfer {
|
||||
int128 i,
|
||||
int128 j,
|
||||
bool tokenApprovalNeeded,
|
||||
TransferType transferType,
|
||||
bool transferNeeded, // TODO remove this with the encoding
|
||||
address receiver
|
||||
) = _decodeData(data);
|
||||
|
||||
_transfer(
|
||||
tokenIn,
|
||||
msg.sender,
|
||||
// Receiver can never be the pool, since the pool expects funds in the router contract
|
||||
// Thus, this call will only ever be used to transfer funds from the user into the router.
|
||||
address(this),
|
||||
amountIn,
|
||||
transferType
|
||||
);
|
||||
|
||||
if (tokenApprovalNeeded && tokenIn != nativeToken) {
|
||||
// slither-disable-next-line unused-return
|
||||
IERC20(tokenIn).forceApprove(address(pool), type(uint256).max);
|
||||
@@ -134,7 +121,7 @@ contract CurveExecutor is IExecutor, TokenTransfer {
|
||||
int128 i,
|
||||
int128 j,
|
||||
bool tokenApprovalNeeded,
|
||||
TransferType transferType,
|
||||
bool transferNeeded,
|
||||
address receiver
|
||||
)
|
||||
{
|
||||
@@ -145,7 +132,7 @@ contract CurveExecutor is IExecutor, TokenTransfer {
|
||||
i = int128(uint128(uint8(data[61])));
|
||||
j = int128(uint128(uint8(data[62])));
|
||||
tokenApprovalNeeded = data[63] != 0;
|
||||
transferType = TransferType(uint8(data[64]));
|
||||
transferNeeded = data[64] != 0;
|
||||
receiver = address(bytes20(data[65:85]));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,14 +11,14 @@ import {SafeTransferLib} from "@solady/utils/SafeTransferLib.sol";
|
||||
import {LibBytes} from "@solady/utils/LibBytes.sol";
|
||||
import {Config, EkuboPoolKey} from "@ekubo/types/poolKey.sol";
|
||||
import {MAX_SQRT_RATIO, MIN_SQRT_RATIO} from "@ekubo/types/sqrtRatio.sol";
|
||||
import {TokenTransfer} from "./TokenTransfer.sol";
|
||||
import "../OneTransferFromOnly.sol";
|
||||
|
||||
contract EkuboExecutor is
|
||||
IExecutor,
|
||||
ILocker,
|
||||
IPayer,
|
||||
ICallback,
|
||||
TokenTransfer
|
||||
OneTransferFromOnly
|
||||
{
|
||||
error EkuboExecutor__InvalidDataLength();
|
||||
error EkuboExecutor__CoreOnly();
|
||||
@@ -32,7 +32,9 @@ contract EkuboExecutor is
|
||||
bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256)
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -44,13 +46,8 @@ contract EkuboExecutor is
|
||||
if (data.length < 93) revert EkuboExecutor__InvalidDataLength();
|
||||
|
||||
// amountIn must be at most type(int128).MAX
|
||||
calculatedAmount = uint256(
|
||||
_lock(
|
||||
bytes.concat(
|
||||
bytes16(uint128(amountIn)), bytes20(msg.sender), data
|
||||
)
|
||||
)
|
||||
);
|
||||
calculatedAmount =
|
||||
uint256(_lock(bytes.concat(bytes16(uint128(amountIn)), data)));
|
||||
}
|
||||
|
||||
function handleCallback(bytes calldata raw)
|
||||
@@ -126,10 +123,11 @@ contract EkuboExecutor is
|
||||
int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16])));
|
||||
uint128 tokenInDebtAmount = uint128(nextAmountIn);
|
||||
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 tokenIn = address(bytes20(swapData[57:77]));
|
||||
address receiver = address(bytes20(swapData[38:58]));
|
||||
address tokenIn = address(bytes20(swapData[58:78]));
|
||||
|
||||
address nextTokenIn = tokenIn;
|
||||
|
||||
@@ -163,7 +161,7 @@ contract EkuboExecutor is
|
||||
offset += HOP_BYTE_LEN;
|
||||
}
|
||||
|
||||
_pay(tokenIn, tokenInDebtAmount, sender, transferType);
|
||||
_pay(tokenIn, tokenInDebtAmount, transferFromNeeded, transferNeeded);
|
||||
core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn));
|
||||
return nextAmountIn;
|
||||
}
|
||||
@@ -171,8 +169,8 @@ contract EkuboExecutor is
|
||||
function _pay(
|
||||
address token,
|
||||
uint128 amount,
|
||||
address sender,
|
||||
uint8 transferType
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded
|
||||
) internal {
|
||||
address target = address(core);
|
||||
|
||||
@@ -186,11 +184,11 @@ contract EkuboExecutor is
|
||||
mstore(free, shl(224, 0x0c11dedd))
|
||||
mstore(add(free, 4), token)
|
||||
mstore(add(free, 36), shl(128, amount))
|
||||
mstore(add(free, 52), shl(96, sender))
|
||||
mstore(add(free, 72), shl(248, transferType))
|
||||
mstore(add(free, 52), shl(248, transferFromNeeded))
|
||||
mstore(add(free, 53), shl(248, transferNeeded))
|
||||
|
||||
// 4 (selector) + 32 (token) + 16 (amount) + 20 (sender) + 1 (transferType) = 73
|
||||
if iszero(call(gas(), target, 0, free, 73, 0, 0)) {
|
||||
// 4 (selector) + 32 (token) + 16 (amount) + 1 (transferFromNeeded) + 1 (transferNeeded) = 54
|
||||
if iszero(call(gas(), target, 0, free, 54, 0, 0)) {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
@@ -201,9 +199,17 @@ contract EkuboExecutor is
|
||||
function _payCallback(bytes calldata payData) internal {
|
||||
address token = address(bytes20(payData[12:32])); // This arg is abi-encoded
|
||||
uint128 amount = uint128(bytes16(payData[32:48]));
|
||||
address sender = address(bytes20(payData[48:68]));
|
||||
TransferType transferType = TransferType(uint8(payData[68]));
|
||||
_transfer(token, sender, address(core), amount, transferType);
|
||||
bool transferFromNeeded = (payData[48] != 0);
|
||||
bool transferNeeded = (payData[49] != 0);
|
||||
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
|
||||
|
||||
@@ -9,12 +9,12 @@ error MaverickV2Executor__InvalidDataLength();
|
||||
error MaverickV2Executor__InvalidTarget();
|
||||
error MaverickV2Executor__InvalidFactory();
|
||||
|
||||
contract MaverickV2Executor is IExecutor, TokenTransfer {
|
||||
contract MaverickV2Executor is IExecutor {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
address public immutable factory;
|
||||
|
||||
constructor(address _factory, address _permit2) TokenTransfer(_permit2) {
|
||||
constructor(address _factory, address _permit2) {
|
||||
if (_factory == address(0)) {
|
||||
revert MaverickV2Executor__InvalidFactory();
|
||||
}
|
||||
@@ -30,9 +30,9 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
|
||||
address target;
|
||||
address receiver;
|
||||
IERC20 tokenIn;
|
||||
TransferType transferType;
|
||||
bool transferNeeded;
|
||||
|
||||
(tokenIn, target, receiver, transferType) = _decodeData(data);
|
||||
(tokenIn, target, receiver, transferNeeded) = _decodeData(data);
|
||||
|
||||
_verifyPairAddress(target);
|
||||
IMaverickV2Pool pool = IMaverickV2Pool(target);
|
||||
@@ -47,9 +47,15 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
|
||||
tickLimit: tickLimit
|
||||
});
|
||||
|
||||
_transfer(
|
||||
address(tokenIn), msg.sender, target, givenAmount, transferType
|
||||
);
|
||||
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 unused-return
|
||||
(, calculatedAmount) = pool.swap(receiver, swapParams, "");
|
||||
}
|
||||
@@ -61,7 +67,7 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
|
||||
IERC20 inToken,
|
||||
address target,
|
||||
address receiver,
|
||||
TransferType transferType
|
||||
bool transferNeeded
|
||||
)
|
||||
{
|
||||
if (data.length != 61) {
|
||||
@@ -70,7 +76,7 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
|
||||
inToken = IERC20(address(bytes20(data[0:20])));
|
||||
target = address(bytes20(data[20:40]));
|
||||
receiver = address(bytes20(data[40:60]));
|
||||
transferType = TransferType(uint8(data[60]));
|
||||
transferNeeded = (data[60] != 0);
|
||||
}
|
||||
|
||||
function _verifyPairAddress(address target) internal view {
|
||||
|
||||
@@ -59,8 +59,8 @@ contract UniswapV2Executor is IExecutor {
|
||||
|
||||
calculatedAmount = _getAmountOut(target, givenAmount, zeroForOne);
|
||||
|
||||
if (transferNeeded){
|
||||
if (tokenIn == address(0)) {
|
||||
if (transferNeeded) {
|
||||
if (address(tokenIn) == address(0)) {
|
||||
payable(target).transfer(givenAmount);
|
||||
} else {
|
||||
// slither-disable-next-line arbitrary-send-erc20
|
||||
@@ -93,8 +93,8 @@ contract UniswapV2Executor is IExecutor {
|
||||
inToken = IERC20(address(bytes20(data[0:20])));
|
||||
target = address(bytes20(data[20:40]));
|
||||
receiver = address(bytes20(data[40:60]));
|
||||
zeroForOne = uint8(data[60]) > 0;
|
||||
transferNeeded = bool(data[61]);
|
||||
zeroForOne = data[60] != 0;
|
||||
transferNeeded = data[61] != 0;
|
||||
}
|
||||
|
||||
function _getAmountOut(address target, uint256 amountIn, bool zeroForOne)
|
||||
|
||||
@@ -51,8 +51,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
|
||||
address receiver,
|
||||
address target,
|
||||
bool zeroForOne,
|
||||
bool inTransferNeeded,
|
||||
bool inBetweenSwapsTransferNeeded
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded
|
||||
) = _decodeData(data);
|
||||
|
||||
_verifyPairAddress(tokenIn, tokenOut, fee, target);
|
||||
@@ -62,11 +62,7 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
|
||||
IUniswapV3Pool pool = IUniswapV3Pool(target);
|
||||
|
||||
bytes memory callbackData = _makeV3CallbackData(
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
fee,
|
||||
inTransferNeeded,
|
||||
inBetweenSwapsTransferNeeded
|
||||
tokenIn, tokenOut, fee, transferFromNeeded, transferNeeded
|
||||
);
|
||||
|
||||
{
|
||||
@@ -104,8 +100,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
|
||||
|
||||
address tokenIn = address(bytes20(msgData[132:152]));
|
||||
|
||||
bool inTransferNeeded = bool(msgData[175]);
|
||||
bool inBetweenSwapsTransferNeeded = bool(msgData[176]);
|
||||
bool transferFromNeeded = msgData[175] != 0;
|
||||
bool transferNeeded = msgData[176] != 0;
|
||||
address sender = address(bytes20(msgData[176:196]));
|
||||
|
||||
verifyCallback(msgData[132:]);
|
||||
@@ -113,9 +109,9 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
|
||||
uint256 amountOwed =
|
||||
amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta);
|
||||
|
||||
if (inTransferNeeded) {
|
||||
if (transferFromNeeded) {
|
||||
_transfer(msg.sender);
|
||||
} else if (inBetweenSwapsTransferNeeded) {
|
||||
} else if (transferNeeded) {
|
||||
if (tokenIn == address(0)) {
|
||||
payable(msg.sender).transfer(amountOwed);
|
||||
} else {
|
||||
@@ -152,8 +148,8 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
|
||||
address receiver,
|
||||
address target,
|
||||
bool zeroForOne,
|
||||
bool inTransferNeeded,
|
||||
bool inBetweenSwapsTransferNeeded
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded
|
||||
)
|
||||
{
|
||||
if (data.length != 85) {
|
||||
@@ -165,23 +161,23 @@ contract UniswapV3Executor is IExecutor, ICallback, OneTransferFromOnly {
|
||||
receiver = address(bytes20(data[43:63]));
|
||||
target = address(bytes20(data[63:83]));
|
||||
zeroForOne = uint8(data[83]) > 0;
|
||||
inTransferNeeded = bool(data[84]);
|
||||
inBetweenSwapsTransferNeeded = bool(data[85]);
|
||||
transferFromNeeded = uint8(data[84]) > 0;
|
||||
transferNeeded = uint8(data[85]) > 0;
|
||||
}
|
||||
|
||||
function _makeV3CallbackData(
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
uint24 fee,
|
||||
bool inTransferNeeded,
|
||||
bool inBetweenSwapsTransferNeeded
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded
|
||||
) internal view returns (bytes memory) {
|
||||
return abi.encodePacked(
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
fee,
|
||||
inTransferNeeded,
|
||||
inBetweenSwapsTransferNeeded,
|
||||
transferFromNeeded,
|
||||
transferNeeded,
|
||||
msg.sender
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ pragma solidity ^0.8.26;
|
||||
|
||||
import "@interfaces/IExecutor.sol";
|
||||
import {ICallback} from "@interfaces/ICallback.sol";
|
||||
import {TokenTransfer} from "./TokenTransfer.sol";
|
||||
import {
|
||||
IERC20,
|
||||
SafeERC20
|
||||
@@ -23,6 +22,7 @@ import {IUnlockCallback} from
|
||||
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
|
||||
import {TransientStateLibrary} from
|
||||
"@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
|
||||
import "../OneTransferFromOnly.sol";
|
||||
|
||||
error UniswapV4Executor__InvalidDataLength();
|
||||
error UniswapV4Executor__NotPoolManager();
|
||||
@@ -37,7 +37,7 @@ contract UniswapV4Executor is
|
||||
IExecutor,
|
||||
IUnlockCallback,
|
||||
ICallback,
|
||||
TokenTransfer
|
||||
OneTransferFromOnly
|
||||
{
|
||||
using SafeERC20 for IERC20;
|
||||
using CurrencyLibrary for Currency;
|
||||
@@ -57,7 +57,7 @@ contract UniswapV4Executor is
|
||||
}
|
||||
|
||||
constructor(IPoolManager _poolManager, address _permit2)
|
||||
TokenTransfer(_permit2)
|
||||
OneTransferFromOnly(_permit2)
|
||||
{
|
||||
poolManager = _poolManager;
|
||||
_self = address(this);
|
||||
@@ -82,7 +82,8 @@ contract UniswapV4Executor is
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
bool zeroForOne,
|
||||
TransferType transferType,
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded,
|
||||
address receiver,
|
||||
UniswapV4Executor.UniswapV4Pool[] memory pools
|
||||
) = _decodeData(data);
|
||||
@@ -100,8 +101,8 @@ contract UniswapV4Executor is
|
||||
key,
|
||||
zeroForOne,
|
||||
amountIn,
|
||||
msg.sender,
|
||||
transferType,
|
||||
transferFromNeeded,
|
||||
transferNeeded,
|
||||
receiver,
|
||||
bytes("")
|
||||
);
|
||||
@@ -123,8 +124,8 @@ contract UniswapV4Executor is
|
||||
currencyIn,
|
||||
path,
|
||||
amountIn,
|
||||
msg.sender,
|
||||
transferType,
|
||||
transferFromNeeded,
|
||||
transferNeeded,
|
||||
receiver
|
||||
);
|
||||
}
|
||||
@@ -142,24 +143,26 @@ contract UniswapV4Executor is
|
||||
address tokenIn,
|
||||
address tokenOut,
|
||||
bool zeroForOne,
|
||||
TransferType transferType,
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded,
|
||||
address receiver,
|
||||
UniswapV4Pool[] memory pools
|
||||
)
|
||||
{
|
||||
if (data.length < 88) {
|
||||
if (data.length < 89) {
|
||||
revert UniswapV4Executor__InvalidDataLength();
|
||||
}
|
||||
|
||||
tokenIn = address(bytes20(data[0:20]));
|
||||
tokenOut = address(bytes20(data[20:40]));
|
||||
zeroForOne = (data[40] != 0);
|
||||
transferType = TransferType(uint8(data[41]));
|
||||
receiver = address(bytes20(data[42:62]));
|
||||
transferFromNeeded = (data[41] != 0);
|
||||
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);
|
||||
bytes memory poolsData = data[62:];
|
||||
bytes memory poolsData = data[63:];
|
||||
uint256 offset = 0;
|
||||
for (uint256 i = 0; i < poolsLength; i++) {
|
||||
address intermediaryToken;
|
||||
@@ -239,8 +242,8 @@ contract UniswapV4Executor is
|
||||
* @param poolKey The key of the pool to swap in.
|
||||
* @param zeroForOne Whether the swap is from token0 to token1 (true) or vice versa (false).
|
||||
* @param amountIn The amount of tokens to swap in.
|
||||
* @param sender The address of the sender.
|
||||
* @param transferType The type of transfer in to use.
|
||||
* @param transferFromNeeded Whether to transferFrom input tokens into the core contract from the swapper's wallet .
|
||||
* @param transferNeeded Whether to transfer input tokens into the core contract from the router contract
|
||||
* @param receiver The address of the receiver.
|
||||
* @param hookData Additional data for hook contracts.
|
||||
*/
|
||||
@@ -248,8 +251,8 @@ contract UniswapV4Executor is
|
||||
PoolKey memory poolKey,
|
||||
bool zeroForOne,
|
||||
uint128 amountIn,
|
||||
address sender,
|
||||
TransferType transferType,
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded,
|
||||
address receiver,
|
||||
bytes calldata hookData
|
||||
) external returns (uint128) {
|
||||
@@ -262,7 +265,7 @@ contract UniswapV4Executor is
|
||||
if (amount > amountIn) {
|
||||
revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount);
|
||||
}
|
||||
_settle(currencyIn, amount, sender, transferType);
|
||||
_settle(currencyIn, amount, transferFromNeeded, transferNeeded);
|
||||
|
||||
Currency currencyOut =
|
||||
zeroForOne ? poolKey.currency1 : poolKey.currency0;
|
||||
@@ -275,16 +278,16 @@ contract UniswapV4Executor is
|
||||
* @param currencyIn The currency of the input token.
|
||||
* @param path The path to swap along.
|
||||
* @param amountIn The amount of tokens to swap in.
|
||||
* @param sender The address of the sender.
|
||||
* @param transferType The type of transfer in to use.
|
||||
* @param transferFromNeeded Whether to transferFrom input tokens into the core contract from the swapper's wallet .
|
||||
* @param transferNeeded Whether to transfer input tokens into the core contract from the router contract
|
||||
* @param receiver The address of the receiver.
|
||||
*/
|
||||
function swapExactInput(
|
||||
Currency currencyIn,
|
||||
PathKey[] calldata path,
|
||||
uint128 amountIn,
|
||||
address sender,
|
||||
TransferType transferType,
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded,
|
||||
address receiver
|
||||
) external returns (uint128) {
|
||||
uint128 amountOut = 0;
|
||||
@@ -315,7 +318,7 @@ contract UniswapV4Executor is
|
||||
if (amount > amountIn) {
|
||||
revert UniswapV4Executor__V4TooMuchRequested(amountIn, amount);
|
||||
}
|
||||
_settle(currencyIn, amount, sender, transferType);
|
||||
_settle(currencyIn, amount, transferFromNeeded, transferNeeded);
|
||||
|
||||
_take(
|
||||
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.
|
||||
* @param currency The currency to settle.
|
||||
* @param amount The amount to send.
|
||||
* @param sender The address of the payer.
|
||||
* @param transferType The type of transfer to use.
|
||||
* @param transferFromNeeded Whether to manually transferFrom input tokens into the
|
||||
* 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.
|
||||
*/
|
||||
function _settle(
|
||||
Currency currency,
|
||||
uint256 amount,
|
||||
address sender,
|
||||
TransferType transferType
|
||||
bool transferFromNeeded,
|
||||
bool transferNeeded
|
||||
) internal {
|
||||
if (amount == 0) return;
|
||||
poolManager.sync(currency);
|
||||
@@ -403,13 +408,18 @@ contract UniswapV4Executor is
|
||||
// slither-disable-next-line unused-return
|
||||
poolManager.settle{value: amount}();
|
||||
} else {
|
||||
_transfer(
|
||||
Currency.unwrap(currency),
|
||||
sender,
|
||||
address(poolManager),
|
||||
amount,
|
||||
transferType
|
||||
);
|
||||
if (transferFromNeeded) {
|
||||
// transferFrom swapper's wallet into the core contract
|
||||
_transfer(msg.sender);
|
||||
} else if (transferNeeded) {
|
||||
address tokenIn = Currency.unwrap(currency);
|
||||
// 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
|
||||
poolManager.settle();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user