fix: Simplify the BebopExecutor and fix Single tests

Make specific quotes that are expected to be used by the TychoRouter for the tests. Do not use the BebopHarness
Commented out Aggregate tests

Took 6 hours 40 minutes
This commit is contained in:
Diana Carvalho
2025-08-12 16:11:42 +01:00
parent ee3d0cc060
commit 76a09d0402
11 changed files with 804 additions and 1722 deletions

View File

@@ -3,14 +3,10 @@ pub mod encoding;
use std::str::FromStr;
use alloy::{
primitives::{B256, U256},
signers::local::PrivateKeySigner,
};
use alloy::{primitives::B256, signers::local::PrivateKeySigner};
use tycho_common::{models::Chain, Bytes};
use tycho_execution::encoding::{
evm::encoder_builders::TychoRouterEncoderBuilder,
models::{BebopOrderType, UserTransferType},
evm::encoder_builders::TychoRouterEncoderBuilder, models::UserTransferType,
tycho_encoder::TychoEncoder,
};
@@ -75,236 +71,9 @@ pub fn get_tycho_router_encoder(user_transfer_type: UserTransferType) -> Box<dyn
/// Builds the complete Bebop calldata in the format expected by the encoder
/// Returns: partialFillOffset (1 byte) | bebop_calldata (selector + ABI encoded params)
///
/// # 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 (just the struct, not full calldata)
/// * `signatures` - Vector of (signature_bytes, signature_type) tuples
pub fn build_bebop_calldata(
order_type: BebopOrderType,
filled_taker_amount: U256,
quote_data: &[u8],
signatures: Vec<(Vec<u8>, u8)>,
) -> Bytes {
// Step 1: Determine selector and partialFillOffset based on order type
let (selector, partial_fill_offset) = match order_type {
BebopOrderType::Single => (
[0x4d, 0xce, 0xbc, 0xba], // swapSingle selector
12u8, /* partialFillOffset (388 = 4 + 12*32) - after order and
* signature offset */
),
BebopOrderType::Aggregate => (
[0xa2, 0xf7, 0x48, 0x93], // swapAggregate selector
2u8, /* partialFillOffset (68 = 4 + 2*32) - aggregate still
* uses offsets */
),
};
// Step 2: Build the ABI-encoded parameters based on order type
let encoded_params = match order_type {
BebopOrderType::Single => {
// swapSingle(Single order, MakerSignature signature, uint256 filledTakerAmount)
encode_single_params(quote_data, &signatures[0], filled_taker_amount)
}
BebopOrderType::Aggregate => {
// swapAggregate(Aggregate order, MakerSignature[] signatures, uint256
// filledTakerAmount)
encode_aggregate_params(quote_data, &signatures, filled_taker_amount)
}
};
// Step 3: Combine selector and encoded parameters into complete calldata
let mut bebop_calldata = Vec::new();
bebop_calldata.extend_from_slice(&selector);
bebop_calldata.extend_from_slice(&encoded_params);
// Step 4: Prepend partialFillOffset to create final user_data
pub fn build_bebop_calldata(calldata: &[u8], partial_fill_offset: u8) -> Bytes {
let mut user_data = vec![partial_fill_offset];
user_data.extend_from_slice(&bebop_calldata);
user_data.extend_from_slice(calldata);
Bytes::from(user_data)
}
fn encode_single_params(
order_data: &[u8], // Already ABI-encoded Single struct
signature: &(Vec<u8>, u8),
filled_taker_amount: U256,
) -> Vec<u8> {
// abi.encode() with (struct, struct, uint256) where:
// - Single struct: all fixed-size fields, encoded inline
// - MakerSignature struct: has dynamic bytes field, needs offset
// - uint256: fixed-size, encoded inline
let mut encoded = Vec::new();
// 1. Order struct fields are encoded inline (11 fields * 32 bytes = 352 bytes)
encoded.extend_from_slice(order_data);
// 2. Offset to MakerSignature data (points after all inline data)
// Offset = 352 (order) + 32 (this offset) + 32 (filledTakerAmount) = 416
encoded.extend_from_slice(&U256::from(416).to_be_bytes::<32>());
// 3. filledTakerAmount inline
encoded.extend_from_slice(&filled_taker_amount.to_be_bytes::<32>());
// 4. MakerSignature struct data at the offset
let signature_struct = encode_maker_signature(signature);
encoded.extend_from_slice(&signature_struct);
encoded
}
fn encode_aggregate_params(
order_data: &[u8], // Already ABI-encoded Aggregate struct
signatures: &[(Vec<u8>, u8)],
filled_taker_amount: U256,
) -> Vec<u8> {
// abi.encode() with (struct, struct[], uint256) where:
// - Aggregate struct: has dynamic arrays, gets offset
// - MakerSignature[] array: dynamic, gets offset
// - uint256: fixed-size, encoded inline
//
// CRITICAL FIX: The issue is that Rust's ABI encoder produces aggregate orders
// that are 32 bytes larger than Solidity's (1536 vs 1504 bytes). We cannot just
// adjust the offset while keeping the wrong-sized data. Instead, we need to
// truncate the extra 32 bytes from the Rust-encoded order data to match Solidity.
let mut encoded = Vec::new();
// Fixed: Using alloy::primitives::Bytes for the commands field produces the correct
// 1504-byte encoding that matches Solidity. No truncation needed anymore.
let corrected_order_data = order_data;
// Calculate offsets
let order_offset = 96; // After 3 words (3 * 32 = 96)
let signatures_offset = order_offset + corrected_order_data.len();
// Write the three parameter slots
encoded.extend_from_slice(&U256::from(order_offset).to_be_bytes::<32>());
encoded.extend_from_slice(&U256::from(signatures_offset).to_be_bytes::<32>());
encoded.extend_from_slice(&filled_taker_amount.to_be_bytes::<32>());
// Append the corrected order data
encoded.extend_from_slice(corrected_order_data);
// Manually encode the signatures array to exactly match the working test
// Array length
encoded.extend_from_slice(&U256::from(signatures.len()).to_be_bytes::<32>());
// For 2 signatures, we need offsets for each struct
if signatures.len() == 2 {
// First signature starts after the two offset words (64 bytes)
let sig1_offset = 64;
// Calculate size of first signature struct:
// - offset to bytes field: 32
// - flags field: 32
// - length of bytes: 32
// - actual signature bytes + padding
let sig1 = &signatures[0];
let sig1_padding = (32 - (sig1.0.len() % 32)) % 32;
let sig1_size = 64 + 32 + sig1.0.len() + sig1_padding;
// Second signature starts after first
let sig2_offset = sig1_offset + sig1_size;
// Write offsets
encoded.extend_from_slice(&U256::from(sig1_offset).to_be_bytes::<32>());
encoded.extend_from_slice(&U256::from(sig2_offset).to_be_bytes::<32>());
// Encode each MakerSignature struct
for signature in signatures {
// Offset to signatureBytes within this struct (always 64)
encoded.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
// Flags (signature type)
encoded.extend_from_slice(&U256::from(signature.1).to_be_bytes::<32>());
// SignatureBytes length
encoded.extend_from_slice(&U256::from(signature.0.len()).to_be_bytes::<32>());
// SignatureBytes data
encoded.extend_from_slice(&signature.0);
// Padding to 32-byte boundary
let padding = (32 - (signature.0.len() % 32)) % 32;
encoded.extend(vec![0u8; padding]);
}
} else {
// General case for any number of signatures
let signatures_array = encode_maker_signatures_array(signatures);
encoded.extend_from_slice(&signatures_array);
}
encoded
}
fn encode_maker_signature(signature: &(Vec<u8>, u8)) -> Vec<u8> {
let mut encoded = Vec::new();
// MakerSignature struct has two fields:
// - bytes signatureBytes (dynamic) - offset at position 0
// - uint256 flags - at position 32
// Offset to signatureBytes (always 64 for this struct layout)
encoded.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
// Flags (signature type)
encoded.extend_from_slice(&U256::from(signature.1).to_be_bytes::<32>());
// SignatureBytes (length + data)
encoded.extend_from_slice(&U256::from(signature.0.len()).to_be_bytes::<32>());
encoded.extend_from_slice(&signature.0);
// Pad to 32-byte boundary
let padding = (32 - (signature.0.len() % 32)) % 32;
encoded.extend(vec![0u8; padding]);
encoded
}
fn encode_maker_signatures_array(signatures: &[(Vec<u8>, u8)]) -> Vec<u8> {
let mut encoded = Vec::new();
// Array length
encoded.extend_from_slice(&U256::from(signatures.len()).to_be_bytes::<32>());
// Calculate offsets for each struct (relative to start of array data)
let mut struct_data = Vec::new();
let mut struct_offsets = Vec::new();
let struct_offsets_size = 32 * signatures.len();
let mut current_offset = struct_offsets_size;
for signature in signatures {
struct_offsets.push(current_offset);
// Build struct data
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 (signature type)
struct_bytes.extend_from_slice(&U256::from(signature.1).to_be_bytes::<32>());
// SignatureBytes length
struct_bytes.extend_from_slice(&U256::from(signature.0.len()).to_be_bytes::<32>());
// SignatureBytes data (padded to 32 byte boundary)
struct_bytes.extend_from_slice(&signature.0);
let padding = (32 - (signature.0.len() % 32)) % 32;
struct_bytes.extend(vec![0u8; padding]);
current_offset += struct_bytes.len();
struct_data.push(struct_bytes);
}
// Write struct offsets
for offset in struct_offsets {
encoded.extend_from_slice(&U256::from(offset).to_be_bytes::<32>());
}
// Write struct data
for data in struct_data {
encoded.extend_from_slice(&data);
}
encoded
}

View File

@@ -1,20 +1,15 @@
use std::{collections::HashMap, str::FromStr};
use alloy::{
hex::encode,
primitives::{Address, U256},
sol_types::SolValue,
};
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::{BebopOrderType, Solution, Swap, UserTransferType},
models::{Solution, Swap, UserTransferType},
};
use crate::common::{
build_bebop_calldata, encoding::encode_tycho_router_call, eth, eth_chain, get_signer,
get_tycho_router_encoder, ondo, usdc, weth,
encoding::encode_tycho_router_call, eth, eth_chain, get_signer, get_tycho_router_encoder, weth,
};
mod common;
@@ -599,135 +594,142 @@ fn test_uniswap_v3_balancer_v3() {
write_calldata_to_file("test_uniswap_v3_balancer_v3", 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,
protocol_state: 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_calldata(
BebopOrderType::Single,
U256::from(0), // 0 means fill entire order
&quote_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),
protocol_state: None,
};
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("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(), /* Must match order taker_address */
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]
// 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,
// protocol_state: 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_calldata(
// BebopOrderType::Single,
// U256::from(0), // 0 means fill entire order
// &quote_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),
// protocol_state: None,
// };
//
// 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("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(), /* Must match order
// taker_address */ 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());
// }

View File

@@ -1,17 +1,12 @@
mod common;
use std::{collections::HashMap, str::FromStr};
use alloy::{
hex,
hex::encode,
primitives::{Address, Bytes as AlloyBytes, U256},
sol_types::SolValue,
};
use alloy::{hex, 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::{BebopOrderType, Solution, Swap, UserTransferType},
models::{Solution, Swap, UserTransferType},
};
use crate::common::{
@@ -590,55 +585,21 @@ fn test_single_encoding_strategy_balancer_v3() {
#[test]
fn test_single_encoding_strategy_bebop() {
// Use the same mainnet data from Solidity tests
// Transaction: https://etherscan.io/tx/0x6279bc970273b6e526e86d9b69133c2ca1277e697ba25375f5e6fc4df50c0c94
// The quote was done separately where the sender is the router and the receiver is a random
// user let _router = Bytes::from_str("0x3Ede3eCa2a72B3aeCC820E955B36f38437D01395").unwrap();
let user = Bytes::from_str("0xd2068e04cf586f76eece7ba5beb779d7bb1474a1").unwrap();
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();
let amount_out = BigUint::from_str("194477331556159832309").unwrap(); // 203.8 ONDO
let partial_fill_offset = 12;
// 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();
let calldata = Bytes::from_str("0x4dcebcba00000000000000000000000000000000000000000000000000000000689b548f0000000000000000000000003ede3eca2a72b3aecc820e955b36f38437d0139500000000000000000000000067336cec42645f55059eff241cb02ea5cc52ff86000000000000000000000000000000000000000000000000279ead5d9685f25b000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000faba6f8e4a5e8ab82f62fe7c39859fa577269be3000000000000000000000000000000000000000000000000000000000bebc20000000000000000000000000000000000000000000000000a8aea46aa4ec5c0f5000000000000000000000000d2068e04cf586f76eece7ba5beb779d7bb1474a100000000000000000000000000000000000000000000000000000000000000005230bcb979c81cebf94a3b5c08bcfa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000414ce40058ff07f11d9224c2c8d1e58369e4a90173856202d8d2a17da48058ad683dedb742eda0d4c0cf04cf1c09138898dd7fd06f97268ea7f74ef9b42d29bf4c1b00000000000000000000000000000000000000000000000000000000000000").unwrap();
// Build user_data with the quote and signature using new calldata format
let user_data = build_bebop_calldata(
BebopOrderType::Single,
U256::ZERO, // 0 means fill entire order
&quote_data,
vec![(signature, 0)], // ETH_SIGN signature type
);
let user_data = build_bebop_calldata(&calldata, partial_fill_offset);
let bebop_component = ProtocolComponent {
id: String::from("bebop-rfq"),
@@ -658,17 +619,14 @@ fn test_single_encoding_strategy_bebop() {
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,
sender: user.clone(),
receiver: user,
swaps: vec![swap],
..Default::default()
};
@@ -691,131 +649,138 @@ fn test_single_encoding_strategy_bebop() {
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
// Use native ETH as tycho's token_in
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 = AlloyBytes::from(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_calldata(
BebopOrderType::Aggregate,
U256::ZERO, // 0 means fill entire order
&quote_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),
protocol_state: None,
};
// 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 order taker as both sender and receiver to match the test setup
sender: Bytes::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap(), /* Order taker */
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::TransferFrom,
&eth(),
None,
)
.unwrap()
.data;
let hex_calldata = hex::encode(&calldata);
write_calldata_to_file("test_single_encoding_strategy_bebop_aggregate", 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
// // Use native ETH as tycho's token_in
// 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 = AlloyBytes::from(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_calldata(
// BebopOrderType::Aggregate,
// U256::ZERO, // 0 means fill entire order
// &quote_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),
// protocol_state: None,
// };
//
// // 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 order taker as both sender and receiver to match the test setup
// sender: Bytes::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap(), /* Order
// taker */ 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::TransferFrom,
// &eth(),
// None,
// )
// .unwrap()
// .data;
// let hex_calldata = hex::encode(&calldata);
//
// write_calldata_to_file("test_single_encoding_strategy_bebop_aggregate",
// hex_calldata.as_str()); }