Merge branch 'main' into router/dc/ENG-4454-remove-executor-script

This commit is contained in:
dianacarvalho1
2025-05-15 16:18:33 +01:00
committed by GitHub
10 changed files with 106 additions and 76 deletions

View File

@@ -59,9 +59,12 @@ jobs:
git config --global url."https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com".insteadOf ssh://github.com git config --global url."https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com".insteadOf ssh://github.com
- name: Setup toolchain - name: Setup toolchain
uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 id: toolchain
uses: actions-rs/toolchain@v1
with: with:
toolchain: ${{ matrix.toolchain }} toolchain: nightly
components: rustfmt, clippy
override: true
- name: Setup Rust Cache - name: Setup Rust Cache
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6
@@ -100,28 +103,20 @@ jobs:
echo "https://${{ steps.generate-token.outputs.token }}@github.com" > ~/.git-credentials echo "https://${{ steps.generate-token.outputs.token }}@github.com" > ~/.git-credentials
git config --global url."https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com".insteadOf ssh://github.com git config --global url."https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com".insteadOf ssh://github.com
- name: Setup clippy toolchain - stable - name: Setup toolchain
uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 id: toolchain
uses: actions-rs/toolchain@v1
with: with:
toolchain: stable toolchain: nightly
components: clippy components: rustfmt, clippy
override: true
- name: Setup Rust Cache - name: Setup Rust Cache
uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 uses: Swatinem/rust-cache@v2
with: with:
cache-on-failure: true cache-on-failure: true
- run: cargo clippy --workspace --lib --all-targets --all-features -- -D clippy::dbg-macro - name: Clippy
env: run: cargo clippy --workspace --all-targets --all-features
RUSTFLAGS: -Dwarnings
- run: cargo check --no-default-features - name: Rustfmt
env: run: cargo fmt --all --check
RUSTFLAGS: -Dwarnings
- name: Setup rustfmt toolchain - nightly
uses: dtolnay/rust-toolchain@a02741459ec5e501b9843ed30b535ca0a0376ae4
with:
components: rustfmt
- run: cargo +nightly fmt --all --check

View File

@@ -1,3 +1,23 @@
## [0.90.0](https://github.com/propeller-heads/tycho-execution/compare/0.89.0...0.90.0) (2025-05-15)
### Features
* Explicitly handle the TransferType.NONE case ([65bd0d0](https://github.com/propeller-heads/tycho-execution/commit/65bd0d07499b79c261efdb00debb19487d6af543))
* Verify the amount out was received correctly for arbitrage swaps ([70230bf](https://github.com/propeller-heads/tycho-execution/commit/70230bf05f4bdfdd54f62799b72bd998d351983e))
### Bug Fixes
* Revert if the TransferType is not valid ([b0c254a](https://github.com/propeller-heads/tycho-execution/commit/b0c254add44e56d10f203eae30301c861fd9d8ff))
## [0.89.0](https://github.com/propeller-heads/tycho-execution/compare/0.88.0...0.89.0) (2025-05-14)
### Features
* Remove special handling of the Univ4 callback ([f14c8ee](https://github.com/propeller-heads/tycho-execution/commit/f14c8ee29ba67ca4cd8cfe8b00e8b907a0352f9a))
## [0.88.0](https://github.com/propeller-heads/tycho-execution/compare/0.87.0...0.88.0) (2025-05-06) ## [0.88.0](https://github.com/propeller-heads/tycho-execution/compare/0.87.0...0.88.0) (2025-05-06)

2
Cargo.lock generated
View File

@@ -4469,7 +4469,7 @@ dependencies = [
[[package]] [[package]]
name = "tycho-execution" name = "tycho-execution"
version = "0.88.0" version = "0.90.0"
dependencies = [ dependencies = [
"alloy", "alloy",
"alloy-primitives", "alloy-primitives",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "tycho-execution" name = "tycho-execution"
version = "0.88.0" version = "0.90.0"
edition = "2021" edition = "2021"
description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors."
repository = "https://github.com/propeller-heads/tycho-execution" repository = "https://github.com/propeller-heads/tycho-execution"

View File

@@ -127,7 +127,9 @@ contract Dispatcher {
tstore(_CURRENTLY_SWAPPING_EXECUTOR_SLOT, 0) tstore(_CURRENTLY_SWAPPING_EXECUTOR_SLOT, 0)
} }
// this is necessary because the delegatecall will prepend extra bytes we don't want like the length and prefix // The final callback result should not be ABI encoded. That is why we are decoding here.
// ABI encoding is very gas expensive and we want to avoid it if possible.
// The result from `handleCallback` is always ABI encoded.
bytes memory decodedResult = abi.decode(result, (bytes)); bytes memory decodedResult = abi.decode(result, (bytes));
return decodedResult; return decodedResult;
} }

View File

@@ -436,15 +436,14 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
Address.sendValue(payable(receiver), amountOut); Address.sendValue(payable(receiver), amountOut);
} }
if (tokenIn != tokenOut) { _verifyAmountOutWasReceived(
uint256 currentBalanceTokenOut = _balanceOf(tokenOut, receiver); tokenIn,
uint256 userAmount = currentBalanceTokenOut - initialBalanceTokenOut; tokenOut,
if (userAmount != amountOut) { initialBalanceTokenOut,
revert TychoRouter__AmountOutNotFullyReceived( amountOut,
userAmount, amountOut receiver,
); amountIn
} );
}
} }
/** /**
@@ -493,15 +492,14 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
Address.sendValue(payable(receiver), amountOut); Address.sendValue(payable(receiver), amountOut);
} }
if (tokenIn != tokenOut) { _verifyAmountOutWasReceived(
uint256 currentBalanceTokenOut = _balanceOf(tokenOut, receiver); tokenIn,
uint256 userAmount = currentBalanceTokenOut - initialBalanceTokenOut; tokenOut,
if (userAmount != amountOut) { initialBalanceTokenOut,
revert TychoRouter__AmountOutNotFullyReceived( amountOut,
userAmount, amountOut receiver,
); amountIn
} );
}
} }
/** /**
@@ -546,16 +544,14 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
_unwrapETH(amountOut); _unwrapETH(amountOut);
Address.sendValue(payable(receiver), amountOut); Address.sendValue(payable(receiver), amountOut);
} }
_verifyAmountOutWasReceived(
if (tokenIn != tokenOut) { tokenIn,
uint256 currentBalanceTokenOut = _balanceOf(tokenOut, receiver); tokenOut,
uint256 userAmount = currentBalanceTokenOut - initialBalanceTokenOut; initialBalanceTokenOut,
if (userAmount != amountOut) { amountOut,
revert TychoRouter__AmountOutNotFullyReceived( receiver,
userAmount, amountOut amountIn
); );
}
}
} }
/** /**
@@ -657,13 +653,8 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
/** /**
* @dev We use the fallback function to allow flexibility on callback. * @dev We use the fallback function to allow flexibility on callback.
*/ */
fallback() external { fallback(bytes calldata data) external returns (bytes memory) {
bytes memory result = _callHandleCallbackOnExecutor(msg.data); return _callHandleCallbackOnExecutor(data);
// slither-disable-next-line assembly
assembly ("memory-safe") {
// Propagate the result
return(add(result, 32), mload(result))
}
} }
/** /**
@@ -778,18 +769,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
require(msg.sender.code.length != 0); require(msg.sender.code.length != 0);
} }
/**
* @dev Called by UniswapV4 pool manager after achieving unlock state.
*/
function unlockCallback(bytes calldata data)
external
returns (bytes memory)
{
if (data.length < 24) revert TychoRouter__InvalidDataLength();
bytes memory result = _callHandleCallbackOnExecutor(data);
return result;
}
/** /**
* @dev Gets balance of a token for a given address. Supports both native ETH and ERC20 tokens. * @dev Gets balance of a token for a given address. Supports both native ETH and ERC20 tokens.
*/ */
@@ -801,4 +780,27 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard {
return return
token == address(0) ? owner.balance : IERC20(token).balanceOf(owner); token == address(0) ? owner.balance : IERC20(token).balanceOf(owner);
} }
/**
* @dev Verifies that the expected amount of output tokens was received by the receiver.
* It also handles the case of arbitrage swaps where the input and output tokens are the same.
*/
function _verifyAmountOutWasReceived(
address tokenIn,
address tokenOut,
uint256 initialBalanceTokenOut,
uint256 amountOut,
address receiver,
uint256 amountIn
) internal view {
uint256 currentBalanceTokenOut = _balanceOf(tokenOut, receiver);
if (tokenIn == tokenOut) {
// If it is an arbitrage, we need to remove the amountIn from the initial balance to get a correct userAmount
initialBalanceTokenOut -= amountIn;
}
uint256 userAmount = currentBalanceTokenOut - initialBalanceTokenOut;
if (userAmount != amountOut) {
revert TychoRouter__AmountOutNotFullyReceived(userAmount, amountOut);
}
}
} }

View File

@@ -149,6 +149,11 @@ contract CurveExecutor is IExecutor, TokenTransfer {
receiver = address(bytes20(data[65:85])); receiver = address(bytes20(data[65:85]));
} }
/**
* @dev Even though this contract is mostly called through delegatecall, we still want to be able to receive ETH.
* This is needed when using the executor directly and it makes testing easier.
* There are some curve pools that take ETH directly.
*/
receive() external payable { receive() external payable {
require(msg.sender.code.length != 0); require(msg.sender.code.length != 0);
} }

View File

@@ -13,14 +13,12 @@ contract MaverickV2Executor is IExecutor, TokenTransfer {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
address public immutable factory; address public immutable factory;
address private immutable self;
constructor(address _factory, address _permit2) TokenTransfer(_permit2) { constructor(address _factory, address _permit2) TokenTransfer(_permit2) {
if (_factory == address(0)) { if (_factory == address(0)) {
revert MaverickV2Executor__InvalidFactory(); revert MaverickV2Executor__InvalidFactory();
} }
factory = _factory; factory = _factory;
self = address(this);
} }
// slither-disable-next-line locked-ether // slither-disable-next-line locked-ether

View File

@@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@permit2/src/interfaces/IAllowanceTransfer.sol"; import "@permit2/src/interfaces/IAllowanceTransfer.sol";
error TokenTransfer__AddressZero(); error TokenTransfer__AddressZero();
error TokenTransfer__InvalidTransferType();
contract TokenTransfer { contract TokenTransfer {
using SafeERC20 for IERC20; using SafeERC20 for IERC20;
@@ -45,7 +46,9 @@ contract TokenTransfer {
uint256 amount, uint256 amount,
TransferType transferType TransferType transferType
) internal { ) internal {
if (transferType == TransferType.TRANSFER_TO_PROTOCOL) { if (transferType == TransferType.NONE) {
return;
} else if (transferType == TransferType.TRANSFER_TO_PROTOCOL) {
if (tokenIn == address(0)) { if (tokenIn == address(0)) {
payable(receiver).transfer(amount); payable(receiver).transfer(amount);
} else { } else {
@@ -65,6 +68,8 @@ contract TokenTransfer {
permit2.transferFrom( permit2.transferFrom(
sender, address(this), uint160(amount), tokenIn sender, address(this), uint160(amount), tokenIn
); );
} else {
revert TokenTransfer__InvalidTransferType();
} }
} }
} }

View File

@@ -184,8 +184,11 @@ contract UniswapV4Executor is
external external
returns (bytes memory) returns (bytes memory)
{ {
verifyCallback(data); bytes calldata stripped = data[68:];
return _unlockCallback(data); verifyCallback(stripped);
// Our general callback logic returns a not ABI encoded result.
// However, the pool manager expects the result to be ABI encoded. That is why we need to encode it here again.
return abi.encode(_unlockCallback(stripped));
} }
function verifyCallback(bytes calldata) public view poolManagerOnly {} function verifyCallback(bytes calldata) public view poolManagerOnly {}