Merge branch 'refs/heads/main' into feat/bebop-rfq-encoder-and-executor

This commit is contained in:
Diana Carvalho
2025-08-14 09:58:21 +01:00
20 changed files with 1063 additions and 765 deletions

View File

@@ -1,3 +1,17 @@
## [0.114.0](https://github.com/propeller-heads/tycho-execution/compare/0.113.0...0.114.0) (2025-08-14)
### Features
* add hashflow executor ([e03ecf4](https://github.com/propeller-heads/tycho-execution/commit/e03ecf48d50f08b739a9d862b4746c3a055ea6e9))
## [0.113.0](https://github.com/propeller-heads/tycho-execution/compare/0.112.2...0.113.0) (2025-08-14)
### Features
* Add estimated_amount_in to Swap. Add SwapBuilder ([5eb9973](https://github.com/propeller-heads/tycho-execution/commit/5eb9973dbd5ca4eb2f4d15c3f4803b4bc7dfa1ea))
## [0.112.2](https://github.com/propeller-heads/tycho-execution/compare/0.112.1...0.112.2) (2025-08-07)

2
Cargo.lock generated
View File

@@ -4659,7 +4659,7 @@ dependencies = [
[[package]]
name = "tycho-execution"
version = "0.112.2"
version = "0.114.0"
dependencies = [
"alloy",
"chrono",

View File

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

View File

@@ -7,7 +7,7 @@ use tycho_common::{
};
use tycho_execution::encoding::{
evm::encoder_builders::TychoRouterEncoderBuilder,
models::{Solution, Swap, UserTransferType},
models::{Solution, Swap, SwapBuilder, UserTransferType},
};
fn main() {
@@ -44,6 +44,7 @@ fn main() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
// Then we create a solution object with the previous swap
@@ -87,56 +88,51 @@ fn main() {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f")
.expect("Failed to create DAI address");
let swap_weth_dai = Swap {
component: ProtocolComponent {
let swap_weth_dai = SwapBuilder::new(
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,
protocol_state: None,
};
let swap_weth_wbtc = Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.5)
.build();
// Split 0 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%
let swap_weth_wbtc = SwapBuilder::new(
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,
protocol_state: None,
};
let swap_dai_usdc = Swap {
component: ProtocolComponent {
weth.clone(),
wbtc.clone(),
)
.build();
let swap_dai_usdc = SwapBuilder::new(
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,
protocol_state: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
dai.clone(),
usdc.clone(),
)
.build();
let swap_wbtc_usdc = SwapBuilder::new(
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,
protocol_state: None,
};
wbtc.clone(),
usdc.clone(),
)
.build();
let mut complex_solution = solution.clone();
complex_solution.swaps = vec![swap_weth_dai, swap_weth_wbtc, swap_dai_usdc, swap_wbtc_usdc];

View File

@@ -83,6 +83,7 @@ fn main() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_usdc_usdt = Swap {
component: ProtocolComponent {
@@ -101,6 +102,7 @@ fn main() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
// Then we create a solution object with the previous swap

View File

@@ -0,0 +1,120 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "../RestrictTransferFrom.sol";
import "@interfaces/IExecutor.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
error HashflowExecutor__InvalidHashflowRouter();
error HashflowExecutor__InvalidDataLength();
interface IHashflowRouter {
struct RFQTQuote {
address pool;
address externalAccount;
address trader;
address effectiveTrader;
address baseToken;
address quoteToken;
uint256 effectiveBaseTokenAmount;
uint256 baseTokenAmount;
uint256 quoteTokenAmount;
uint256 quoteExpiry;
uint256 nonce;
bytes32 txid;
bytes signature; // ECDSA signature of the quote, 65 bytes
}
function tradeRFQT(RFQTQuote calldata quote) external payable;
}
contract HashflowExecutor is IExecutor, RestrictTransferFrom {
using SafeERC20 for IERC20;
address public constant HASHFLOW_ROUTER =
0x55084eE0fEf03f14a305cd24286359A35D735151;
address public constant NATIVE_TOKEN =
0x0000000000000000000000000000000000000000;
constructor(address _permit2) RestrictTransferFrom(_permit2) {}
function swap(uint256 givenAmount, bytes calldata data)
external
payable
returns (uint256 calculatedAmount)
{
(
IHashflowRouter.RFQTQuote memory quote,
bool approvalNeeded,
TransferType transferType
) = _decodeData(data);
// Slippage checks
if (givenAmount > quote.baseTokenAmount) {
// Do not transfer more than the quote's maximum permitted amount.
givenAmount = quote.baseTokenAmount;
}
quote.effectiveBaseTokenAmount = givenAmount;
if (approvalNeeded && quote.baseToken != NATIVE_TOKEN) {
// slither-disable-next-line unused-return
IERC20(quote.baseToken).forceApprove(
HASHFLOW_ROUTER, type(uint256).max
);
}
uint256 ethValue = 0;
if (quote.baseToken == NATIVE_TOKEN) {
ethValue = quote.effectiveBaseTokenAmount;
}
_transfer(
address(this), transferType, address(quote.baseToken), givenAmount
);
uint256 balanceBefore = _balanceOf(quote.quoteToken);
IHashflowRouter(HASHFLOW_ROUTER).tradeRFQT{value: ethValue}(quote);
uint256 balanceAfter = _balanceOf(quote.quoteToken);
calculatedAmount = balanceAfter - balanceBefore;
}
function _decodeData(bytes calldata data)
internal
pure
returns (
IHashflowRouter.RFQTQuote memory quote,
bool approvalNeeded,
TransferType transferType
)
{
if (data.length != 347) {
revert HashflowExecutor__InvalidDataLength();
}
approvalNeeded = data[0] != 0;
transferType = TransferType(uint8(data[1]));
quote.pool = address(bytes20(data[2:22]));
quote.externalAccount = address(bytes20(data[22:42]));
quote.trader = address(bytes20(data[42:62]));
quote.effectiveTrader = address(bytes20(data[62:82]));
quote.baseToken = address(bytes20(data[82:102]));
quote.quoteToken = address(bytes20(data[102:122]));
quote.effectiveBaseTokenAmount = 0; // Not included in the calldata, set in the swap function
quote.baseTokenAmount = uint256(bytes32(data[122:154]));
quote.quoteTokenAmount = uint256(bytes32(data[154:186]));
quote.quoteExpiry = uint256(bytes32(data[186:218]));
quote.nonce = uint256(bytes32(data[218:250]));
quote.txid = bytes32(data[250:282]);
quote.signature = data[282:347];
}
function _balanceOf(address token)
internal
view
returns (uint256 balance)
{
balance = token == NATIVE_TOKEN
? address(this).balance
: IERC20(token).balanceOf(address(this));
}
}

View File

@@ -0,0 +1,280 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.26;
import "@src/executors/HashflowExecutor.sol";
import {Constants} from "../Constants.sol";
import "forge-std/Test.sol";
contract HashflowUtils is Test {
constructor() {}
function encodeRfqtQuote(
IHashflowRouter.RFQTQuote memory quote,
bool approvalNeeded,
RestrictTransferFrom.TransferType transferType
) internal pure returns (bytes memory) {
return abi.encodePacked(
approvalNeeded, // needsApproval (1 byte)
uint8(transferType), // transferType (1 byte)
quote.pool, // pool (20 bytes)
quote.externalAccount, // externalAccount (20 bytes)
quote.trader, // trader (20 bytes)
quote.effectiveTrader, // effectiveTrader (20 bytes)
quote.baseToken, // baseToken (20 bytes)
quote.quoteToken, // quoteToken (20 bytes)
quote.baseTokenAmount, // baseTokenAmount (32 bytes)
quote.quoteTokenAmount, // quoteTokenAmount (32 bytes)
quote.quoteExpiry, // quoteExpiry (32 bytes)
quote.nonce, // nonce (32 bytes)
quote.txid, // txid (32 bytes)
quote.signature // signature data
);
}
function encodeRfqtQuoteWithDefaults(IHashflowRouter.RFQTQuote memory quote)
internal
pure
returns (bytes memory)
{
return
encodeRfqtQuote(quote, true, RestrictTransferFrom.TransferType.None);
}
}
contract HashflowExecutorECR20Test is Constants, HashflowUtils {
using SafeERC20 for IERC20;
HashflowExecutorExposed executor;
uint256 forkBlock;
IERC20 WETH = IERC20(WETH_ADDR);
IERC20 USDC = IERC20(USDC_ADDR);
function setUp() public {
forkBlock = 23124977; // Using expiry date: 1755001853, ECR20
vm.createSelectFork("mainnet", forkBlock);
executor = new HashflowExecutorExposed(PERMIT2_ADDRESS);
}
function testDecodeParams() public view {
IHashflowRouter.RFQTQuote memory expected_quote = rfqtQuote();
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(expected_quote);
(
IHashflowRouter.RFQTQuote memory quote,
bool approvalNeeded,
RestrictTransferFrom.TransferType transferType
) = executor.decodeData(encodedQuote);
assertEq(quote.pool, expected_quote.pool, "pool mismatch");
assertEq(
quote.externalAccount,
expected_quote.externalAccount,
"externalAccount mismatch"
);
assertEq(quote.trader, expected_quote.trader, "trader mismatch");
assertEq(
quote.effectiveTrader,
expected_quote.effectiveTrader,
"effectiveTrader mismatch"
);
assertEq(
quote.baseToken, expected_quote.baseToken, "baseToken mismatch"
);
assertEq(
quote.quoteToken, expected_quote.quoteToken, "quoteToken mismatch"
);
assertEq(
quote.effectiveBaseTokenAmount,
expected_quote.effectiveBaseTokenAmount,
"effectiveBaseTokenAmount mismatch"
);
assertEq(
quote.baseTokenAmount,
expected_quote.baseTokenAmount,
"baseTokenAmount mismatch"
);
assertEq(
quote.quoteTokenAmount,
expected_quote.quoteTokenAmount,
"quoteTokenAmount mismatch"
);
assertEq(
quote.quoteExpiry,
expected_quote.quoteExpiry,
"quoteExpiry mismatch"
);
assertEq(quote.nonce, expected_quote.nonce, "nonce mismatch");
assertEq(quote.txid, expected_quote.txid, "txid mismatch");
assertEq(
quote.signature, expected_quote.signature, "signature mismatch"
);
assertEq(approvalNeeded, true, "Approval flag mismatch");
assertEq(
uint8(transferType),
uint8(RestrictTransferFrom.TransferType.None),
"Transfer type mismatch"
);
}
function testDecodeParamsInvalidDataLength() public {
bytes memory invalidData = new bytes(10);
vm.expectRevert(HashflowExecutor__InvalidDataLength.selector);
executor.decodeData(invalidData);
}
function testSwapNoSlippage() public {
address trader = address(executor);
IHashflowRouter.RFQTQuote memory quote = rfqtQuote();
uint256 amountIn = quote.baseTokenAmount;
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote);
deal(USDC_ADDR, address(executor), amountIn);
uint256 balanceBefore = WETH.balanceOf(trader);
vm.prank(trader);
uint256 amountOut = executor.swap(amountIn, encodedQuote);
uint256 balanceAfter = WETH.balanceOf(trader);
assertGt(balanceAfter, balanceBefore);
assertEq(balanceAfter - balanceBefore, amountOut);
assertEq(amountOut, quote.quoteTokenAmount);
}
function testSwapRouterAmountUnderQuoteAmount() public {
address trader = address(executor);
IHashflowRouter.RFQTQuote memory quote = rfqtQuote();
uint256 amountIn = quote.baseTokenAmount - 1;
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote);
deal(USDC_ADDR, address(executor), amountIn);
uint256 balanceBefore = WETH.balanceOf(trader);
vm.prank(trader);
uint256 amountOut = executor.swap(amountIn, encodedQuote);
uint256 balanceAfter = WETH.balanceOf(trader);
assertGt(balanceAfter, balanceBefore);
assertEq(balanceAfter - balanceBefore, amountOut);
assertLt(amountOut, quote.quoteTokenAmount);
}
function testSwapRouterAmountOverQuoteAmount() public {
address trader = address(executor);
IHashflowRouter.RFQTQuote memory quote = rfqtQuote();
uint256 amountIn = quote.baseTokenAmount + 1;
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote);
deal(USDC_ADDR, address(executor), amountIn);
uint256 balanceBefore = WETH.balanceOf(trader);
vm.prank(trader);
uint256 amountOut = executor.swap(amountIn, encodedQuote);
uint256 balanceAfter = WETH.balanceOf(trader);
assertGt(balanceAfter, balanceBefore);
assertEq(balanceAfter - balanceBefore, amountOut);
assertEq(amountOut, quote.quoteTokenAmount);
}
function rfqtQuote()
internal
view
returns (IHashflowRouter.RFQTQuote memory)
{
return IHashflowRouter.RFQTQuote({
pool: address(0x4cE18FD7b44F40Aebd6911362d3AC25F14D5007f),
externalAccount: address(0x50C03775C8E5b6227F1E00C4b3e479b4A7C57983),
trader: address(executor),
effectiveTrader: address(ALICE),
baseToken: USDC_ADDR,
quoteToken: WETH_ADDR,
effectiveBaseTokenAmount: 0,
baseTokenAmount: 100,
quoteTokenAmount: 23224549208,
quoteExpiry: 1755001853,
nonce: 1755001793084,
txid: bytes32(
uint256(
0x12500006400064000000174813b960ffffffffffffff00293fdb4569fe760000
)
),
signature: hex"5b26977fecaf794c3d6900b9523b9632b5c62623f92732347dc9f24d8b5c4d611f5d733bbe82b594b6b47ab8aa1923c9f6b8aa66ef822ce412a767200f1520e11b"
});
}
}
contract HashflowExecutorNativeTest is Constants, HashflowUtils {
using SafeERC20 for IERC20;
HashflowExecutorExposed executor;
uint256 forkBlock;
IERC20 WETH = IERC20(WETH_ADDR);
IERC20 USDC = IERC20(USDC_ADDR);
function setUp() public {
forkBlock = 23125321; // Using expiry date: 1755006017, Native
vm.createSelectFork("mainnet", forkBlock);
executor = new HashflowExecutorExposed(PERMIT2_ADDRESS);
}
function testSwapNoSlippage() public {
address trader = address(executor);
IHashflowRouter.RFQTQuote memory quote = rfqtQuote();
uint256 amountIn = quote.baseTokenAmount;
bytes memory encodedQuote = encodeRfqtQuoteWithDefaults(quote);
vm.deal(address(executor), amountIn);
uint256 balanceBefore = USDC.balanceOf(trader);
vm.prank(trader);
uint256 amountOut = executor.swap(amountIn, encodedQuote);
uint256 balanceAfter = USDC.balanceOf(trader);
assertGt(balanceAfter, balanceBefore);
assertEq(balanceAfter - balanceBefore, amountOut);
assertEq(amountOut, quote.quoteTokenAmount);
}
function rfqtQuote()
internal
view
returns (IHashflowRouter.RFQTQuote memory)
{
return IHashflowRouter.RFQTQuote({
pool: address(0x51199bE500A8c59262478b621B1096F17638dc6F),
externalAccount: address(0xCe79b081c0c924cb67848723ed3057234d10FC6b),
trader: address(executor),
effectiveTrader: address(ALICE),
baseToken: address(0x0000000000000000000000000000000000000000),
quoteToken: USDC_ADDR,
effectiveBaseTokenAmount: 0,
baseTokenAmount: 10000000000000000,
quoteTokenAmount: 43930745,
quoteExpiry: 1755006017,
nonce: 1755005977455,
txid: bytes32(
uint256(
0x1250000640006400019071ef777818ffffffffffffff0029401b1bc51da00000
)
),
signature: hex"4c3554c928e4b15cd53d1047aee69a66103effa5107047b84949e48460b6978f25da9ad5b9ed31aa9ab2130e597fabea872f14b8c1b166ea079413cbaf2f4b4c1c"
});
}
}
contract HashflowExecutorExposed is HashflowExecutor {
constructor(address _permit2) HashflowExecutor(_permit2) {}
function decodeData(bytes calldata data)
external
pure
returns (
IHashflowRouter.RFQTQuote memory quote,
bool approvalNeeded,
TransferType transferType
)
{
return _decodeData(data);
}
}

View File

@@ -87,7 +87,7 @@ mod tests {
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
use super::*;
use crate::encoding::models::Swap;
use crate::encoding::models::SwapBuilder;
fn weth() -> Bytes {
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
@@ -105,41 +105,26 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap_weth_wbtc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".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,
protocol_state: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_usdc_dai = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: usdc.clone(),
token_out: dai.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_weth_wbtc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4".to_string(), ..Default::default() },
weth.clone(),
wbtc.clone(),
)
.build();
let swap_wbtc_usdc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4".to_string(), ..Default::default() },
wbtc.clone(),
usdc.clone(),
)
.build();
let swap_usdc_dai = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v2".to_string(), ..Default::default() },
usdc.clone(),
dai.clone(),
)
.build();
let swaps = vec![swap_weth_wbtc.clone(), swap_wbtc_usdc.clone(), swap_usdc_dai.clone()];
let grouped_swaps = group_swaps(&swaps);
@@ -179,52 +164,34 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap_wbtc_weth = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: weth.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_weth_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.5f64,
user_data: None,
protocol_state: None,
};
let swap_weth_dai = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.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,
protocol_state: None,
};
let swap_dai_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: dai.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_wbtc_weth = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4".to_string(), ..Default::default() },
wbtc.clone(),
weth.clone(),
)
.build();
let swap_weth_usdc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4".to_string(), ..Default::default() },
weth.clone(),
usdc.clone(),
)
.split(0.5f64)
.build();
let swap_weth_dai = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4".to_string(), ..Default::default() },
weth.clone(),
dai.clone(),
)
.build();
// Split 0 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%
let swap_dai_usdc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4".to_string(), ..Default::default() },
dai.clone(),
usdc.clone(),
)
.build();
let swaps = vec![
swap_wbtc_weth.clone(),
swap_weth_usdc.clone(),
@@ -275,52 +242,38 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap_weth_wbtc = Swap {
component: ProtocolComponent {
let swap_weth_wbtc = SwapBuilder::new(
ProtocolComponent {
protocol_system: "vm:balancer_v3".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: wbtc.clone(),
split: 0.5f64,
user_data: None,
protocol_state: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
weth.clone(),
wbtc.clone(),
)
.split(0.5f64)
.build();
let swap_wbtc_usdc = SwapBuilder::new(
ProtocolComponent {
protocol_system: "vm:balancer_v3".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap_weth_dai = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.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,
protocol_state: None,
};
let swap_dai_usdc = Swap {
component: ProtocolComponent {
protocol_system: "uniswap_v4".to_string(),
..Default::default()
},
token_in: dai.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
wbtc.clone(),
usdc.clone(),
)
.build();
let swap_weth_dai = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4".to_string(), ..Default::default() },
weth.clone(),
dai.clone(),
)
.build();
let swap_dai_usdc = SwapBuilder::new(
ProtocolComponent { protocol_system: "uniswap_v4".to_string(), ..Default::default() },
dai.clone(),
usdc.clone(),
)
.build();
let swaps = vec![
swap_weth_wbtc.clone(),

View File

@@ -518,7 +518,6 @@ mod tests {
};
use super::*;
use crate::encoding::models::Swap;
fn eth_chain() -> Chain {
Chain::Ethereum
@@ -539,8 +538,8 @@ mod tests {
}
mod single {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_single_swap_strategy_encoder() {
// Performs a single swap from WETH to DAI on a USV2 pool, with no grouping
@@ -549,18 +548,16 @@ mod tests {
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
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,
protocol_state: None,
};
weth.clone(),
dai.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SingleSwapStrategyEncoder::new(
eth_chain(),
@@ -611,18 +608,16 @@ mod tests {
let checked_amount = BigUint::from_str("1_640_000000000000000000").unwrap();
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
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,
protocol_state: None,
};
weth.clone(),
dai.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SingleSwapStrategyEncoder::new(
eth_chain(),
@@ -672,6 +667,7 @@ mod tests {
mod sequential {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_sequential_swap_strategy_encoder_no_permit2() {
@@ -683,30 +679,26 @@ mod tests {
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swap_weth_wbtc = Swap {
component: ProtocolComponent {
let swap_weth_wbtc = SwapBuilder::new(
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,
protocol_state: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
weth.clone(),
wbtc.clone(),
)
.build();
let swap_wbtc_usdc = SwapBuilder::new(
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,
protocol_state: None,
};
wbtc.clone(),
usdc.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SequentialSwapStrategyEncoder::new(
eth_chain(),
@@ -764,6 +756,7 @@ mod tests {
mod split {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_split_input_cyclic_swap() {
@@ -779,8 +772,8 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
// USDC -> WETH (Pool 1) - 60% of input
let swap_usdc_weth_pool1 = Swap {
component: ProtocolComponent {
let swap_usdc_weth_pool1 = SwapBuilder::new(
ProtocolComponent {
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
* Pool 1 */
protocol_system: "uniswap_v3".to_string(),
@@ -794,16 +787,15 @@ mod tests {
},
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0.6f64, // 60% of input
user_data: None,
protocol_state: None,
};
usdc.clone(),
weth.clone(),
)
.split(0.6f64)
.build();
// USDC -> WETH (Pool 2) - 40% of input (remaining)
let swap_usdc_weth_pool2 = Swap {
component: ProtocolComponent {
let swap_usdc_weth_pool2 = SwapBuilder::new(
ProtocolComponent {
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
* Pool 2 */
protocol_system: "uniswap_v3".to_string(),
@@ -817,16 +809,14 @@ mod tests {
},
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0f64,
user_data: None, // Remaining 40%
protocol_state: None,
};
usdc.clone(),
weth.clone(),
)
.build();
// WETH -> USDC (Pool 2)
let swap_weth_usdc_pool2 = Swap {
component: ProtocolComponent {
let swap_weth_usdc_pool2 = SwapBuilder::new(
ProtocolComponent {
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), /* USDC-WETH USV2
* Pool 2 */
protocol_system: "uniswap_v2".to_string(),
@@ -840,13 +830,10 @@ mod tests {
},
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.0f64,
user_data: None,
protocol_state: None,
};
weth.clone(),
usdc.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SplitSwapStrategyEncoder::new(
eth_chain(),
@@ -935,8 +922,8 @@ mod tests {
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swap_usdc_weth_v2 = Swap {
component: ProtocolComponent {
let swap_usdc_weth_v2 = SwapBuilder::new(
ProtocolComponent {
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(), // USDC-WETH USV2
protocol_system: "uniswap_v2".to_string(),
static_attributes: {
@@ -949,15 +936,13 @@ mod tests {
},
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0.0f64,
user_data: None,
protocol_state: None,
};
usdc.clone(),
weth.clone(),
)
.build();
let swap_weth_usdc_v3_pool1 = Swap {
component: ProtocolComponent {
let swap_weth_usdc_v3_pool1 = SwapBuilder::new(
ProtocolComponent {
id: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640".to_string(), /* USDC-WETH USV3
* Pool 1 */
protocol_system: "uniswap_v3".to_string(),
@@ -971,17 +956,16 @@ mod tests {
},
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.6f64,
user_data: None,
protocol_state: None,
};
weth.clone(),
usdc.clone(),
)
.split(0.6f64)
.build();
let swap_weth_usdc_v3_pool2 = Swap {
component: ProtocolComponent {
let swap_weth_usdc_v3_pool2 = SwapBuilder::new(
ProtocolComponent {
id: "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".to_string(), /* USDC-WETH USV3
* Pool 2 */
* Pool 1 */
protocol_system: "uniswap_v3".to_string(),
static_attributes: {
let mut attrs = HashMap::new();
@@ -993,12 +977,10 @@ mod tests {
},
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0.0f64,
user_data: None,
protocol_state: None,
};
weth.clone(),
usdc.clone(),
)
.build();
let swap_encoder_registry = get_swap_encoder_registry();
let encoder = SplitSwapStrategyEncoder::new(

View File

@@ -197,7 +197,7 @@ mod tests {
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
use super::*;
use crate::encoding::models::Swap;
use crate::encoding::models::{Swap, SwapBuilder};
#[test]
fn test_validate_path_single_swap() {
@@ -205,18 +205,16 @@ mod tests {
let eth = Bytes::from_str("0x0000000000000000000000000000000000000000").unwrap();
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
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,
protocol_state: None,
}];
weth.clone(),
dai.clone(),
)
.build()];
let result = validator.validate_swap_path(&swaps, &weth, &dai, &None, &eth, &weth);
assert_eq!(result, Ok(()));
}
@@ -229,30 +227,27 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
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,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.5f64)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
},
dai.clone(),
usdc.clone(),
)
.build(),
];
let result = validator.validate_swap_path(&swaps, &weth, &usdc, &None, &eth, &weth);
assert_eq!(result, Ok(()));
@@ -268,31 +263,28 @@ mod tests {
let wbtc = Bytes::from_str("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599").unwrap();
let disconnected_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5,
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.split(0.5f64)
.build(),
// This swap is disconnected from the WETH->DAI path
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: wbtc.clone(),
token_out: usdc.clone(),
split: 0.0,
user_data: None,
protocol_state: None,
},
wbtc.clone(),
usdc.clone(),
)
.build(),
];
let result =
validator.validate_swap_path(&disconnected_swaps, &weth, &usdc, &None, &eth, &weth);
@@ -310,30 +302,26 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let cyclic_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
usdc.clone(),
weth.clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
},
weth.clone(),
usdc.clone(),
)
.build(),
];
// Test with USDC as both given token and checked token
@@ -349,18 +337,17 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let unreachable_swaps = vec![Swap {
component: ProtocolComponent {
let unreachable_swaps = vec![SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 1.0,
user_data: None,
protocol_state: None,
}];
weth.clone(),
dai.clone(),
)
.split(1.0)
.build()];
let result =
validator.validate_swap_path(&unreachable_swaps, &weth, &usdc, &None, &eth, &weth);
assert!(matches!(
@@ -389,18 +376,16 @@ mod tests {
let validator = SplitSwapValidator;
let weth = Bytes::from_str("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").unwrap();
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
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,
protocol_state: None,
}];
weth.clone(),
dai.clone(),
)
.build()];
let result = validator.validate_split_percentages(&swaps);
assert_eq!(result, Ok(()));
}
@@ -413,42 +398,38 @@ mod tests {
// Valid case: Multiple swaps with proper splits (50%, 30%, remainder)
let valid_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.5)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.3,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.3)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool3".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.0, // Remainder (20%)
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.build(),
];
assert!(validator
.validate_split_percentages(&valid_swaps)
@@ -462,30 +443,28 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let invalid_total_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.7,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.7)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.3,
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.split(0.3)
.build(),
];
assert!(matches!(
validator.validate_split_percentages(&invalid_total_swaps),
@@ -500,30 +479,27 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let invalid_zero_position_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.0,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5,
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.split(0.5)
.build(),
];
assert!(matches!(
validator.validate_split_percentages(&invalid_zero_position_swaps),
@@ -538,42 +514,38 @@ mod tests {
let dai = Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let invalid_overflow_swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.6,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.6)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool2".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.5,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth.clone(),
dai.clone(),
)
.split(0.5)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "pool3".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: dai.clone(),
split: 0.0,
user_data: None,
protocol_state: None,
},
weth.clone(),
dai.clone(),
)
.build(),
];
assert!(matches!(
validator.validate_split_percentages(&invalid_overflow_swaps),
@@ -588,18 +560,16 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let weth = Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap();
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth.clone(),
token_out: usdc.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
}];
weth.clone(),
usdc.clone(),
)
.build()];
let result = validator.validate_swap_path(
&swaps,
@@ -619,18 +589,16 @@ mod tests {
let usdc = Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let weth = Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap();
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
ProtocolComponent {
id: "pool1".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: usdc.clone(),
token_out: weth.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
}];
usdc.clone(),
weth.clone(),
)
.build()];
let result = validator.validate_swap_path(
&swaps,

View File

@@ -121,7 +121,7 @@ mod tests {
use tycho_common::models::protocol::ProtocolComponent;
use super::*;
use crate::encoding::models::Swap;
use crate::encoding::models::SwapBuilder;
fn weth() -> Bytes {
Bytes::from(hex!("c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2").to_vec())
@@ -174,18 +174,16 @@ mod tests {
#[case] expected_transfer: TransferType,
) {
// The swap token is the same as the given token, which is not the native token
let swaps = vec![Swap {
component: ProtocolComponent {
let swaps = vec![SwapBuilder::new(
ProtocolComponent {
protocol_system: "uniswap_v2".to_string(),
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
..Default::default()
},
token_in: swap_token_in.clone(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
}];
swap_token_in.clone(),
dai(),
)
.build()];
let swap = SwapGroup {
protocol_system: protocol,
token_in: swap_token_in,
@@ -240,18 +238,16 @@ mod tests {
token_in: usdc(),
token_out: dai(),
split: 0f64,
swaps: vec![Swap {
component: ProtocolComponent {
swaps: vec![SwapBuilder::new(
ProtocolComponent {
protocol_system: protocol.unwrap().to_string(),
id: component_id().to_string(),
..Default::default()
},
token_in: usdc(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
}],
usdc(),
dai(),
)
.build()],
})
};

View File

@@ -762,10 +762,14 @@ mod tests {
};
use super::*;
use crate::encoding::{evm::utils::write_calldata_to_file, models::TransferType};
use crate::encoding::{
evm::utils::write_calldata_to_file,
models::{SwapBuilder, TransferType},
};
mod uniswap_v2 {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_encode_uniswap_v2() {
let usv2_pool = ProtocolComponent {
@@ -775,14 +779,7 @@ mod tests {
let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f");
let swap = Swap {
component: usv2_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(usv2_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
receiver: Bytes::from("0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e"), // BOB
exact_out: false,
@@ -822,6 +819,7 @@ mod tests {
mod uniswap_v3 {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_encode_uniswap_v3() {
let fee = BigInt::from(500);
@@ -836,14 +834,7 @@ mod tests {
};
let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
let token_out = Bytes::from("0x6b175474e89094c44da98b954eedeac495271d0f");
let swap = Swap {
component: usv3_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(usv3_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
receiver: Bytes::from("0x0000000000000000000000000000000000000001"),
exact_out: false,
@@ -886,6 +877,7 @@ mod tests {
mod balancer_v2 {
use super::*;
use crate::encoding::models::SwapBuilder;
#[test]
fn test_encode_balancer_v2() {
@@ -898,14 +890,7 @@ mod tests {
};
let token_in = Bytes::from("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2");
let token_out = Bytes::from("0xba100000625a3754423978a60c9317c58a424e3D");
let swap = Swap {
component: balancer_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(balancer_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
// The receiver was generated with `makeAddr("bob") using forge`
receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"),
@@ -952,7 +937,6 @@ mod tests {
mod uniswap_v4 {
use super::*;
use crate::encoding::evm::utils::write_calldata_to_file;
#[test]
fn test_encode_uniswap_v4_simple_swap() {
@@ -972,14 +956,7 @@ mod tests {
static_attributes,
..Default::default()
};
let swap = Swap {
component: usv4_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(usv4_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
// The receiver is ALICE to match the solidity tests
receiver: Bytes::from("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2"),
@@ -1046,14 +1023,7 @@ mod tests {
..Default::default()
};
let swap = Swap {
component: usv4_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(usv4_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
receiver: Bytes::from("0x0000000000000000000000000000000000000001"),
@@ -1144,23 +1114,12 @@ mod tests {
..Default::default()
};
let initial_swap = Swap {
component: usde_usdt_component,
token_in: usde_address.clone(),
token_out: usdt_address.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let second_swap = Swap {
component: usdt_wbtc_component,
token_in: usdt_address,
token_out: wbtc_address.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let initial_swap =
SwapBuilder::new(usde_usdt_component, usde_address.clone(), usdt_address.clone())
.build();
let second_swap =
SwapBuilder::new(usdt_wbtc_component, usdt_address.clone(), wbtc_address.clone())
.build();
let encoder = UniswapV4SwapEncoder::new(
String::from("0xF62849F9A0B5Bf2913b396098F7c7019b51A820a"),
@@ -1211,7 +1170,6 @@ mod tests {
}
mod ekubo {
use super::*;
use crate::encoding::evm::utils::write_calldata_to_file;
const RECEIVER: &str = "ca4f73fe97d0b987a0d12b39bbd562c779bab6f6"; // Random address
@@ -1231,14 +1189,7 @@ mod tests {
let component = ProtocolComponent { static_attributes, ..Default::default() };
let swap = Swap {
component,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(component, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
receiver: RECEIVER.into(),
@@ -1291,8 +1242,8 @@ mod tests {
transfer_type: TransferType::Transfer,
};
let first_swap = Swap {
component: ProtocolComponent {
let first_swap = SwapBuilder::new(
ProtocolComponent {
static_attributes: HashMap::from([
("fee".to_string(), Bytes::from(0_u64)),
("tick_spacing".to_string(), Bytes::from(0_u32)),
@@ -1303,15 +1254,13 @@ mod tests {
]),
..Default::default()
},
token_in: group_token_in.clone(),
token_out: intermediary_token.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
group_token_in.clone(),
intermediary_token.clone(),
)
.build();
let second_swap = Swap {
component: ProtocolComponent {
let second_swap = SwapBuilder::new(
ProtocolComponent {
// 0.0025% fee & 0.005% base pool
static_attributes: HashMap::from([
("fee".to_string(), Bytes::from(461168601842738_u64)),
@@ -1320,12 +1269,10 @@ mod tests {
]),
..Default::default()
},
token_in: intermediary_token.clone(),
token_out: group_token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
intermediary_token.clone(),
group_token_out.clone(),
)
.build();
let first_encoded_swap = encoder
.encode_swap(&first_swap, &encoding_context)
@@ -1434,19 +1381,18 @@ mod tests {
) {
let mut static_attributes: HashMap<String, Bytes> = HashMap::new();
static_attributes.insert("coins".into(), Bytes::from_str(coins).unwrap());
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "pool-id".into(),
protocol_system: String::from("vm:curve"),
static_attributes,
..Default::default()
},
token_in: Bytes::from(token_in),
token_out: Bytes::from(token_out),
split: 0f64,
user_data: None,
protocol_state: None,
};
Bytes::from(token_in),
Bytes::from(token_out),
)
.build();
let encoder =
CurveSwapEncoder::new(String::default(), Chain::Ethereum, curve_config()).unwrap();
let (i, j) = encoder
@@ -1480,14 +1426,9 @@ mod tests {
};
let token_in = Bytes::from("0x6B175474E89094C44Da98b954EedeAC495271d0F");
let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let swap = Swap {
component: curve_tri_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap =
SwapBuilder::new(curve_tri_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
// The receiver was generated with `makeAddr("bob") using forge`
receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"),
@@ -1553,14 +1494,7 @@ mod tests {
};
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let token_out = Bytes::from("0x4c9EDD5852cd905f086C759E8383e09bff1E68B3");
let swap = Swap {
component: curve_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(curve_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
// The receiver was generated with `makeAddr("bob") using forge`
receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"),
@@ -1627,14 +1561,7 @@ mod tests {
};
let token_in = Bytes::from("0x0000000000000000000000000000000000000000");
let token_out = Bytes::from("0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84");
let swap = Swap {
component: curve_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(curve_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
// The receiver was generated with `makeAddr("bob") using forge`
receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"),
@@ -1702,14 +1629,7 @@ mod tests {
};
let token_in = Bytes::from("0x7bc3485026ac48b6cf9baf0a377477fff5703af8");
let token_out = Bytes::from("0xc71ea051a5f82c67adcf634c36ffe6334793d24c");
let swap = Swap {
component: balancer_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(balancer_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
// The receiver was generated with `makeAddr("bob") using forge`
receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"),
@@ -1761,14 +1681,7 @@ mod tests {
};
let token_in = Bytes::from("0x40D16FC0246aD3160Ccc09B8D0D3A2cD28aE6C2f");
let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let swap = Swap {
component: maverick_pool,
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
let swap = SwapBuilder::new(maverick_pool, token_in.clone(), token_out.clone()).build();
let encoding_context = EncodingContext {
// The receiver was generated with `makeAddr("bob") using forge`
receiver: Bytes::from("0x1d96f2f6bef1202e4ce1ff6dad0c2cb002861d3e"),

View File

@@ -393,7 +393,7 @@ mod tests {
use tycho_common::models::{protocol::ProtocolComponent, Chain};
use super::*;
use crate::encoding::models::Swap;
use crate::encoding::models::{Swap, SwapBuilder};
fn dai() -> Bytes {
Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap()
@@ -428,20 +428,18 @@ mod tests {
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);
Swap {
component: ProtocolComponent {
SwapBuilder::new(
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,
protocol_state: None,
}
usdc().clone(),
eth().clone(),
)
.build()
}
fn swap_eth_pepe_univ4() -> Swap<'static> {
@@ -450,20 +448,18 @@ mod tests {
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);
Swap {
component: ProtocolComponent {
SwapBuilder::new(
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,
protocol_state: None,
}
eth().clone(),
pepe().clone(),
)
.build()
}
fn router_address() -> Bytes {
@@ -501,18 +497,16 @@ mod tests {
fn test_encode_router_calldata_single_swap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let eth_amount_in = BigUint::from(1000u32);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth().clone(),
dai().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -567,31 +561,27 @@ mod tests {
fn test_encode_router_calldata_sequential_swap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let eth_amount_in = BigUint::from(1000u32);
let swap_weth_dai = Swap {
component: ProtocolComponent {
let swap_weth_dai = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth().clone(),
dai().clone(),
)
.build();
let swap_dai_usdc = Swap {
component: ProtocolComponent {
let swap_dai_usdc = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: usdc(),
split: 0f64,
user_data: None,
protocol_state: None,
};
dai().clone(),
usdc().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -661,18 +651,16 @@ mod tests {
#[test]
fn test_validate_passes_for_wrap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth().clone(),
dai().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -691,18 +679,16 @@ mod tests {
#[test]
fn test_validate_fails_for_wrap_wrong_input() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
weth().clone(),
dai().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -726,18 +712,16 @@ mod tests {
#[test]
fn test_validate_fails_for_wrap_wrong_first_swap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: eth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
};
eth().clone(),
dai().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -781,18 +765,16 @@ mod tests {
#[test]
fn test_validate_passes_for_unwrap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
};
dai().clone(),
weth().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -810,18 +792,16 @@ mod tests {
#[test]
fn test_validate_fails_for_unwrap_wrong_output() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
};
dai().clone(),
weth().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -846,18 +826,16 @@ mod tests {
#[test]
fn test_validate_fails_for_unwrap_wrong_last_swap() {
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: eth(),
split: 0f64,
user_data: None,
protocol_state: None,
};
dai().clone(),
eth().clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -887,42 +865,36 @@ mod tests {
// (some of the pool addresses in this test are fake)
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0.5f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
dai().clone(),
weth().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
dai().clone(),
weth().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
},
weth().clone(),
dai().clone(),
)
.build(),
];
let solution = Solution {
@@ -945,54 +917,46 @@ mod tests {
// (some of the pool addresses in this test are fake)
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
dai().clone(),
weth().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: usdc(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth().clone(),
usdc().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: usdc(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
usdc().clone(),
dai().clone(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: wbtc(),
split: 0f64,
user_data: None,
protocol_state: None,
},
dai().clone(),
wbtc().clone(),
)
.build(),
];
let solution = Solution {
@@ -1022,42 +986,37 @@ mod tests {
// (some of the pool addresses in this test are fake)
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
weth(),
dai(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0.5f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
dai(),
weth(),
)
.split(0.5)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
},
dai(),
weth(),
)
.build(),
];
let solution = Solution {
@@ -1080,30 +1039,26 @@ mod tests {
// (some of the pool addresses in this test are fake)
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let swaps = vec![
Swap {
component: ProtocolComponent {
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: weth(),
token_out: dai(),
split: 0f64,
user_data: None,
protocol_state: None,
},
Swap {
component: ProtocolComponent {
id: "0x0000000000000000000000000000000000000000".to_string(),
weth(),
dai(),
)
.build(),
SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: dai(),
token_out: weth(),
split: 0f64,
user_data: None,
protocol_state: None,
},
dai(),
weth(),
)
.build(),
];
let solution = Solution {
@@ -1137,7 +1092,7 @@ mod tests {
use tycho_common::{models::protocol::ProtocolComponent, Bytes};
use super::*;
use crate::encoding::models::{Solution, Swap};
use crate::encoding::models::Solution;
#[test]
fn test_executor_encoder_encode() {
@@ -1147,18 +1102,16 @@ mod tests {
let token_in = weth();
let token_out = dai();
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
token_in.clone(),
token_out.clone(),
)
.build();
let solution = Solution {
exact_out: false,
@@ -1209,17 +1162,16 @@ mod tests {
let token_in = weth();
let token_out = dai();
let swap = Swap {
component: ProtocolComponent {
let swap = SwapBuilder::new(
ProtocolComponent {
id: "0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11".to_string(),
protocol_system: "uniswap_v2".to_string(),
..Default::default()
},
token_in: token_in.clone(),
token_out: token_out.clone(),
split: 0f64,
user_data: None,
protocol_state: None,
};
token_in.clone(),
token_out.clone(),
)
.build();
let solution = Solution {
exact_out: false,

View File

@@ -89,6 +89,9 @@ pub struct Swap<'a> {
/// Optional protocol state used to perform the swap.
#[serde(skip)]
pub protocol_state: Option<&'a dyn ProtocolSim>,
/// Optional estimated amount in for this Swap. This is necessary for RFQ protocols. This value
/// is used to request the quote
pub estimated_amount_in: Option<BigUint>,
}
impl<'a> Swap<'a> {
@@ -99,8 +102,17 @@ impl<'a> Swap<'a> {
split: f64,
user_data: Option<Bytes>,
protocol_state: Option<&'a dyn ProtocolSim>,
estimated_amount_in: Option<BigUint>,
) -> Self {
Self { component: component.into(), token_in, token_out, split, user_data, protocol_state }
Self {
component: component.into(),
token_in,
token_out,
split,
user_data,
protocol_state,
estimated_amount_in,
}
}
}
@@ -115,6 +127,66 @@ impl<'a> PartialEq for Swap<'a> {
}
}
pub struct SwapBuilder<'a> {
component: ProtocolComponent,
token_in: Bytes,
token_out: Bytes,
split: f64,
user_data: Option<Bytes>,
protocol_state: Option<&'a dyn ProtocolSim>,
estimated_amount_in: Option<BigUint>,
}
impl<'a> SwapBuilder<'a> {
pub fn new<T: Into<ProtocolComponent>>(
component: T,
token_in: Bytes,
token_out: Bytes,
) -> Self {
Self {
component: component.into(),
token_in,
token_out,
split: 0.0,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
}
}
pub fn split(mut self, split: f64) -> Self {
self.split = split;
self
}
pub fn user_data(mut self, user_data: Bytes) -> Self {
self.user_data = Some(user_data);
self
}
pub fn protocol_state(mut self, protocol_state: &'a dyn ProtocolSim) -> Self {
self.protocol_state = Some(protocol_state);
self
}
pub fn estimated_amount_in(mut self, estimated_amount_in: BigUint) -> Self {
self.estimated_amount_in = Some(estimated_amount_in);
self
}
pub fn build(self) -> Swap<'a> {
Swap {
component: self.component,
token_in: self.token_in,
token_out: self.token_out,
split: self.split,
user_data: self.user_data,
protocol_state: self.protocol_state,
estimated_amount_in: self.estimated_amount_in,
}
}
}
/// Represents a transaction to be executed.
///
/// # Fields
@@ -262,6 +334,7 @@ mod tests {
0.5,
user_data.clone(),
None,
None,
);
assert_eq!(swap.token_in, Bytes::from("0x12"));
assert_eq!(swap.token_out, Bytes::from("0x34"));

View File

@@ -49,6 +49,7 @@ fn test_uniswap_v3_uniswap_v2() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
@@ -61,6 +62,7 @@ fn test_uniswap_v3_uniswap_v2() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -130,6 +132,7 @@ fn test_uniswap_v3_uniswap_v3() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
@@ -150,6 +153,7 @@ fn test_uniswap_v3_uniswap_v3() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -218,6 +222,7 @@ fn test_uniswap_v3_curve() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_usdt = Swap {
@@ -248,6 +253,7 @@ fn test_uniswap_v3_curve() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -308,6 +314,7 @@ fn test_balancer_v2_uniswap_v2() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_usdc = Swap {
@@ -321,6 +328,7 @@ fn test_balancer_v2_uniswap_v2() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -384,6 +392,7 @@ fn test_multi_protocol() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let balancer_swap_weth_wbtc = Swap {
@@ -397,6 +406,7 @@ fn test_multi_protocol() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let curve_swap_wbtc_usdt = Swap {
@@ -427,6 +437,7 @@ fn test_multi_protocol() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
// Ekubo
@@ -450,6 +461,7 @@ fn test_multi_protocol() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
// USV4
@@ -474,6 +486,7 @@ fn test_multi_protocol() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -547,6 +560,7 @@ fn test_uniswap_v3_balancer_v3() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_qnt = Swap {
component: ProtocolComponent {
@@ -559,6 +573,7 @@ fn test_uniswap_v3_balancer_v3() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);

View File

@@ -42,6 +42,7 @@ fn test_single_encoding_strategy_ekubo() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -95,6 +96,7 @@ fn test_single_encoding_strategy_maverick() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -160,6 +162,7 @@ fn test_single_encoding_strategy_usv4_eth_in() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -227,6 +230,7 @@ fn test_single_encoding_strategy_usv4_eth_out() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -302,6 +306,7 @@ fn test_single_encoding_strategy_usv4_grouped_swap() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_eth_pepe = Swap {
@@ -316,6 +321,7 @@ fn test_single_encoding_strategy_usv4_grouped_swap() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -425,6 +431,7 @@ fn test_single_encoding_strategy_curve() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -493,6 +500,7 @@ fn test_single_encoding_strategy_curve_st_eth() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -547,6 +555,7 @@ fn test_single_encoding_strategy_balancer_v3() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);

View File

@@ -38,6 +38,7 @@ fn test_sequential_swap_strategy_encoder() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
@@ -50,6 +51,7 @@ fn test_sequential_swap_strategy_encoder() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -106,6 +108,7 @@ fn test_sequential_swap_strategy_encoder_no_permit2() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
@@ -118,6 +121,7 @@ fn test_sequential_swap_strategy_encoder_no_permit2() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -220,6 +224,7 @@ fn test_sequential_strategy_cyclic_swap() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
// WETH -> USDC (Pool 2)
@@ -243,6 +248,7 @@ fn test_sequential_strategy_cyclic_swap() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -342,6 +348,7 @@ fn test_sequential_swap_strategy_encoder_unwrap() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_weth = Swap {
component: ProtocolComponent {
@@ -354,6 +361,7 @@ fn test_sequential_swap_strategy_encoder_unwrap() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);

View File

@@ -34,6 +34,7 @@ fn test_single_swap_strategy_encoder() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -121,6 +122,7 @@ fn test_single_swap_strategy_encoder_no_permit2() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
@@ -204,6 +206,7 @@ fn test_single_swap_strategy_encoder_no_transfer_in() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::None);
@@ -288,6 +291,7 @@ fn test_single_swap_strategy_encoder_wrap() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -341,6 +345,7 @@ fn test_single_swap_strategy_encoder_unwrap() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);

View File

@@ -43,6 +43,7 @@ fn test_split_swap_strategy_encoder() {
split: 0.5f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_weth_wbtc = Swap {
component: ProtocolComponent {
@@ -58,6 +59,7 @@ fn test_split_swap_strategy_encoder() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_dai_usdc = Swap {
component: ProtocolComponent {
@@ -70,6 +72,7 @@ fn test_split_swap_strategy_encoder() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_wbtc_usdc = Swap {
component: ProtocolComponent {
@@ -82,6 +85,7 @@ fn test_split_swap_strategy_encoder() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -149,6 +153,7 @@ fn test_split_input_cyclic_swap() {
split: 0.6f64, // 60% of input
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
// USDC -> WETH (Pool 2) - 40% of input (remaining)
@@ -172,6 +177,7 @@ fn test_split_input_cyclic_swap() {
split: 0f64,
user_data: None, // Remaining 40%
protocol_state: None,
estimated_amount_in: None,
};
// WETH -> USDC (Pool 2)
@@ -195,6 +201,7 @@ fn test_split_input_cyclic_swap() {
split: 0.0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);
@@ -316,6 +323,7 @@ fn test_split_output_cyclic_swap() {
split: 0.0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_weth_usdc_v3_pool1 = Swap {
@@ -336,6 +344,7 @@ fn test_split_output_cyclic_swap() {
split: 0.6f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_weth_usdc_v3_pool2 = Swap {
@@ -358,6 +367,7 @@ fn test_split_output_cyclic_swap() {
split: 0.0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFromPermit2);

View File

@@ -52,6 +52,7 @@ fn test_sequential_swap_usx() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let swap_usdc_usdt = Swap {
component: ProtocolComponent {
@@ -70,6 +71,7 @@ fn test_sequential_swap_usx() {
split: 0f64,
user_data: None,
protocol_state: None,
estimated_amount_in: None,
};
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);