chore: merge main
Took 3 minutes
This commit is contained in:
@@ -111,8 +111,7 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
|
||||
/**
|
||||
* @notice Executes a swap operation based on a predefined swap graph, supporting internal token amount splits.
|
||||
* This function enables multi-step swaps, optional ETH wrapping/unwrapping, and validates the output amount
|
||||
* against a user-specified minimum. This function expects the input tokens to already be in the router at
|
||||
* the time of calling.
|
||||
* against a user-specified minimum. This function performs a transferFrom to retrieve tokens from the caller.
|
||||
*
|
||||
* @dev
|
||||
* - If `wrapEth` is true, the contract wraps the provided native ETH into WETH and uses it as the sell token.
|
||||
@@ -144,7 +143,11 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
|
||||
address receiver,
|
||||
bytes calldata swaps
|
||||
) public payable whenNotPaused nonReentrant returns (uint256 amountOut) {
|
||||
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
|
||||
if (address(tokenIn) != address(0)) {
|
||||
IERC20(tokenIn).safeTransferFrom(
|
||||
msg.sender, address(this), amountIn
|
||||
);
|
||||
}
|
||||
return _splitSwapChecked(
|
||||
amountIn,
|
||||
tokenIn,
|
||||
@@ -897,6 +900,25 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
|
||||
_handleCallback(fullData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Called by PancakeV3 pool when swapping on it.
|
||||
*/
|
||||
function pancakeV3SwapCallback(
|
||||
int256, /* amount0Delta */
|
||||
int256, /* amount1Delta */
|
||||
bytes calldata data
|
||||
) external {
|
||||
if (data.length < 24) revert TychoRouter__InvalidDataLength();
|
||||
// We are taking advantage of the fact that the data we need is already encoded in the correct format inside msg.data
|
||||
// This way we preserve the bytes calldata (and don't need to convert it to bytes memory)
|
||||
uint256 dataOffset = 4 + 32 + 32 + 32; // Skip selector + 2 ints + data_offset
|
||||
uint256 dataLength =
|
||||
uint256(bytes32(msg.data[dataOffset:dataOffset + 32]));
|
||||
|
||||
bytes calldata fullData = msg.data[4:dataOffset + 32 + dataLength];
|
||||
_handleCallback(fullData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Called by UniswapV4 pool manager after achieving unlock state.
|
||||
*/
|
||||
@@ -908,4 +930,44 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
|
||||
_handleCallback(data);
|
||||
return "";
|
||||
}
|
||||
|
||||
function locked(uint256) external {
|
||||
address executor = address(0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D);
|
||||
|
||||
// slither-disable-next-line controlled-delegatecall,low-level-calls
|
||||
(bool success, bytes memory result) = executor.delegatecall(msg.data);
|
||||
|
||||
if (!success) {
|
||||
revert(
|
||||
string(
|
||||
result.length > 0
|
||||
? result
|
||||
: abi.encodePacked("Callback failed")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// slither-disable-next-line assembly
|
||||
assembly ("memory-safe") {
|
||||
// Propagate the swappedAmount
|
||||
return(add(result, 32), 16)
|
||||
}
|
||||
}
|
||||
|
||||
function payCallback(uint256, address /*token*/ ) external {
|
||||
address executor = address(0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D);
|
||||
|
||||
// slither-disable-next-line controlled-delegatecall,low-level-calls
|
||||
(bool success, bytes memory result) = executor.delegatecall(msg.data);
|
||||
|
||||
if (!success) {
|
||||
revert(
|
||||
string(
|
||||
result.length > 0
|
||||
? result
|
||||
: abi.encodePacked("Callback failed")
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
154
foundry/src/executors/EkuboExecutor.sol
Normal file
154
foundry/src/executors/EkuboExecutor.sol
Normal file
@@ -0,0 +1,154 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||
import {IExecutor} from "@interfaces/IExecutor.sol";
|
||||
import {ICore} from "@ekubo/interfaces/ICore.sol";
|
||||
import {ILocker, IPayer} from "@ekubo/interfaces/IFlashAccountant.sol";
|
||||
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol";
|
||||
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";
|
||||
|
||||
contract EkuboExecutor is IExecutor, ILocker, IPayer {
|
||||
error EkuboExecutor__InvalidDataLength();
|
||||
error EkuboExecutor__CoreOnly();
|
||||
error EkuboExecutor__UnknownCallback();
|
||||
|
||||
ICore immutable core;
|
||||
|
||||
uint256 constant POOL_DATA_OFFSET = 92;
|
||||
uint256 constant HOP_BYTE_LEN = 52;
|
||||
|
||||
constructor(address _core) {
|
||||
core = ICore(_core);
|
||||
}
|
||||
|
||||
function swap(uint256 amountIn, bytes calldata data)
|
||||
external
|
||||
payable
|
||||
returns (uint256 calculatedAmount)
|
||||
{
|
||||
if (data.length < 92) revert EkuboExecutor__InvalidDataLength();
|
||||
|
||||
// amountIn must be at most type(int128).MAX
|
||||
calculatedAmount =
|
||||
uint256(_lock(bytes.concat(bytes16(uint128(amountIn)), data)));
|
||||
}
|
||||
|
||||
function locked(uint256) external coreOnly {
|
||||
int128 nextAmountIn = int128(uint128(bytes16(msg.data[36:52])));
|
||||
uint128 tokenInDebtAmount = uint128(nextAmountIn);
|
||||
|
||||
address receiver = address(bytes20(msg.data[52:72]));
|
||||
address tokenIn = address(bytes20(msg.data[72:POOL_DATA_OFFSET]));
|
||||
|
||||
address nextTokenIn = tokenIn;
|
||||
|
||||
uint256 hopsLength = (msg.data.length - POOL_DATA_OFFSET) / HOP_BYTE_LEN;
|
||||
|
||||
uint256 offset = POOL_DATA_OFFSET;
|
||||
|
||||
for (uint256 i = 0; i < hopsLength; i++) {
|
||||
address nextTokenOut =
|
||||
address(bytes20(LibBytes.loadCalldata(msg.data, offset)));
|
||||
Config poolConfig =
|
||||
Config.wrap(LibBytes.loadCalldata(msg.data, offset + 20));
|
||||
|
||||
(address token0, address token1, bool isToken1) = nextTokenIn
|
||||
> nextTokenOut
|
||||
? (nextTokenOut, nextTokenIn, true)
|
||||
: (nextTokenIn, nextTokenOut, false);
|
||||
|
||||
// slither-disable-next-line calls-loop
|
||||
(int128 delta0, int128 delta1) = core.swap_611415377(
|
||||
EkuboPoolKey(token0, token1, poolConfig),
|
||||
nextAmountIn,
|
||||
isToken1,
|
||||
isToken1 ? MAX_SQRT_RATIO : MIN_SQRT_RATIO,
|
||||
0
|
||||
);
|
||||
|
||||
nextTokenIn = nextTokenOut;
|
||||
nextAmountIn = -(isToken1 ? delta0 : delta1);
|
||||
|
||||
offset += HOP_BYTE_LEN;
|
||||
}
|
||||
|
||||
_pay(tokenIn, tokenInDebtAmount);
|
||||
|
||||
core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn));
|
||||
|
||||
// slither-disable-next-line assembly
|
||||
assembly ("memory-safe") {
|
||||
mstore(0, nextAmountIn)
|
||||
return(0x10, 16)
|
||||
}
|
||||
}
|
||||
|
||||
function payCallback(uint256, address token) external coreOnly {
|
||||
uint128 amount = uint128(bytes16(msg.data[68:84]));
|
||||
|
||||
SafeTransferLib.safeTransfer(token, address(core), amount);
|
||||
}
|
||||
|
||||
function _lock(bytes memory data)
|
||||
internal
|
||||
returns (uint128 swappedAmount)
|
||||
{
|
||||
address target = address(core);
|
||||
|
||||
// slither-disable-next-line assembly
|
||||
assembly ("memory-safe") {
|
||||
let args := mload(0x40)
|
||||
|
||||
// Selector of lock()
|
||||
mstore(args, shl(224, 0xf83d08ba))
|
||||
|
||||
// We only copy the data, not the length, because the length is read from the calldata size
|
||||
let len := mload(data)
|
||||
mcopy(add(args, 4), add(data, 32), len)
|
||||
|
||||
// If the call failed, pass through the revert
|
||||
if iszero(call(gas(), target, 0, args, add(len, 36), 0, 0)) {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
|
||||
returndatacopy(0, 0, 16)
|
||||
swappedAmount := shr(128, mload(0))
|
||||
}
|
||||
}
|
||||
|
||||
function _pay(address token, uint128 amount) internal {
|
||||
address target = address(core);
|
||||
|
||||
if (token == NATIVE_TOKEN_ADDRESS) {
|
||||
SafeTransferLib.safeTransferETH(target, amount);
|
||||
} else {
|
||||
// slither-disable-next-line assembly
|
||||
assembly ("memory-safe") {
|
||||
let free := mload(0x40)
|
||||
// selector of pay(address)
|
||||
mstore(free, shl(224, 0x0c11dedd))
|
||||
mstore(add(free, 4), token)
|
||||
mstore(add(free, 36), shl(128, amount))
|
||||
|
||||
// if it failed, pass through revert
|
||||
if iszero(call(gas(), target, 0, free, 52, 0, 0)) {
|
||||
returndatacopy(0, 0, returndatasize())
|
||||
revert(0, returndatasize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// To receive withdrawals from Core
|
||||
receive() external payable {}
|
||||
|
||||
modifier coreOnly() {
|
||||
if (msg.sender != address(core)) revert EkuboExecutor__CoreOnly();
|
||||
_;
|
||||
}
|
||||
}
|
||||
@@ -8,21 +8,24 @@ import "@uniswap-v2/contracts/interfaces/IUniswapV2Pair.sol";
|
||||
error UniswapV2Executor__InvalidDataLength();
|
||||
error UniswapV2Executor__InvalidTarget();
|
||||
error UniswapV2Executor__InvalidFactory();
|
||||
error UniswapV2Executor__InvalidInitCode();
|
||||
|
||||
contract UniswapV2Executor is IExecutor {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
bytes32 internal constant POOL_INIT_CODE_HASH =
|
||||
0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;
|
||||
|
||||
address public immutable factory;
|
||||
bytes32 public immutable initCode;
|
||||
address private immutable self;
|
||||
|
||||
constructor(address _factory) {
|
||||
constructor(address _factory, bytes32 _initCode) {
|
||||
if (_factory == address(0)) {
|
||||
revert UniswapV2Executor__InvalidFactory();
|
||||
}
|
||||
if (_initCode == bytes32(0)) {
|
||||
revert UniswapV2Executor__InvalidInitCode();
|
||||
}
|
||||
factory = _factory;
|
||||
initCode = _initCode;
|
||||
self = address(this);
|
||||
}
|
||||
|
||||
@@ -102,9 +105,7 @@ contract UniswapV2Executor is IExecutor {
|
||||
uint160(
|
||||
uint256(
|
||||
keccak256(
|
||||
abi.encodePacked(
|
||||
hex"ff", factory, salt, POOL_INIT_CODE_HASH
|
||||
)
|
||||
abi.encodePacked(hex"ff", factory, salt, initCode)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -4,30 +4,33 @@ pragma solidity ^0.8.26;
|
||||
import "@interfaces/IExecutor.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import "@uniswap/v3-updated/CallbackValidationV2.sol";
|
||||
import "@interfaces/ICallback.sol";
|
||||
|
||||
error UniswapV3Executor__InvalidDataLength();
|
||||
error UniswapV3Executor__InvalidFactory();
|
||||
error UniswapV3Executor__InvalidTarget();
|
||||
error UniswapV3Executor__InvalidInitCode();
|
||||
|
||||
contract UniswapV3Executor is IExecutor, ICallback {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
bytes32 internal constant POOL_INIT_CODE_HASH =
|
||||
0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54;
|
||||
uint160 private constant MIN_SQRT_RATIO = 4295128739;
|
||||
uint160 private constant MAX_SQRT_RATIO =
|
||||
1461446703485210103287273052203988822378723970342;
|
||||
|
||||
address public immutable factory;
|
||||
bytes32 public immutable initCode;
|
||||
address private immutable self;
|
||||
|
||||
constructor(address _factory) {
|
||||
constructor(address _factory, bytes32 _initCode) {
|
||||
if (_factory == address(0)) {
|
||||
revert UniswapV3Executor__InvalidFactory();
|
||||
}
|
||||
if (_initCode == bytes32(0)) {
|
||||
revert UniswapV3Executor__InvalidInitCode();
|
||||
}
|
||||
factory = _factory;
|
||||
initCode = _initCode;
|
||||
self = address(this);
|
||||
}
|
||||
|
||||
@@ -102,8 +105,7 @@ contract UniswapV3Executor is IExecutor, ICallback {
|
||||
address tokenOut = address(bytes20(data[20:40]));
|
||||
uint24 poolFee = uint24(bytes3(data[40:43]));
|
||||
|
||||
// slither-disable-next-line unused-return
|
||||
CallbackValidationV2.verifyCallback(factory, tokenIn, tokenOut, poolFee);
|
||||
_verifyPairAddress(tokenIn, tokenOut, poolFee, msg.sender);
|
||||
}
|
||||
|
||||
function uniswapV3SwapCallback(
|
||||
@@ -167,7 +169,7 @@ contract UniswapV3Executor is IExecutor, ICallback {
|
||||
hex"ff",
|
||||
factory,
|
||||
keccak256(abi.encode(token0, token1, fee)),
|
||||
POOL_INIT_CODE_HASH
|
||||
initCode
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user