Merge pull request #281 from propeller-heads/dc/ENG-4804-tycho-encoder-send-sync

fix: Make TychoEncode Send + Sync
This commit is contained in:
dianacarvalho1
2025-10-17 09:22:44 +01:00
committed by GitHub
22 changed files with 173 additions and 145 deletions

View File

@@ -93,9 +93,7 @@ contract TychoRouter is
address indexed token, uint256 amount, address indexed receiver
);
constructor(address _permit2, address weth)
RestrictTransferFrom(_permit2)
{
constructor(address _permit2, address weth) RestrictTransferFrom(_permit2) {
if (_permit2 == address(0) || weth == address(0)) {
revert TychoRouter__AddressZero();
}
@@ -808,6 +806,7 @@ contract TychoRouter is
/**
* @dev Gets balance of a token for a given address. Supports both native ETH and ERC20 tokens.
*/
// forgefmt: disable-start
function _balanceOf(address token, address owner)
internal
view
@@ -815,7 +814,7 @@ contract TychoRouter is
{
return
token == address(0) ? owner.balance : IERC20(token).balanceOf(owner);
}
}// forgefmt: disable-end
/**
* @dev Verifies that the expected amount of output tokens was received by the receiver.

View File

@@ -43,7 +43,12 @@ contract BalancerV3Executor is IExecutor, RestrictTransferFrom, ICallback {
calculatedAmount = abi.decode(abi.decode(result, (bytes)), (uint256));
}
function verifyCallback(bytes calldata /*data*/ ) public view {
function verifyCallback(
bytes calldata /*data*/
)
public
view
{
if (msg.sender != address(VAULT)) {
revert BalancerV3Executor__SenderIsNotVault(msg.sender);
}

View File

@@ -29,7 +29,9 @@ contract BebopExecutor is IExecutor, RestrictTransferFrom {
constructor(address _bebopSettlement, address _permit2)
RestrictTransferFrom(_permit2)
{
if (_bebopSettlement == address(0)) revert BebopExecutor__ZeroAddress();
if (_bebopSettlement == address(0)) {
revert BebopExecutor__ZeroAddress();
}
bebopSettlement = _bebopSettlement;
}

View File

@@ -55,7 +55,9 @@ contract CurveExecutor is IExecutor, RestrictTransferFrom {
payable
returns (uint256)
{
if (data.length != 85) revert CurveExecutor__InvalidDataLength();
if (data.length != 85) {
revert CurveExecutor__InvalidDataLength();
}
(
address tokenIn,
@@ -91,11 +93,13 @@ contract CurveExecutor is IExecutor, RestrictTransferFrom {
// crypto or llamma
if (tokenIn == nativeToken || tokenOut == nativeToken) {
// slither-disable-next-line arbitrary-send-eth
CryptoPoolETH(pool).exchange{value: ethAmount}(
uint256(int256(i)), uint256(int256(j)), amountIn, 0, true
);
CryptoPoolETH(pool)
.exchange{
value: ethAmount
}(uint256(int256(i)), uint256(int256(j)), amountIn, 0, true);
} else {
CryptoPool(pool).exchange(
CryptoPool(pool)
.exchange(
uint256(int256(i)), uint256(int256(j)), amountIn, 0
);
}
@@ -149,11 +153,7 @@ contract CurveExecutor is IExecutor, RestrictTransferFrom {
require(msg.sender.code.length != 0);
}
function _balanceOf(address token)
internal
view
returns (uint256 balance)
{
function _balanceOf(address token) internal view returns (uint256 balance) {
balance = token == nativeToken
? address(this).balance
: IERC20(token).balanceOf(address(this));

View File

@@ -102,15 +102,18 @@ contract EkuboExecutor is
}
}
function payCallback(uint256, address /*token*/ ) external coreOnly {
function payCallback(
uint256,
address /*token*/
)
external
coreOnly
{
// Without selector and locker id
_payCallback(msg.data[36:]);
}
function _lock(bytes memory data)
internal
returns (uint128 swappedAmount)
{
function _lock(bytes memory data) internal returns (uint128 swappedAmount) {
address target = address(core);
// slither-disable-next-line assembly

View File

@@ -67,9 +67,8 @@ contract HashflowExecutor is IExecutor, RestrictTransferFrom {
if (approvalNeeded && quote.baseToken != NATIVE_TOKEN) {
// slither-disable-next-line unused-return
IERC20(quote.baseToken).forceApprove(
hashflowRouter, type(uint256).max
);
IERC20(quote.baseToken)
.forceApprove(hashflowRouter, type(uint256).max);
}
uint256 ethValue = 0;

View File

@@ -42,8 +42,8 @@ contract MaverickV2Executor is IExecutor, RestrictTransferFrom {
bool isTokenAIn = pool.tokenA() == tokenIn;
int32 tickLimit = isTokenAIn ? type(int32).max : type(int32).min;
IMaverickV2Pool.SwapParams memory swapParams = IMaverickV2Pool
.SwapParams({
IMaverickV2Pool.SwapParams memory swapParams =
IMaverickV2Pool.SwapParams({
amount: givenAmount,
tokenAIn: isTokenAIn,
exactOutput: false,

View File

@@ -121,7 +121,9 @@ contract UniswapV3Executor is IExecutor, ICallback, RestrictTransferFrom {
int256, /* amount0Delta */
int256, /* amount1Delta */
bytes calldata /* data */
) external {
)
external
{
handleCallback(msg.data);
}

View File

@@ -11,19 +11,23 @@ import {
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
import {
Currency, CurrencyLibrary
Currency,
CurrencyLibrary
} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
import {IUnlockCallback} from
"@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {SafeCast as V4SafeCast} from
"@uniswap/v4-core/src/libraries/SafeCast.sol";
import {TransientStateLibrary} from
"@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
import {
IUnlockCallback
} from "@uniswap/v4-core/src/interfaces/callback/IUnlockCallback.sol";
import {
SafeCast as V4SafeCast
} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {
TransientStateLibrary
} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
import "../RestrictTransferFrom.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../../lib/bytes/LibPrefixLengthEncodedByteArray.sol";
@@ -115,7 +119,9 @@ contract UniswapV4Executor is
PathKey[] memory path = new PathKey[](pools.length);
for (uint256 i = 0; i < pools.length; i++) {
path[i] = PathKey({
intermediateCurrency: Currency.wrap(pools[i].intermediaryToken),
intermediateCurrency: Currency.wrap(
pools[i].intermediaryToken
),
fee: pools[i].fee,
tickSpacing: pools[i].tickSpacing,
hooks: IHooks(pools[i].hook),

View File

@@ -37,7 +37,9 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
address _reactor,
address _native_address
) {
if (_tychoRouter == address(0)) revert UniswapXFiller__AddressZero();
if (_tychoRouter == address(0)) {
revert UniswapXFiller__AddressZero();
}
if (_reactor == address(0)) revert UniswapXFiller__AddressZero();
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
@@ -73,9 +75,8 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
// The TychoRouter will take the input tokens from the filler
if (tokenInApprovalNeeded) {
// Native ETH input is not supported by UniswapX
IERC20(order.input.token).forceApprove(
tychoRouter, type(uint256).max
);
IERC20(order.input.token)
.forceApprove(tychoRouter, type(uint256).max);
}
// slither-disable-next-line low-level-calls
@@ -125,7 +126,9 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
external
onlyRole(DEFAULT_ADMIN_ROLE)
{
if (receiver == address(0)) revert UniswapXFiller__AddressZero();
if (receiver == address(0)) {
revert UniswapXFiller__AddressZero();
}
for (uint256 i = 0; i < tokens.length; i++) {
// slither-disable-next-line calls-loop
@@ -144,7 +147,9 @@ contract UniswapXFiller is AccessControl, IReactorCallback {
external
onlyRole(DEFAULT_ADMIN_ROLE)
{
if (receiver == address(0)) revert UniswapXFiller__AddressZero();
if (receiver == address(0)) {
revert UniswapXFiller__AddressZero();
}
uint256 amount = address(this).balance;
if (amount > 0) {

View File

@@ -108,7 +108,8 @@ contract Constants is Test, BaseConstants {
address LDO_POOL = 0x9409280DC1e6D33AB7A8C6EC03e5763FB61772B5;
address CRV_POOL = 0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511;
address AAVE_POOL = 0xDeBF20617708857ebe4F679508E7b7863a8A8EeE;
address FRAXPYUSD_POOL = address(0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb);
address FRAXPYUSD_POOL =
address(0xA5588F7cdf560811710A2D82D3C9c99769DB1Dcb);
address TRICRYPTO2_POOL = 0xD51a44d3FaE010294C616388b506AcdA1bfAAE46;
address SUSD_POOL = 0xA5407eAE9Ba41422680e2e00537571bcC53efBfD;
address FRAX_USDC_POOL = 0xDcEF968d416a41Cdac0ED8702fAC8128A64241A2;

View File

@@ -176,8 +176,8 @@ contract GasTest is Commands, Test, Constants {
returns (IAllowanceTransfer.PermitSingle memory, bytes memory)
{
IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in);
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer
.PermitSingle({
IAllowanceTransfer.PermitSingle memory permitSingle =
IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: tokenIn,
amount: uint160(amount_in),
@@ -223,8 +223,9 @@ contract GasTest is Commands, Test, Constants {
)
);
bytes32 digest =
keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash));
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", domainSeparator, permitHash)
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
return abi.encodePacked(r, s, v);

View File

@@ -2,8 +2,9 @@
pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import {LibPrefixLengthEncodedByteArray} from
"../lib/bytes/LibPrefixLengthEncodedByteArray.sol";
import {
LibPrefixLengthEncodedByteArray
} from "../lib/bytes/LibPrefixLengthEncodedByteArray.sol";
contract LibPrefixLengthEncodedByteArrayTest is Test {
using LibPrefixLengthEncodedByteArray for bytes;

View File

@@ -25,8 +25,8 @@ contract Permit2TestHelper is Constants {
uint256 amount_in
) internal returns (IAllowanceTransfer.PermitSingle memory, bytes memory) {
IERC20(tokenIn).approve(PERMIT2_ADDRESS, amount_in);
IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer
.PermitSingle({
IAllowanceTransfer.PermitSingle memory permitSingle =
IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: tokenIn,
amount: uint160(amount_in),
@@ -78,8 +78,9 @@ contract Permit2TestHelper is Constants {
)
);
bytes32 digest =
keccak256(abi.encodePacked("\x19\x01", domainSeparator, permitHash));
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", domainSeparator, permitHash)
);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digest);
return abi.encodePacked(r, s, v);

View File

@@ -185,10 +185,7 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
IAllowanceTransfer.PermitSingle memory emptyPermitSingle =
IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: address(0),
amount: 0,
expiration: 0,
nonce: 0
token: address(0), amount: 0, expiration: 0, nonce: 0
}),
spender: address(0),
sigDeadline: 0
@@ -219,7 +216,9 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
)
);
uint256 amountOut = tychoRouter.sequentialSwapPermit2{value: amountIn}(
uint256 amountOut = tychoRouter.sequentialSwapPermit2{
value: amountIn
}(
amountIn,
address(0),
USDC_ADDR,
@@ -502,8 +501,9 @@ contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
// Approve permit2
vm.startPrank(ALICE);
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
bytes memory callData =
loadCallDataFromFile("test_sequential_swap_strategy_encoder_unwrap");
bytes memory callData = loadCallDataFromFile(
"test_sequential_swap_strategy_encoder_unwrap"
);
(bool success,) = tychoRouterAddr.call(callData);
vm.stopPrank();

View File

@@ -205,10 +205,7 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
IAllowanceTransfer.PermitSingle memory emptyPermitSingle =
IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: address(0),
amount: 0,
expiration: 0,
nonce: 0
token: address(0), amount: 0, expiration: 0, nonce: 0
}),
spender: address(0),
sigDeadline: 0
@@ -225,7 +222,9 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
bytes memory swap =
encodeSingleSwap(address(usv2Executor), protocolData);
uint256 amountOut = tychoRouter.singleSwapPermit2{value: amountIn}(
uint256 amountOut = tychoRouter.singleSwapPermit2{
value: amountIn
}(
amountIn,
address(0),
DAI_ADDR,
@@ -338,8 +337,9 @@ contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
vm.startPrank(ALICE);
IERC20(WETH_ADDR).approve(tychoRouterAddr, type(uint256).max);
bytes memory callData =
loadCallDataFromFile("test_single_swap_strategy_encoder_no_permit2");
bytes memory callData = loadCallDataFromFile(
"test_single_swap_strategy_encoder_no_permit2"
);
(bool success,) = tychoRouterAddr.call(callData);
vm.stopPrank();

View File

@@ -252,10 +252,7 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
IAllowanceTransfer.PermitSingle memory emptyPermitSingle =
IAllowanceTransfer.PermitSingle({
details: IAllowanceTransfer.PermitDetails({
token: address(0),
amount: 0,
expiration: 0,
nonce: 0
token: address(0), amount: 0, expiration: 0, nonce: 0
}),
spender: address(0),
sigDeadline: 0
@@ -274,7 +271,9 @@ contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
bytes[] memory swaps = new bytes[](1);
swaps[0] = swap;
uint256 amountOut = tychoRouter.splitSwapPermit2{value: amountIn}(
uint256 amountOut = tychoRouter.splitSwapPermit2{
value: amountIn
}(
amountIn,
address(0),
DAI_ADDR,

View File

@@ -2,8 +2,9 @@
pragma solidity ^0.8.26;
import "../TychoRouterTestSetup.sol";
import {BalancerV3Executor__InvalidDataLength} from
"../../src/executors/BalancerV3Executor.sol";
import {
BalancerV3Executor__InvalidDataLength
} from "../../src/executors/BalancerV3Executor.sol";
contract BalancerV3ExecutorExposed is BalancerV3Executor {
constructor(address _permit2) BalancerV3Executor(_permit2) {}
@@ -31,7 +32,8 @@ contract BalancerV3ExecutorTest is Constants, TestUtils {
address WETH_osETH_pool =
address(0x57c23c58B1D8C3292c15BEcF07c62C5c52457A42);
address osETH_ADDR = address(0xf1C9acDc66974dFB6dEcB12aA385b9cD01190E38);
address waEthWETH_ADDR = address(0x0bfc9d54Fc184518A81162F8fB99c2eACa081202);
address waEthWETH_ADDR =
address(0x0bfc9d54Fc184518A81162F8fB99c2eACa081202);
function setUp() public {
uint256 forkBlock = 22625131;

View File

@@ -7,8 +7,9 @@ import "@src/executors/BebopExecutor.sol";
import {Constants} from "../Constants.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
import {SafeERC20} from
"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {
SafeERC20
} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
contract BebopExecutorExposed is BebopExecutor {
constructor(address _bebopSettlement, address _permit2)

View File

@@ -31,11 +31,9 @@ contract HashflowUtils is Test {
);
}
function encodeRfqtQuoteWithDefaults(IHashflowRouter.RFQTQuote memory quote)
internal
pure
returns (bytes memory)
{
function encodeRfqtQuoteWithDefaults(
IHashflowRouter.RFQTQuote memory quote
) internal pure returns (bytes memory) {
return
encodeRfqtQuote(quote, true, RestrictTransferFrom.TransferType.None);
}
@@ -183,7 +181,9 @@ contract HashflowExecutorECR20Test is Constants, TestUtils, HashflowUtils {
{
return IHashflowRouter.RFQTQuote({
pool: address(0x5d8853028fbF6a2da43c7A828cc5f691E9456B44),
externalAccount: address(0x9bA0CF1588E1DFA905eC948F7FE5104dD40EDa31),
externalAccount: address(
0x9bA0CF1588E1DFA905eC948F7FE5104dD40EDa31
),
trader: address(ALICE),
effectiveTrader: address(ALICE),
baseToken: WETH_ADDR,
@@ -243,7 +243,9 @@ contract HashflowExecutorNativeTest is Constants, HashflowUtils {
{
return IHashflowRouter.RFQTQuote({
pool: address(0x713DC4Df480235dBe2fB766E7120Cbd4041Dcb58),
externalAccount: address(0x111BB8c3542F2B92fb41B8d913c01D3788431111),
externalAccount: address(
0x111BB8c3542F2B92fb41B8d913c01D3788431111
),
trader: address(ALICE),
effectiveTrader: address(ALICE),
baseToken: address(0x0000000000000000000000000000000000000000),

View File

@@ -92,9 +92,7 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
additionalValidationData: ""
}),
input: InputToken({
token: address(WETH_ADDR),
amount: amountIn,
maxAmount: amountIn
token: address(WETH_ADDR), amount: amountIn, maxAmount: amountIn
}),
outputs: outputs,
sig: "",
@@ -132,7 +130,8 @@ contract UniswapXFillerTest is Test, TychoRouterTestSetup {
vm.startPrank(address(0xD213e6F6dCB2DBaC03FA28b893F6dA1BD822e852));
// Approve Permit2
IERC20(DAI_ADDR).approve(
IERC20(DAI_ADDR)
.approve(
address(0x000000000022D473030F116dDEE9F6B43aC78BA3), amountIn
);
vm.stopPrank();

View File

@@ -25,7 +25,7 @@ use crate::encoding::{
/// Tycho is only responsible for generating the internal swap plan. **The user must encode the
/// outer function call arguments themselves** and verify that they enforce correct and secure
/// behavior.
pub trait TychoEncoder {
pub trait TychoEncoder: Send + Sync {
/// Encodes a list of [`Solution`]s into [`EncodedSolution`]s, which include the function
/// signature and internal swap call data.
///