Merge branch 'refs/heads/main' into feat/bebop-rfq-encoder-and-executor
# Conflicts: # foundry/test/TychoRouterProtocolIntegration.t.sol # foundry/test/TychoRouterTestSetup.sol # foundry/test/assets/calldata.txt # foundry/test/protocols/BebopExecutor.t.sol # src/encoding/evm/tycho_encoders.rs Took 4 minutes
This commit is contained in:
@@ -72,7 +72,9 @@ jobs:
|
|||||||
cache-on-failure: true
|
cache-on-failure: true
|
||||||
|
|
||||||
- name: Install latest nextest release
|
- name: Install latest nextest release
|
||||||
uses: taiki-e/install-action@b239071b2aedda3db20530301c2d88cd538e90d3
|
uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: nextest@0.9.98
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo nextest run --workspace --lib --all-targets && cargo test --doc
|
run: cargo nextest run --workspace --lib --all-targets && cargo test --doc
|
||||||
|
|||||||
37
CHANGELOG.md
37
CHANGELOG.md
@@ -1,3 +1,40 @@
|
|||||||
|
## [0.101.3](https://github.com/propeller-heads/tycho-execution/compare/0.101.2...0.101.3) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Add optimized_transfers_integration_tests.rs ([df63b87](https://github.com/propeller-heads/tycho-execution/commit/df63b87569df93bc773cd1430ae4a1e673cff659))
|
||||||
|
|
||||||
|
## [0.101.2](https://github.com/propeller-heads/tycho-execution/compare/0.101.1...0.101.2) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* After rebase fixes ([1d263f8](https://github.com/propeller-heads/tycho-execution/commit/1d263f8b4c76f11febaa8d32a1c8aca46734bc5e))
|
||||||
|
|
||||||
|
## [0.101.1](https://github.com/propeller-heads/tycho-execution/compare/0.101.0...0.101.1) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Exclude foundry files from the rust crate ([fa13f09](https://github.com/propeller-heads/tycho-execution/commit/fa13f09d3e922373eacfb15b07e79fdf09759e1a))
|
||||||
|
* Remove unnecessary clones from encoding ([e704151](https://github.com/propeller-heads/tycho-execution/commit/e704151404917715d1f871be2c3f13ef03d376c2))
|
||||||
|
|
||||||
|
## [0.101.0](https://github.com/propeller-heads/tycho-execution/compare/0.100.1...0.101.0) (2025-06-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **curve:** Support passing eth/weth instead of weth/eth in encoding ([525c16a](https://github.com/propeller-heads/tycho-execution/commit/525c16a117393f205a3b1bf3e9677c702415463f))
|
||||||
|
|
||||||
|
## [0.100.1](https://github.com/propeller-heads/tycho-execution/compare/0.100.0...0.100.1) (2025-06-13)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Correct misleading error message ([a6cf215](https://github.com/propeller-heads/tycho-execution/commit/a6cf2159d0811e7bbc01dcf61537a797c7934a1b))
|
||||||
|
* When choosing strategy, check if the grouped solution has any split ([9599425](https://github.com/propeller-heads/tycho-execution/commit/95994250b1516285372f94aa814177b5a250d014))
|
||||||
|
|
||||||
## [0.100.0](https://github.com/propeller-heads/tycho-execution/compare/0.99.0...0.100.0) (2025-06-06)
|
## [0.100.0](https://github.com/propeller-heads/tycho-execution/compare/0.99.0...0.100.0) (2025-06-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -4658,7 +4658,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tycho-execution"
|
name = "tycho-execution"
|
||||||
version = "0.100.0"
|
version = "0.101.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy",
|
"alloy",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tycho-execution"
|
name = "tycho-execution"
|
||||||
version = "0.100.0"
|
version = "0.101.3"
|
||||||
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"
|
||||||
@@ -10,6 +10,14 @@ keywords = ["propellerheads", "solver", "defi", "dex", "mev"]
|
|||||||
license = "MIT"
|
license = "MIT"
|
||||||
categories = ["finance", "cryptography::cryptocurrencies"]
|
categories = ["finance", "cryptography::cryptocurrencies"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
exclude = [
|
||||||
|
"foundry/*",
|
||||||
|
"foundry",
|
||||||
|
"tests/*",
|
||||||
|
"tests/common",
|
||||||
|
".github/*",
|
||||||
|
".gitmodules",
|
||||||
|
]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "tycho-encode"
|
name = "tycho-encode"
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
// SPDX-License-Identifier: BUSL-1.1
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import "@src/executors/UniswapV4Executor.sol";
|
|
||||||
import {TychoRouter} from "@src/TychoRouter.sol";
|
import {TychoRouter} from "@src/TychoRouter.sol";
|
||||||
import "./TychoRouterTestSetup.sol";
|
import "./TychoRouterTestSetup.sol";
|
||||||
import "./executors/UniswapV4Utils.sol";
|
|
||||||
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
|
|
||||||
|
|
||||||
contract TychoRouterTest is TychoRouterTestSetup {
|
contract TychoRouterTest is TychoRouterTestSetup {
|
||||||
bytes32 public constant EXECUTOR_SETTER_ROLE =
|
bytes32 public constant EXECUTOR_SETTER_ROLE =
|
||||||
@@ -91,16 +88,16 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function testWithdrawERC20Tokens() public {
|
function testWithdrawERC20Tokens() public {
|
||||||
vm.startPrank(BOB);
|
IERC20[] memory tokens = new IERC20[](2);
|
||||||
mintTokens(100 ether, tychoRouterAddr);
|
tokens[0] = IERC20(WETH_ADDR);
|
||||||
vm.stopPrank();
|
tokens[1] = IERC20(USDC_ADDR);
|
||||||
|
for (uint256 i = 0; i < tokens.length; i++) {
|
||||||
|
deal(address(tokens[i]), tychoRouterAddr, 100 ether);
|
||||||
|
}
|
||||||
|
|
||||||
vm.startPrank(FUND_RESCUER);
|
vm.startPrank(FUND_RESCUER);
|
||||||
IERC20[] memory tokensArray = new IERC20[](3);
|
|
||||||
tokensArray[0] = IERC20(address(tokens[0]));
|
tychoRouter.withdraw(tokens, FUND_RESCUER);
|
||||||
tokensArray[1] = IERC20(address(tokens[1]));
|
|
||||||
tokensArray[2] = IERC20(address(tokens[2]));
|
|
||||||
tychoRouter.withdraw(tokensArray, FUND_RESCUER);
|
|
||||||
|
|
||||||
// Check balances after withdrawing
|
// Check balances after withdrawing
|
||||||
for (uint256 i = 0; i < tokens.length; i++) {
|
for (uint256 i = 0; i < tokens.length; i++) {
|
||||||
@@ -113,21 +110,22 @@ contract TychoRouterTest is TychoRouterTestSetup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function testWithdrawERC20TokensFailures() public {
|
function testWithdrawERC20TokensFailures() public {
|
||||||
mintTokens(100 ether, tychoRouterAddr);
|
IERC20[] memory tokens = new IERC20[](2);
|
||||||
IERC20[] memory tokensArray = new IERC20[](3);
|
tokens[0] = IERC20(WETH_ADDR);
|
||||||
tokensArray[0] = IERC20(address(tokens[0]));
|
tokens[1] = IERC20(USDC_ADDR);
|
||||||
tokensArray[1] = IERC20(address(tokens[1]));
|
for (uint256 i = 0; i < tokens.length; i++) {
|
||||||
tokensArray[2] = IERC20(address(tokens[2]));
|
deal(address(tokens[i]), tychoRouterAddr, 100 ether);
|
||||||
|
}
|
||||||
|
|
||||||
vm.startPrank(FUND_RESCUER);
|
vm.startPrank(FUND_RESCUER);
|
||||||
vm.expectRevert(TychoRouter__AddressZero.selector);
|
vm.expectRevert(TychoRouter__AddressZero.selector);
|
||||||
tychoRouter.withdraw(tokensArray, address(0));
|
tychoRouter.withdraw(tokens, address(0));
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
|
|
||||||
// Not role FUND_RESCUER
|
// Not role FUND_RESCUER
|
||||||
vm.startPrank(BOB);
|
vm.startPrank(BOB);
|
||||||
vm.expectRevert();
|
vm.expectRevert();
|
||||||
tychoRouter.withdraw(tokensArray, FUND_RESCUER);
|
tychoRouter.withdraw(tokens, FUND_RESCUER);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,131 +2,10 @@
|
|||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import "./TychoRouterTestSetup.sol";
|
import "./TychoRouterTestSetup.sol";
|
||||||
import "./executors/UniswapV4Utils.sol";
|
import "./protocols/UniswapV4Utils.sol";
|
||||||
import "@src/executors/BebopExecutor.sol";
|
import "@src/executors/BebopExecutor.sol";
|
||||||
|
|
||||||
contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
|
contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
|
||||||
function testSingleSwapUSV4CallbackPermit2() public {
|
|
||||||
vm.startPrank(ALICE);
|
|
||||||
uint256 amountIn = 100 ether;
|
|
||||||
deal(USDE_ADDR, ALICE, amountIn);
|
|
||||||
(
|
|
||||||
IAllowanceTransfer.PermitSingle memory permitSingle,
|
|
||||||
bytes memory signature
|
|
||||||
) = handlePermit2Approval(USDE_ADDR, tychoRouterAddr, amountIn);
|
|
||||||
|
|
||||||
UniswapV4Executor.UniswapV4Pool[] memory pools =
|
|
||||||
new UniswapV4Executor.UniswapV4Pool[](1);
|
|
||||||
pools[0] = UniswapV4Executor.UniswapV4Pool({
|
|
||||||
intermediaryToken: USDT_ADDR,
|
|
||||||
fee: uint24(100),
|
|
||||||
tickSpacing: int24(1)
|
|
||||||
});
|
|
||||||
|
|
||||||
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
|
|
||||||
USDE_ADDR,
|
|
||||||
USDT_ADDR,
|
|
||||||
true,
|
|
||||||
RestrictTransferFrom.TransferType.TransferFrom,
|
|
||||||
ALICE,
|
|
||||||
pools
|
|
||||||
);
|
|
||||||
|
|
||||||
bytes memory swap =
|
|
||||||
encodeSingleSwap(address(usv4Executor), protocolData);
|
|
||||||
|
|
||||||
tychoRouter.singleSwapPermit2(
|
|
||||||
amountIn,
|
|
||||||
USDE_ADDR,
|
|
||||||
USDT_ADDR,
|
|
||||||
99943850,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
ALICE,
|
|
||||||
permitSingle,
|
|
||||||
signature,
|
|
||||||
swap
|
|
||||||
);
|
|
||||||
|
|
||||||
assertEq(IERC20(USDT_ADDR).balanceOf(ALICE), 99963618);
|
|
||||||
vm.stopPrank();
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSplitSwapMultipleUSV4Callback() public {
|
|
||||||
// This test has two uniswap v4 hops that will be executed inside of the V4 pool manager
|
|
||||||
// USDE -> USDT -> WBTC
|
|
||||||
uint256 amountIn = 100 ether;
|
|
||||||
deal(USDE_ADDR, ALICE, amountIn);
|
|
||||||
|
|
||||||
UniswapV4Executor.UniswapV4Pool[] memory pools =
|
|
||||||
new UniswapV4Executor.UniswapV4Pool[](2);
|
|
||||||
pools[0] = UniswapV4Executor.UniswapV4Pool({
|
|
||||||
intermediaryToken: USDT_ADDR,
|
|
||||||
fee: uint24(100),
|
|
||||||
tickSpacing: int24(1)
|
|
||||||
});
|
|
||||||
pools[1] = UniswapV4Executor.UniswapV4Pool({
|
|
||||||
intermediaryToken: WBTC_ADDR,
|
|
||||||
fee: uint24(3000),
|
|
||||||
tickSpacing: int24(60)
|
|
||||||
});
|
|
||||||
|
|
||||||
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
|
|
||||||
USDE_ADDR,
|
|
||||||
WBTC_ADDR,
|
|
||||||
true,
|
|
||||||
RestrictTransferFrom.TransferType.TransferFrom,
|
|
||||||
ALICE,
|
|
||||||
pools
|
|
||||||
);
|
|
||||||
|
|
||||||
bytes memory swap =
|
|
||||||
encodeSingleSwap(address(usv4Executor), protocolData);
|
|
||||||
|
|
||||||
vm.startPrank(ALICE);
|
|
||||||
IERC20(USDE_ADDR).approve(tychoRouterAddr, amountIn);
|
|
||||||
tychoRouter.singleSwap(
|
|
||||||
amountIn,
|
|
||||||
USDE_ADDR,
|
|
||||||
WBTC_ADDR,
|
|
||||||
118280,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
ALICE,
|
|
||||||
true,
|
|
||||||
swap
|
|
||||||
);
|
|
||||||
|
|
||||||
assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSingleUSV4IntegrationGroupedSwap() public {
|
|
||||||
// Test created with calldata from our router encoder.
|
|
||||||
|
|
||||||
// Performs a single swap from USDC to PEPE though ETH using two
|
|
||||||
// consecutive USV4 pools. It's a single swap because it is a consecutive grouped swaps
|
|
||||||
//
|
|
||||||
// USDC ──(USV4)──> ETH ───(USV4)──> PEPE
|
|
||||||
//
|
|
||||||
deal(USDC_ADDR, ALICE, 1 ether);
|
|
||||||
uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE);
|
|
||||||
|
|
||||||
// Approve permit2
|
|
||||||
vm.startPrank(ALICE);
|
|
||||||
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
|
||||||
bytes memory callData = loadCallDataFromFile(
|
|
||||||
"test_single_encoding_strategy_usv4_grouped_swap"
|
|
||||||
);
|
|
||||||
(bool success,) = tychoRouterAddr.call(callData);
|
|
||||||
|
|
||||||
vm.stopPrank();
|
|
||||||
|
|
||||||
uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE);
|
|
||||||
|
|
||||||
assertTrue(success, "Call Failed");
|
|
||||||
assertEq(balanceAfter - balanceBefore, 123172000092711286554274694);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testMultiProtocolIntegration() public {
|
function testMultiProtocolIntegration() public {
|
||||||
// Test created with calldata from our router encoder.
|
// Test created with calldata from our router encoder.
|
||||||
//
|
//
|
||||||
@@ -171,130 +50,6 @@ contract TychoRouterTestProtocolIntegration is TychoRouterTestSetup {
|
|||||||
assertEq(balanceAfter - balanceBefore, 235610487387677804636755778);
|
assertEq(balanceAfter - balanceBefore, 235610487387677804636755778);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testSingleUSV4IntegrationOutputETH() public {
|
|
||||||
// Test created with calldata from our router encoder.
|
|
||||||
|
|
||||||
// Performs a single swap from USDC to ETH without wrapping or unwrapping
|
|
||||||
//
|
|
||||||
// USDC ───(USV4)──> ETH
|
|
||||||
//
|
|
||||||
deal(USDC_ADDR, ALICE, 3000_000000);
|
|
||||||
uint256 balanceBefore = ALICE.balance;
|
|
||||||
|
|
||||||
// Approve permit2
|
|
||||||
vm.startPrank(ALICE);
|
|
||||||
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
|
||||||
|
|
||||||
bytes memory callData =
|
|
||||||
loadCallDataFromFile("test_single_encoding_strategy_usv4_eth_out");
|
|
||||||
(bool success,) = tychoRouterAddr.call(callData);
|
|
||||||
|
|
||||||
vm.stopPrank();
|
|
||||||
|
|
||||||
uint256 balanceAfter = ALICE.balance;
|
|
||||||
|
|
||||||
assertTrue(success, "Call Failed");
|
|
||||||
console.logUint(balanceAfter - balanceBefore);
|
|
||||||
assertEq(balanceAfter - balanceBefore, 1474406268748155809);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSingleMaverickIntegration() public {
|
|
||||||
deal(GHO_ADDR, ALICE, 1 ether);
|
|
||||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
|
||||||
|
|
||||||
vm.startPrank(ALICE);
|
|
||||||
IERC20(GHO_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
|
||||||
|
|
||||||
bytes memory callData =
|
|
||||||
loadCallDataFromFile("test_single_encoding_strategy_maverick");
|
|
||||||
(bool success,) = tychoRouterAddr.call(callData);
|
|
||||||
|
|
||||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
|
||||||
|
|
||||||
assertTrue(success, "Call Failed");
|
|
||||||
assertGe(balanceAfter - balanceBefore, 999725);
|
|
||||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSingleEkuboIntegration() public {
|
|
||||||
vm.stopPrank();
|
|
||||||
|
|
||||||
deal(ALICE, 1 ether);
|
|
||||||
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
|
||||||
|
|
||||||
// Approve permit2
|
|
||||||
vm.startPrank(ALICE);
|
|
||||||
bytes memory callData =
|
|
||||||
loadCallDataFromFile("test_single_encoding_strategy_ekubo");
|
|
||||||
(bool success,) = tychoRouterAddr.call{value: 1 ether}(callData);
|
|
||||||
|
|
||||||
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
|
||||||
|
|
||||||
assertTrue(success, "Call Failed");
|
|
||||||
assertGe(balanceAfter - balanceBefore, 26173932);
|
|
||||||
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSingleCurveIntegration() public {
|
|
||||||
deal(UWU_ADDR, ALICE, 1 ether);
|
|
||||||
|
|
||||||
vm.startPrank(ALICE);
|
|
||||||
IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
|
||||||
bytes memory callData =
|
|
||||||
loadCallDataFromFile("test_single_encoding_strategy_curve");
|
|
||||||
(bool success,) = tychoRouterAddr.call(callData);
|
|
||||||
|
|
||||||
assertTrue(success, "Call Failed");
|
|
||||||
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 2877855391767);
|
|
||||||
|
|
||||||
vm.stopPrank();
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSingleSwapUSV3Permit2() public {
|
|
||||||
// Trade 1 WETH for DAI with 1 swap on Uniswap V3 using Permit2
|
|
||||||
// Tests entire USV3 flow including callback
|
|
||||||
// 1 WETH -> DAI
|
|
||||||
// (USV3)
|
|
||||||
vm.startPrank(ALICE);
|
|
||||||
uint256 amountIn = 10 ** 18;
|
|
||||||
deal(WETH_ADDR, ALICE, amountIn);
|
|
||||||
(
|
|
||||||
IAllowanceTransfer.PermitSingle memory permitSingle,
|
|
||||||
bytes memory signature
|
|
||||||
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
|
|
||||||
|
|
||||||
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
|
|
||||||
bool zeroForOne = false;
|
|
||||||
bytes memory protocolData = encodeUniswapV3Swap(
|
|
||||||
WETH_ADDR,
|
|
||||||
DAI_ADDR,
|
|
||||||
ALICE,
|
|
||||||
DAI_WETH_USV3,
|
|
||||||
zeroForOne,
|
|
||||||
RestrictTransferFrom.TransferType.TransferFrom
|
|
||||||
);
|
|
||||||
bytes memory swap =
|
|
||||||
encodeSingleSwap(address(usv3Executor), protocolData);
|
|
||||||
|
|
||||||
tychoRouter.singleSwapPermit2(
|
|
||||||
amountIn,
|
|
||||||
WETH_ADDR,
|
|
||||||
DAI_ADDR,
|
|
||||||
expAmountOut - 1,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
ALICE,
|
|
||||||
permitSingle,
|
|
||||||
signature,
|
|
||||||
swap
|
|
||||||
);
|
|
||||||
|
|
||||||
uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(ALICE);
|
|
||||||
assertGe(finalBalance, expAmountOut);
|
|
||||||
|
|
||||||
vm.stopPrank();
|
|
||||||
}
|
|
||||||
|
|
||||||
function testSingleBebopIntegration() public {
|
function testSingleBebopIntegration() public {
|
||||||
// The calldata swaps 200 USDC for ONDO
|
// The calldata swaps 200 USDC for ONDO
|
||||||
// The receiver in the order is 0xc5564C13A157E6240659fb81882A28091add8670
|
// The receiver in the order is 0xc5564C13A157E6240659fb81882A28091add8670
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ pragma solidity ^0.8.26;
|
|||||||
import "@src/executors/UniswapV4Executor.sol";
|
import "@src/executors/UniswapV4Executor.sol";
|
||||||
import {TychoRouter} from "@src/TychoRouter.sol";
|
import {TychoRouter} from "@src/TychoRouter.sol";
|
||||||
import "./TychoRouterTestSetup.sol";
|
import "./TychoRouterTestSetup.sol";
|
||||||
import "./executors/UniswapV4Utils.sol";
|
|
||||||
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
|
|
||||||
|
|
||||||
contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
|
contract TychoRouterSequentialSwapTest is TychoRouterTestSetup {
|
||||||
function _getSequentialSwaps() internal view returns (bytes[] memory) {
|
function _getSequentialSwaps() internal view returns (bytes[] memory) {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ pragma solidity ^0.8.26;
|
|||||||
import "@src/executors/UniswapV4Executor.sol";
|
import "@src/executors/UniswapV4Executor.sol";
|
||||||
import {TychoRouter} from "@src/TychoRouter.sol";
|
import {TychoRouter} from "@src/TychoRouter.sol";
|
||||||
import "./TychoRouterTestSetup.sol";
|
import "./TychoRouterTestSetup.sol";
|
||||||
import "./executors/UniswapV4Utils.sol";
|
|
||||||
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
|
|
||||||
|
|
||||||
contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
|
contract TychoRouterSingleSwapTest is TychoRouterTestSetup {
|
||||||
function testSingleSwapPermit2() public {
|
function testSingleSwapPermit2() public {
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ pragma solidity ^0.8.26;
|
|||||||
import "@src/executors/UniswapV4Executor.sol";
|
import "@src/executors/UniswapV4Executor.sol";
|
||||||
import {TychoRouter, RestrictTransferFrom} from "@src/TychoRouter.sol";
|
import {TychoRouter, RestrictTransferFrom} from "@src/TychoRouter.sol";
|
||||||
import "./TychoRouterTestSetup.sol";
|
import "./TychoRouterTestSetup.sol";
|
||||||
import "./executors/UniswapV4Utils.sol";
|
|
||||||
import {SafeCallback} from "@uniswap/v4-periphery/src/base/SafeCallback.sol";
|
|
||||||
|
|
||||||
contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
|
contract TychoRouterSplitSwapTest is TychoRouterTestSetup {
|
||||||
function _getSplitSwaps(bool transferFrom)
|
function _getSplitSwaps(bool transferFrom)
|
||||||
|
|||||||
@@ -1,23 +1,28 @@
|
|||||||
// SPDX-License-Identifier: BUSL-1.1
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import "../src/executors/BalancerV2Executor.sol";
|
// Executors
|
||||||
import "../src/executors/CurveExecutor.sol";
|
import {BalancerV2Executor} from "../src/executors/BalancerV2Executor.sol";
|
||||||
import "../src/executors/EkuboExecutor.sol";
|
import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol";
|
||||||
import "../src/executors/UniswapV2Executor.sol";
|
import {CurveExecutor} from "../src/executors/CurveExecutor.sol";
|
||||||
import "../src/executors/UniswapV3Executor.sol";
|
import {EkuboExecutor} from "../src/executors/EkuboExecutor.sol";
|
||||||
import "../src/executors/UniswapV4Executor.sol";
|
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
|
||||||
|
import {UniswapV2Executor} from "../src/executors/UniswapV2Executor.sol";
|
||||||
|
import {
|
||||||
|
UniswapV3Executor,
|
||||||
|
IUniswapV3Pool
|
||||||
|
} from "../src/executors/UniswapV3Executor.sol";
|
||||||
|
import {UniswapV4Executor} from "../src/executors/UniswapV4Executor.sol";
|
||||||
|
import {BebopExecutorHarness} from "./executors/BebopExecutor.t.sol";
|
||||||
|
|
||||||
|
// Test utilities and mocks
|
||||||
import "./Constants.sol";
|
import "./Constants.sol";
|
||||||
import "./mock/MockERC20.sol";
|
import "./TestUtils.sol";
|
||||||
|
import {Permit2TestHelper} from "./Permit2TestHelper.sol";
|
||||||
|
|
||||||
|
// Core contracts and interfaces
|
||||||
import "@src/TychoRouter.sol";
|
import "@src/TychoRouter.sol";
|
||||||
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
||||||
import {PoolManager} from "@uniswap/v4-core/src/PoolManager.sol";
|
|
||||||
import {WETH} from "../lib/permit2/lib/solmate/src/tokens/WETH.sol";
|
|
||||||
import {Permit2TestHelper} from "./Permit2TestHelper.sol";
|
|
||||||
import "./TestUtils.sol";
|
|
||||||
import {MaverickV2Executor} from "../src/executors/MaverickV2Executor.sol";
|
|
||||||
import {BalancerV3Executor} from "../src/executors/BalancerV3Executor.sol";
|
|
||||||
import {BebopExecutorHarness} from "./executors/BebopExecutor.t.sol";
|
|
||||||
|
|
||||||
contract TychoRouterExposed is TychoRouter {
|
contract TychoRouterExposed is TychoRouter {
|
||||||
constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {}
|
constructor(address _permit2, address weth) TychoRouter(_permit2, weth) {}
|
||||||
@@ -70,7 +75,6 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
|||||||
MaverickV2Executor public maverickv2Executor;
|
MaverickV2Executor public maverickv2Executor;
|
||||||
BalancerV3Executor public balancerV3Executor;
|
BalancerV3Executor public balancerV3Executor;
|
||||||
BebopExecutorHarness public bebopExecutor;
|
BebopExecutorHarness public bebopExecutor;
|
||||||
MockERC20[] tokens;
|
|
||||||
|
|
||||||
function getForkBlock() public view virtual returns (uint256) {
|
function getForkBlock() public view virtual returns (uint256) {
|
||||||
return 22082754;
|
return 22082754;
|
||||||
@@ -89,12 +93,6 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
|||||||
vm.startPrank(EXECUTOR_SETTER);
|
vm.startPrank(EXECUTOR_SETTER);
|
||||||
tychoRouter.setExecutors(executors);
|
tychoRouter.setExecutors(executors);
|
||||||
vm.stopPrank();
|
vm.stopPrank();
|
||||||
|
|
||||||
vm.startPrank(BOB);
|
|
||||||
tokens.push(new MockERC20("Token A", "A"));
|
|
||||||
tokens.push(new MockERC20("Token B", "B"));
|
|
||||||
tokens.push(new MockERC20("Token C", "C"));
|
|
||||||
vm.stopPrank();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function deployRouter() public returns (TychoRouterExposed) {
|
function deployRouter() public returns (TychoRouterExposed) {
|
||||||
@@ -152,18 +150,6 @@ contract TychoRouterTestSetup is Constants, Permit2TestHelper, TestUtils {
|
|||||||
return executors;
|
return executors;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev Mints tokens to the given address
|
|
||||||
* @param amount The amount of tokens to mint
|
|
||||||
* @param to The address to mint tokens to
|
|
||||||
*/
|
|
||||||
function mintTokens(uint256 amount, address to) internal {
|
|
||||||
for (uint256 i = 0; i < tokens.length; i++) {
|
|
||||||
// slither-disable-next-line calls-loop
|
|
||||||
tokens[i].mint(to, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function pleEncode(bytes[] memory data)
|
function pleEncode(bytes[] memory data)
|
||||||
public
|
public
|
||||||
pure
|
pure
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,18 +0,0 @@
|
|||||||
// SPDX-License-Identifier: Unlicense
|
|
||||||
pragma solidity ^0.8.26;
|
|
||||||
|
|
||||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
||||||
|
|
||||||
contract MockERC20 is ERC20 {
|
|
||||||
constructor(string memory name_, string memory symbol_)
|
|
||||||
ERC20(name_, symbol_)
|
|
||||||
{}
|
|
||||||
|
|
||||||
function mint(address to, uint256 amount) external {
|
|
||||||
_mint(to, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
function decimals() public view virtual override returns (uint8) {
|
|
||||||
return 18;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
// SPDX-License-Identifier: BUSL-1.1
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
|
import "../TychoRouterTestSetup.sol";
|
||||||
import "@src/executors/CurveExecutor.sol";
|
import "@src/executors/CurveExecutor.sol";
|
||||||
import {Test} from "../../lib/forge-std/src/Test.sol";
|
|
||||||
import {Constants} from "../Constants.sol";
|
import {Constants} from "../Constants.sol";
|
||||||
|
import {Test} from "../../lib/forge-std/src/Test.sol";
|
||||||
|
|
||||||
interface ICurvePool {
|
interface ICurvePool {
|
||||||
function coins(uint256 i) external view returns (address);
|
function coins(uint256 i) external view returns (address);
|
||||||
@@ -393,3 +394,20 @@ contract CurveExecutorTest is Test, Constants {
|
|||||||
return (coinInIndex, coinOutIndex);
|
return (coinInIndex, coinOutIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
|
||||||
|
function testSingleCurveIntegration() public {
|
||||||
|
deal(UWU_ADDR, ALICE, 1 ether);
|
||||||
|
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
IERC20(UWU_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||||
|
bytes memory callData =
|
||||||
|
loadCallDataFromFile("test_single_encoding_strategy_curve");
|
||||||
|
(bool success,) = tychoRouterAddr.call(callData);
|
||||||
|
|
||||||
|
assertTrue(success, "Call Failed");
|
||||||
|
assertEq(IERC20(WETH_ADDR).balanceOf(ALICE), 2877855391767);
|
||||||
|
|
||||||
|
vm.stopPrank();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,8 +2,9 @@
|
|||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import "../TestUtils.sol";
|
import "../TestUtils.sol";
|
||||||
import {Constants} from "../Constants.sol";
|
import "../TychoRouterTestSetup.sol";
|
||||||
import "@src/executors/EkuboExecutor.sol";
|
import "@src/executors/EkuboExecutor.sol";
|
||||||
|
import {Constants} from "../Constants.sol";
|
||||||
import {ICore} from "@ekubo/interfaces/ICore.sol";
|
import {ICore} from "@ekubo/interfaces/ICore.sol";
|
||||||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol";
|
import {NATIVE_TOKEN_ADDRESS} from "@ekubo/math/constants.sol";
|
||||||
@@ -158,3 +159,24 @@ contract EkuboExecutorTest is Constants, TestUtils {
|
|||||||
multiHopSwap(loadCallDataFromFile("test_ekubo_encode_swap_multi"));
|
multiHopSwap(loadCallDataFromFile("test_ekubo_encode_swap_multi"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
|
||||||
|
function testSingleEkuboIntegration() public {
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
deal(ALICE, 1 ether);
|
||||||
|
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
// Approve permit2
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
bytes memory callData =
|
||||||
|
loadCallDataFromFile("test_single_encoding_strategy_ekubo");
|
||||||
|
(bool success,) = tychoRouterAddr.call{value: 1 ether}(callData);
|
||||||
|
|
||||||
|
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
assertTrue(success, "Call Failed");
|
||||||
|
assertGe(balanceAfter - balanceBefore, 26173932);
|
||||||
|
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
// SPDX-License-Identifier: BUSL-1.1
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
|
import "../TestUtils.sol";
|
||||||
|
import "../TychoRouterTestSetup.sol";
|
||||||
import "@src/executors/MaverickV2Executor.sol";
|
import "@src/executors/MaverickV2Executor.sol";
|
||||||
import {Constants} from "../Constants.sol";
|
import {Constants} from "../Constants.sol";
|
||||||
import "../TestUtils.sol";
|
|
||||||
|
|
||||||
contract MaverickV2ExecutorExposed is MaverickV2Executor {
|
contract MaverickV2ExecutorExposed is MaverickV2Executor {
|
||||||
constructor(address _factory, address _permit2)
|
constructor(address _factory, address _permit2)
|
||||||
@@ -126,3 +127,23 @@ contract MaverickV2ExecutorTest is TestUtils, Constants {
|
|||||||
assertEq(balanceAfter - balanceBefore, amountOut);
|
assertEq(balanceAfter - balanceBefore, amountOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
|
||||||
|
function testSingleMaverickIntegration() public {
|
||||||
|
deal(GHO_ADDR, ALICE, 1 ether);
|
||||||
|
uint256 balanceBefore = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
IERC20(GHO_ADDR).approve(tychoRouterAddr, type(uint256).max);
|
||||||
|
|
||||||
|
bytes memory callData =
|
||||||
|
loadCallDataFromFile("test_single_encoding_strategy_maverick");
|
||||||
|
(bool success,) = tychoRouterAddr.call(callData);
|
||||||
|
|
||||||
|
uint256 balanceAfter = IERC20(USDC_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
assertTrue(success, "Call Failed");
|
||||||
|
assertGe(balanceAfter - balanceBefore, 999725);
|
||||||
|
assertEq(IERC20(WETH_ADDR).balanceOf(tychoRouterAddr), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
// SPDX-License-Identifier: BUSL-1.1
|
// SPDX-License-Identifier: BUSL-1.1
|
||||||
pragma solidity ^0.8.26;
|
pragma solidity ^0.8.26;
|
||||||
|
|
||||||
import "@src/executors/UniswapV3Executor.sol";
|
import "../TychoRouterTestSetup.sol";
|
||||||
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
|
import "@permit2/src/interfaces/IAllowanceTransfer.sol";
|
||||||
import {Test} from "../../lib/forge-std/src/Test.sol";
|
import "@src/executors/UniswapV3Executor.sol";
|
||||||
import {Constants} from "../Constants.sol";
|
import {Constants} from "../Constants.sol";
|
||||||
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
|
import {Permit2TestHelper} from "../Permit2TestHelper.sol";
|
||||||
|
import {Test} from "../../lib/forge-std/src/Test.sol";
|
||||||
|
|
||||||
contract UniswapV3ExecutorExposed is UniswapV3Executor {
|
contract UniswapV3ExecutorExposed is UniswapV3Executor {
|
||||||
constructor(address _factory, bytes32 _initCode, address _permit2)
|
constructor(address _factory, bytes32 _initCode, address _permit2)
|
||||||
@@ -43,7 +44,6 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
|
|||||||
|
|
||||||
UniswapV3ExecutorExposed uniswapV3Exposed;
|
UniswapV3ExecutorExposed uniswapV3Exposed;
|
||||||
UniswapV3ExecutorExposed pancakeV3Exposed;
|
UniswapV3ExecutorExposed pancakeV3Exposed;
|
||||||
IERC20 WETH = IERC20(WETH_ADDR);
|
|
||||||
IERC20 DAI = IERC20(DAI_ADDR);
|
IERC20 DAI = IERC20(DAI_ADDR);
|
||||||
IAllowanceTransfer permit2;
|
IAllowanceTransfer permit2;
|
||||||
|
|
||||||
@@ -211,3 +211,50 @@ contract UniswapV3ExecutorTest is Test, Constants, Permit2TestHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
|
||||||
|
function testSingleSwapUSV3Permit2() public {
|
||||||
|
// Trade 1 WETH for DAI with 1 swap on Uniswap V3 using Permit2
|
||||||
|
// Tests entire USV3 flow including callback
|
||||||
|
// 1 WETH -> DAI
|
||||||
|
// (USV3)
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
uint256 amountIn = 10 ** 18;
|
||||||
|
deal(WETH_ADDR, ALICE, amountIn);
|
||||||
|
(
|
||||||
|
IAllowanceTransfer.PermitSingle memory permitSingle,
|
||||||
|
bytes memory signature
|
||||||
|
) = handlePermit2Approval(WETH_ADDR, tychoRouterAddr, amountIn);
|
||||||
|
|
||||||
|
uint256 expAmountOut = 1205_128428842122129186; //Swap 1 WETH for 1205.12 DAI
|
||||||
|
bool zeroForOne = false;
|
||||||
|
bytes memory protocolData = encodeUniswapV3Swap(
|
||||||
|
WETH_ADDR,
|
||||||
|
DAI_ADDR,
|
||||||
|
ALICE,
|
||||||
|
DAI_WETH_USV3,
|
||||||
|
zeroForOne,
|
||||||
|
RestrictTransferFrom.TransferType.TransferFrom
|
||||||
|
);
|
||||||
|
bytes memory swap =
|
||||||
|
encodeSingleSwap(address(usv3Executor), protocolData);
|
||||||
|
|
||||||
|
tychoRouter.singleSwapPermit2(
|
||||||
|
amountIn,
|
||||||
|
WETH_ADDR,
|
||||||
|
DAI_ADDR,
|
||||||
|
expAmountOut - 1,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
ALICE,
|
||||||
|
permitSingle,
|
||||||
|
signature,
|
||||||
|
swap
|
||||||
|
);
|
||||||
|
|
||||||
|
uint256 finalBalance = IERC20(DAI_ADDR).balanceOf(ALICE);
|
||||||
|
assertGe(finalBalance, expAmountOut);
|
||||||
|
|
||||||
|
vm.stopPrank();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ pragma solidity ^0.8.26;
|
|||||||
|
|
||||||
import "../../src/executors/UniswapV4Executor.sol";
|
import "../../src/executors/UniswapV4Executor.sol";
|
||||||
import "../TestUtils.sol";
|
import "../TestUtils.sol";
|
||||||
|
import "../TychoRouterTestSetup.sol";
|
||||||
import "./UniswapV4Utils.sol";
|
import "./UniswapV4Utils.sol";
|
||||||
import "@src/executors/UniswapV4Executor.sol";
|
import "@src/executors/UniswapV4Executor.sol";
|
||||||
import {Constants} from "../Constants.sol";
|
import {Constants} from "../Constants.sol";
|
||||||
@@ -211,3 +212,175 @@ contract UniswapV4ExecutorTest is Constants, TestUtils {
|
|||||||
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
|
assertTrue(IERC20(WBTC_ADDR).balanceOf(ALICE) == amountOut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contract TychoRouterForBalancerV3Test is TychoRouterTestSetup {
|
||||||
|
function testSingleSwapUSV4CallbackPermit2() public {
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
uint256 amountIn = 100 ether;
|
||||||
|
deal(USDE_ADDR, ALICE, amountIn);
|
||||||
|
(
|
||||||
|
IAllowanceTransfer.PermitSingle memory permitSingle,
|
||||||
|
bytes memory signature
|
||||||
|
) = handlePermit2Approval(USDE_ADDR, tychoRouterAddr, amountIn);
|
||||||
|
|
||||||
|
UniswapV4Executor.UniswapV4Pool[] memory pools =
|
||||||
|
new UniswapV4Executor.UniswapV4Pool[](1);
|
||||||
|
pools[0] = UniswapV4Executor.UniswapV4Pool({
|
||||||
|
intermediaryToken: USDT_ADDR,
|
||||||
|
fee: uint24(100),
|
||||||
|
tickSpacing: int24(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
|
||||||
|
USDE_ADDR,
|
||||||
|
USDT_ADDR,
|
||||||
|
true,
|
||||||
|
RestrictTransferFrom.TransferType.TransferFrom,
|
||||||
|
ALICE,
|
||||||
|
pools
|
||||||
|
);
|
||||||
|
|
||||||
|
bytes memory swap =
|
||||||
|
encodeSingleSwap(address(usv4Executor), protocolData);
|
||||||
|
|
||||||
|
tychoRouter.singleSwapPermit2(
|
||||||
|
amountIn,
|
||||||
|
USDE_ADDR,
|
||||||
|
USDT_ADDR,
|
||||||
|
99943850,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
ALICE,
|
||||||
|
permitSingle,
|
||||||
|
signature,
|
||||||
|
swap
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEq(IERC20(USDT_ADDR).balanceOf(ALICE), 99963618);
|
||||||
|
vm.stopPrank();
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSplitSwapMultipleUSV4Callback() public {
|
||||||
|
// This test has two uniswap v4 hops that will be executed inside of the V4 pool manager
|
||||||
|
// USDE -> USDT -> WBTC
|
||||||
|
uint256 amountIn = 100 ether;
|
||||||
|
deal(USDE_ADDR, ALICE, amountIn);
|
||||||
|
|
||||||
|
UniswapV4Executor.UniswapV4Pool[] memory pools =
|
||||||
|
new UniswapV4Executor.UniswapV4Pool[](2);
|
||||||
|
pools[0] = UniswapV4Executor.UniswapV4Pool({
|
||||||
|
intermediaryToken: USDT_ADDR,
|
||||||
|
fee: uint24(100),
|
||||||
|
tickSpacing: int24(1)
|
||||||
|
});
|
||||||
|
pools[1] = UniswapV4Executor.UniswapV4Pool({
|
||||||
|
intermediaryToken: WBTC_ADDR,
|
||||||
|
fee: uint24(3000),
|
||||||
|
tickSpacing: int24(60)
|
||||||
|
});
|
||||||
|
|
||||||
|
bytes memory protocolData = UniswapV4Utils.encodeExactInput(
|
||||||
|
USDE_ADDR,
|
||||||
|
WBTC_ADDR,
|
||||||
|
true,
|
||||||
|
RestrictTransferFrom.TransferType.TransferFrom,
|
||||||
|
ALICE,
|
||||||
|
pools
|
||||||
|
);
|
||||||
|
|
||||||
|
bytes memory swap =
|
||||||
|
encodeSingleSwap(address(usv4Executor), protocolData);
|
||||||
|
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
IERC20(USDE_ADDR).approve(tychoRouterAddr, amountIn);
|
||||||
|
tychoRouter.singleSwap(
|
||||||
|
amountIn,
|
||||||
|
USDE_ADDR,
|
||||||
|
WBTC_ADDR,
|
||||||
|
118280,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
ALICE,
|
||||||
|
true,
|
||||||
|
swap
|
||||||
|
);
|
||||||
|
|
||||||
|
assertEq(IERC20(WBTC_ADDR).balanceOf(ALICE), 118281);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSingleUSV4IntegrationGroupedSwap() public {
|
||||||
|
// Test created with calldata from our router encoder.
|
||||||
|
|
||||||
|
// Performs a single swap from USDC to PEPE though ETH using two
|
||||||
|
// consecutive USV4 pools. It's a single swap because it is a consecutive grouped swaps
|
||||||
|
//
|
||||||
|
// USDC ──(USV4)──> ETH ───(USV4)──> PEPE
|
||||||
|
//
|
||||||
|
deal(USDC_ADDR, ALICE, 1 ether);
|
||||||
|
uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
// Approve permit2
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||||
|
bytes memory callData = loadCallDataFromFile(
|
||||||
|
"test_single_encoding_strategy_usv4_grouped_swap"
|
||||||
|
);
|
||||||
|
(bool success,) = tychoRouterAddr.call(callData);
|
||||||
|
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
assertTrue(success, "Call Failed");
|
||||||
|
assertEq(balanceAfter - balanceBefore, 123172000092711286554274694);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSingleUSV4IntegrationInputETH() public {
|
||||||
|
// Test created with calldata from our router encoder.
|
||||||
|
|
||||||
|
// Performs a single swap from ETH to PEPE without wrapping or unwrapping
|
||||||
|
//
|
||||||
|
// ETH ───(USV4)──> PEPE
|
||||||
|
//
|
||||||
|
deal(ALICE, 1 ether);
|
||||||
|
uint256 balanceBefore = IERC20(PEPE_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
bytes memory callData =
|
||||||
|
loadCallDataFromFile("test_single_encoding_strategy_usv4_eth_in");
|
||||||
|
(bool success,) = tychoRouterAddr.call{value: 1 ether}(callData);
|
||||||
|
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
uint256 balanceAfter = IERC20(PEPE_ADDR).balanceOf(ALICE);
|
||||||
|
|
||||||
|
assertTrue(success, "Call Failed");
|
||||||
|
assertEq(balanceAfter - balanceBefore, 235610487387677804636755778);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testSingleUSV4IntegrationOutputETH() public {
|
||||||
|
// Test created with calldata from our router encoder.
|
||||||
|
|
||||||
|
// Performs a single swap from USDC to ETH without wrapping or unwrapping
|
||||||
|
//
|
||||||
|
// USDC ───(USV4)──> ETH
|
||||||
|
//
|
||||||
|
deal(USDC_ADDR, ALICE, 3000_000000);
|
||||||
|
uint256 balanceBefore = ALICE.balance;
|
||||||
|
|
||||||
|
// Approve permit2
|
||||||
|
vm.startPrank(ALICE);
|
||||||
|
IERC20(USDC_ADDR).approve(PERMIT2_ADDRESS, type(uint256).max);
|
||||||
|
|
||||||
|
bytes memory callData =
|
||||||
|
loadCallDataFromFile("test_single_encoding_strategy_usv4_eth_out");
|
||||||
|
(bool success,) = tychoRouterAddr.call(callData);
|
||||||
|
|
||||||
|
vm.stopPrank();
|
||||||
|
|
||||||
|
uint256 balanceAfter = ALICE.balance;
|
||||||
|
|
||||||
|
assertTrue(success, "Call Failed");
|
||||||
|
console.logUint(balanceAfter - balanceBefore);
|
||||||
|
assertEq(balanceAfter - balanceBefore, 1474406268748155809);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,8 +75,8 @@ pub fn encode_tycho_router_call(
|
|||||||
chain_id: u64,
|
chain_id: u64,
|
||||||
encoded_solution: EncodedSolution,
|
encoded_solution: EncodedSolution,
|
||||||
solution: &Solution,
|
solution: &Solution,
|
||||||
user_transfer_type: UserTransferType,
|
user_transfer_type: &UserTransferType,
|
||||||
native_address: Bytes,
|
native_address: &Bytes,
|
||||||
signer: Option<PrivateKeySigner>,
|
signer: Option<PrivateKeySigner>,
|
||||||
) -> Result<Transaction, EncodingError> {
|
) -> Result<Transaction, EncodingError> {
|
||||||
let (mut unwrap, mut wrap) = (false, false);
|
let (mut unwrap, mut wrap) = (false, false);
|
||||||
@@ -137,7 +137,7 @@ pub fn encode_tycho_router_call(
|
|||||||
wrap,
|
wrap,
|
||||||
unwrap,
|
unwrap,
|
||||||
receiver,
|
receiver,
|
||||||
user_transfer_type == UserTransferType::TransferFrom,
|
user_transfer_type == &UserTransferType::TransferFrom,
|
||||||
encoded_solution.swaps,
|
encoded_solution.swaps,
|
||||||
)
|
)
|
||||||
.abi_encode()
|
.abi_encode()
|
||||||
@@ -172,7 +172,7 @@ pub fn encode_tycho_router_call(
|
|||||||
wrap,
|
wrap,
|
||||||
unwrap,
|
unwrap,
|
||||||
receiver,
|
receiver,
|
||||||
user_transfer_type == UserTransferType::TransferFrom,
|
user_transfer_type == &UserTransferType::TransferFrom,
|
||||||
encoded_solution.swaps,
|
encoded_solution.swaps,
|
||||||
)
|
)
|
||||||
.abi_encode()
|
.abi_encode()
|
||||||
@@ -209,7 +209,7 @@ pub fn encode_tycho_router_call(
|
|||||||
unwrap,
|
unwrap,
|
||||||
n_tokens,
|
n_tokens,
|
||||||
receiver,
|
receiver,
|
||||||
user_transfer_type == UserTransferType::TransferFrom,
|
user_transfer_type == &UserTransferType::TransferFrom,
|
||||||
encoded_solution.swaps,
|
encoded_solution.swaps,
|
||||||
)
|
)
|
||||||
.abi_encode()
|
.abi_encode()
|
||||||
@@ -218,7 +218,7 @@ pub fn encode_tycho_router_call(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let contract_interaction = encode_input(&encoded_solution.function_signature, method_calldata);
|
let contract_interaction = encode_input(&encoded_solution.function_signature, method_calldata);
|
||||||
let value = if solution.given_token == native_address {
|
let value = if solution.given_token == *native_address {
|
||||||
solution.given_amount.clone()
|
solution.given_amount.clone()
|
||||||
} else {
|
} else {
|
||||||
BigUint::ZERO
|
BigUint::ZERO
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ pub struct SwapGroup {
|
|||||||
///
|
///
|
||||||
/// An example where this applies is the case of USV4, which uses a PoolManager contract
|
/// An example where this applies is the case of USV4, which uses a PoolManager contract
|
||||||
/// to save token transfers on consecutive swaps.
|
/// to save token transfers on consecutive swaps.
|
||||||
pub fn group_swaps(swaps: Vec<Swap>) -> Vec<SwapGroup> {
|
pub fn group_swaps(swaps: &Vec<Swap>) -> Vec<SwapGroup> {
|
||||||
let mut grouped_swaps: Vec<SwapGroup> = Vec::new();
|
let mut grouped_swaps: Vec<SwapGroup> = Vec::new();
|
||||||
let mut current_group: Option<SwapGroup> = None;
|
let mut current_group: Option<SwapGroup> = None;
|
||||||
let mut last_swap_protocol = "".to_string();
|
let mut last_swap_protocol = "".to_string();
|
||||||
@@ -127,7 +127,7 @@ mod tests {
|
|||||||
split: 0f64,
|
split: 0f64,
|
||||||
user_data: None,
|
user_data: None,
|
||||||
};
|
};
|
||||||
let grouped_swaps = group_swaps(vec![
|
let grouped_swaps = group_swaps(&vec![
|
||||||
swap_weth_wbtc.clone(),
|
swap_weth_wbtc.clone(),
|
||||||
swap_wbtc_usdc.clone(),
|
swap_wbtc_usdc.clone(),
|
||||||
swap_usdc_dai.clone(),
|
swap_usdc_dai.clone(),
|
||||||
@@ -211,7 +211,7 @@ mod tests {
|
|||||||
split: 0f64,
|
split: 0f64,
|
||||||
user_data: None,
|
user_data: None,
|
||||||
};
|
};
|
||||||
let grouped_swaps = group_swaps(vec![
|
let grouped_swaps = group_swaps(&vec![
|
||||||
swap_wbtc_weth.clone(),
|
swap_wbtc_weth.clone(),
|
||||||
swap_weth_usdc.clone(),
|
swap_weth_usdc.clone(),
|
||||||
swap_weth_dai.clone(),
|
swap_weth_dai.clone(),
|
||||||
@@ -303,7 +303,7 @@ mod tests {
|
|||||||
user_data: None,
|
user_data: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let grouped_swaps = group_swaps(vec![
|
let grouped_swaps = group_swaps(&vec![
|
||||||
swap_weth_wbtc.clone(),
|
swap_weth_wbtc.clone(),
|
||||||
swap_wbtc_usdc.clone(),
|
swap_wbtc_usdc.clone(),
|
||||||
swap_weth_dai.clone(),
|
swap_weth_dai.clone(),
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ impl SingleSwapStrategyEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StrategyEncoder for SingleSwapStrategyEncoder {
|
impl StrategyEncoder for SingleSwapStrategyEncoder {
|
||||||
fn encode_strategy(&self, solution: Solution) -> Result<EncodedSolution, EncodingError> {
|
fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
|
||||||
let grouped_swaps = group_swaps(solution.clone().swaps);
|
let grouped_swaps = group_swaps(&solution.swaps);
|
||||||
let number_of_groups = grouped_swaps.len();
|
let number_of_groups = grouped_swaps.len();
|
||||||
if number_of_groups != 1 {
|
if number_of_groups != 1 {
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
"Executor strategy only supports exactly one swap for non-groupable protocols. Found {number_of_groups}",
|
"Single strategy only supports exactly one swap for non-groupable protocols. Found {number_of_groups}",
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,15 +91,15 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let (mut unwrap, mut wrap) = (false, false);
|
let (mut unwrap, mut wrap) = (false, false);
|
||||||
if let Some(action) = solution.native_action.clone() {
|
if let Some(action) = &solution.native_action {
|
||||||
match action {
|
match *action {
|
||||||
NativeAction::Wrap => wrap = true,
|
NativeAction::Wrap => wrap = true,
|
||||||
NativeAction::Unwrap => unwrap = true,
|
NativeAction::Unwrap => unwrap = true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let protocol = grouped_swap.protocol_system.clone();
|
let protocol = &grouped_swap.protocol_system;
|
||||||
let swap_encoder = self
|
let swap_encoder = self
|
||||||
.get_swap_encoder(&protocol)
|
.get_swap_encoder(protocol)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
EncodingError::InvalidInput(format!(
|
EncodingError::InvalidInput(format!(
|
||||||
"Swap encoder not found for protocol: {protocol}"
|
"Swap encoder not found for protocol: {protocol}"
|
||||||
@@ -111,9 +111,9 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
|||||||
|
|
||||||
let transfer = self
|
let transfer = self
|
||||||
.transfer_optimization
|
.transfer_optimization
|
||||||
.get_transfers(grouped_swap.clone(), solution.given_token.clone(), wrap, false);
|
.get_transfers(grouped_swap, &solution.given_token, wrap, false);
|
||||||
let encoding_context = EncodingContext {
|
let encoding_context = EncodingContext {
|
||||||
receiver: swap_receiver.clone(),
|
receiver: swap_receiver,
|
||||||
exact_out: solution.exact_out,
|
exact_out: solution.exact_out,
|
||||||
router_address: Some(self.router_address.clone()),
|
router_address: Some(self.router_address.clone()),
|
||||||
group_token_in: grouped_swap.token_in.clone(),
|
group_token_in: grouped_swap.token_in.clone(),
|
||||||
@@ -123,7 +123,7 @@ impl StrategyEncoder for SingleSwapStrategyEncoder {
|
|||||||
|
|
||||||
let mut grouped_protocol_data: Vec<u8> = vec![];
|
let mut grouped_protocol_data: Vec<u8> = vec![];
|
||||||
for swap in grouped_swap.swaps.iter() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
|
||||||
grouped_protocol_data.extend(protocol_data);
|
grouped_protocol_data.extend(protocol_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ impl SequentialSwapStrategyEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
||||||
fn encode_strategy(&self, solution: Solution) -> Result<EncodedSolution, EncodingError> {
|
fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
|
||||||
self.sequential_swap_validator
|
self.sequential_swap_validator
|
||||||
.validate_swap_path(
|
.validate_swap_path(
|
||||||
&solution.swaps,
|
&solution.swaps,
|
||||||
@@ -224,11 +224,11 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
|||||||
&self.wrapped_address,
|
&self.wrapped_address,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let grouped_swaps = group_swaps(solution.swaps);
|
let grouped_swaps = group_swaps(&solution.swaps);
|
||||||
|
|
||||||
let mut wrap = false;
|
let mut wrap = false;
|
||||||
if let Some(action) = solution.native_action.clone() {
|
if let Some(action) = &solution.native_action {
|
||||||
if action == NativeAction::Wrap {
|
if action == &NativeAction::Wrap {
|
||||||
wrap = true
|
wrap = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -236,9 +236,9 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
|||||||
let mut swaps = vec![];
|
let mut swaps = vec![];
|
||||||
let mut next_in_between_swap_optimization_allowed = true;
|
let mut next_in_between_swap_optimization_allowed = true;
|
||||||
for (i, grouped_swap) in grouped_swaps.iter().enumerate() {
|
for (i, grouped_swap) in grouped_swaps.iter().enumerate() {
|
||||||
let protocol = grouped_swap.protocol_system.clone();
|
let protocol = &grouped_swap.protocol_system;
|
||||||
let swap_encoder = self
|
let swap_encoder = self
|
||||||
.get_swap_encoder(&protocol)
|
.get_swap_encoder(protocol)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
EncodingError::InvalidInput(format!(
|
EncodingError::InvalidInput(format!(
|
||||||
"Swap encoder not found for protocol: {protocol}",
|
"Swap encoder not found for protocol: {protocol}",
|
||||||
@@ -249,19 +249,19 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
|||||||
let next_swap = grouped_swaps.get(i + 1);
|
let next_swap = grouped_swaps.get(i + 1);
|
||||||
let (swap_receiver, next_swap_optimization) = self
|
let (swap_receiver, next_swap_optimization) = self
|
||||||
.transfer_optimization
|
.transfer_optimization
|
||||||
.get_receiver(solution.receiver.clone(), next_swap)?;
|
.get_receiver(&solution.receiver, next_swap)?;
|
||||||
next_in_between_swap_optimization_allowed = next_swap_optimization;
|
next_in_between_swap_optimization_allowed = next_swap_optimization;
|
||||||
|
|
||||||
let transfer = self
|
let transfer = self
|
||||||
.transfer_optimization
|
.transfer_optimization
|
||||||
.get_transfers(
|
.get_transfers(
|
||||||
grouped_swap.clone(),
|
grouped_swap,
|
||||||
solution.given_token.clone(),
|
&solution.given_token,
|
||||||
wrap,
|
wrap,
|
||||||
in_between_swap_optimization_allowed,
|
in_between_swap_optimization_allowed,
|
||||||
);
|
);
|
||||||
let encoding_context = EncodingContext {
|
let encoding_context = EncodingContext {
|
||||||
receiver: swap_receiver.clone(),
|
receiver: swap_receiver,
|
||||||
exact_out: solution.exact_out,
|
exact_out: solution.exact_out,
|
||||||
router_address: Some(self.router_address.clone()),
|
router_address: Some(self.router_address.clone()),
|
||||||
group_token_in: grouped_swap.token_in.clone(),
|
group_token_in: grouped_swap.token_in.clone(),
|
||||||
@@ -271,8 +271,7 @@ impl StrategyEncoder for SequentialSwapStrategyEncoder {
|
|||||||
|
|
||||||
let mut grouped_protocol_data: Vec<u8> = vec![];
|
let mut grouped_protocol_data: Vec<u8> = vec![];
|
||||||
for swap in grouped_swap.swaps.iter() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let protocol_data =
|
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
|
||||||
swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
|
||||||
grouped_protocol_data.extend(protocol_data);
|
grouped_protocol_data.extend(protocol_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,7 +375,7 @@ impl SplitSwapStrategyEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl StrategyEncoder for SplitSwapStrategyEncoder {
|
impl StrategyEncoder for SplitSwapStrategyEncoder {
|
||||||
fn encode_strategy(&self, solution: Solution) -> Result<EncodedSolution, EncodingError> {
|
fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
|
||||||
self.split_swap_validator
|
self.split_swap_validator
|
||||||
.validate_split_percentages(&solution.swaps)?;
|
.validate_split_percentages(&solution.swaps)?;
|
||||||
self.split_swap_validator
|
self.split_swap_validator
|
||||||
@@ -391,20 +390,17 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
|
|
||||||
// The tokens array is composed of the given token, the checked token and all the
|
// The tokens array is composed of the given token, the checked token and all the
|
||||||
// intermediary tokens in between. The contract expects the tokens to be in this order.
|
// intermediary tokens in between. The contract expects the tokens to be in this order.
|
||||||
let solution_tokens: HashSet<Bytes> =
|
let solution_tokens: HashSet<&Bytes> = vec![&solution.given_token, &solution.checked_token]
|
||||||
vec![solution.given_token.clone(), solution.checked_token.clone()]
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let grouped_swaps = group_swaps(solution.swaps);
|
let grouped_swaps = group_swaps(&solution.swaps);
|
||||||
|
|
||||||
let intermediary_tokens: HashSet<Bytes> = grouped_swaps
|
let intermediary_tokens: HashSet<&Bytes> = grouped_swaps
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|grouped_swap| {
|
.flat_map(|grouped_swap| vec![&grouped_swap.token_in, &grouped_swap.token_out])
|
||||||
vec![grouped_swap.token_in.clone(), grouped_swap.token_out.clone()]
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
let mut intermediary_tokens: Vec<Bytes> = intermediary_tokens
|
let mut intermediary_tokens: Vec<&Bytes> = intermediary_tokens
|
||||||
.difference(&solution_tokens)
|
.difference(&solution_tokens)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
@@ -413,8 +409,8 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
intermediary_tokens.sort();
|
intermediary_tokens.sort();
|
||||||
|
|
||||||
let (mut unwrap, mut wrap) = (false, false);
|
let (mut unwrap, mut wrap) = (false, false);
|
||||||
if let Some(action) = solution.native_action.clone() {
|
if let Some(action) = &solution.native_action {
|
||||||
match action {
|
match *action {
|
||||||
NativeAction::Wrap => wrap = true,
|
NativeAction::Wrap => wrap = true,
|
||||||
NativeAction::Unwrap => unwrap = true,
|
NativeAction::Unwrap => unwrap = true,
|
||||||
}
|
}
|
||||||
@@ -422,23 +418,23 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
|
|
||||||
let mut tokens = Vec::with_capacity(2 + intermediary_tokens.len());
|
let mut tokens = Vec::with_capacity(2 + intermediary_tokens.len());
|
||||||
if wrap {
|
if wrap {
|
||||||
tokens.push(self.wrapped_address.clone());
|
tokens.push(&self.wrapped_address);
|
||||||
} else {
|
} else {
|
||||||
tokens.push(solution.given_token.clone());
|
tokens.push(&solution.given_token);
|
||||||
}
|
}
|
||||||
tokens.extend(intermediary_tokens);
|
tokens.extend(intermediary_tokens);
|
||||||
|
|
||||||
if unwrap {
|
if unwrap {
|
||||||
tokens.push(self.wrapped_address.clone());
|
tokens.push(&self.wrapped_address);
|
||||||
} else {
|
} else {
|
||||||
tokens.push(solution.checked_token.clone());
|
tokens.push(&solution.checked_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut swaps = vec![];
|
let mut swaps = vec![];
|
||||||
for grouped_swap in grouped_swaps.iter() {
|
for grouped_swap in grouped_swaps.iter() {
|
||||||
let protocol = grouped_swap.protocol_system.clone();
|
let protocol = &grouped_swap.protocol_system;
|
||||||
let swap_encoder = self
|
let swap_encoder = self
|
||||||
.get_swap_encoder(&protocol)
|
.get_swap_encoder(protocol)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
EncodingError::InvalidInput(format!(
|
EncodingError::InvalidInput(format!(
|
||||||
"Swap encoder not found for protocol: {protocol}",
|
"Swap encoder not found for protocol: {protocol}",
|
||||||
@@ -452,9 +448,9 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
};
|
};
|
||||||
let transfer = self
|
let transfer = self
|
||||||
.transfer_optimization
|
.transfer_optimization
|
||||||
.get_transfers(grouped_swap.clone(), solution.given_token.clone(), wrap, false);
|
.get_transfers(grouped_swap, &solution.given_token, wrap, false);
|
||||||
let encoding_context = EncodingContext {
|
let encoding_context = EncodingContext {
|
||||||
receiver: swap_receiver.clone(),
|
receiver: swap_receiver,
|
||||||
exact_out: solution.exact_out,
|
exact_out: solution.exact_out,
|
||||||
router_address: Some(self.router_address.clone()),
|
router_address: Some(self.router_address.clone()),
|
||||||
group_token_in: grouped_swap.token_in.clone(),
|
group_token_in: grouped_swap.token_in.clone(),
|
||||||
@@ -464,14 +460,13 @@ impl StrategyEncoder for SplitSwapStrategyEncoder {
|
|||||||
|
|
||||||
let mut grouped_protocol_data: Vec<u8> = vec![];
|
let mut grouped_protocol_data: Vec<u8> = vec![];
|
||||||
for swap in grouped_swap.swaps.iter() {
|
for swap in grouped_swap.swaps.iter() {
|
||||||
let protocol_data =
|
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
|
||||||
swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
|
||||||
grouped_protocol_data.extend(protocol_data);
|
grouped_protocol_data.extend(protocol_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
let swap_data = self.encode_swap_header(
|
let swap_data = self.encode_swap_header(
|
||||||
get_token_position(tokens.clone(), grouped_swap.token_in.clone())?,
|
get_token_position(&tokens, &grouped_swap.token_in)?,
|
||||||
get_token_position(tokens.clone(), grouped_swap.token_out.clone())?,
|
get_token_position(&tokens, &grouped_swap.token_out)?,
|
||||||
percentage_to_uint24(grouped_swap.split),
|
percentage_to_uint24(grouped_swap.split),
|
||||||
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
|
Bytes::from_str(swap_encoder.executor_address()).map_err(|_| {
|
||||||
EncodingError::FatalError("Invalid executor address".to_string())
|
EncodingError::FatalError("Invalid executor address".to_string())
|
||||||
@@ -581,7 +576,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let encoded_solution = encoder
|
let encoded_solution = encoder
|
||||||
.encode_strategy(solution.clone())
|
.encode_strategy(&solution)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let expected_swap = String::from(concat!(
|
let expected_swap = String::from(concat!(
|
||||||
@@ -642,7 +637,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let encoded_solution = encoder
|
let encoded_solution = encoder
|
||||||
.encode_strategy(solution.clone())
|
.encode_strategy(&solution)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let expected_input = [
|
let expected_input = [
|
||||||
@@ -724,7 +719,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let encoded_solution = encoder
|
let encoded_solution = encoder
|
||||||
.encode_strategy(solution.clone())
|
.encode_strategy(&solution)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let hex_calldata = encode(&encoded_solution.swaps);
|
let hex_calldata = encode(&encoded_solution.swaps);
|
||||||
@@ -864,7 +859,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let encoded_solution = encoder
|
let encoded_solution = encoder
|
||||||
.encode_strategy(solution.clone())
|
.encode_strategy(&solution)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let hex_calldata = hex::encode(&encoded_solution.swaps);
|
let hex_calldata = hex::encode(&encoded_solution.swaps);
|
||||||
@@ -1014,7 +1009,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let encoded_solution = encoder
|
let encoded_solution = encoder
|
||||||
.encode_strategy(solution.clone())
|
.encode_strategy(&solution)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let hex_calldata = hex::encode(&encoded_solution.swaps);
|
let hex_calldata = hex::encode(&encoded_solution.swaps);
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ impl SplitSwapValidator {
|
|||||||
/// * The sum of all non-remainder splits for each token is < 1 (100%)
|
/// * The sum of all non-remainder splits for each token is < 1 (100%)
|
||||||
/// * There are no negative split amounts
|
/// * There are no negative split amounts
|
||||||
pub fn validate_split_percentages(&self, swaps: &[Swap]) -> Result<(), EncodingError> {
|
pub fn validate_split_percentages(&self, swaps: &[Swap]) -> Result<(), EncodingError> {
|
||||||
let mut swaps_by_token: HashMap<Bytes, Vec<&Swap>> = HashMap::new();
|
let mut swaps_by_token: HashMap<&Bytes, Vec<&Swap>> = HashMap::new();
|
||||||
for swap in swaps {
|
for swap in swaps {
|
||||||
if swap.split >= 1.0 {
|
if swap.split >= 1.0 {
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
@@ -127,7 +127,7 @@ impl SplitSwapValidator {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
swaps_by_token
|
swaps_by_token
|
||||||
.entry(swap.token_in.clone())
|
.entry(&swap.token_in)
|
||||||
.or_default()
|
.or_default()
|
||||||
.push(swap);
|
.push(swap);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,12 +33,12 @@ impl TransferOptimization {
|
|||||||
/// Returns the transfer type that should be used for the current transfer.
|
/// Returns the transfer type that should be used for the current transfer.
|
||||||
pub fn get_transfers(
|
pub fn get_transfers(
|
||||||
&self,
|
&self,
|
||||||
swap: SwapGroup,
|
swap: &SwapGroup,
|
||||||
given_token: Bytes,
|
given_token: &Bytes,
|
||||||
wrap: bool,
|
wrap: bool,
|
||||||
in_between_swap_optimization: bool,
|
in_between_swap_optimization: bool,
|
||||||
) -> TransferType {
|
) -> TransferType {
|
||||||
let is_first_swap = swap.token_in == given_token;
|
let is_first_swap = swap.token_in == *given_token;
|
||||||
let in_transfer_required: bool =
|
let in_transfer_required: bool =
|
||||||
IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.protocol_system.as_str());
|
IN_TRANSFER_REQUIRED_PROTOCOLS.contains(&swap.protocol_system.as_str());
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ impl TransferOptimization {
|
|||||||
// is necessary for the next swap transfer type decision).
|
// is necessary for the next swap transfer type decision).
|
||||||
pub fn get_receiver(
|
pub fn get_receiver(
|
||||||
&self,
|
&self,
|
||||||
solution_receiver: Bytes,
|
solution_receiver: &Bytes,
|
||||||
next_swap: Option<&SwapGroup>,
|
next_swap: Option<&SwapGroup>,
|
||||||
) -> Result<(Bytes, bool), EncodingError> {
|
) -> Result<(Bytes, bool), EncodingError> {
|
||||||
if let Some(next) = next_swap {
|
if let Some(next) = next_swap {
|
||||||
@@ -104,7 +104,7 @@ impl TransferOptimization {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// last swap - there is no next swap
|
// last swap - there is no next swap
|
||||||
Ok((solution_receiver, false))
|
Ok((solution_receiver.clone(), false))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,12 +189,8 @@ mod tests {
|
|||||||
};
|
};
|
||||||
let optimization =
|
let optimization =
|
||||||
TransferOptimization::new(eth(), weth(), user_transfer_type, router_address());
|
TransferOptimization::new(eth(), weth(), user_transfer_type, router_address());
|
||||||
let transfer = optimization.get_transfers(
|
let transfer =
|
||||||
swap.clone(),
|
optimization.get_transfers(&swap, &given_token, wrap, in_between_swap_optimization);
|
||||||
given_token,
|
|
||||||
wrap,
|
|
||||||
in_between_swap_optimization,
|
|
||||||
);
|
|
||||||
assert_eq!(transfer, expected_transfer);
|
assert_eq!(transfer, expected_transfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,7 +245,7 @@ mod tests {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = optimization.get_receiver(receiver(), next_swap.as_ref());
|
let result = optimization.get_receiver(&receiver(), next_swap.as_ref());
|
||||||
|
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
let (actual_receiver, optimization_flag) = result.unwrap();
|
let (actual_receiver, optimization_flag) = result.unwrap();
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ impl SwapEncoder for UniswapV2SwapEncoder {
|
|||||||
|
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError> {
|
) -> Result<Vec<u8>, EncodingError> {
|
||||||
let token_in_address = bytes_to_address(&swap.token_in)?;
|
let token_in_address = bytes_to_address(&swap.token_in)?;
|
||||||
let token_out_address = bytes_to_address(&swap.token_out)?;
|
let token_out_address = bytes_to_address(&swap.token_out)?;
|
||||||
@@ -105,8 +105,8 @@ impl SwapEncoder for UniswapV3SwapEncoder {
|
|||||||
|
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError> {
|
) -> Result<Vec<u8>, EncodingError> {
|
||||||
let token_in_address = bytes_to_address(&swap.token_in)?;
|
let token_in_address = bytes_to_address(&swap.token_in)?;
|
||||||
let token_out_address = bytes_to_address(&swap.token_out)?;
|
let token_out_address = bytes_to_address(&swap.token_out)?;
|
||||||
@@ -114,7 +114,7 @@ impl SwapEncoder for UniswapV3SwapEncoder {
|
|||||||
let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address);
|
let zero_to_one = Self::get_zero_to_one(token_in_address, token_out_address);
|
||||||
let component_id = Address::from_str(&swap.component.id)
|
let component_id = Address::from_str(&swap.component.id)
|
||||||
.map_err(|_| EncodingError::FatalError("Invalid USV3 component id".to_string()))?;
|
.map_err(|_| EncodingError::FatalError("Invalid USV3 component id".to_string()))?;
|
||||||
let pool_fee_bytes = get_static_attribute(&swap, "fee")?;
|
let pool_fee_bytes = get_static_attribute(swap, "fee")?;
|
||||||
|
|
||||||
let pool_fee_u24 = pad_to_fixed_size::<3>(&pool_fee_bytes)
|
let pool_fee_u24 = pad_to_fixed_size::<3>(&pool_fee_bytes)
|
||||||
.map_err(|_| EncodingError::FatalError("Failed to extract fee bytes".to_string()))?;
|
.map_err(|_| EncodingError::FatalError("Failed to extract fee bytes".to_string()))?;
|
||||||
@@ -166,15 +166,15 @@ impl SwapEncoder for UniswapV4SwapEncoder {
|
|||||||
|
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError> {
|
) -> Result<Vec<u8>, EncodingError> {
|
||||||
let fee = get_static_attribute(&swap, "key_lp_fee")?;
|
let fee = get_static_attribute(swap, "key_lp_fee")?;
|
||||||
|
|
||||||
let pool_fee_u24 = pad_to_fixed_size::<3>(&fee)
|
let pool_fee_u24 = pad_to_fixed_size::<3>(&fee)
|
||||||
.map_err(|_| EncodingError::FatalError("Failed to pad fee bytes".to_string()))?;
|
.map_err(|_| EncodingError::FatalError("Failed to pad fee bytes".to_string()))?;
|
||||||
|
|
||||||
let tick_spacing = get_static_attribute(&swap, "tick_spacing")?;
|
let tick_spacing = get_static_attribute(swap, "tick_spacing")?;
|
||||||
|
|
||||||
let pool_tick_spacing_u24 = pad_to_fixed_size::<3>(&tick_spacing).map_err(|_| {
|
let pool_tick_spacing_u24 = pad_to_fixed_size::<3>(&tick_spacing).map_err(|_| {
|
||||||
EncodingError::FatalError("Failed to pad tick spacing bytes".to_string())
|
EncodingError::FatalError("Failed to pad tick spacing bytes".to_string())
|
||||||
@@ -249,15 +249,15 @@ impl SwapEncoder for BalancerV2SwapEncoder {
|
|||||||
|
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError> {
|
) -> Result<Vec<u8>, EncodingError> {
|
||||||
let token_approvals_manager = ProtocolApprovalsManager::new()?;
|
let token_approvals_manager = ProtocolApprovalsManager::new()?;
|
||||||
let token = bytes_to_address(&swap.token_in)?;
|
let token = bytes_to_address(&swap.token_in)?;
|
||||||
let approval_needed: bool;
|
let approval_needed: bool;
|
||||||
|
|
||||||
if let Some(router_address) = encoding_context.router_address {
|
if let Some(router_address) = &encoding_context.router_address {
|
||||||
let tycho_router_address = bytes_to_address(&router_address)?;
|
let tycho_router_address = bytes_to_address(router_address)?;
|
||||||
approval_needed = token_approvals_manager.approval_needed(
|
approval_needed = token_approvals_manager.approval_needed(
|
||||||
token,
|
token,
|
||||||
tycho_router_address,
|
tycho_router_address,
|
||||||
@@ -310,28 +310,28 @@ impl SwapEncoder for EkuboSwapEncoder {
|
|||||||
|
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError> {
|
) -> Result<Vec<u8>, EncodingError> {
|
||||||
if encoding_context.exact_out {
|
if encoding_context.exact_out {
|
||||||
return Err(EncodingError::InvalidInput("exact out swaps not implemented".to_string()));
|
return Err(EncodingError::InvalidInput("exact out swaps not implemented".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let fee = u64::from_be_bytes(
|
let fee = u64::from_be_bytes(
|
||||||
get_static_attribute(&swap, "fee")?
|
get_static_attribute(swap, "fee")?
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| EncodingError::FatalError("fee should be an u64".to_string()))?,
|
.map_err(|_| EncodingError::FatalError("fee should be an u64".to_string()))?,
|
||||||
);
|
);
|
||||||
|
|
||||||
let tick_spacing = u32::from_be_bytes(
|
let tick_spacing = u32::from_be_bytes(
|
||||||
get_static_attribute(&swap, "tick_spacing")?
|
get_static_attribute(swap, "tick_spacing")?
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EncodingError::FatalError("tick_spacing should be an u32".to_string())
|
EncodingError::FatalError("tick_spacing should be an u32".to_string())
|
||||||
})?,
|
})?,
|
||||||
);
|
);
|
||||||
|
|
||||||
let extension: Address = get_static_attribute(&swap, "extension")?
|
let extension: Address = get_static_attribute(swap, "extension")?
|
||||||
.as_slice()
|
.as_slice()
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| EncodingError::FatalError("extension should be an address".to_string()))?;
|
.map_err(|_| EncodingError::FatalError("extension should be an address".to_string()))?;
|
||||||
@@ -372,6 +372,7 @@ pub struct CurveSwapEncoder {
|
|||||||
executor_address: String,
|
executor_address: String,
|
||||||
native_token_curve_address: String,
|
native_token_curve_address: String,
|
||||||
native_token_address: Bytes,
|
native_token_address: Bytes,
|
||||||
|
wrapped_native_token_address: Bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CurveSwapEncoder {
|
impl CurveSwapEncoder {
|
||||||
@@ -407,14 +408,32 @@ impl CurveSwapEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some curve pools support both ETH and WETH as tokens.
|
||||||
|
// They do the wrapping/unwrapping inside the pool
|
||||||
|
fn normalize_token(&self, token: Address, coins: &[Address]) -> Result<Address, EncodingError> {
|
||||||
|
let native_token_address = bytes_to_address(&self.native_token_address)?;
|
||||||
|
let wrapped_native_token_address = bytes_to_address(&self.wrapped_native_token_address)?;
|
||||||
|
if token == native_token_address && !coins.contains(&token) {
|
||||||
|
Ok(wrapped_native_token_address)
|
||||||
|
} else if token == wrapped_native_token_address && !coins.contains(&token) {
|
||||||
|
Ok(native_token_address)
|
||||||
|
} else {
|
||||||
|
Ok(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn get_coin_indexes(
|
fn get_coin_indexes(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
token_in: Address,
|
token_in: Address,
|
||||||
token_out: Address,
|
token_out: Address,
|
||||||
) -> Result<(U8, U8), EncodingError> {
|
) -> Result<(U8, U8), EncodingError> {
|
||||||
let coins_bytes = get_static_attribute(&swap, "coins")?;
|
let coins_bytes = get_static_attribute(swap, "coins")?;
|
||||||
let coins: Vec<Address> = from_str(std::str::from_utf8(&coins_bytes)?)?;
|
let coins: Vec<Address> = from_str(std::str::from_utf8(&coins_bytes)?)?;
|
||||||
|
|
||||||
|
let token_in = self.normalize_token(token_in, &coins)?;
|
||||||
|
let token_out = self.normalize_token(token_out, &coins)?;
|
||||||
|
|
||||||
let i = coins
|
let i = coins
|
||||||
.iter()
|
.iter()
|
||||||
.position(|&addr| addr == token_in)
|
.position(|&addr| addr == token_in)
|
||||||
@@ -450,13 +469,14 @@ impl SwapEncoder for CurveSwapEncoder {
|
|||||||
executor_address,
|
executor_address,
|
||||||
native_token_address: chain.native_token()?,
|
native_token_address: chain.native_token()?,
|
||||||
native_token_curve_address,
|
native_token_curve_address,
|
||||||
|
wrapped_native_token_address: chain.wrapped_token()?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError> {
|
) -> Result<Vec<u8>, EncodingError> {
|
||||||
let token_approvals_manager = ProtocolApprovalsManager::new()?;
|
let token_approvals_manager = ProtocolApprovalsManager::new()?;
|
||||||
let native_token_curve_address = Address::from_str(&self.native_token_curve_address)
|
let native_token_curve_address = Address::from_str(&self.native_token_curve_address)
|
||||||
@@ -477,9 +497,9 @@ impl SwapEncoder for CurveSwapEncoder {
|
|||||||
|
|
||||||
let component_address = Address::from_str(&swap.component.id)
|
let component_address = Address::from_str(&swap.component.id)
|
||||||
.map_err(|_| EncodingError::FatalError("Invalid curve pool address".to_string()))?;
|
.map_err(|_| EncodingError::FatalError("Invalid curve pool address".to_string()))?;
|
||||||
if let Some(router_address) = encoding_context.router_address {
|
if let Some(router_address) = &encoding_context.router_address {
|
||||||
if token_in != native_token_curve_address {
|
if token_in != native_token_curve_address {
|
||||||
let tycho_router_address = bytes_to_address(&router_address)?;
|
let tycho_router_address = bytes_to_address(router_address)?;
|
||||||
approval_needed = token_approvals_manager.approval_needed(
|
approval_needed = token_approvals_manager.approval_needed(
|
||||||
token_in,
|
token_in,
|
||||||
tycho_router_address,
|
tycho_router_address,
|
||||||
@@ -492,7 +512,7 @@ impl SwapEncoder for CurveSwapEncoder {
|
|||||||
approval_needed = true;
|
approval_needed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let factory_bytes = get_static_attribute(&swap, "factory")?.to_vec();
|
let factory_bytes = get_static_attribute(swap, "factory")?.to_vec();
|
||||||
// the conversion to Address is necessary to checksum the address
|
// the conversion to Address is necessary to checksum the address
|
||||||
let factory_address =
|
let factory_address =
|
||||||
Address::from_str(std::str::from_utf8(&factory_bytes).map_err(|_| {
|
Address::from_str(std::str::from_utf8(&factory_bytes).map_err(|_| {
|
||||||
@@ -552,8 +572,8 @@ impl SwapEncoder for MaverickV2SwapEncoder {
|
|||||||
|
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError> {
|
) -> Result<Vec<u8>, EncodingError> {
|
||||||
let component_id = AlloyBytes::from_str(&swap.component.id)
|
let component_id = AlloyBytes::from_str(&swap.component.id)
|
||||||
.map_err(|_| EncodingError::FatalError("Invalid component ID".to_string()))?;
|
.map_err(|_| EncodingError::FatalError("Invalid component ID".to_string()))?;
|
||||||
@@ -595,8 +615,8 @@ impl SwapEncoder for BalancerV3SwapEncoder {
|
|||||||
|
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError> {
|
) -> Result<Vec<u8>, EncodingError> {
|
||||||
let pool = Address::from_str(&swap.component.id).map_err(|_| {
|
let pool = Address::from_str(&swap.component.id).map_err(|_| {
|
||||||
EncodingError::FatalError("Invalid pool address for Balancer v3".to_string())
|
EncodingError::FatalError("Invalid pool address for Balancer v3".to_string())
|
||||||
@@ -1052,7 +1072,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1112,7 +1132,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1177,7 +1197,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
@@ -1249,7 +1269,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
@@ -1322,7 +1342,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
@@ -1417,10 +1437,10 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let initial_encoded_swap = encoder
|
let initial_encoded_swap = encoder
|
||||||
.encode_swap(initial_swap, context.clone())
|
.encode_swap(&initial_swap, &context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let second_encoded_swap = encoder
|
let second_encoded_swap = encoder
|
||||||
.encode_swap(second_swap, context)
|
.encode_swap(&second_swap, &context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let combined_hex =
|
let combined_hex =
|
||||||
@@ -1501,7 +1521,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
@@ -1577,11 +1597,11 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let first_encoded_swap = encoder
|
let first_encoded_swap = encoder
|
||||||
.encode_swap(first_swap, encoding_context.clone())
|
.encode_swap(&first_swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let second_encoded_swap = encoder
|
let second_encoded_swap = encoder
|
||||||
.encode_swap(second_swap, encoding_context)
|
.encode_swap(&second_swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let combined_hex =
|
let combined_hex =
|
||||||
@@ -1703,7 +1723,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let (i, j) = encoder
|
let (i, j) = encoder
|
||||||
.get_coin_indexes(
|
.get_coin_indexes(
|
||||||
swap,
|
&swap,
|
||||||
Address::from_str(token_in).unwrap(),
|
Address::from_str(token_in).unwrap(),
|
||||||
Address::from_str(token_out).unwrap(),
|
Address::from_str(token_out).unwrap(),
|
||||||
)
|
)
|
||||||
@@ -1755,7 +1775,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
@@ -1827,7 +1847,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
@@ -1909,7 +1929,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
@@ -1974,7 +1994,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
@@ -2033,7 +2053,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let encoded_swap = encoder
|
let encoded_swap = encoder
|
||||||
.encode_swap(swap, encoding_context)
|
.encode_swap(&swap, &encoding_context)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
|
|||||||
@@ -89,33 +89,36 @@ impl TychoRouterEncoder {
|
|||||||
fn encode_solution(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
|
fn encode_solution(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError> {
|
||||||
self.validate_solution(solution)?;
|
self.validate_solution(solution)?;
|
||||||
let protocols: HashSet<String> = solution
|
let protocols: HashSet<String> = solution
|
||||||
.clone()
|
|
||||||
.swaps
|
.swaps
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|swap| swap.component.protocol_system)
|
.map(|swap| swap.component.protocol_system.clone())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut encoded_solution = if (solution.swaps.len() == 1) ||
|
let mut encoded_solution = if (solution.swaps.len() == 1) ||
|
||||||
(protocols.len() == 1 &&
|
((protocols.len() == 1 &&
|
||||||
protocols
|
protocols
|
||||||
.iter()
|
.iter()
|
||||||
.any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str())))
|
.any(|p| GROUPABLE_PROTOCOLS.contains(&p.as_str()))) &&
|
||||||
|
solution
|
||||||
|
.swaps
|
||||||
|
.iter()
|
||||||
|
.all(|swap| swap.split == 0.0))
|
||||||
{
|
{
|
||||||
self.single_swap_strategy
|
self.single_swap_strategy
|
||||||
.encode_strategy(solution.clone())?
|
.encode_strategy(solution)?
|
||||||
} else if solution
|
} else if solution
|
||||||
.swaps
|
.swaps
|
||||||
.iter()
|
.iter()
|
||||||
.all(|swap| swap.split == 0.0)
|
.all(|swap| swap.split == 0.0)
|
||||||
{
|
{
|
||||||
self.sequential_swap_strategy
|
self.sequential_swap_strategy
|
||||||
.encode_strategy(solution.clone())?
|
.encode_strategy(solution)?
|
||||||
} else {
|
} else {
|
||||||
self.split_swap_strategy
|
self.split_swap_strategy
|
||||||
.encode_strategy(solution.clone())?
|
.encode_strategy(solution)?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(permit2) = self.permit2.clone() {
|
if let Some(permit2) = &self.permit2 {
|
||||||
let permit = permit2.get_permit(
|
let permit = permit2.get_permit(
|
||||||
&self.router_address,
|
&self.router_address,
|
||||||
&solution.sender,
|
&solution.sender,
|
||||||
@@ -153,8 +156,8 @@ impl TychoEncoder for TychoRouterEncoder {
|
|||||||
self.chain.id,
|
self.chain.id,
|
||||||
encoded_solution,
|
encoded_solution,
|
||||||
solution,
|
solution,
|
||||||
self.user_transfer_type.clone(),
|
&self.user_transfer_type,
|
||||||
self.chain.native_token()?.clone(),
|
&self.chain.native_token()?,
|
||||||
self.signer.clone(),
|
self.signer.clone(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@@ -185,8 +188,8 @@ impl TychoEncoder for TychoRouterEncoder {
|
|||||||
}
|
}
|
||||||
let native_address = self.chain.native_token()?;
|
let native_address = self.chain.native_token()?;
|
||||||
let wrapped_address = self.chain.wrapped_token()?;
|
let wrapped_address = self.chain.wrapped_token()?;
|
||||||
if let Some(native_action) = solution.clone().native_action {
|
if let Some(native_action) = &solution.native_action {
|
||||||
if native_action == NativeAction::Wrap {
|
if native_action == &NativeAction::Wrap {
|
||||||
if solution.given_token != native_address {
|
if solution.given_token != native_address {
|
||||||
return Err(EncodingError::FatalError(
|
return Err(EncodingError::FatalError(
|
||||||
"Native token must be the input token in order to wrap".to_string(),
|
"Native token must be the input token in order to wrap".to_string(),
|
||||||
@@ -200,7 +203,7 @@ impl TychoEncoder for TychoRouterEncoder {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if native_action == NativeAction::Unwrap {
|
} else if native_action == &NativeAction::Unwrap {
|
||||||
if solution.checked_token != native_address {
|
if solution.checked_token != native_address {
|
||||||
return Err(EncodingError::FatalError(
|
return Err(EncodingError::FatalError(
|
||||||
"Native token must be the output token in order to unwrap".to_string(),
|
"Native token must be the output token in order to unwrap".to_string(),
|
||||||
@@ -223,17 +226,17 @@ impl TychoEncoder for TychoRouterEncoder {
|
|||||||
// so we don't count the split tokens more than once
|
// so we don't count the split tokens more than once
|
||||||
if swap.split != 0.0 {
|
if swap.split != 0.0 {
|
||||||
if !split_tokens_already_considered.contains(&swap.token_in) {
|
if !split_tokens_already_considered.contains(&swap.token_in) {
|
||||||
solution_tokens.push(swap.token_in.clone());
|
solution_tokens.push(&swap.token_in);
|
||||||
split_tokens_already_considered.insert(swap.token_in.clone());
|
split_tokens_already_considered.insert(&swap.token_in);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// it might be the last swap of the split or a regular swap
|
// it might be the last swap of the split or a regular swap
|
||||||
if !split_tokens_already_considered.contains(&swap.token_in) {
|
if !split_tokens_already_considered.contains(&swap.token_in) {
|
||||||
solution_tokens.push(swap.token_in.clone());
|
solution_tokens.push(&swap.token_in);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if i == solution.swaps.len() - 1 {
|
if i == solution.swaps.len() - 1 {
|
||||||
solution_tokens.push(swap.token_out.clone());
|
solution_tokens.push(&swap.token_out);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +244,7 @@ impl TychoEncoder for TychoRouterEncoder {
|
|||||||
solution_tokens
|
solution_tokens
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<HashSet<Bytes>>()
|
.collect::<HashSet<&Bytes>>()
|
||||||
.len()
|
.len()
|
||||||
{
|
{
|
||||||
if let Some(last_swap) = solution.swaps.last() {
|
if let Some(last_swap) = solution.swaps.last() {
|
||||||
@@ -252,7 +255,7 @@ impl TychoEncoder for TychoRouterEncoder {
|
|||||||
} else {
|
} else {
|
||||||
// it is a valid cyclical swap
|
// it is a valid cyclical swap
|
||||||
// we don't support any wrapping or unwrapping in this case
|
// we don't support any wrapping or unwrapping in this case
|
||||||
if let Some(_native_action) = solution.clone().native_action {
|
if let Some(_native_action) = &solution.native_action {
|
||||||
return Err(EncodingError::FatalError(
|
return Err(EncodingError::FatalError(
|
||||||
"Wrapping/Unwrapping is not available in cyclical swaps".to_string(),
|
"Wrapping/Unwrapping is not available in cyclical swaps".to_string(),
|
||||||
));
|
));
|
||||||
@@ -283,9 +286,9 @@ impl TychoExecutorEncoder {
|
|||||||
|
|
||||||
fn encode_executor_calldata(
|
fn encode_executor_calldata(
|
||||||
&self,
|
&self,
|
||||||
solution: Solution,
|
solution: &Solution,
|
||||||
) -> Result<EncodedSolution, EncodingError> {
|
) -> Result<EncodedSolution, EncodingError> {
|
||||||
let grouped_swaps = group_swaps(solution.clone().swaps);
|
let grouped_swaps = group_swaps(&solution.swaps);
|
||||||
let number_of_groups = grouped_swaps.len();
|
let number_of_groups = grouped_swaps.len();
|
||||||
if number_of_groups > 1 {
|
if number_of_groups > 1 {
|
||||||
return Err(EncodingError::InvalidInput(format!(
|
return Err(EncodingError::InvalidInput(format!(
|
||||||
@@ -297,8 +300,6 @@ impl TychoExecutorEncoder {
|
|||||||
.first()
|
.first()
|
||||||
.ok_or_else(|| EncodingError::FatalError("Swap grouping failed".to_string()))?;
|
.ok_or_else(|| EncodingError::FatalError("Swap grouping failed".to_string()))?;
|
||||||
|
|
||||||
let receiver = solution.receiver;
|
|
||||||
|
|
||||||
let swap_encoder = self
|
let swap_encoder = self
|
||||||
.swap_encoder_registry
|
.swap_encoder_registry
|
||||||
.get_encoder(&grouped_swap.protocol_system)
|
.get_encoder(&grouped_swap.protocol_system)
|
||||||
@@ -319,14 +320,14 @@ impl TychoExecutorEncoder {
|
|||||||
TransferType::None
|
TransferType::None
|
||||||
};
|
};
|
||||||
let encoding_context = EncodingContext {
|
let encoding_context = EncodingContext {
|
||||||
receiver: receiver.clone(),
|
receiver: solution.receiver.clone(),
|
||||||
exact_out: solution.exact_out,
|
exact_out: solution.exact_out,
|
||||||
router_address: None,
|
router_address: None,
|
||||||
group_token_in: grouped_swap.token_in.clone(),
|
group_token_in: grouped_swap.token_in.clone(),
|
||||||
group_token_out: grouped_swap.token_out.clone(),
|
group_token_out: grouped_swap.token_out.clone(),
|
||||||
transfer_type: transfer,
|
transfer_type: transfer,
|
||||||
};
|
};
|
||||||
let protocol_data = swap_encoder.encode_swap(swap.clone(), encoding_context.clone())?;
|
let protocol_data = swap_encoder.encode_swap(swap, &encoding_context)?;
|
||||||
grouped_protocol_data.extend(protocol_data);
|
grouped_protocol_data.extend(protocol_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +354,7 @@ impl TychoEncoder for TychoExecutorEncoder {
|
|||||||
.ok_or(EncodingError::FatalError("No solutions found".to_string()))?;
|
.ok_or(EncodingError::FatalError("No solutions found".to_string()))?;
|
||||||
self.validate_solution(solution)?;
|
self.validate_solution(solution)?;
|
||||||
|
|
||||||
let encoded_solution = self.encode_executor_calldata(solution.clone())?;
|
let encoded_solution = self.encode_executor_calldata(solution)?;
|
||||||
|
|
||||||
Ok(vec![encoded_solution])
|
Ok(vec![encoded_solution])
|
||||||
}
|
}
|
||||||
@@ -609,6 +610,32 @@ mod tests {
|
|||||||
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e21dd0d3");
|
assert_eq!(&hex::encode(transactions[0].clone().data)[..8], "e21dd0d3");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_encode_router_calldata_split_swap_group() {
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
let mut swap_usdc_eth = swap_usdc_eth_univ4();
|
||||||
|
swap_usdc_eth.split = 0.5; // Set split to 50%
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: usdc(),
|
||||||
|
given_amount: BigUint::from_str("1000_000000").unwrap(),
|
||||||
|
checked_token: eth(),
|
||||||
|
checked_amount: BigUint::from_str("105_152_000000000000000000").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_usdc_eth, swap_usdc_eth_univ4()],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution_res = encoder.encode_solution(&solution);
|
||||||
|
assert!(encoded_solution_res.is_ok());
|
||||||
|
|
||||||
|
let encoded_solution = encoded_solution_res.unwrap();
|
||||||
|
assert!(encoded_solution
|
||||||
|
.function_signature
|
||||||
|
.contains("splitSwap"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_validate_fails_for_exact_out() {
|
fn test_validate_fails_for_exact_out() {
|
||||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
@@ -1260,98 +1287,7 @@ mod tests {
|
|||||||
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::encoding::{
|
use crate::encoding::evm::utils::{biguint_to_u256, write_calldata_to_file};
|
||||||
evm::utils::{biguint_to_u256, write_calldata_to_file},
|
|
||||||
models::BebopOrderType,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Builds Bebop user_data with support for single or multiple signatures
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * `order_type` - The type of Bebop order (Single or Aggregate)
|
|
||||||
/// * `filled_taker_amount` - Amount to fill (0 means fill entire order)
|
|
||||||
/// * `quote_data` - The ABI-encoded order data
|
|
||||||
/// * `signatures` - Vector of (signature_bytes, signature_type) tuples
|
|
||||||
/// - For Single orders: expects exactly 1 signature
|
|
||||||
/// - For Aggregate orders: expects 1 or more signatures (one per maker)
|
|
||||||
fn build_bebop_user_data(
|
|
||||||
order_type: BebopOrderType,
|
|
||||||
filled_taker_amount: U256,
|
|
||||||
quote_data: &[u8],
|
|
||||||
signatures: Vec<(Vec<u8>, u8)>, // (signature, signature_type)
|
|
||||||
) -> Bytes {
|
|
||||||
// ABI encode MakerSignature[] array
|
|
||||||
// Format: offset_to_array | array_length | [offset_to_struct_i]... | [struct_i_data]...
|
|
||||||
let mut encoded_maker_sigs = Vec::new();
|
|
||||||
|
|
||||||
// Calculate total size needed
|
|
||||||
let array_offset = 32; // offset to array start
|
|
||||||
let array_length_size = 32;
|
|
||||||
let struct_offsets_size = 32 * signatures.len();
|
|
||||||
let _header_size = array_length_size + struct_offsets_size;
|
|
||||||
|
|
||||||
// Build each struct's data and calculate offsets
|
|
||||||
let mut struct_data = Vec::new();
|
|
||||||
let mut struct_offsets = Vec::new();
|
|
||||||
// Offsets are relative to the start of array data, not the absolute position
|
|
||||||
// Array data starts after array length, so first offset is after all offset values
|
|
||||||
let mut current_offset = struct_offsets_size; // Just the space for offsets, not including array length
|
|
||||||
|
|
||||||
for (signature, signature_type) in &signatures {
|
|
||||||
struct_offsets.push(current_offset);
|
|
||||||
|
|
||||||
// Each struct contains:
|
|
||||||
// - offset to signatureBytes (32 bytes) - always 0x40 (64)
|
|
||||||
// - flags (32 bytes)
|
|
||||||
// - signatureBytes length (32 bytes)
|
|
||||||
// - signatureBytes data (padded to 32 bytes)
|
|
||||||
let mut struct_bytes = Vec::new();
|
|
||||||
|
|
||||||
// Offset to signatureBytes within this struct
|
|
||||||
struct_bytes.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
|
|
||||||
|
|
||||||
// Flags (contains signature type) - AFTER the offset, not before!
|
|
||||||
let flags = U256::from(*signature_type);
|
|
||||||
struct_bytes.extend_from_slice(&flags.to_be_bytes::<32>());
|
|
||||||
|
|
||||||
// SignatureBytes length
|
|
||||||
struct_bytes.extend_from_slice(&U256::from(signature.len()).to_be_bytes::<32>());
|
|
||||||
|
|
||||||
// SignatureBytes data (padded to 32 byte boundary)
|
|
||||||
struct_bytes.extend_from_slice(signature);
|
|
||||||
let padding = (32 - (signature.len() % 32)) % 32;
|
|
||||||
struct_bytes.extend_from_slice(&vec![0u8; padding]);
|
|
||||||
|
|
||||||
current_offset += struct_bytes.len();
|
|
||||||
struct_data.push(struct_bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the complete ABI encoded array
|
|
||||||
// Offset to array (always 0x20 for a single parameter)
|
|
||||||
encoded_maker_sigs.extend_from_slice(&U256::from(array_offset).to_be_bytes::<32>());
|
|
||||||
|
|
||||||
// Array length
|
|
||||||
encoded_maker_sigs.extend_from_slice(&U256::from(signatures.len()).to_be_bytes::<32>());
|
|
||||||
|
|
||||||
// Struct offsets (relative to start of array data)
|
|
||||||
for offset in struct_offsets {
|
|
||||||
encoded_maker_sigs.extend_from_slice(&U256::from(offset).to_be_bytes::<32>());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Struct data
|
|
||||||
for data in struct_data {
|
|
||||||
encoded_maker_sigs.extend_from_slice(&data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build complete user_data
|
|
||||||
let mut user_data = Vec::new();
|
|
||||||
user_data.push(order_type as u8);
|
|
||||||
user_data.extend_from_slice(&filled_taker_amount.to_be_bytes::<32>());
|
|
||||||
user_data.extend_from_slice(&(quote_data.len() as u32).to_be_bytes());
|
|
||||||
user_data.extend_from_slice(quote_data);
|
|
||||||
user_data.extend_from_slice(&encoded_maker_sigs);
|
|
||||||
Bytes::from(user_data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_signer() -> PrivateKeySigner {
|
fn get_signer() -> PrivateKeySigner {
|
||||||
// Set up a mock private key for signing (Alice's pk in our contract tests)
|
// Set up a mock private key for signing (Alice's pk in our contract tests)
|
||||||
@@ -2050,7 +1986,6 @@ mod tests {
|
|||||||
mod optimized_transfers {
|
mod optimized_transfers {
|
||||||
// In this module we test the ability to chain swaps or not. Different protocols are
|
// In this module we test the ability to chain swaps or not. Different protocols are
|
||||||
// tested. The encoded data is used for solidity tests as well
|
// tested. The encoded data is used for solidity tests as well
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -2403,140 +2338,6 @@ mod tests {
|
|||||||
write_calldata_to_file("test_balancer_v2_uniswap_v2", hex_calldata.as_str());
|
write_calldata_to_file("test_balancer_v2_uniswap_v2", hex_calldata.as_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_uniswap_v3_bebop() {
|
|
||||||
// Note: This test does not assert anything. It is only used to obtain
|
|
||||||
// integration test data for our router solidity test.
|
|
||||||
//
|
|
||||||
// Performs a sequential swap from WETH to ONDO through USDC using USV3 and
|
|
||||||
// Bebop RFQ
|
|
||||||
//
|
|
||||||
// WETH ───(USV3)──> USDC ───(Bebop RFQ)──> ONDO
|
|
||||||
|
|
||||||
let weth = weth();
|
|
||||||
let usdc = usdc();
|
|
||||||
let ondo = ondo();
|
|
||||||
|
|
||||||
// First swap: WETH -> USDC via UniswapV3
|
|
||||||
let swap_weth_usdc = Swap {
|
|
||||||
component: ProtocolComponent {
|
|
||||||
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* WETH-USDC USV3 Pool 0.05% */
|
|
||||||
protocol_system: "uniswap_v3".to_string(),
|
|
||||||
static_attributes: {
|
|
||||||
let mut attrs = HashMap::new();
|
|
||||||
attrs.insert(
|
|
||||||
"fee".to_string(),
|
|
||||||
Bytes::from(BigInt::from(500).to_signed_bytes_be()),
|
|
||||||
);
|
|
||||||
attrs
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
token_in: weth.clone(),
|
|
||||||
token_out: usdc.clone(),
|
|
||||||
split: 0f64,
|
|
||||||
user_data: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Second swap: USDC -> ONDO via Bebop RFQ using real order data
|
|
||||||
// Using the same real order from the mainnet transaction at block 22667985
|
|
||||||
let expiry = 1749483840u64; // Real expiry from the order
|
|
||||||
let taker_address =
|
|
||||||
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(); // Real taker
|
|
||||||
let maker_address =
|
|
||||||
Address::from_str("0xCe79b081c0c924cb67848723ed3057234d10FC6b").unwrap(); // Real maker
|
|
||||||
let maker_nonce = 1749483765992417u64; // Real nonce
|
|
||||||
let taker_token = Address::from_str(&usdc.to_string()).unwrap();
|
|
||||||
let maker_token = Address::from_str(&ondo.to_string()).unwrap();
|
|
||||||
// Using the real order amounts
|
|
||||||
let taker_amount = U256::from_str("200000000").unwrap(); // 200 USDC (6 decimals)
|
|
||||||
let maker_amount = U256::from_str("237212396774431060000").unwrap(); // 237.21 ONDO (18 decimals)
|
|
||||||
let receiver =
|
|
||||||
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(); // Real receiver
|
|
||||||
let packed_commands = U256::ZERO;
|
|
||||||
let flags = U256::from_str("51915842898789398998206002334703507894664330885127600393944965515693155942400").unwrap(); // Real flags
|
|
||||||
|
|
||||||
// Encode using standard ABI encoding (not packed)
|
|
||||||
let quote_data = (
|
|
||||||
expiry,
|
|
||||||
taker_address,
|
|
||||||
maker_address,
|
|
||||||
maker_nonce,
|
|
||||||
taker_token,
|
|
||||||
maker_token,
|
|
||||||
taker_amount,
|
|
||||||
maker_amount,
|
|
||||||
receiver,
|
|
||||||
packed_commands,
|
|
||||||
flags,
|
|
||||||
)
|
|
||||||
.abi_encode();
|
|
||||||
|
|
||||||
// Real signature from the order
|
|
||||||
let signature = hex::decode("eb5419631614978da217532a40f02a8f2ece37d8cfb94aaa602baabbdefb56b474f4c2048a0f56502caff4ea7411d99eed6027cd67dc1088aaf4181dcb0df7051c").unwrap();
|
|
||||||
|
|
||||||
// Build user_data with the quote and signature
|
|
||||||
let user_data = build_bebop_user_data(
|
|
||||||
BebopOrderType::Single,
|
|
||||||
U256::from(0), // 0 means fill entire order
|
|
||||||
"e_data,
|
|
||||||
vec![(signature, 0)], // ETH_SIGN signature type (0)
|
|
||||||
);
|
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
|
||||||
id: String::from("bebop-rfq"),
|
|
||||||
protocol_system: String::from("rfq:bebop"),
|
|
||||||
static_attributes: HashMap::new(), // No static attributes needed
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let swap_usdc_ondo = Swap {
|
|
||||||
component: bebop_component,
|
|
||||||
token_in: usdc.clone(),
|
|
||||||
token_out: ondo.clone(),
|
|
||||||
split: 0f64,
|
|
||||||
user_data: Some(user_data),
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
|
||||||
|
|
||||||
let solution = Solution {
|
|
||||||
exact_out: false,
|
|
||||||
given_token: weth,
|
|
||||||
// Use ~0.099 WETH to get approximately 200 USDC from UniswapV3
|
|
||||||
// This should leave only dust amount in the router after Bebop consumes 200
|
|
||||||
// USDC
|
|
||||||
given_amount: BigUint::from_str("99000000000000000").unwrap(), // 0.099 WETH
|
|
||||||
checked_token: ondo,
|
|
||||||
checked_amount: BigUint::from_str("237212396774431060000").unwrap(), /* Expected ONDO from Bebop order */
|
|
||||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
|
|
||||||
.unwrap(),
|
|
||||||
receiver: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670")
|
|
||||||
.unwrap(), // Using the real order receiver
|
|
||||||
swaps: vec![swap_weth_usdc, swap_usdc_ondo],
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoded_solution = encoder
|
|
||||||
.encode_solutions(vec![solution.clone()])
|
|
||||||
.unwrap()[0]
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let calldata = encode_tycho_router_call(
|
|
||||||
eth_chain().id,
|
|
||||||
encoded_solution,
|
|
||||||
&solution,
|
|
||||||
UserTransferType::TransferFrom,
|
|
||||||
eth(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.data;
|
|
||||||
|
|
||||||
let hex_calldata = encode(&calldata);
|
|
||||||
write_calldata_to_file("test_uniswap_v3_bebop", hex_calldata.as_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_multi_protocol() {
|
fn test_multi_protocol() {
|
||||||
// Note: This test does not assert anything. It is only used to obtain
|
// Note: This test does not assert anything. It is only used to obtain
|
||||||
@@ -3827,252 +3628,6 @@ mod tests {
|
|||||||
hex_calldata.as_str(),
|
hex_calldata.as_str(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_single_encoding_strategy_bebop() {
|
|
||||||
// Use the same mainnet data from Solidity tests
|
|
||||||
// Transaction: https://etherscan.io/tx/0x6279bc970273b6e526e86d9b69133c2ca1277e697ba25375f5e6fc4df50c0c94
|
|
||||||
let token_in = usdc();
|
|
||||||
let token_out = ondo();
|
|
||||||
let amount_in = BigUint::from_str("200000000").unwrap(); // 200 USDC
|
|
||||||
let amount_out = BigUint::from_str("237212396774431060000").unwrap(); // 237.21 ONDO
|
|
||||||
|
|
||||||
// Create the exact same order from mainnet
|
|
||||||
let expiry = 1749483840u64;
|
|
||||||
let taker_address =
|
|
||||||
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(); // Order receiver from mainnet
|
|
||||||
let maker_address =
|
|
||||||
Address::from_str("0xCe79b081c0c924cb67848723ed3057234d10FC6b").unwrap();
|
|
||||||
let maker_nonce = 1749483765992417u64;
|
|
||||||
let taker_token = Address::from_str(&token_in.to_string()).unwrap();
|
|
||||||
let maker_token = Address::from_str(&token_out.to_string()).unwrap();
|
|
||||||
let taker_amount = U256::from_str(&amount_in.to_string()).unwrap();
|
|
||||||
let maker_amount = U256::from_str(&amount_out.to_string()).unwrap();
|
|
||||||
let receiver = taker_address; // Same as taker_address in this order
|
|
||||||
let packed_commands = U256::ZERO;
|
|
||||||
let flags = U256::from_str(
|
|
||||||
"51915842898789398998206002334703507894664330885127600393944965515693155942400",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Encode using standard ABI encoding (not packed)
|
|
||||||
let quote_data = (
|
|
||||||
expiry,
|
|
||||||
taker_address,
|
|
||||||
maker_address,
|
|
||||||
maker_nonce,
|
|
||||||
taker_token,
|
|
||||||
maker_token,
|
|
||||||
taker_amount,
|
|
||||||
maker_amount,
|
|
||||||
receiver,
|
|
||||||
packed_commands,
|
|
||||||
flags,
|
|
||||||
)
|
|
||||||
.abi_encode();
|
|
||||||
|
|
||||||
// Real signature from mainnet
|
|
||||||
let signature = hex::decode("eb5419631614978da217532a40f02a8f2ece37d8cfb94aaa602baabbdefb56b474f4c2048a0f56502caff4ea7411d99eed6027cd67dc1088aaf4181dcb0df7051c").unwrap();
|
|
||||||
|
|
||||||
// Build user_data with the quote and signature
|
|
||||||
let user_data = build_bebop_user_data(
|
|
||||||
BebopOrderType::Single,
|
|
||||||
U256::ZERO, // 0 means fill entire order
|
|
||||||
"e_data,
|
|
||||||
vec![(signature, 0)], // ETH_SIGN signature type
|
|
||||||
);
|
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
|
||||||
id: String::from("bebop-rfq"),
|
|
||||||
protocol_system: String::from("rfq:bebop"),
|
|
||||||
static_attributes: HashMap::new(), // No static attributes needed
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let swap = Swap {
|
|
||||||
component: bebop_component,
|
|
||||||
token_in: token_in.clone(),
|
|
||||||
token_out: token_out.clone(),
|
|
||||||
split: 0f64,
|
|
||||||
user_data: Some(user_data),
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
|
||||||
|
|
||||||
let taker_address_bytes = Bytes::from_str(&taker_address.to_string()).unwrap();
|
|
||||||
|
|
||||||
let solution = Solution {
|
|
||||||
exact_out: false,
|
|
||||||
given_token: token_in,
|
|
||||||
given_amount: amount_in,
|
|
||||||
checked_token: token_out,
|
|
||||||
checked_amount: amount_out, // Expected output amount
|
|
||||||
// Use the order's taker address as sender and receiver
|
|
||||||
sender: taker_address_bytes.clone(),
|
|
||||||
receiver: taker_address_bytes,
|
|
||||||
swaps: vec![swap],
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoded_solution = encoder
|
|
||||||
.encode_solutions(vec![solution.clone()])
|
|
||||||
.unwrap()[0]
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let calldata = encode_tycho_router_call(
|
|
||||||
eth_chain().id,
|
|
||||||
encoded_solution,
|
|
||||||
&solution,
|
|
||||||
UserTransferType::TransferFrom,
|
|
||||||
eth(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.data;
|
|
||||||
let hex_calldata = hex::encode(&calldata);
|
|
||||||
write_calldata_to_file(
|
|
||||||
"test_single_encoding_strategy_bebop",
|
|
||||||
hex_calldata.as_str(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_single_encoding_strategy_bebop_aggregate() {
|
|
||||||
// Use real mainnet aggregate order data from CLAUDE.md
|
|
||||||
// Transaction: https://etherscan.io/tx/0xec88410136c287280da87d0a37c1cb745f320406ca3ae55c678dec11996c1b1c
|
|
||||||
// For testing, use WETH directly to avoid delegatecall + native ETH complexities
|
|
||||||
let token_in = eth();
|
|
||||||
let token_out = usdc();
|
|
||||||
let amount_in = BigUint::from_str("9850000000000000").unwrap(); // 0.00985 WETH
|
|
||||||
let amount_out = BigUint::from_str("17969561").unwrap(); // 17.969561 USDC
|
|
||||||
|
|
||||||
// Create the exact aggregate order from mainnet
|
|
||||||
let expiry = 1746367285u64;
|
|
||||||
let taker_address =
|
|
||||||
Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap();
|
|
||||||
let receiver =
|
|
||||||
Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap();
|
|
||||||
|
|
||||||
// Set up makers
|
|
||||||
let maker_addresses = vec![
|
|
||||||
Address::from_str("0x67336Cec42645F55059EfF241Cb02eA5cC52fF86").unwrap(),
|
|
||||||
Address::from_str("0xBF19CbF0256f19f39A016a86Ff3551ecC6f2aAFE").unwrap(),
|
|
||||||
];
|
|
||||||
let maker_nonces = vec![U256::from(1746367197308u64), U256::from(15460096u64)];
|
|
||||||
|
|
||||||
// 2D arrays for tokens
|
|
||||||
// We use WETH as a taker token even when handling native ETH
|
|
||||||
let taker_tokens =
|
|
||||||
vec![vec![Address::from_slice(&weth())], vec![Address::from_slice(&weth())]];
|
|
||||||
let maker_tokens = vec![
|
|
||||||
vec![Address::from_slice(&token_out)],
|
|
||||||
vec![Address::from_slice(&token_out)],
|
|
||||||
];
|
|
||||||
|
|
||||||
// 2D arrays for amounts
|
|
||||||
let taker_amounts = vec![
|
|
||||||
vec![U256::from_str("5812106401997138").unwrap()],
|
|
||||||
vec![U256::from_str("4037893598002862").unwrap()],
|
|
||||||
];
|
|
||||||
let maker_amounts = vec![
|
|
||||||
vec![U256::from_str("10607211").unwrap()],
|
|
||||||
vec![U256::from_str("7362350").unwrap()],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Commands and flags from the real transaction
|
|
||||||
let commands = hex!("00040004").to_vec();
|
|
||||||
let flags = U256::from_str(
|
|
||||||
"95769172144825922628485191511070792431742484643425438763224908097896054784000",
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Encode Aggregate order - must match IBebopSettlement.Aggregate struct exactly
|
|
||||||
let quote_data = (
|
|
||||||
U256::from(expiry), // expiry as U256
|
|
||||||
taker_address,
|
|
||||||
maker_addresses,
|
|
||||||
maker_nonces, // Array of maker nonces
|
|
||||||
taker_tokens, // 2D array
|
|
||||||
maker_tokens,
|
|
||||||
taker_amounts, // 2D array
|
|
||||||
maker_amounts,
|
|
||||||
receiver,
|
|
||||||
commands,
|
|
||||||
flags,
|
|
||||||
)
|
|
||||||
.abi_encode();
|
|
||||||
|
|
||||||
// Use real signatures from the mainnet transaction
|
|
||||||
let sig1 = hex::decode("d5abb425f9bac1f44d48705f41a8ab9cae207517be8553d2c03b06a88995f2f351ab8ce7627a87048178d539dd64fd2380245531a0c8e43fdc614652b1f32fc71c").unwrap();
|
|
||||||
let sig2 = hex::decode("f38c698e48a3eac48f184bc324fef0b135ee13705ab38cc0bbf5a792f21002f051e445b9e7d57cf24c35e17629ea35b3263591c4abf8ca87ffa44b41301b89c41b").unwrap();
|
|
||||||
|
|
||||||
// Build user_data with ETH_SIGN flag (0) for both signatures
|
|
||||||
let signatures = vec![
|
|
||||||
(sig1, 0u8), // ETH_SIGN for maker 1
|
|
||||||
(sig2, 0u8), // ETH_SIGN for maker 2
|
|
||||||
];
|
|
||||||
|
|
||||||
let user_data = build_bebop_user_data(
|
|
||||||
BebopOrderType::Aggregate,
|
|
||||||
U256::from(0), // 0 means fill entire aggregate order
|
|
||||||
"e_data,
|
|
||||||
signatures,
|
|
||||||
);
|
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
|
||||||
id: String::from("bebop-rfq"),
|
|
||||||
protocol_system: String::from("rfq:bebop"),
|
|
||||||
static_attributes: HashMap::new(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let swap = Swap {
|
|
||||||
component: bebop_component,
|
|
||||||
token_in: token_in.clone(),
|
|
||||||
token_out: token_out.clone(),
|
|
||||||
split: 0f64,
|
|
||||||
user_data: Some(user_data),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use TransferFrom for WETH token transfer
|
|
||||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
|
||||||
|
|
||||||
let solution = Solution {
|
|
||||||
exact_out: false,
|
|
||||||
given_token: token_in.clone(),
|
|
||||||
given_amount: amount_in,
|
|
||||||
checked_token: token_out,
|
|
||||||
checked_amount: amount_out,
|
|
||||||
// Use ALICE as sender but order receiver as receiver
|
|
||||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), /* ALICE */
|
|
||||||
receiver: Bytes::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6")
|
|
||||||
.unwrap(), /* Order receiver */
|
|
||||||
swaps: vec![swap],
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let encoded_solution = encoder
|
|
||||||
.encode_solutions(vec![solution.clone()])
|
|
||||||
.unwrap()[0]
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let calldata = encode_tycho_router_call(
|
|
||||||
eth_chain().id,
|
|
||||||
encoded_solution,
|
|
||||||
&solution,
|
|
||||||
UserTransferType::None,
|
|
||||||
eth(),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.unwrap()
|
|
||||||
.data;
|
|
||||||
let hex_calldata = hex::encode(&calldata);
|
|
||||||
|
|
||||||
write_calldata_to_file(
|
|
||||||
"test_single_encoding_strategy_bebop_aggregate",
|
|
||||||
hex_calldata.as_str(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ pub fn percentage_to_uint24(decimal: f64) -> U24 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the position of a token in a list of tokens.
|
/// Gets the position of a token in a list of tokens.
|
||||||
pub fn get_token_position(tokens: Vec<Bytes>, token: Bytes) -> Result<U8, EncodingError> {
|
pub fn get_token_position(tokens: &Vec<&Bytes>, token: &Bytes) -> Result<U8, EncodingError> {
|
||||||
let position = U8::from(
|
let position = U8::from(
|
||||||
tokens
|
tokens
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ pub struct EncodingContext {
|
|||||||
/// * `Transfer`: Transfer the token from the router into the protocol.
|
/// * `Transfer`: Transfer the token from the router into the protocol.
|
||||||
/// * `None`: No transfer is needed. Tokens are already in the pool.
|
/// * `None`: No transfer is needed. Tokens are already in the pool.
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub enum TransferType {
|
pub enum TransferType {
|
||||||
TransferFrom = 0,
|
TransferFrom = 0,
|
||||||
Transfer = 1,
|
Transfer = 1,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ pub trait StrategyEncoder {
|
|||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// * `Result<EncodedSwaps, EncodingError>`
|
/// * `Result<EncodedSwaps, EncodingError>`
|
||||||
fn encode_strategy(&self, solution: Solution) -> Result<EncodedSolution, EncodingError>;
|
fn encode_strategy(&self, solution: &Solution) -> Result<EncodedSolution, EncodingError>;
|
||||||
|
|
||||||
/// Retrieves the swap encoder for a specific protocol system.
|
/// Retrieves the swap encoder for a specific protocol system.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ pub trait SwapEncoder: Sync + Send {
|
|||||||
/// The encoded swap data as bytes, directly executable on the executor contract
|
/// The encoded swap data as bytes, directly executable on the executor contract
|
||||||
fn encode_swap(
|
fn encode_swap(
|
||||||
&self,
|
&self,
|
||||||
swap: Swap,
|
swap: &Swap,
|
||||||
encoding_context: EncodingContext,
|
encoding_context: &EncodingContext,
|
||||||
) -> Result<Vec<u8>, EncodingError>;
|
) -> Result<Vec<u8>, EncodingError>;
|
||||||
|
|
||||||
/// Returns the address of the protocol-specific executor contract.
|
/// Returns the address of the protocol-specific executor contract.
|
||||||
|
|||||||
278
tests/common/encoding.rs
Normal file
278
tests/common/encoding.rs
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use alloy::{
|
||||||
|
primitives::{Address, Keccak256, U256},
|
||||||
|
signers::{local::PrivateKeySigner, Signature, SignerSync},
|
||||||
|
sol_types::{eip712_domain, SolStruct, SolValue},
|
||||||
|
};
|
||||||
|
use num_bigint::BigUint;
|
||||||
|
use tycho_common::Bytes;
|
||||||
|
use tycho_execution::encoding::{
|
||||||
|
errors::EncodingError,
|
||||||
|
evm::{
|
||||||
|
approvals::permit2::PermitSingle,
|
||||||
|
utils::{biguint_to_u256, bytes_to_address},
|
||||||
|
},
|
||||||
|
models,
|
||||||
|
models::{EncodedSolution, NativeAction, Solution, Transaction, UserTransferType},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Encodes a transaction for the Tycho Router using one of its supported swap methods.
|
||||||
|
///
|
||||||
|
/// # Overview
|
||||||
|
///
|
||||||
|
/// This function provides an **example implementation** of how to encode a call to the Tycho
|
||||||
|
/// Router. It handles all currently supported swap selectors such as:
|
||||||
|
/// - `singleSwap`
|
||||||
|
/// - `singleSwapPermit2`
|
||||||
|
/// - `sequentialSwap`
|
||||||
|
/// - `sequentialSwapPermit2`
|
||||||
|
/// - `splitSwap`
|
||||||
|
/// - `splitSwapPermit2`
|
||||||
|
///
|
||||||
|
/// The encoding includes handling of native asset wrapping/unwrapping, permit2 support,
|
||||||
|
/// and proper input argument formatting based on the function signature string.
|
||||||
|
///
|
||||||
|
/// # ⚠️ Important Responsibility Note
|
||||||
|
///
|
||||||
|
/// This function is intended as **an illustrative example only**. **Users must implement
|
||||||
|
/// their own encoding logic** to ensure:
|
||||||
|
/// - Full control of parameters passed to the router.
|
||||||
|
/// - Proper validation and setting of critical inputs such as `minAmountOut`.
|
||||||
|
/// - Signing of permit2 objects.
|
||||||
|
///
|
||||||
|
/// While Tycho is responsible for encoding the swap paths themselves, the input arguments
|
||||||
|
/// to the router's methods act as **guardrails** for on-chain execution safety.
|
||||||
|
/// Thus, the user must **take responsibility** for ensuring correctness of all input parameters,
|
||||||
|
/// including `minAmountOut`, `receiver`, and permit2 logic.
|
||||||
|
///
|
||||||
|
/// # Min Amount Out
|
||||||
|
///
|
||||||
|
/// The `minAmountOut` calculation used here is just an example.
|
||||||
|
/// You should ideally:
|
||||||
|
/// - Query an external service (e.g., DEX aggregators, oracle, off-chain price feed).
|
||||||
|
/// - Use your own strategy to determine an accurate and safe minimum acceptable output amount.
|
||||||
|
///
|
||||||
|
/// ⚠️ If `minAmountOut` is too low, your swap may be front-run or sandwiched, resulting in loss of
|
||||||
|
/// funds.
|
||||||
|
///
|
||||||
|
/// # Parameters
|
||||||
|
/// - `encoded_solution`: The solution already encoded by Tycho.
|
||||||
|
/// - `solution`: The high-level solution including tokens, amounts, and receiver info.
|
||||||
|
/// - `token_in_already_in_router`: Whether the input token is already present in the router.
|
||||||
|
/// - `router_address`: The address of the Tycho Router contract.
|
||||||
|
/// - `native_address`: The address used to represent the native token
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// A `Result<Transaction, EncodingError>` that either contains the full transaction data (to,
|
||||||
|
/// value, data), or an error if the inputs are invalid.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
/// - Returns `EncodingError::FatalError` if the function signature is unsupported or required
|
||||||
|
/// fields (e.g., permit or signature) are missing.
|
||||||
|
pub fn encode_tycho_router_call(
|
||||||
|
chain_id: u64,
|
||||||
|
encoded_solution: EncodedSolution,
|
||||||
|
solution: &Solution,
|
||||||
|
user_transfer_type: &UserTransferType,
|
||||||
|
native_address: &Bytes,
|
||||||
|
signer: Option<PrivateKeySigner>,
|
||||||
|
) -> Result<Transaction, EncodingError> {
|
||||||
|
let (mut unwrap, mut wrap) = (false, false);
|
||||||
|
if let Some(action) = solution.native_action.clone() {
|
||||||
|
match action {
|
||||||
|
NativeAction::Wrap => wrap = true,
|
||||||
|
NativeAction::Unwrap => unwrap = true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let given_amount = biguint_to_u256(&solution.given_amount);
|
||||||
|
let min_amount_out = biguint_to_u256(&solution.checked_amount);
|
||||||
|
let given_token = bytes_to_address(&solution.given_token)?;
|
||||||
|
let checked_token = bytes_to_address(&solution.checked_token)?;
|
||||||
|
let receiver = bytes_to_address(&solution.receiver)?;
|
||||||
|
let n_tokens = U256::from(encoded_solution.n_tokens);
|
||||||
|
let (permit, signature) = if let Some(p) = encoded_solution.permit {
|
||||||
|
let permit = Some(
|
||||||
|
PermitSingle::try_from(&p)
|
||||||
|
.map_err(|_| EncodingError::InvalidInput("Invalid permit".to_string()))?,
|
||||||
|
);
|
||||||
|
let signer = signer
|
||||||
|
.ok_or(EncodingError::FatalError("Signer must be set to use permit2".to_string()))?;
|
||||||
|
let signature = sign_permit(chain_id, &p, signer)?;
|
||||||
|
(permit, signature.as_bytes().to_vec())
|
||||||
|
} else {
|
||||||
|
(None, vec![])
|
||||||
|
};
|
||||||
|
|
||||||
|
let method_calldata = if encoded_solution
|
||||||
|
.function_signature
|
||||||
|
.contains("singleSwapPermit2")
|
||||||
|
{
|
||||||
|
(
|
||||||
|
given_amount,
|
||||||
|
given_token,
|
||||||
|
checked_token,
|
||||||
|
min_amount_out,
|
||||||
|
wrap,
|
||||||
|
unwrap,
|
||||||
|
receiver,
|
||||||
|
permit.ok_or(EncodingError::FatalError(
|
||||||
|
"permit2 object must be set to use permit2".to_string(),
|
||||||
|
))?,
|
||||||
|
signature,
|
||||||
|
encoded_solution.swaps,
|
||||||
|
)
|
||||||
|
.abi_encode()
|
||||||
|
} else if encoded_solution
|
||||||
|
.function_signature
|
||||||
|
.contains("singleSwap")
|
||||||
|
{
|
||||||
|
(
|
||||||
|
given_amount,
|
||||||
|
given_token,
|
||||||
|
checked_token,
|
||||||
|
min_amount_out,
|
||||||
|
wrap,
|
||||||
|
unwrap,
|
||||||
|
receiver,
|
||||||
|
*user_transfer_type == UserTransferType::TransferFrom,
|
||||||
|
encoded_solution.swaps,
|
||||||
|
)
|
||||||
|
.abi_encode()
|
||||||
|
} else if encoded_solution
|
||||||
|
.function_signature
|
||||||
|
.contains("sequentialSwapPermit2")
|
||||||
|
{
|
||||||
|
(
|
||||||
|
given_amount,
|
||||||
|
given_token,
|
||||||
|
checked_token,
|
||||||
|
min_amount_out,
|
||||||
|
wrap,
|
||||||
|
unwrap,
|
||||||
|
receiver,
|
||||||
|
permit.ok_or(EncodingError::FatalError(
|
||||||
|
"permit2 object must be set to use permit2".to_string(),
|
||||||
|
))?,
|
||||||
|
signature,
|
||||||
|
encoded_solution.swaps,
|
||||||
|
)
|
||||||
|
.abi_encode()
|
||||||
|
} else if encoded_solution
|
||||||
|
.function_signature
|
||||||
|
.contains("sequentialSwap")
|
||||||
|
{
|
||||||
|
(
|
||||||
|
given_amount,
|
||||||
|
given_token,
|
||||||
|
checked_token,
|
||||||
|
min_amount_out,
|
||||||
|
wrap,
|
||||||
|
unwrap,
|
||||||
|
receiver,
|
||||||
|
*user_transfer_type == UserTransferType::TransferFrom,
|
||||||
|
encoded_solution.swaps,
|
||||||
|
)
|
||||||
|
.abi_encode()
|
||||||
|
} else if encoded_solution
|
||||||
|
.function_signature
|
||||||
|
.contains("splitSwapPermit2")
|
||||||
|
{
|
||||||
|
(
|
||||||
|
given_amount,
|
||||||
|
given_token,
|
||||||
|
checked_token,
|
||||||
|
min_amount_out,
|
||||||
|
wrap,
|
||||||
|
unwrap,
|
||||||
|
n_tokens,
|
||||||
|
receiver,
|
||||||
|
permit.ok_or(EncodingError::FatalError(
|
||||||
|
"permit2 object must be set to use permit2".to_string(),
|
||||||
|
))?,
|
||||||
|
signature,
|
||||||
|
encoded_solution.swaps,
|
||||||
|
)
|
||||||
|
.abi_encode()
|
||||||
|
} else if encoded_solution
|
||||||
|
.function_signature
|
||||||
|
.contains("splitSwap")
|
||||||
|
{
|
||||||
|
(
|
||||||
|
given_amount,
|
||||||
|
given_token,
|
||||||
|
checked_token,
|
||||||
|
min_amount_out,
|
||||||
|
wrap,
|
||||||
|
unwrap,
|
||||||
|
n_tokens,
|
||||||
|
receiver,
|
||||||
|
*user_transfer_type == UserTransferType::TransferFrom,
|
||||||
|
encoded_solution.swaps,
|
||||||
|
)
|
||||||
|
.abi_encode()
|
||||||
|
} else {
|
||||||
|
Err(EncodingError::FatalError("Invalid function signature for Tycho router".to_string()))?
|
||||||
|
};
|
||||||
|
|
||||||
|
let contract_interaction = encode_input(&encoded_solution.function_signature, method_calldata);
|
||||||
|
let value = if solution.given_token == *native_address {
|
||||||
|
solution.given_amount.clone()
|
||||||
|
} else {
|
||||||
|
BigUint::ZERO
|
||||||
|
};
|
||||||
|
Ok(Transaction { to: encoded_solution.interacting_with, value, data: contract_interaction })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signs a Permit2 `PermitSingle` struct using the EIP-712 signing scheme.
|
||||||
|
///
|
||||||
|
/// This function constructs an EIP-712 domain specific to the Permit2 contract and computes the
|
||||||
|
/// hash of the provided `PermitSingle`. It then uses the given `PrivateKeySigner` to produce
|
||||||
|
/// a cryptographic signature of the permit.
|
||||||
|
///
|
||||||
|
/// # Warning
|
||||||
|
/// This is only an **example implementation** provided for reference purposes.
|
||||||
|
/// **Do not rely on this in production.** You should implement your own version.
|
||||||
|
fn sign_permit(
|
||||||
|
chain_id: u64,
|
||||||
|
permit_single: &models::PermitSingle,
|
||||||
|
signer: PrivateKeySigner,
|
||||||
|
) -> Result<Signature, EncodingError> {
|
||||||
|
let permit2_address = Address::from_str("0x000000000022D473030F116dDEE9F6B43aC78BA3")
|
||||||
|
.map_err(|_| EncodingError::FatalError("Permit2 address not valid".to_string()))?;
|
||||||
|
let domain = eip712_domain! {
|
||||||
|
name: "Permit2",
|
||||||
|
chain_id: chain_id,
|
||||||
|
verifying_contract: permit2_address,
|
||||||
|
};
|
||||||
|
let permit_single: PermitSingle = PermitSingle::try_from(permit_single)?;
|
||||||
|
let hash = permit_single.eip712_signing_hash(&domain);
|
||||||
|
signer
|
||||||
|
.sign_hash_sync(&hash)
|
||||||
|
.map_err(|e| {
|
||||||
|
EncodingError::FatalError(format!("Failed to sign permit2 approval with error: {e}"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes the input data for a function call to the given function selector.
|
||||||
|
fn encode_input(selector: &str, mut encoded_args: Vec<u8>) -> Vec<u8> {
|
||||||
|
let mut hasher = Keccak256::new();
|
||||||
|
hasher.update(selector.as_bytes());
|
||||||
|
let selector_bytes = &hasher.finalize()[..4];
|
||||||
|
let mut call_data = selector_bytes.to_vec();
|
||||||
|
// Remove extra prefix if present (32 bytes for dynamic data)
|
||||||
|
// Alloy encoding is including a prefix for dynamic data indicating the offset or length
|
||||||
|
// but at this point we don't want that
|
||||||
|
if encoded_args.len() > 32 &&
|
||||||
|
encoded_args[..32] ==
|
||||||
|
[0u8; 31]
|
||||||
|
.into_iter()
|
||||||
|
.chain([32].to_vec())
|
||||||
|
.collect::<Vec<u8>>()
|
||||||
|
{
|
||||||
|
encoded_args = encoded_args[32..].to_vec();
|
||||||
|
}
|
||||||
|
call_data.extend(encoded_args);
|
||||||
|
call_data
|
||||||
|
}
|
||||||
63
tests/common/mod.rs
Normal file
63
tests/common/mod.rs
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#![allow(dead_code)]
|
||||||
|
pub mod encoding;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use alloy::{primitives::B256, signers::local::PrivateKeySigner};
|
||||||
|
use tycho_common::{models::Chain as TychoCommonChain, Bytes};
|
||||||
|
use tycho_execution::encoding::{
|
||||||
|
evm::encoder_builders::TychoRouterEncoderBuilder,
|
||||||
|
models::{Chain, UserTransferType},
|
||||||
|
tycho_encoder::TychoEncoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn router_address() -> Bytes {
|
||||||
|
Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eth_chain() -> Chain {
|
||||||
|
TychoCommonChain::Ethereum.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eth() -> Bytes {
|
||||||
|
Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn weth() -> Bytes {
|
||||||
|
Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn usdc() -> Bytes {
|
||||||
|
Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dai() -> Bytes {
|
||||||
|
Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn wbtc() -> Bytes {
|
||||||
|
Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pepe() -> Bytes {
|
||||||
|
Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_signer() -> PrivateKeySigner {
|
||||||
|
// Set up a mock private key for signing (Alice's pk in our contract tests)
|
||||||
|
let private_key =
|
||||||
|
"0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234".to_string();
|
||||||
|
|
||||||
|
let pk = B256::from_str(&private_key).unwrap();
|
||||||
|
PrivateKeySigner::from_bytes(&pk).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_tycho_router_encoder(user_transfer_type: UserTransferType) -> Box<dyn TychoEncoder> {
|
||||||
|
TychoRouterEncoderBuilder::new()
|
||||||
|
.chain(tycho_common::models::Chain::Ethereum)
|
||||||
|
.user_transfer_type(user_transfer_type)
|
||||||
|
.executors_file_path("config/test_executor_addresses.json".to_string())
|
||||||
|
.router_address(router_address())
|
||||||
|
.build()
|
||||||
|
.expect("Failed to build encoder")
|
||||||
|
}
|
||||||
580
tests/optimized_transfers_integration_tests.rs
Normal file
580
tests/optimized_transfers_integration_tests.rs
Normal file
@@ -0,0 +1,580 @@
|
|||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
|
use alloy::hex::encode;
|
||||||
|
use num_bigint::{BigInt, BigUint};
|
||||||
|
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||||
|
use tycho_execution::encoding::{
|
||||||
|
evm::utils::write_calldata_to_file,
|
||||||
|
models::{Solution, Swap, UserTransferType},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::{
|
||||||
|
encoding::encode_tycho_router_call, eth, eth_chain, get_signer, get_tycho_router_encoder, weth,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod common;
|
||||||
|
// In this module we test the ability to chain swaps or not. Different protocols are
|
||||||
|
// tested. The encoded data is used for solidity tests as well
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uniswap_v3_uniswap_v2() {
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain
|
||||||
|
// integration test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// Performs a sequential swap from WETH to USDC though WBTC using USV3 and USV2
|
||||||
|
// pools
|
||||||
|
//
|
||||||
|
// WETH ───(USV3)──> WBTC ───(USV2)──> USDC
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||||
|
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
|
||||||
|
|
||||||
|
let swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(),
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let swap_wbtc_usdc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: usdc,
|
||||||
|
checked_amount: BigUint::from_str("26173932").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_uniswap_v3_uniswap_v2", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uniswap_v3_uniswap_v3() {
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain
|
||||||
|
// integration test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// Performs a sequential swap from WETH to USDC though WBTC using USV3 pools
|
||||||
|
// There is no optimization between the two USV3 pools, this is currently not
|
||||||
|
// supported.
|
||||||
|
//
|
||||||
|
// WETH ───(USV3)──> WBTC ───(USV3)──> USDC
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||||
|
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
|
||||||
|
|
||||||
|
let swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(),
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let swap_wbtc_usdc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x99ac8cA7087fA4A2A1FB6357269965A2014ABc35".to_string(),
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: usdc,
|
||||||
|
checked_amount: BigUint::from_str("26173932").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_uniswap_v3_uniswap_v3", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uniswap_v3_curve() {
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain
|
||||||
|
// integration test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// Performs a sequential swap from WETH to USDT though WBTC using USV3 and curve
|
||||||
|
// pools
|
||||||
|
//
|
||||||
|
// WETH ───(USV3)──> WBTC ───(curve)──> USDT
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||||
|
let usdt = Bytes::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap();
|
||||||
|
|
||||||
|
let swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(),
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap_wbtc_usdt = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: String::from("0xD51a44d3FaE010294C616388b506AcdA1bfAAE46"),
|
||||||
|
protocol_system: String::from("vm:curve"),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs: HashMap<String, Bytes> = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"factory".into(),
|
||||||
|
Bytes::from(
|
||||||
|
"0x0000000000000000000000000000000000000000"
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
attrs.insert(
|
||||||
|
"coins".into(),
|
||||||
|
Bytes::from_str("0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307863303261616133396232323366653864306130653563346632376561643930383363373536636332225d")
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: usdt.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: usdt,
|
||||||
|
checked_amount: BigUint::from_str("26173932").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_weth_wbtc, swap_wbtc_usdt],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_uniswap_v3_curve", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_balancer_v2_uniswap_v2() {
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain
|
||||||
|
// integration test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// Performs a sequential swap from WETH to USDC though WBTC using balancer and
|
||||||
|
// USV2 pools
|
||||||
|
//
|
||||||
|
// WETH ───(balancer)──> WBTC ───(USV2)──> USDC
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||||
|
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
|
||||||
|
|
||||||
|
let swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e".to_string(),
|
||||||
|
protocol_system: "vm:balancer_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap_wbtc_usdc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: usdc,
|
||||||
|
checked_amount: BigUint::from_str("26173932").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_balancer_v2_uniswap_v2", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multi_protocol() {
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain
|
||||||
|
// integration test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// Performs the following swap:
|
||||||
|
//
|
||||||
|
// DAI ─(USV2)-> WETH ─(bal)─> WBTC ─(curve)─> USDT ─(ekubo)─> USDC ─(USV4)─>
|
||||||
|
// ETH
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let eth = eth();
|
||||||
|
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||||
|
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
|
||||||
|
let usdt = Bytes::from_str("0xdAC17F958D2ee523a2206206994597C13D831ec7").unwrap();
|
||||||
|
let dai = Bytes::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap();
|
||||||
|
|
||||||
|
let usv2_swap_dai_weth = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: dai.clone(),
|
||||||
|
token_out: weth.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let balancer_swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e".to_string(),
|
||||||
|
protocol_system: "vm:balancer_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let curve_swap_wbtc_usdt = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: String::from("0xD51a44d3FaE010294C616388b506AcdA1bfAAE46"),
|
||||||
|
protocol_system: String::from("vm:curve"),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs: HashMap<String, Bytes> = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"factory".into(),
|
||||||
|
Bytes::from(
|
||||||
|
"0x0000000000000000000000000000000000000000"
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
attrs.insert(
|
||||||
|
"coins".into(),
|
||||||
|
Bytes::from_str("0x5b22307864616331376639353864326565353233613232303632303639393435393763313364383331656337222c22307832323630666163356535353432613737336161343466626366656466376331393362633263353939222c22307863303261616133396232323366653864306130653563346632376561643930383363373536636332225d")
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: usdt.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Ekubo
|
||||||
|
|
||||||
|
let component = ProtocolComponent {
|
||||||
|
// All Ekubo swaps go through the core contract - not necessary to specify
|
||||||
|
// pool id for test
|
||||||
|
protocol_system: "ekubo_v2".to_string(),
|
||||||
|
// 0.0025% fee & 0.005% base pool
|
||||||
|
static_attributes: HashMap::from([
|
||||||
|
("fee".to_string(), Bytes::from(461168601842738_u64)),
|
||||||
|
("tick_spacing".to_string(), Bytes::from(50_u32)),
|
||||||
|
("extension".to_string(), Bytes::zero(20)),
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let ekubo_swap_usdt_usdc = Swap {
|
||||||
|
component,
|
||||||
|
token_in: usdt.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// USV4
|
||||||
|
// Fee and tick spacing information for this test is obtained by querying the
|
||||||
|
// USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e
|
||||||
|
// Using the poolKeys function with the first 25 bytes of the pool id
|
||||||
|
let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be());
|
||||||
|
let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be());
|
||||||
|
let mut static_attributes_usdc_eth: HashMap<String, Bytes> = HashMap::new();
|
||||||
|
static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth);
|
||||||
|
static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth);
|
||||||
|
|
||||||
|
let usv4_swap_usdc_eth = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d".to_string(),
|
||||||
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
|
static_attributes: static_attributes_usdc_eth,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: usdc.clone(),
|
||||||
|
token_out: eth.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
// Put all components together
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: dai,
|
||||||
|
given_amount: BigUint::from_str("1500_000000000000000000").unwrap(),
|
||||||
|
checked_token: eth.clone(),
|
||||||
|
checked_amount: BigUint::from_str("732214216964381330").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![
|
||||||
|
usv2_swap_dai_weth,
|
||||||
|
balancer_swap_weth_wbtc,
|
||||||
|
curve_swap_wbtc_usdt,
|
||||||
|
ekubo_swap_usdt_usdc,
|
||||||
|
usv4_swap_usdc_eth,
|
||||||
|
],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð,
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_multi_protocol", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_uniswap_v3_balancer_v3() {
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain
|
||||||
|
// integration test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// WETH ───(USV3)──> WBTC ───(balancer v3)──> QNT
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
|
||||||
|
let qnt = Bytes::from_str("0x4a220e6096b25eadb88358cb44068a3248254675").unwrap();
|
||||||
|
|
||||||
|
let swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xCBCdF9626bC03E24f779434178A73a0B4bad62eD".to_string(),
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let swap_wbtc_qnt = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x571bea0e99e139cd0b6b7d9352ca872dfe0d72dd".to_string(),
|
||||||
|
protocol_system: "vm:balancer_v3".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: qnt.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_0000000000000000").unwrap(),
|
||||||
|
checked_token: qnt,
|
||||||
|
checked_amount: BigUint::from_str("26173932").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_weth_wbtc, swap_wbtc_qnt],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_uniswap_v3_balancer_v3", hex_calldata.as_str());
|
||||||
|
}
|
||||||
574
tests/protocol_integration_tests.rs
Normal file
574
tests/protocol_integration_tests.rs
Normal file
@@ -0,0 +1,574 @@
|
|||||||
|
mod common;
|
||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
|
use alloy::hex::encode;
|
||||||
|
use num_bigint::{BigInt, BigUint};
|
||||||
|
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||||
|
use tycho_execution::encoding::{
|
||||||
|
evm::utils::write_calldata_to_file,
|
||||||
|
models::{Solution, Swap, UserTransferType},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::{
|
||||||
|
encoding::encode_tycho_router_call, eth, eth_chain, get_signer, get_tycho_router_encoder, pepe,
|
||||||
|
usdc, weth,
|
||||||
|
};
|
||||||
|
#[test]
|
||||||
|
fn test_single_encoding_strategy_ekubo() {
|
||||||
|
// ETH ──(EKUBO)──> USDC
|
||||||
|
|
||||||
|
let token_in = eth();
|
||||||
|
let token_out = usdc(); // USDC
|
||||||
|
|
||||||
|
let static_attributes = HashMap::from([
|
||||||
|
("fee".to_string(), Bytes::from(0_u64)),
|
||||||
|
("tick_spacing".to_string(), Bytes::from(0_u32)),
|
||||||
|
("extension".to_string(), Bytes::from("0x51d02a5948496a67827242eabc5725531342527c")), /* Oracle */
|
||||||
|
]);
|
||||||
|
|
||||||
|
let component = ProtocolComponent {
|
||||||
|
// All Ekubo swaps go through the core contract - not necessary to specify pool
|
||||||
|
// id for test
|
||||||
|
protocol_system: "ekubo_v2".to_string(),
|
||||||
|
static_attributes,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component,
|
||||||
|
token_in: token_in.clone(),
|
||||||
|
token_out: token_out.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: token_in,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: token_out,
|
||||||
|
checked_amount: BigUint::from_str("1000").unwrap(),
|
||||||
|
// Alice
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_single_encoding_strategy_ekubo", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_encoding_strategy_maverick() {
|
||||||
|
// GHO -> (maverick) -> USDC
|
||||||
|
let maverick_pool = ProtocolComponent {
|
||||||
|
id: String::from("0x14Cf6D2Fe3E1B326114b07d22A6F6bb59e346c67"),
|
||||||
|
protocol_system: String::from("vm:maverick_v2"),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let token_in = Bytes::from("0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f");
|
||||||
|
let token_out = usdc();
|
||||||
|
let swap = Swap {
|
||||||
|
component: maverick_pool,
|
||||||
|
token_in: token_in.clone(),
|
||||||
|
token_out: token_out.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: token_in,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: token_out,
|
||||||
|
checked_amount: BigUint::from_str("1000").unwrap(),
|
||||||
|
// Alice
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_single_encoding_strategy_maverick", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_encoding_strategy_usv4_eth_in() {
|
||||||
|
// Performs a single swap from ETH to PEPE using a USV4 pool
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain integration
|
||||||
|
// test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// ETH ───(USV4)──> PEPE
|
||||||
|
//
|
||||||
|
let eth = eth();
|
||||||
|
let pepe = pepe();
|
||||||
|
|
||||||
|
let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be());
|
||||||
|
let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be());
|
||||||
|
let mut static_attributes_eth_pepe: HashMap<String, Bytes> = HashMap::new();
|
||||||
|
static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe);
|
||||||
|
static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe);
|
||||||
|
|
||||||
|
let swap_eth_pepe = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9".to_string(),
|
||||||
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
|
static_attributes: static_attributes_eth_pepe,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: eth.clone(),
|
||||||
|
token_out: pepe.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: eth.clone(),
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: pepe,
|
||||||
|
checked_amount: BigUint::from_str("152373460199848577067005852").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_eth_pepe],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð,
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
|
||||||
|
write_calldata_to_file("test_single_encoding_strategy_usv4_eth_in", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_encoding_strategy_usv4_eth_out() {
|
||||||
|
// Performs a single swap from USDC to ETH using a USV4 pool
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain integration
|
||||||
|
// test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// USDC ───(USV4)──> ETH
|
||||||
|
//
|
||||||
|
let eth = eth();
|
||||||
|
let usdc = usdc();
|
||||||
|
|
||||||
|
// Fee and tick spacing information for this test is obtained by querying the
|
||||||
|
// USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e
|
||||||
|
// Using the poolKeys function with the first 25 bytes of the pool id
|
||||||
|
let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be());
|
||||||
|
let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be());
|
||||||
|
let mut static_attributes_usdc_eth: HashMap<String, Bytes> = HashMap::new();
|
||||||
|
static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth);
|
||||||
|
static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth);
|
||||||
|
|
||||||
|
let swap_usdc_eth = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d".to_string(),
|
||||||
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
|
static_attributes: static_attributes_usdc_eth,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: usdc.clone(),
|
||||||
|
token_out: eth.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: usdc,
|
||||||
|
given_amount: BigUint::from_str("3000_000000").unwrap(),
|
||||||
|
checked_token: eth.clone(),
|
||||||
|
checked_amount: BigUint::from_str("1117254495486192350").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_usdc_eth],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð,
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_single_encoding_strategy_usv4_eth_out", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_encoding_strategy_usv4_grouped_swap() {
|
||||||
|
// Performs a sequential swap from USDC to PEPE though ETH using two consecutive
|
||||||
|
// USV4 pools
|
||||||
|
//
|
||||||
|
// USDC ──(USV4)──> ETH ───(USV4)──> PEPE
|
||||||
|
//
|
||||||
|
|
||||||
|
let eth = eth();
|
||||||
|
let usdc = usdc();
|
||||||
|
let pepe = pepe();
|
||||||
|
|
||||||
|
// Fee and tick spacing information for this test is obtained by querying the
|
||||||
|
// USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e
|
||||||
|
// Using the poolKeys function with the first 25 bytes of the pool id
|
||||||
|
let pool_fee_usdc_eth = Bytes::from(BigInt::from(3000).to_signed_bytes_be());
|
||||||
|
let tick_spacing_usdc_eth = Bytes::from(BigInt::from(60).to_signed_bytes_be());
|
||||||
|
let mut static_attributes_usdc_eth: HashMap<String, Bytes> = HashMap::new();
|
||||||
|
static_attributes_usdc_eth.insert("key_lp_fee".into(), pool_fee_usdc_eth);
|
||||||
|
static_attributes_usdc_eth.insert("tick_spacing".into(), tick_spacing_usdc_eth);
|
||||||
|
|
||||||
|
let pool_fee_eth_pepe = Bytes::from(BigInt::from(25000).to_signed_bytes_be());
|
||||||
|
let tick_spacing_eth_pepe = Bytes::from(BigInt::from(500).to_signed_bytes_be());
|
||||||
|
let mut static_attributes_eth_pepe: HashMap<String, Bytes> = HashMap::new();
|
||||||
|
static_attributes_eth_pepe.insert("key_lp_fee".into(), pool_fee_eth_pepe);
|
||||||
|
static_attributes_eth_pepe.insert("tick_spacing".into(), tick_spacing_eth_pepe);
|
||||||
|
|
||||||
|
let swap_usdc_eth = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xdce6394339af00981949f5f3baf27e3610c76326a700af57e4b3e3ae4977f78d".to_string(),
|
||||||
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
|
static_attributes: static_attributes_usdc_eth,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: usdc.clone(),
|
||||||
|
token_out: eth.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap_eth_pepe = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xecd73ecbf77219f21f129c8836d5d686bbc27d264742ddad620500e3e548e2c9".to_string(),
|
||||||
|
protocol_system: "uniswap_v4".to_string(),
|
||||||
|
static_attributes: static_attributes_eth_pepe,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: eth.clone(),
|
||||||
|
token_out: pepe.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: usdc,
|
||||||
|
given_amount: BigUint::from_str("1000_000000").unwrap(),
|
||||||
|
checked_token: pepe,
|
||||||
|
checked_amount: BigUint::from_str("97191013220606467325121599").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_usdc_eth, swap_eth_pepe],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð,
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let expected_input = [
|
||||||
|
"30ace1b1", // Function selector (single swap)
|
||||||
|
"000000000000000000000000000000000000000000000000000000003b9aca00", // amount in
|
||||||
|
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
|
||||||
|
"0000000000000000000000006982508145454ce325ddbe47a25d4ec3d2311933", // token out
|
||||||
|
"0000000000000000000000000000000000000000005064ff624d54346285543f", // min amount out
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
// after this there is the permit and because of the deadlines (that depend on block
|
||||||
|
// time) it's hard to assert
|
||||||
|
|
||||||
|
let expected_swaps = String::from(concat!(
|
||||||
|
// length of ple encoded swaps without padding
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000086",
|
||||||
|
// Swap data header
|
||||||
|
"f62849f9a0b5bf2913b396098f7c7019b51a820a", // executor address
|
||||||
|
// Protocol data
|
||||||
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // group token in
|
||||||
|
"6982508145454ce325ddbe47a25d4ec3d2311933", // group token in
|
||||||
|
"00", // zero2one
|
||||||
|
"00", // transfer type TransferFrom
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
// First pool params
|
||||||
|
"0000000000000000000000000000000000000000", // intermediary token (ETH)
|
||||||
|
"000bb8", // fee
|
||||||
|
"00003c", // tick spacing
|
||||||
|
// Second pool params
|
||||||
|
"6982508145454ce325ddbe47a25d4ec3d2311933", // intermediary token (PEPE)
|
||||||
|
"0061a8", // fee
|
||||||
|
"0001f4", // tick spacing
|
||||||
|
"0000000000000000000000000000000000000000000000000000" // padding
|
||||||
|
));
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
|
||||||
|
assert_eq!(hex_calldata[..456], expected_input);
|
||||||
|
assert_eq!(hex_calldata[1224..], expected_swaps);
|
||||||
|
write_calldata_to_file(
|
||||||
|
"test_single_encoding_strategy_usv4_grouped_swap",
|
||||||
|
hex_calldata.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_encoding_strategy_curve() {
|
||||||
|
// UWU ──(curve 2 crypto pool)──> WETH
|
||||||
|
|
||||||
|
let token_in = Bytes::from("0x55C08ca52497e2f1534B59E2917BF524D4765257"); // UWU
|
||||||
|
let token_out = weth();
|
||||||
|
|
||||||
|
let static_attributes = HashMap::from([(
|
||||||
|
"factory".to_string(),
|
||||||
|
Bytes::from(
|
||||||
|
"0x98ee851a00abee0d95d08cf4ca2bdce32aeaaf7f"
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
)),
|
||||||
|
("coins".to_string(), Bytes::from_str("0x5b22307863303261616133396232323366653864306130653563346632376561643930383363373536636332222c22307835356330386361353234393765326631353334623539653239313762663532346434373635323537225d").unwrap()),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let component = ProtocolComponent {
|
||||||
|
id: String::from("0x77146B0a1d08B6844376dF6d9da99bA7F1b19e71"),
|
||||||
|
protocol_system: String::from("vm:curve"),
|
||||||
|
static_attributes,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component,
|
||||||
|
token_in: token_in.clone(),
|
||||||
|
token_out: token_out.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: token_in,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: token_out,
|
||||||
|
checked_amount: BigUint::from_str("1").unwrap(),
|
||||||
|
// Alice
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_single_encoding_strategy_curve", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_encoding_strategy_curve_st_eth() {
|
||||||
|
// ETH ──(curve stETH pool)──> STETH
|
||||||
|
|
||||||
|
let token_in = eth();
|
||||||
|
let token_out = Bytes::from("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"); // STETH
|
||||||
|
|
||||||
|
let static_attributes = HashMap::from([(
|
||||||
|
"factory".to_string(),
|
||||||
|
Bytes::from(
|
||||||
|
"0x0000000000000000000000000000000000000000"
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("coins".to_string(), Bytes::from_str("0x5b22307865656565656565656565656565656565656565656565656565656565656565656565656565656565222c22307861653761623936353230646533613138653565313131623565616162303935333132643766653834225d").unwrap()),]);
|
||||||
|
|
||||||
|
let component = ProtocolComponent {
|
||||||
|
id: String::from("0xDC24316b9AE028F1497c275EB9192a3Ea0f67022"),
|
||||||
|
protocol_system: String::from("vm:curve"),
|
||||||
|
static_attributes,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component,
|
||||||
|
token_in: token_in.clone(),
|
||||||
|
token_out: token_out.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: token_in,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: token_out,
|
||||||
|
checked_amount: BigUint::from_str("1").unwrap(),
|
||||||
|
// Alice
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_single_encoding_strategy_curve_st_eth", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_encoding_strategy_balancer_v3() {
|
||||||
|
// steakUSDTlite -> (balancer v3) -> steakUSDR
|
||||||
|
let balancer_pool = ProtocolComponent {
|
||||||
|
id: String::from("0xf028ac624074d6793c36dc8a06ecec0f5a39a718"),
|
||||||
|
protocol_system: String::from("vm:balancer_v3"),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let token_in = Bytes::from("0x097ffedb80d4b2ca6105a07a4d90eb739c45a666");
|
||||||
|
let token_out = Bytes::from("0x30881baa943777f92dc934d53d3bfdf33382cab3");
|
||||||
|
let swap = Swap {
|
||||||
|
component: balancer_pool,
|
||||||
|
token_in: token_in.clone(),
|
||||||
|
token_out: token_out.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: token_in,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: token_out,
|
||||||
|
checked_amount: BigUint::from_str("1000").unwrap(),
|
||||||
|
// Alice
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_single_encoding_strategy_balancer_v3", hex_calldata.as_str());
|
||||||
|
}
|
||||||
312
tests/sequential_strategy_integration_tests.rs
Normal file
312
tests/sequential_strategy_integration_tests.rs
Normal file
@@ -0,0 +1,312 @@
|
|||||||
|
mod common;
|
||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
|
use alloy::hex::encode;
|
||||||
|
use num_bigint::{BigInt, BigUint};
|
||||||
|
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||||
|
use tycho_execution::encoding::{
|
||||||
|
evm::utils::write_calldata_to_file,
|
||||||
|
models::{Solution, Swap, UserTransferType},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::{
|
||||||
|
encoding::encode_tycho_router_call, eth, eth_chain, get_signer, get_tycho_router_encoder, usdc,
|
||||||
|
wbtc, weth,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sequential_swap_strategy_encoder() {
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain integration
|
||||||
|
// test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// Performs a sequential swap from WETH to USDC though WBTC using USV2 pools
|
||||||
|
//
|
||||||
|
// WETH ───(USV2)──> WBTC ───(USV2)──> USDC
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let wbtc = wbtc();
|
||||||
|
let usdc = usdc();
|
||||||
|
|
||||||
|
let swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let swap_wbtc_usdc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: usdc,
|
||||||
|
checked_amount: BigUint::from_str("26173932").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð(),
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_sequential_swap_strategy_encoder", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sequential_swap_strategy_encoder_no_permit2() {
|
||||||
|
// Performs a sequential swap from WETH to USDC though WBTC using USV2 pools
|
||||||
|
//
|
||||||
|
// WETH ───(USV2)──> WBTC ───(USV2)──> USDC
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let wbtc = wbtc();
|
||||||
|
let usdc = usdc();
|
||||||
|
|
||||||
|
let swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let swap_wbtc_usdc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: usdc,
|
||||||
|
checked_amount: BigUint::from_str("26173932").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_weth_wbtc, swap_wbtc_usdc],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
|
||||||
|
let expected = String::from(concat!(
|
||||||
|
"e21dd0d3", /* function selector */
|
||||||
|
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", /* amount in */
|
||||||
|
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token ou
|
||||||
|
"00000000000000000000000000000000000000000000000000000000018f61ec", /* min amount out */
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001", /* transfer from
|
||||||
|
* needed */
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000120", /* length ple
|
||||||
|
* encode */
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000a8",
|
||||||
|
// swap 1
|
||||||
|
"0052", // swap length
|
||||||
|
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"bb2b8038a1640196fbe3e38816f3e67cba72d940", // component id
|
||||||
|
"004375dff511095cc5a197a54140a24efef3a416", // receiver (next pool)
|
||||||
|
"00", // zero to one
|
||||||
|
"00", // transfer type TransferFrom
|
||||||
|
// swap 2
|
||||||
|
"0052", // swap length
|
||||||
|
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
|
||||||
|
"2260fac5e5542a773aa44fbcfedf7c193bc2c599", // token in
|
||||||
|
"004375dff511095cc5a197a54140a24efef3a416", // component id
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver (final user)
|
||||||
|
"01", // zero to one
|
||||||
|
"02", // transfer type None
|
||||||
|
"000000000000000000000000000000000000000000000000", // padding
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(hex_calldata, expected);
|
||||||
|
write_calldata_to_file(
|
||||||
|
"test_sequential_swap_strategy_encoder_no_permit2",
|
||||||
|
hex_calldata.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sequential_strategy_cyclic_swap() {
|
||||||
|
// This test has start and end tokens that are the same
|
||||||
|
// The flow is:
|
||||||
|
// USDC -> WETH -> USDC using two pools
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let usdc = usdc();
|
||||||
|
|
||||||
|
// Create two Uniswap V3 pools for the cyclic swap
|
||||||
|
// USDC -> WETH (Pool 1)
|
||||||
|
let swap_usdc_weth = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
|
||||||
|
* Pool 1 */
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs
|
||||||
|
.insert("fee".to_string(), Bytes::from(BigInt::from(500).to_signed_bytes_be()));
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: usdc.clone(),
|
||||||
|
token_out: weth.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// WETH -> USDC (Pool 2)
|
||||||
|
let swap_weth_usdc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
|
||||||
|
* Pool 2 */
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: usdc.clone(),
|
||||||
|
given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals)
|
||||||
|
checked_token: usdc.clone(),
|
||||||
|
checked_amount: BigUint::from_str("99389294").unwrap(), /* Expected output
|
||||||
|
* from test */
|
||||||
|
swaps: vec![swap_usdc_weth, swap_weth_usdc],
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð(),
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let hex_calldata = alloy::hex::encode(&calldata);
|
||||||
|
let expected_input = [
|
||||||
|
"51bcc7b6", // selector
|
||||||
|
"0000000000000000000000000000000000000000000000000000000005f5e100", // given amount
|
||||||
|
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token
|
||||||
|
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token
|
||||||
|
"0000000000000000000000000000000000000000000000000000000005ec8f6e", // min amount out
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap action
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap action
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
let expected_swaps = [
|
||||||
|
"00000000000000000000000000000000000000000000000000000000000000d6", // length of ple encoded swaps without padding
|
||||||
|
"0069", // ple encoded swaps
|
||||||
|
"2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
|
||||||
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out
|
||||||
|
"0001f4", // pool fee
|
||||||
|
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
|
||||||
|
"88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
|
||||||
|
"01", // zero2one
|
||||||
|
"00", // transfer type TransferFrom
|
||||||
|
"0069", // ple encoded swaps
|
||||||
|
"2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out
|
||||||
|
"000bb8", // pool fee
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
|
||||||
|
"00", // zero2one
|
||||||
|
"01", // transfer type Transfer
|
||||||
|
"00000000000000000000", // padding
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
assert_eq!(hex_calldata[..456], expected_input);
|
||||||
|
assert_eq!(hex_calldata[1224..], expected_swaps);
|
||||||
|
write_calldata_to_file("test_sequential_strategy_cyclic_swap", hex_calldata.as_str());
|
||||||
|
}
|
||||||
372
tests/single_strategy_integration_tests.rs
Normal file
372
tests/single_strategy_integration_tests.rs
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
mod common;
|
||||||
|
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use alloy::{hex::encode, primitives::U256, sol_types::SolValue};
|
||||||
|
use num_bigint::BigUint;
|
||||||
|
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||||
|
use tycho_execution::encoding::{
|
||||||
|
evm::utils::{biguint_to_u256, write_calldata_to_file},
|
||||||
|
models::{NativeAction, Solution, Swap, UserTransferType},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::{
|
||||||
|
dai, encoding::encode_tycho_router_call, eth, eth_chain, get_signer, get_tycho_router_encoder,
|
||||||
|
weth,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_swap_strategy_encoder() {
|
||||||
|
// Performs a single swap from WETH to DAI on a USV2 pool, with no grouping
|
||||||
|
// optimizations.
|
||||||
|
let checked_amount = BigUint::from_str("2018817438608734439720").unwrap();
|
||||||
|
let weth = weth();
|
||||||
|
let dai = dai();
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: dai.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: dai,
|
||||||
|
checked_amount: checked_amount.clone(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solutions = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solutions[0].clone(),
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð(),
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let expected_min_amount_encoded = encode(U256::abi_encode(&biguint_to_u256(&checked_amount)));
|
||||||
|
let expected_input = [
|
||||||
|
"30ace1b1", // Function selector
|
||||||
|
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
|
||||||
|
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
|
||||||
|
&expected_min_amount_encoded, // min amount out
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
// after this there is the permit and because of the deadlines (that depend on block
|
||||||
|
// time) it's hard to assert
|
||||||
|
|
||||||
|
let expected_swap = String::from(concat!(
|
||||||
|
// length of encoded swap without padding
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000052",
|
||||||
|
// Swap data
|
||||||
|
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"00", // zero2one
|
||||||
|
"00", // transfer type TransferFrom
|
||||||
|
"0000000000000000000000000000", // padding
|
||||||
|
));
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
|
||||||
|
assert_eq!(hex_calldata[..456], expected_input);
|
||||||
|
assert_eq!(hex_calldata[1224..], expected_swap);
|
||||||
|
write_calldata_to_file("test_single_swap_strategy_encoder", &hex_calldata.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_swap_strategy_encoder_no_permit2() {
|
||||||
|
// Performs a single swap from WETH to DAI on a USV2 pool, without permit2 and no
|
||||||
|
// grouping optimizations.
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let dai = dai();
|
||||||
|
|
||||||
|
let checked_amount = BigUint::from_str("1_640_000000000000000000").unwrap();
|
||||||
|
let expected_min_amount = U256::from_str("1_640_000000000000000000").unwrap();
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: dai.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: dai,
|
||||||
|
checked_amount,
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFrom,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let expected_min_amount_encoded = encode(U256::abi_encode(&expected_min_amount));
|
||||||
|
let expected_input = [
|
||||||
|
"5c4b639c", // Function selector
|
||||||
|
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
|
||||||
|
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
|
||||||
|
&expected_min_amount_encoded, // min amount out
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000001", // transfer from needed
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000120", // offset of swap bytes
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000052", /* length of swap
|
||||||
|
* bytes without
|
||||||
|
* padding */
|
||||||
|
// Swap data
|
||||||
|
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"00", // zero2one
|
||||||
|
"00", // transfer type TransferFrom
|
||||||
|
"0000000000000000000000000000", // padding
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
|
||||||
|
assert_eq!(hex_calldata, expected_input);
|
||||||
|
write_calldata_to_file("test_single_swap_strategy_encoder_no_permit2", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_swap_strategy_encoder_no_transfer_in() {
|
||||||
|
// Performs a single swap from WETH to DAI on a USV2 pool assuming that the tokens
|
||||||
|
// are already in the router
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let dai = dai();
|
||||||
|
|
||||||
|
let checked_amount = BigUint::from_str("1_640_000000000000000000").unwrap();
|
||||||
|
let expected_min_amount = U256::from_str("1_640_000000000000000000").unwrap();
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: dai.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::None);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: dai,
|
||||||
|
checked_amount,
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::None,
|
||||||
|
ð(),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let expected_min_amount_encoded = encode(U256::abi_encode(&expected_min_amount));
|
||||||
|
let expected_input = [
|
||||||
|
"5c4b639c", // Function selector
|
||||||
|
"0000000000000000000000000000000000000000000000000de0b6b3a7640000", // amount in
|
||||||
|
"000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"0000000000000000000000006b175474e89094c44da98b954eedeac495271d0f", // token out
|
||||||
|
&expected_min_amount_encoded, // min amount out
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", /* transfer from not
|
||||||
|
* needed */
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000120", // offset of swap bytes
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000052", /* length of swap
|
||||||
|
* bytes without
|
||||||
|
* padding */
|
||||||
|
// Swap data
|
||||||
|
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"a478c2975ab1ea89e8196811f51a7b7ade33eb11", // component id
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"00", // zero2one
|
||||||
|
"01", // transfer type Transfer
|
||||||
|
"0000000000000000000000000000", // padding
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
|
||||||
|
assert_eq!(hex_calldata, expected_input);
|
||||||
|
write_calldata_to_file(
|
||||||
|
"test_single_swap_strategy_encoder_no_transfer_in",
|
||||||
|
hex_calldata.as_str(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_swap_strategy_encoder_wrap() {
|
||||||
|
// Performs a single swap from WETH to DAI on a USV2 pool, wrapping ETH
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain integration
|
||||||
|
// test data for our router solidity test.
|
||||||
|
|
||||||
|
let dai = dai();
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth(),
|
||||||
|
token_out: dai.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: eth(),
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: dai,
|
||||||
|
checked_amount: BigUint::from_str("1659881924818443699787").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
native_action: Some(NativeAction::Wrap),
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð(),
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_single_swap_strategy_encoder_wrap", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_swap_strategy_encoder_unwrap() {
|
||||||
|
// Performs a single swap from DAI to WETH on a USV2 pool, unwrapping ETH at the end
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain integration
|
||||||
|
// test data for our router solidity test.
|
||||||
|
|
||||||
|
let dai = dai();
|
||||||
|
|
||||||
|
let swap = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: dai.clone(),
|
||||||
|
token_out: weth(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: dai,
|
||||||
|
given_amount: BigUint::from_str("3_000_000000000000000000").unwrap(),
|
||||||
|
checked_token: eth(),
|
||||||
|
checked_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap],
|
||||||
|
native_action: Some(NativeAction::Unwrap),
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð(),
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_single_swap_strategy_encoder_unwrap", hex_calldata.as_str());
|
||||||
|
}
|
||||||
442
tests/split_strategy_integration_tests.rs
Normal file
442
tests/split_strategy_integration_tests.rs
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
mod common;
|
||||||
|
|
||||||
|
use std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
|
use alloy::hex::encode;
|
||||||
|
use num_bigint::{BigInt, BigUint};
|
||||||
|
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
|
||||||
|
use tycho_execution::encoding::{
|
||||||
|
evm::utils::write_calldata_to_file,
|
||||||
|
models::{Solution, Swap, UserTransferType},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::common::{
|
||||||
|
dai, encoding::encode_tycho_router_call, eth, eth_chain, get_signer, get_tycho_router_encoder,
|
||||||
|
usdc, wbtc, weth,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_swap_strategy_encoder() {
|
||||||
|
// Note: This test does not assert anything. It is only used to obtain integration
|
||||||
|
// test data for our router solidity test.
|
||||||
|
//
|
||||||
|
// Performs a split swap from WETH to USDC though WBTC and DAI using USV2 pools
|
||||||
|
//
|
||||||
|
// ┌──(USV2)──> WBTC ───(USV2)──> USDC
|
||||||
|
// WETH ─┤
|
||||||
|
// └──(USV2)──> DAI ───(USV2)──> USDC
|
||||||
|
//
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let dai = dai();
|
||||||
|
let wbtc = wbtc();
|
||||||
|
let usdc = usdc();
|
||||||
|
|
||||||
|
let swap_weth_dai = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: dai.clone(),
|
||||||
|
split: 0.5f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let swap_weth_wbtc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xBb2b8038a1640196FbE3e38816F3e67Cba72D940".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: wbtc.clone(),
|
||||||
|
// This represents the remaining 50%, but to avoid any rounding errors we set
|
||||||
|
// this to 0 to signify "the remainder of the WETH value".
|
||||||
|
// It should still be very close to 50%
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let swap_dai_usdc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: dai.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let swap_wbtc_usdc = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x004375Dff511095CC5A197A54140a24eFEF3A416".to_string(),
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: wbtc.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: weth,
|
||||||
|
given_amount: BigUint::from_str("1_000000000000000000").unwrap(),
|
||||||
|
checked_token: usdc,
|
||||||
|
checked_amount: BigUint::from_str("26173932").unwrap(),
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð(),
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = encode(&calldata);
|
||||||
|
write_calldata_to_file("test_split_swap_strategy_encoder", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_input_cyclic_swap() {
|
||||||
|
// This test has start and end tokens that are the same
|
||||||
|
// The flow is:
|
||||||
|
// ┌─ (USV3, 60% split) ──> WETH ─┐
|
||||||
|
// │ │
|
||||||
|
// USDC ──────┤ ├──(USV2)──> USDC
|
||||||
|
// │ │
|
||||||
|
// └─ (USV3, 40% split) ──> WETH ─┘
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let usdc = usdc();
|
||||||
|
|
||||||
|
// USDC -> WETH (Pool 1) - 60% of input
|
||||||
|
let swap_usdc_weth_pool1 = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
|
||||||
|
* Pool 1 */
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs
|
||||||
|
.insert("fee".to_string(), Bytes::from(BigInt::from(500).to_signed_bytes_be()));
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: usdc.clone(),
|
||||||
|
token_out: weth.clone(),
|
||||||
|
split: 0.6f64, // 60% of input
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// USDC -> WETH (Pool 2) - 40% of input (remaining)
|
||||||
|
let swap_usdc_weth_pool2 = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
|
||||||
|
* Pool 2 */
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: usdc.clone(),
|
||||||
|
token_out: weth.clone(),
|
||||||
|
split: 0f64,
|
||||||
|
user_data: None, // Remaining 40%
|
||||||
|
};
|
||||||
|
|
||||||
|
// WETH -> USDC (Pool 2)
|
||||||
|
let swap_weth_usdc_pool2 = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), /* USDC-WETH USV2
|
||||||
|
* Pool 2 */
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0.0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: usdc.clone(),
|
||||||
|
given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals)
|
||||||
|
checked_token: usdc.clone(),
|
||||||
|
checked_amount: BigUint::from_str("99574171").unwrap(), /* Expected output
|
||||||
|
* from
|
||||||
|
* test */
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_usdc_weth_pool1, swap_usdc_weth_pool2, swap_weth_usdc_pool2],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð(),
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = alloy::hex::encode(&calldata);
|
||||||
|
let expected_input = [
|
||||||
|
"7c553846", // selector
|
||||||
|
"0000000000000000000000000000000000000000000000000000000005f5e100", // given amount
|
||||||
|
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token
|
||||||
|
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token
|
||||||
|
"0000000000000000000000000000000000000000000000000000000005ef619b", // min amount out
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap action
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap action
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // tokens length
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
let expected_swaps = [
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding
|
||||||
|
"006e", // ple encoded swaps
|
||||||
|
"00", // token in index
|
||||||
|
"01", // token out index
|
||||||
|
"999999", // split
|
||||||
|
"2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
|
||||||
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out
|
||||||
|
"0001f4", // pool fee
|
||||||
|
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
|
||||||
|
"88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
|
||||||
|
"01", // zero2one
|
||||||
|
"00", // transfer type TransferFrom
|
||||||
|
"006e", // ple encoded swaps
|
||||||
|
"00", // token in index
|
||||||
|
"01", // token out index
|
||||||
|
"000000", // split
|
||||||
|
"2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
|
||||||
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token out
|
||||||
|
"000bb8", // pool fee
|
||||||
|
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
|
||||||
|
"8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
|
||||||
|
"01", // zero2one
|
||||||
|
"00", // transfer type TransferFrom
|
||||||
|
"0057", // ple encoded swaps
|
||||||
|
"01", // token in index
|
||||||
|
"00", // token out index
|
||||||
|
"000000", // split
|
||||||
|
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address,
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id,
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"00", // zero2one
|
||||||
|
"01", // transfer type Transfer
|
||||||
|
"00000000000000" // padding
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
assert_eq!(hex_calldata[..520], expected_input);
|
||||||
|
assert_eq!(hex_calldata[1288..], expected_swaps);
|
||||||
|
write_calldata_to_file("test_split_input_cyclic_swap", hex_calldata.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_output_cyclic_swap() {
|
||||||
|
// This test has start and end tokens that are the same
|
||||||
|
// The flow is:
|
||||||
|
// ┌─── (USV3, 60% split) ───┐
|
||||||
|
// │ │
|
||||||
|
// USDC ──(USV2) ── WETH──| ├─> USDC
|
||||||
|
// │ │
|
||||||
|
// └─── (USV3, 40% split) ───┘
|
||||||
|
|
||||||
|
let weth = weth();
|
||||||
|
let usdc = usdc();
|
||||||
|
|
||||||
|
let swap_usdc_weth_v2 = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), /* USDC-WETH USV2 */
|
||||||
|
protocol_system: "uniswap_v2".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs
|
||||||
|
.insert("fee".to_string(), Bytes::from(BigInt::from(500).to_signed_bytes_be()));
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: usdc.clone(),
|
||||||
|
token_out: weth.clone(),
|
||||||
|
split: 0.0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap_weth_usdc_v3_pool1 = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
|
||||||
|
* Pool 1 */
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs
|
||||||
|
.insert("fee".to_string(), Bytes::from(BigInt::from(500).to_signed_bytes_be()));
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0.6f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let swap_weth_usdc_v3_pool2 = Swap {
|
||||||
|
component: ProtocolComponent {
|
||||||
|
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
|
||||||
|
* Pool 2 */
|
||||||
|
protocol_system: "uniswap_v3".to_string(),
|
||||||
|
static_attributes: {
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attrs.insert(
|
||||||
|
"fee".to_string(),
|
||||||
|
Bytes::from(BigInt::from(3000).to_signed_bytes_be()),
|
||||||
|
);
|
||||||
|
attrs
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
token_in: weth.clone(),
|
||||||
|
token_out: usdc.clone(),
|
||||||
|
split: 0.0f64,
|
||||||
|
user_data: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
|
||||||
|
|
||||||
|
let solution = Solution {
|
||||||
|
exact_out: false,
|
||||||
|
given_token: usdc.clone(),
|
||||||
|
given_amount: BigUint::from_str("100000000").unwrap(), // 100 USDC (6 decimals)
|
||||||
|
checked_token: usdc.clone(),
|
||||||
|
checked_amount: BigUint::from_str("99025908").unwrap(), /* Expected output
|
||||||
|
* from
|
||||||
|
* test */
|
||||||
|
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||||
|
swaps: vec![swap_usdc_weth_v2, swap_weth_usdc_v3_pool1, swap_weth_usdc_v3_pool2],
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let encoded_solution = encoder
|
||||||
|
.encode_solutions(vec![solution.clone()])
|
||||||
|
.unwrap()[0]
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let calldata = encode_tycho_router_call(
|
||||||
|
eth_chain().id,
|
||||||
|
encoded_solution,
|
||||||
|
&solution,
|
||||||
|
&UserTransferType::TransferFromPermit2,
|
||||||
|
ð(),
|
||||||
|
Some(get_signer()),
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.data;
|
||||||
|
|
||||||
|
let hex_calldata = alloy::hex::encode(&calldata);
|
||||||
|
let expected_input = [
|
||||||
|
"7c553846", // selector
|
||||||
|
"0000000000000000000000000000000000000000000000000000000005f5e100", // given amount
|
||||||
|
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // given token
|
||||||
|
"000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // checked token
|
||||||
|
"0000000000000000000000000000000000000000000000000000000005e703f4", // min amount out
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // wrap action
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000000", // unwrap action
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000002", // tokens length
|
||||||
|
"000000000000000000000000cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
let expected_swaps = [
|
||||||
|
"0000000000000000000000000000000000000000000000000000000000000139", // length of ple encoded swaps without padding
|
||||||
|
"0057", // ple encoded swaps
|
||||||
|
"00", // token in index
|
||||||
|
"01", // token out index
|
||||||
|
"000000", // split
|
||||||
|
"5615deb798bb3e4dfa0139dfa1b3d433cc23b72f", // executor address
|
||||||
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token in
|
||||||
|
"b4e16d0168e52d35cacd2c6185b44281ec28c9dc", // component id
|
||||||
|
"3ede3eca2a72b3aecc820e955b36f38437d01395", // receiver
|
||||||
|
"01", // zero2one
|
||||||
|
"00", // transfer type TransferFrom
|
||||||
|
"006e", // ple encoded swaps
|
||||||
|
"01", // token in index
|
||||||
|
"00", // token out index
|
||||||
|
"999999", // split
|
||||||
|
"2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out
|
||||||
|
"0001f4", // pool fee
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", // component id
|
||||||
|
"00", // zero2one
|
||||||
|
"01", // transfer type Transfer
|
||||||
|
"006e", // ple encoded swaps
|
||||||
|
"01", // token in index
|
||||||
|
"00", // token out index
|
||||||
|
"000000", // split
|
||||||
|
"2e234dae75c793f67a35089c9d99245e1c58470b", // executor address
|
||||||
|
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", // token in
|
||||||
|
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // token out
|
||||||
|
"000bb8", // pool fee
|
||||||
|
"cd09f75e2bf2a4d11f3ab23f1389fcc1621c0cc2", // receiver
|
||||||
|
"8ad599c3a0ff1de082011efddc58f1908eb6e6d8", // component id
|
||||||
|
"00", // zero2one
|
||||||
|
"01", // transfer type Transfer
|
||||||
|
"00000000000000" // padding
|
||||||
|
]
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
assert_eq!(hex_calldata[..520], expected_input);
|
||||||
|
assert_eq!(hex_calldata[1288..], expected_swaps);
|
||||||
|
write_calldata_to_file("test_split_output_cyclic_swap", hex_calldata.as_str());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user