Merge pull request #143 from propeller-heads/router/dc/ENG-4411-refactor-callback-transient-storage

feat: Refactor callback to use transient storage
This commit is contained in:
dianacarvalho1
2025-04-11 08:47:25 +01:00
committed by Diana Carvalho
13 changed files with 144 additions and 210 deletions

View File

@@ -52,7 +52,7 @@ contract Dispatcher {
* @dev Calls an executor, assumes swap.protocolData contains
* protocol-specific data required by the executor.
*/
// slither-disable-next-line delegatecall-loop
// slither-disable-next-line delegatecall-loop,assembly
function _callExecutor(
address executor,
uint256 amount,
@@ -62,6 +62,10 @@ contract Dispatcher {
revert Dispatcher__UnapprovedExecutor(executor);
}
assembly {
tstore(0, executor)
}
// slither-disable-next-line controlled-delegatecall,low-level-calls
(bool success, bytes memory result) = executor.delegatecall(
abi.encodeWithSelector(IExecutor.swap.selector, amount, data)
@@ -80,8 +84,15 @@ contract Dispatcher {
calculatedAmount = abi.decode(result, (uint256));
}
function _handleCallback(bytes calldata data) internal {
address executor = address(uint160(bytes20(data[data.length - 20:])));
// slither-disable-next-line assembly
function _handleCallback(bytes calldata data)
internal
returns (bytes memory)
{
address executor;
assembly {
executor := tload(0)
}
if (!executors[executor]) {
revert Dispatcher__UnapprovedExecutor(executor);
@@ -101,5 +112,14 @@ contract Dispatcher {
)
);
}
// to prevent multiple callbacks
assembly {
tstore(0, 0)
}
// this is necessary because the delegatecall will prepend extra bytes we don't want like the length and prefix
bytes memory decodedResult = abi.decode(result, (bytes));
return decodedResult;
}
}

View File

@@ -745,7 +745,12 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
* @dev We use the fallback function to allow flexibility on callback.
*/
fallback() external {
_handleCallback(msg.data);
bytes memory result = _handleCallback(msg.data);
// slither-disable-next-line assembly
assembly ("memory-safe") {
// Propagate the calculatedAmount
return(add(result, 32), 16)
}
}
/**
@@ -880,45 +885,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
require(msg.sender.code.length != 0);
}
/**
* @dev Called by UniswapV3 pool when swapping on it.
* See in IUniswapV3SwapCallback for documentation.
*/
function uniswapV3SwapCallback(
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 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.
*/
@@ -930,44 +896,4 @@ 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")
)
);
}
}
}

View File

@@ -3,6 +3,7 @@ pragma solidity ^0.8.26;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IExecutor} from "@interfaces/IExecutor.sol";
import {ICallback} from "@interfaces/ICallback.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";
@@ -11,16 +12,19 @@ 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 {
contract EkuboExecutor is IExecutor, ILocker, IPayer, ICallback {
error EkuboExecutor__InvalidDataLength();
error EkuboExecutor__CoreOnly();
error EkuboExecutor__UnknownCallback();
ICore immutable core;
uint256 constant POOL_DATA_OFFSET = 92;
uint256 constant POOL_DATA_OFFSET = 56;
uint256 constant HOP_BYTE_LEN = 52;
bytes4 constant LOCKED_SELECTOR = 0xb45a3c0e; // locked(uint256)
bytes4 constant PAY_CALLBACK_SELECTOR = 0x599d0714; // payCallback(uint256,address)
constructor(address _core) {
core = ICore(_core);
}
@@ -37,60 +41,45 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer {
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);
function handleCallback(bytes calldata raw)
external
returns (bytes memory)
{
verifyCallback(raw);
address receiver = address(bytes20(msg.data[52:72]));
address tokenIn = address(bytes20(msg.data[72:POOL_DATA_OFFSET]));
// Without selector and locker id
bytes calldata stripped = raw[36:];
address nextTokenIn = tokenIn;
bytes4 selector = bytes4(raw[:4]);
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;
bytes memory result = "";
if (selector == LOCKED_SELECTOR) {
int128 calculatedAmount = _locked(stripped);
result = abi.encodePacked(calculatedAmount);
} else if (selector == PAY_CALLBACK_SELECTOR) {
_payCallback(stripped);
} else {
revert EkuboExecutor__UnknownCallback();
}
_pay(tokenIn, tokenInDebtAmount);
return result;
}
core.withdraw(nextTokenIn, receiver, uint128(nextAmountIn));
function verifyCallback(bytes calldata) public view coreOnly {}
function locked(uint256) external coreOnly {
// Without selector and locker id
int128 calculatedAmount = _locked(msg.data[36:]);
// slither-disable-next-line assembly
assembly ("memory-safe") {
mstore(0, nextAmountIn)
mstore(0, calculatedAmount)
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 payCallback(uint256, address /*token*/ ) external coreOnly {
// Without selector and locker id
_payCallback(msg.data[36:]);
}
function _lock(bytes memory data)
@@ -121,6 +110,52 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer {
}
}
function _locked(bytes calldata swapData) internal returns (int128) {
int128 nextAmountIn = int128(uint128(bytes16(swapData[0:16])));
uint128 tokenInDebtAmount = uint128(nextAmountIn);
address receiver = address(bytes20(swapData[16:36]));
address tokenIn = address(bytes20(swapData[36:POOL_DATA_OFFSET]));
address nextTokenIn = tokenIn;
uint256 hopsLength = (swapData.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(swapData, offset)));
Config poolConfig =
Config.wrap(LibBytes.loadCalldata(swapData, 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));
return nextAmountIn;
}
function _pay(address token, uint128 amount) internal {
address target = address(core);
@@ -144,6 +179,13 @@ contract EkuboExecutor is IExecutor, ILocker, IPayer {
}
}
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]));
SafeTransferLib.safeTransfer(token, address(core), amount);
}
// To receive withdrawals from Core
receive() external payable {}

View File

@@ -80,6 +80,7 @@ contract UniswapV3Executor is IExecutor, ICallback {
returns (bytes memory result)
{
// The data has the following layout:
// - selector (4 bytes)
// - amount0Delta (32 bytes)
// - amount1Delta (32 bytes)
// - dataOffset (32 bytes)
@@ -87,11 +88,11 @@ contract UniswapV3Executor is IExecutor, ICallback {
// - protocolData (variable length)
(int256 amount0Delta, int256 amount1Delta) =
abi.decode(msgData[:64], (int256, int256));
abi.decode(msgData[4:68], (int256, int256));
address tokenIn = address(bytes20(msgData[128:148]));
address tokenIn = address(bytes20(msgData[132:152]));
verifyCallback(msgData[128:]);
verifyCallback(msgData[132:]);
uint256 amountOwed =
amount0Delta > 0 ? uint256(amount0Delta) : uint256(amount1Delta);
@@ -147,10 +148,10 @@ contract UniswapV3Executor is IExecutor, ICallback {
function _makeV3CallbackData(address tokenIn, address tokenOut, uint24 fee)
internal
view
pure
returns (bytes memory)
{
return abi.encodePacked(tokenIn, tokenOut, fee, self);
return abi.encodePacked(tokenIn, tokenOut, fee);
}
function _verifyPairAddress(

View File

@@ -41,7 +41,6 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback {
address tokenIn,
address tokenOut,
bool zeroForOne,
address callbackExecutor,
UniswapV4Executor.UniswapV4Pool[] memory pools
) = _decodeData(data);
@@ -107,14 +106,13 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback {
params[2] = abi.encode(Currency.wrap(tokenOut), uint256(0));
swapData = abi.encode(actions, params);
}
bytes memory fullData = abi.encodePacked(swapData, callbackExecutor);
uint256 tokenOutBalanceBefore;
tokenOutBalanceBefore = tokenOut == address(0)
? address(this).balance
: IERC20(tokenOut).balanceOf(address(this));
executeActions(fullData);
executeActions(swapData);
uint256 tokenOutBalanceAfter;
@@ -140,22 +138,20 @@ contract UniswapV4Executor is IExecutor, V4Router, ICallback {
address tokenIn,
address tokenOut,
bool zeroForOne,
address callbackExecutor,
UniswapV4Pool[] memory pools
)
{
if (data.length < 87) {
if (data.length < 67) {
revert UniswapV4Executor__InvalidDataLength();
}
tokenIn = address(bytes20(data[0:20]));
tokenOut = address(bytes20(data[20:40]));
zeroForOne = (data[40] != 0);
callbackExecutor = address(bytes20(data[41:61]));
uint256 poolsLength = (data.length - 61) / 26; // 26 bytes per pool object
uint256 poolsLength = (data.length - 41) / 26; // 26 bytes per pool object
pools = new UniswapV4Pool[](poolsLength);
bytes memory poolsData = data[61:];
bytes memory poolsData = data[41:];
uint256 offset = 0;
for (uint256 i = 0; i < poolsLength; i++) {
address intermediaryToken;

View File

@@ -58,7 +58,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup {
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
// Encoded solution generated using `test_split_encoding_strategy_usv4`
(bool success,) = tychoRouterAddr.call(
hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000006814875700000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ed015f0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000413a7c6367c69ac46fc2b633fd53e583b74b20ec9b3ea83b069fe564765560a4cb335af200fd90ddb5f56d11e469c11a97420499f1b3ee0c1db13149a74daa90db1b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008c008a0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d231193300f62849f9a0b5bf2913b396098f7c7019b51a820a0000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000000000000000"
hex"7c553846000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000006982508145454ce325ddbe47a25d4ec3d23119330000000000000000000000000000000000000000005064ff624d54346285543f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000003b9aca0000000000000000000000000000000000000000000000000000000000681f1d6200000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f7976a0000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000416af8ab519cbe8f466aed6188c8966ca4910d72d40d2f4360f62dbe75864b77ce5ec168870bb1540673b3f720c4f2bfaa5de2a79813c857ad5cc49b20217c65041c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007800760001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb486982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000000bb800003c6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000"
);
vm.stopPrank();
@@ -81,7 +81,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup {
// Encoded solution generated using `test_split_encoding_strategy_usv4_eth_in`
(bool success,) = tychoRouterAddr.call{value: 1 ether}(
hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a7640000000000000000000000000000000000000000000000000000000000006814877000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ed017800000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004193acc98d79044e8ec1bc3ced832dc679e38ac8c6fe9b5befd1e5e44cb44edb0e365f1c5d6e3ca6590ed1a053f1841aede29e5b573f046387aff794520a0f22581b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007200700001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d231193301f62849f9a0b5bf2913b396098f7c7019b51a820a6982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000000000000000000000000000"
hex"7c5538460000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933000000000000000000000000000000000000000000c87c939ae635f92dc2379c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000681f1d7100000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f79779000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000041c9ad125cc7c7188d1f0cd61dd8ee0d752d45c89ade1df275c5e0ce17525c6ff137bd976ebd0d17223c007e1f0a2806d086b4c7015460ebd21fef8a6caf8368d51c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e005c0001000000f62849f9a0b5bf2913b396098f7c7019b51a820a00000000000000000000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933016982508145454ce325ddbe47a25d4ec3d23119330061a80001f40000"
);
vm.stopPrank();
@@ -108,7 +108,7 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup {
// Encoded solution generated using `test_split_encoding_strategy_usv4_eth_out`
(bool success,) = tychoRouterAddr.call(
hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000000000000000000000000000000000006814878000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067ed018800000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000280000000000000000000000000000000000000000000000000000000000000004190134d2d142caff6dbea417292a15685119bd676b2b73bad35fe39f720f7c3163f16d057327499019506b6f690a3916fd3375c579c9cb814113b1516187380531b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007200700001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000f62849f9a0b5bf2913b396098f7c7019b51a820a0000000000000000000000000000000000000000000bb800003c0000000000000000000000000000"
hex"7c55384600000000000000000000000000000000000000000000000000000000b2d05e00000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f81490b4f29aade000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000000000000000b2d05e0000000000000000000000000000000000000000000000000000000000681f1d7e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d013950000000000000000000000000000000000000000000000000000000067f797860000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000000412beedebe0200a3d4ca03f34697af8ec027fe6c164e3d3b78e17d1c327dcfc8241ce8bda513f092e54b35856637e5e485a06cc8c1c6287f15f469d47e84fd91341b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e005c0001000000f62849f9a0b5bf2913b396098f7c7019b51a820aa0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000000000000000000000000000000000000000000bb800003c0000"
);
vm.stopPrank();
@@ -173,13 +173,6 @@ contract TychoRouterTestIntegration is TychoRouterTestSetup {
tychoRouter.setExecutors(executors);
vm.stopPrank();
// TEMPORARY while the Ekubo executor address is hardcoded in TychoRouter
// This allows us to change the code at that address to be the testing executor code
vm.etch(
0x4f88f6630a33dB05BEa1FeF7Dc7ff7508D1c531D,
0x3D7Ebc40AF7092E3F1C81F2e996cbA5Cae2090d7.code
);
deal(ALICE, 1 ether);
uint256 balancerBefore = IERC20(USDC_ADDR).balanceOf(ALICE);

View File

@@ -492,9 +492,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tickSpacing: int24(1)
});
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDT_ADDR, true, address(usv4Executor), pools
);
bytes memory protocolData =
UniswapV4Utils.encodeExactInput(USDE_ADDR, USDT_ADDR, true, pools);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData
@@ -540,9 +539,8 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
tickSpacing: int24(60)
});
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
USDE_ADDR, WBTC_ADDR, true, address(usv4Executor), pools
);
bytes memory protocolData =
UniswapV4Utils.encodeExactInput(USDE_ADDR, WBTC_ADDR, true, pools);
bytes memory swap = encodeSplitSwap(
uint8(0), uint8(1), uint24(0), address(usv4Executor), protocolData

View File

@@ -111,6 +111,7 @@ contract UniswapV3ExecutorTest is Test, Constants {
uint256 dataLength = protocolData.length;
bytes memory callbackData = abi.encodePacked(
bytes4(0xfa461e33),
int256(amountOwed), // amount0Delta
int256(0), // amount1Delta
dataOffset,
@@ -124,24 +125,6 @@ contract UniswapV3ExecutorTest is Test, Constants {
assertEq(finalPoolReserve - initialPoolReserve, amountOwed);
}
function testSwapIntegration() public {
uint256 amountIn = 10 ** 18;
deal(WETH_ADDR, address(uniswapV3Exposed), amountIn);
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
bool zeroForOne = false;
bytes memory data = encodeUniswapV3Swap(
WETH_ADDR, DAI_ADDR, address(this), DAI_WETH_USV3, zeroForOne
);
uint256 amountOut = uniswapV3Exposed.swap(amountIn, data);
assertGe(amountOut, expAmountOut);
assertEq(IERC20(WETH_ADDR).balanceOf(address(uniswapV3Exposed)), 0);
assertGe(IERC20(DAI_ADDR).balanceOf(address(this)), expAmountOut);
}
function testSwapFailureInvalidTarget() public {
uint256 amountIn = 10 ** 18;
deal(WETH_ADDR, address(uniswapV3Exposed), amountIn);

View File

@@ -18,7 +18,6 @@ contract UniswapV4ExecutorExposed is UniswapV4Executor {
address tokenIn,
address tokenOut,
bool zeroForOne,
address callbackExecutor,
UniswapV4Pool[] memory pools
)
{
@@ -62,21 +61,19 @@ contract UniswapV4ExecutorTest is Test, Constants {
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDT_ADDR, zeroForOne, address(uniswapV4Exposed), pools
USDE_ADDR, USDT_ADDR, zeroForOne, pools
);
(
address tokenIn,
address tokenOut,
bool zeroForOneDecoded,
address callbackExecutor,
UniswapV4Executor.UniswapV4Pool[] memory decodedPools
) = uniswapV4Exposed.decodeData(data);
assertEq(tokenIn, USDE_ADDR);
assertEq(tokenOut, USDT_ADDR);
assertEq(zeroForOneDecoded, zeroForOne);
assertEq(callbackExecutor, address(uniswapV4Exposed));
assertEq(decodedPools.length, 2);
assertEq(decodedPools[0].intermediaryToken, USDT_ADDR);
assertEq(decodedPools[0].fee, pool1Fee);
@@ -101,9 +98,8 @@ contract UniswapV4ExecutorTest is Test, Constants {
tickSpacing: int24(1)
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, USDT_ADDR, true, address(uniswapV4Exposed), pools
);
bytes memory data =
UniswapV4Utils.encodeExactInput(USDE_ADDR, USDT_ADDR, true, pools);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
@@ -118,7 +114,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
// USDE -> USDT
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_simple_swap
bytes memory protocolData =
hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701f62849f9a0b5bf2913b396098f7c7019b51a820adac17f958d2ee523a2206206994597c13d831ec7000064000001";
hex"4c9edd5852cd905f086c759e8383e09bff1e68b3dac17f958d2ee523a2206206994597c13d831ec701dac17f958d2ee523a2206206994597c13d831ec7000064000001";
uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);
uint256 usdeBalanceBeforePool = USDE.balanceOf(poolManager);
@@ -155,9 +151,8 @@ contract UniswapV4ExecutorTest is Test, Constants {
tickSpacing: int24(60)
});
bytes memory data = UniswapV4Utils.encodeExactInput(
USDE_ADDR, WBTC_ADDR, true, address(uniswapV4Exposed), pools
);
bytes memory data =
UniswapV4Utils.encodeExactInput(USDE_ADDR, WBTC_ADDR, true, pools);
uint256 amountOut = uniswapV4Exposed.swap(amountIn, data);
assertEq(USDE.balanceOf(poolManager), usdeBalanceBeforePool + amountIn);
@@ -175,7 +170,7 @@ contract UniswapV4ExecutorTest is Test, Constants {
// Generated by the Tycho swap encoder - test_encode_uniswap_v4_sequential_swap
bytes memory protocolData =
hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901f62849f9a0b5bf2913b396098f7c7019b51a820adac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c";
hex"4c9edd5852cd905f086c759e8383e09bff1e68b32260fac5e5542a773aa44fbcfedf7c193bc2c59901dac17f958d2ee523a2206206994597c13d831ec70000640000012260fac5e5542a773aa44fbcfedf7c193bc2c599000bb800003c";
uint256 amountIn = 100 ether;
deal(USDE_ADDR, address(uniswapV4Exposed), amountIn);

View File

@@ -8,7 +8,6 @@ library UniswapV4Utils {
address tokenIn,
address tokenOut,
bool zeroForOne,
address callbackExecutor,
UniswapV4Executor.UniswapV4Pool[] memory pools
) public pure returns (bytes memory) {
bytes memory encodedPools;
@@ -22,8 +21,6 @@ library UniswapV4Utils {
);
}
return abi.encodePacked(
tokenIn, tokenOut, zeroForOne, callbackExecutor, encodedPools
);
return abi.encodePacked(tokenIn, tokenOut, zeroForOne, encodedPools);
}
}

View File

@@ -1326,9 +1326,9 @@ mod tests {
let expected_swaps = String::from(concat!(
// length of ple encoded swaps without padding
"000000000000000000000000000000000000000000000000000000000000008c",
"0000000000000000000000000000000000000000000000000000000000000078",
// ple encoded swaps
"008a", // Swap length
"0076", // Swap length
"00", // token in index
"01", // token out index
"000000", // split
@@ -1338,7 +1338,6 @@ mod tests {
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in
"6982508145454ce325ddbe47a25d4ec3d2311933", // group token in
"00", // zero2one
"f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address
// First pool params
"0000000000000000000000000000000000000000", // intermediary token (ETH)
"000bb8", // fee
@@ -1347,7 +1346,7 @@ mod tests {
"6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE)
"0061a8", // fee
"0001f4", // tick spacing
"0000000000000000000000000000000000000000" // padding
"0000000000000000" // padding
));
let hex_calldata = encode(&calldata);

View File

@@ -196,21 +196,11 @@ impl SwapEncoder for UniswapV4SwapEncoder {
let group_token_out_address = bytes_to_address(&encoding_context.group_token_out)?;
let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address);
let callback_executor =
bytes_to_address(&Bytes::from_str(&self.executor_address).map_err(|_| {
EncodingError::FatalError("Invalid UniswapV4 executor address".into())
})?)?;
let pool_params =
(token_out_address, pool_fee_u24, pool_tick_spacing_u24).abi_encode_packed();
let args = (
group_token_in_address,
group_token_out_address,
zero_to_one,
callback_executor,
pool_params,
);
let args = (group_token_in_address, group_token_out_address, zero_to_one, pool_params);
Ok(args.abi_encode_packed())
}
@@ -785,8 +775,6 @@ mod tests {
"dac17f958d2ee523a2206206994597c13d831ec7",
// zero for one
"01",
// executor address
"f62849f9a0b5bf2913b396098f7c7019b51a820a",
// pool params:
// - intermediary token
"dac17f958d2ee523a2206206994597c13d831ec7",
@@ -950,8 +938,6 @@ mod tests {
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
// zero for one
"01",
// executor address
"f62849f9a0b5bf2913b396098f7c7019b51a820a",
// pool params:
// - intermediary token USDT
"dac17f958d2ee523a2206206994597c13d831ec7",

View File

@@ -1044,8 +1044,6 @@ mod tests {
"6982508145454ce325ddbe47a25d4ec3d2311933",
// zero for one
"00",
// executor address
"f62849f9a0b5bf2913b396098f7c7019b51a820a",
// first pool intermediary token (ETH)
"0000000000000000000000000000000000000000",
// fee