From 0125118d226aa4545df8d03aa9ebea27ccbb94a9 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Mon, 5 May 2025 10:06:10 +0100 Subject: [PATCH 01/10] chore: Remove unused self from MaverickV2Executor --- don't change below this line --- ENG-4492 Took 7 minutes --- foundry/src/executors/MaverickV2Executor.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/foundry/src/executors/MaverickV2Executor.sol b/foundry/src/executors/MaverickV2Executor.sol index 0775177..caf8ff0 100644 --- a/foundry/src/executors/MaverickV2Executor.sol +++ b/foundry/src/executors/MaverickV2Executor.sol @@ -13,14 +13,12 @@ contract MaverickV2Executor is IExecutor, TokenTransfer { using SafeERC20 for IERC20; address public immutable factory; - address private immutable self; constructor(address _factory, address _permit2) TokenTransfer(_permit2) { if (_factory == address(0)) { revert MaverickV2Executor__InvalidFactory(); } factory = _factory; - self = address(this); } // slither-disable-next-line locked-ether From f14c8ee29ba67ca4cd8cfe8b00e8b907a0352f9a Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Fri, 9 May 2025 10:48:54 +0100 Subject: [PATCH 02/10] feat: Remove special handling of the Univ4 callback The problem was that the pool manager was expecting an ABI encoded result to be returned and we were not returning that (we were returning just a result) Special thanks to Max for figuring this out Took 31 minutes --- foundry/src/Dispatcher.sol | 4 +++- foundry/src/TychoRouter.sol | 21 ++------------------- foundry/src/executors/UniswapV4Executor.sol | 7 +++++-- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/foundry/src/Dispatcher.sol b/foundry/src/Dispatcher.sol index 1bb6209..dcbd1b4 100644 --- a/foundry/src/Dispatcher.sol +++ b/foundry/src/Dispatcher.sol @@ -127,7 +127,9 @@ contract Dispatcher { 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)); return decodedResult; } diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index 4d9d7b7..c237d64 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -657,13 +657,8 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { /** * @dev We use the fallback function to allow flexibility on callback. */ - fallback() external { - bytes memory result = _callHandleCallbackOnExecutor(msg.data); - // slither-disable-next-line assembly - assembly ("memory-safe") { - // Propagate the result - return(add(result, 32), mload(result)) - } + fallback(bytes calldata data) external returns (bytes memory) { + return _callHandleCallbackOnExecutor(data); } /** @@ -778,18 +773,6 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { 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. */ diff --git a/foundry/src/executors/UniswapV4Executor.sol b/foundry/src/executors/UniswapV4Executor.sol index f594adc..9b878ad 100644 --- a/foundry/src/executors/UniswapV4Executor.sol +++ b/foundry/src/executors/UniswapV4Executor.sol @@ -184,8 +184,11 @@ contract UniswapV4Executor is external returns (bytes memory) { - verifyCallback(data); - return _unlockCallback(data); + bytes calldata stripped = data[68:]; + 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 {} From 444fc2d7b01758fcbf1b0fb2fe72a73be0d16bac Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Mon, 12 May 2025 10:48:07 +0100 Subject: [PATCH 03/10] docs: Add comment on receive() in CurveExecutor Took 9 minutes --- foundry/src/executors/CurveExecutor.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/foundry/src/executors/CurveExecutor.sol b/foundry/src/executors/CurveExecutor.sol index ef2af90..e10a213 100644 --- a/foundry/src/executors/CurveExecutor.sol +++ b/foundry/src/executors/CurveExecutor.sol @@ -149,6 +149,11 @@ contract CurveExecutor is IExecutor, TokenTransfer { 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 { require(msg.sender.code.length != 0); } From 37e9d2b7120c7c02f60a19c6cd4f4ca0afa3f37b Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 13 May 2025 14:52:05 +0100 Subject: [PATCH 04/10] ci: fix cargo clippy command in CI Took 7 minutes Took 4 minutes --- .github/workflows/tests-and-lints-template.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests-and-lints-template.yaml b/.github/workflows/tests-and-lints-template.yaml index 2c9ffcc..64665c4 100644 --- a/.github/workflows/tests-and-lints-template.yaml +++ b/.github/workflows/tests-and-lints-template.yaml @@ -111,7 +111,7 @@ jobs: with: cache-on-failure: true - - run: cargo clippy --workspace --lib --all-targets --all-features -- -D clippy::dbg-macro + - run: cargo +nightly clippy --workspace --lib --all-targets --all-features -- -D clippy::dbg-macro env: RUSTFLAGS: -Dwarnings From 67eba8d7d2bcfd5cad2e7aa4d0c82cf09f48442b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 14 May 2025 07:54:05 +0000 Subject: [PATCH 05/10] chore(release): 0.89.0 [skip ci] ## [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)) --- CHANGELOG.md | 7 +++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4509807..62ef86d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [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) diff --git a/Cargo.lock b/Cargo.lock index dac751c..83286ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4469,7 +4469,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.88.0" +version = "0.89.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 30ec15a..5c51272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.88.0" +version = "0.89.0" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution" From 70230bf05f4bdfdd54f62799b72bd998d351983e Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 13 May 2025 11:41:11 +0100 Subject: [PATCH 06/10] feat: Verify the amount out was received correctly for arbitrage swaps Took 25 minutes Took 3 minutes Took 23 seconds --- foundry/src/TychoRouter.sol | 75 +++++++++++++++++++++++-------------- 1 file changed, 47 insertions(+), 28 deletions(-) diff --git a/foundry/src/TychoRouter.sol b/foundry/src/TychoRouter.sol index c237d64..5c41e9f 100644 --- a/foundry/src/TychoRouter.sol +++ b/foundry/src/TychoRouter.sol @@ -436,15 +436,14 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { Address.sendValue(payable(receiver), amountOut); } - if (tokenIn != tokenOut) { - uint256 currentBalanceTokenOut = _balanceOf(tokenOut, receiver); - uint256 userAmount = currentBalanceTokenOut - initialBalanceTokenOut; - if (userAmount != amountOut) { - revert TychoRouter__AmountOutNotFullyReceived( - userAmount, amountOut - ); - } - } + _verifyAmountOutWasReceived( + tokenIn, + tokenOut, + initialBalanceTokenOut, + amountOut, + receiver, + amountIn + ); } /** @@ -493,15 +492,14 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { Address.sendValue(payable(receiver), amountOut); } - if (tokenIn != tokenOut) { - uint256 currentBalanceTokenOut = _balanceOf(tokenOut, receiver); - uint256 userAmount = currentBalanceTokenOut - initialBalanceTokenOut; - if (userAmount != amountOut) { - revert TychoRouter__AmountOutNotFullyReceived( - userAmount, amountOut - ); - } - } + _verifyAmountOutWasReceived( + tokenIn, + tokenOut, + initialBalanceTokenOut, + amountOut, + receiver, + amountIn + ); } /** @@ -546,16 +544,14 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { _unwrapETH(amountOut); Address.sendValue(payable(receiver), amountOut); } - - if (tokenIn != tokenOut) { - uint256 currentBalanceTokenOut = _balanceOf(tokenOut, receiver); - uint256 userAmount = currentBalanceTokenOut - initialBalanceTokenOut; - if (userAmount != amountOut) { - revert TychoRouter__AmountOutNotFullyReceived( - userAmount, amountOut - ); - } - } + _verifyAmountOutWasReceived( + tokenIn, + tokenOut, + initialBalanceTokenOut, + amountOut, + receiver, + amountIn + ); } /** @@ -784,4 +780,27 @@ contract TychoRouter is AccessControl, Dispatcher, Pausable, ReentrancyGuard { return 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); + } + } } From 65bd0d07499b79c261efdb00debb19487d6af543 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Tue, 13 May 2025 12:31:44 +0100 Subject: [PATCH 07/10] feat: Explicitly handle the TransferType.NONE case Took 8 minutes --- foundry/src/executors/TokenTransfer.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol index b5f8629..05e296a 100644 --- a/foundry/src/executors/TokenTransfer.sol +++ b/foundry/src/executors/TokenTransfer.sol @@ -45,7 +45,9 @@ contract TokenTransfer { uint256 amount, TransferType transferType ) internal { - if (transferType == TransferType.TRANSFER_TO_PROTOCOL) { + if (transferType == TransferType.NONE) { + return; + } else if (transferType == TransferType.TRANSFER_TO_PROTOCOL) { if (tokenIn == address(0)) { payable(receiver).transfer(amount); } else { From b0c254add44e56d10f203eae30301c861fd9d8ff Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Wed, 14 May 2025 09:02:55 +0100 Subject: [PATCH 08/10] fix: Revert if the TransferType is not valid Took 8 minutes --- foundry/src/executors/TokenTransfer.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/foundry/src/executors/TokenTransfer.sol b/foundry/src/executors/TokenTransfer.sol index 05e296a..6eac1c0 100644 --- a/foundry/src/executors/TokenTransfer.sol +++ b/foundry/src/executors/TokenTransfer.sol @@ -6,6 +6,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@permit2/src/interfaces/IAllowanceTransfer.sol"; error TokenTransfer__AddressZero(); +error TokenTransfer__InvalidTransferType(); contract TokenTransfer { using SafeERC20 for IERC20; @@ -67,6 +68,8 @@ contract TokenTransfer { permit2.transferFrom( sender, address(this), uint160(amount), tokenIn ); + } else { + revert TokenTransfer__InvalidTransferType(); } } } From 2e71a3e0ff0327e35bcc77811dd68bdbf0a2ffb4 Mon Sep 17 00:00:00 2001 From: Diana Carvalho Date: Thu, 15 May 2025 15:44:41 +0100 Subject: [PATCH 09/10] ci: fix lint ci Took 1 minute --- .../workflows/tests-and-lints-template.yaml | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/.github/workflows/tests-and-lints-template.yaml b/.github/workflows/tests-and-lints-template.yaml index 64665c4..6f85b08 100644 --- a/.github/workflows/tests-and-lints-template.yaml +++ b/.github/workflows/tests-and-lints-template.yaml @@ -59,9 +59,12 @@ jobs: git config --global url."https://x-access-token:${{ steps.generate-token.outputs.token }}@github.com".insteadOf ssh://github.com - name: Setup toolchain - uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 + id: toolchain + uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.toolchain }} + toolchain: nightly + components: rustfmt, clippy + override: true - name: Setup Rust Cache uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 @@ -100,28 +103,20 @@ jobs: 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 - - name: Setup clippy toolchain - stable - uses: dtolnay/rust-toolchain@888c2e1ea69ab0d4330cbf0af1ecc7b68f368cc1 + - name: Setup toolchain + id: toolchain + uses: actions-rs/toolchain@v1 with: - toolchain: stable - components: clippy - + toolchain: nightly + components: rustfmt, clippy + override: true - name: Setup Rust Cache - uses: Swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 + uses: Swatinem/rust-cache@v2 with: cache-on-failure: true - - run: cargo +nightly clippy --workspace --lib --all-targets --all-features -- -D clippy::dbg-macro - env: - RUSTFLAGS: -Dwarnings + - name: Clippy + run: cargo clippy --workspace --all-targets --all-features - - run: cargo check --no-default-features - env: - RUSTFLAGS: -Dwarnings - - - name: Setup rustfmt toolchain - nightly - uses: dtolnay/rust-toolchain@a02741459ec5e501b9843ed30b535ca0a0376ae4 - with: - components: rustfmt - - - run: cargo +nightly fmt --all --check + - name: Rustfmt + run: cargo fmt --all --check From 7f7084ca7a0e7b315ef5ccea7f262cb67044a5c8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 15 May 2025 14:51:47 +0000 Subject: [PATCH 10/10] chore(release): 0.90.0 [skip ci] ## [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)) --- CHANGELOG.md | 13 +++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ef86d..38cf75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## [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) diff --git a/Cargo.lock b/Cargo.lock index 83286ca..34133d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4469,7 +4469,7 @@ dependencies = [ [[package]] name = "tycho-execution" -version = "0.89.0" +version = "0.90.0" dependencies = [ "alloy", "alloy-primitives", diff --git a/Cargo.toml b/Cargo.toml index 5c51272..9cc28e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "tycho-execution" -version = "0.89.0" +version = "0.90.0" edition = "2021" description = "Provides tools for encoding and executing swaps against Tycho router and protocol executors." repository = "https://github.com/propeller-heads/tycho-execution"