test: update encoder tests for aggregate orders

This commit is contained in:
pedrobergamini
2025-06-17 21:39:47 -03:00
parent 5418846619
commit 5b8de033de
2 changed files with 113 additions and 103 deletions

View File

@@ -771,11 +771,16 @@ impl SwapEncoder for BebopSwapEncoder {
let settlement_address = Address::from_str(&self.settlement_address) let settlement_address = Address::from_str(&self.settlement_address)
.map_err(|_| EncodingError::FatalError("Invalid settlement address".to_string()))?; .map_err(|_| EncodingError::FatalError("Invalid settlement address".to_string()))?;
// Native ETH doesn't need approval, only ERC20 tokens do
if token_to_approve == Address::ZERO {
approval_needed = false;
} else {
approval_needed = token_approvals_manager.approval_needed( approval_needed = token_approvals_manager.approval_needed(
token_to_approve, token_to_approve,
tycho_router_address, tycho_router_address,
settlement_address, settlement_address,
)?; )?;
}
} else { } else {
approval_needed = true; approval_needed = true;
} }
@@ -2158,7 +2163,7 @@ mod tests {
}; };
let encoding_context = EncodingContext { let encoding_context = EncodingContext {
receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"), /* Original taker */ receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"), // Taker address from tx
exact_out: false, exact_out: false,
router_address: Some(Bytes::zero(20)), router_address: Some(Bytes::zero(20)),
group_token_in: token_in.clone(), group_token_in: token_in.clone(),
@@ -2198,40 +2203,43 @@ mod tests {
// Create user_data for a Bebop Aggregate RFQ quote // Create user_data for a Bebop Aggregate RFQ quote
let order_type = BebopOrderType::Aggregate as u8; let order_type = BebopOrderType::Aggregate as u8;
let signature_type = 1u8; // EIP712 let signature_type = 0u8; // ETH_SIGN
// Create a valid ABI-encoded Aggregate order // Use real mainnet data from https://etherscan.io/tx/0xec88410136c287280da87d0a37c1cb745f320406ca3ae55c678dec11996c1b1c
// For this test: single input (DAI) -> single output (WBTC) to match the test setup // Swap: 0.00985 ETH → 17.969561 USDC
let aggregate_order = BebopAggregate { let aggregate_order = BebopAggregate {
expiry: U256::from(1234567890u64), expiry: U256::from(1746367285u64),
taker_address: Address::from([0x11; 20]), taker_address: Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap(),
maker_addresses: vec![Address::from([0x22; 20]), Address::from([0x33; 20])], maker_addresses: vec![
maker_nonces: vec![U256::from(12345u64), U256::from(12346u64)], Address::from_str("0x67336Cec42645F55059EfF241Cb02eA5cC52fF86").unwrap(),
Address::from_str("0xBF19CbF0256f19f39A016a86Ff3551ecC6f2aAFE").unwrap(),
],
maker_nonces: vec![U256::from(1746367197308u64), U256::from(15460096u64)],
taker_tokens: vec![ taker_tokens: vec![
vec![Address::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap()], /* DAI for maker 1 */ vec![Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap()], // WETH for maker 1
vec![Address::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap()], /* DAI for maker 2 */ vec![Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap()], // WETH for maker 2
], ],
maker_tokens: vec![ maker_tokens: vec![
vec![Address::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599").unwrap()], /* WBTC from maker 1 */ vec![Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap()], // USDC from maker 1
vec![Address::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599").unwrap()], /* WBTC from maker 2 */ vec![Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap()], // USDC from maker 2
], ],
taker_amounts: vec![ taker_amounts: vec![
vec![U256::from(500000000000000000u64)], // 0.5 DAI for maker 1 vec![U256::from(5812106401997138u64)], // First maker takes ~0.0058 ETH
vec![U256::from(500000000000000000u64)], // 0.5 DAI for maker 2 vec![U256::from(4037893598002862u64)], // Second maker takes ~0.0040 ETH
], ],
maker_amounts: vec![ maker_amounts: vec![
vec![U256::from(1250000u64)], // 0.0125 WBTC from maker 1 vec![U256::from(10607211u64)], // First maker gives ~10.6 USDC
vec![U256::from(1250000u64)], // 0.0125 WBTC from maker 2 vec![U256::from(7362350u64)], // Second maker gives ~7.36 USDC
], ],
receiver: Address::from([0x44; 20]), receiver: Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap(),
commands: hex::decode("00040004").unwrap().into(), commands: hex::decode("00040004").unwrap().into(),
flags: U256::from(0), flags: U256::from_str("95769172144825922628485191511070792431742484643425438763224908097896054784000").unwrap(),
}; };
let quote_data = aggregate_order.abi_encode(); let quote_data = aggregate_order.abi_encode();
// For aggregate orders with ECDSA, use 65-byte signatures (2 makers) // Real signatures from mainnet transaction
let sig1 = hex::decode("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001").unwrap(); let sig1 = hex::decode("d5abb425f9bac1f44d48705f41a8ab9cae207517be8553d2c03b06a88995f2f351ab8ce7627a87048178d539dd64fd2380245531a0c8e43fdc614652b1f32fc71c").unwrap();
let sig2 = hex::decode("1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101").unwrap(); let sig2 = hex::decode("f38c698e48a3eac48f184bc324fef0b135ee13705ab38cc0bbf5a792f21002f051e445b9e7d57cf24c35e17629ea35b3263591c4abf8ca87ffa44b41301b89c41b").unwrap();
// Build ABI-encoded MakerSignature[] array with 2 signatures // Build ABI-encoded MakerSignature[] array with 2 signatures
let flags = U256::from(signature_type); let flags = U256::from(signature_type);
@@ -2274,7 +2282,7 @@ mod tests {
let padding2 = (32 - (sig2.len() % 32)) % 32; let padding2 = (32 - (sig2.len() % 32)) % 32;
encoded_maker_sigs.extend_from_slice(&vec![0u8; padding2]); encoded_maker_sigs.extend_from_slice(&vec![0u8; padding2]);
let filled_taker_amount = U256::from(1000000u64); // 1 USDC (6 decimals) let filled_taker_amount = U256::from(0u64); // 0 means fill entire order
let mut user_data = Vec::new(); let mut user_data = Vec::new();
user_data.push(order_type); user_data.push(order_type);
@@ -2290,8 +2298,8 @@ mod tests {
..Default::default() ..Default::default()
}; };
let token_in = Bytes::from("0x6B175474E89094C44Da98b954EedeAC495271d0F"); // DAI let token_in = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // WETH
let token_out = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); // WBTC let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
let swap = Swap { let swap = Swap {
component: bebop_component, component: bebop_component,
token_in: token_in.clone(), token_in: token_in.clone(),
@@ -2301,12 +2309,12 @@ mod tests {
}; };
let encoding_context = EncodingContext { let encoding_context = EncodingContext {
receiver: Bytes::from("0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e"), // BOB receiver: Bytes::from("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6"), // Taker address from tx
exact_out: false, exact_out: false,
router_address: Some(Bytes::zero(20)), router_address: Some(Bytes::zero(20)),
group_token_in: token_in.clone(), group_token_in: token_in.clone(),
group_token_out: token_out.clone(), group_token_out: token_out.clone(),
transfer_type: TransferType::TransferFrom, transfer_type: TransferType::Transfer,
}; };
let encoder = BebopSwapEncoder::new( let encoder = BebopSwapEncoder::new(
@@ -2338,24 +2346,24 @@ mod tests {
// - offset to signatureBytes // - offset to signatureBytes
"0000000000000000000000000000000000000000000000000000000000000040", "0000000000000000000000000000000000000000000000000000000000000040",
// - flags // - flags
"0000000000000000000000000000000000000000000000000000000000000001",
// - signatureBytes length (65 = 0x41)
"0000000000000000000000000000000000000000000000000000000000000041",
// - signatureBytes (65 bytes padded to 96)
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
"0000000000000000000000000000000000000000000000000000000000000000", // - signatureBytes length (66 = 0x42, actual length of signatures)
"0100000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000042",
// - signatureBytes (66 bytes padded to 96)
"d5abb425f9bac1f44d48705f41a8ab9cae207517be8553d2c03b06a88995f2f3",
"51ab8ce7627a87048178d539dd64fd2380245531a0c8e43fdc614652b1f32fc7",
"1c00000000000000000000000000000000000000000000000000000000000000",
// Second struct data: // Second struct data:
// - offset to signatureBytes // - offset to signatureBytes
"0000000000000000000000000000000000000000000000000000000000000040", "0000000000000000000000000000000000000000000000000000000000000040",
// - flags // - flags
"0000000000000000000000000000000000000000000000000000000000000001", "0000000000000000000000000000000000000000000000000000000000000000",
// - signatureBytes length (65 = 0x41) // - signatureBytes length (66 = 0x42)
"0000000000000000000000000000000000000000000000000000000000000041", "0000000000000000000000000000000000000000000000000000000000000042",
// - signatureBytes (65 bytes padded to 96) // - signatureBytes (66 bytes padded to 96)
"1111111111111111111111111111111111111111111111111111111111111111", "f38c698e48a3eac48f184bc324fef0b135ee13705ab38cc0bbf5a792f21002f0",
"1111111111111111111111111111111111111111111111111111111111111111", "51e445b9e7d57cf24c35e17629ea35b3263591c4abf8ca87ffa44b41301b89c4",
"0100000000000000000000000000000000000000000000000000000000000000" "1b00000000000000000000000000000000000000000000000000000000000000"
); );
// The quote data is now an ABI-encoded BebopAggregate struct // The quote data is now an ABI-encoded BebopAggregate struct
@@ -2366,15 +2374,15 @@ mod tests {
let expected_hex = format!( let expected_hex = format!(
"{}{}{}{}{}{}{}{}{}{}", "{}{}{}{}{}{}{}{}{}{}",
// token in // token in
"6b175474e89094c44da98b954eedeac495271d0f", "c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
// token out // token out
"2260fac5e5542a773aa44fbcfedf7c193bc2c599", "a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
// transfer type TransferFrom // transfer type Transfer
"00", "01",
// order type Aggregate (1) // order type Aggregate (1)
"01", "01",
// filledTakerAmount (1000000 = 0x0f4240) // filledTakerAmount (0 for full fill)
"00000000000000000000000000000000000000000000000000000000000f4240", "0000000000000000000000000000000000000000000000000000000000000000",
// quote data length // quote data length
&quote_data_length, &quote_data_length,
// quote data // quote data

View File

@@ -415,6 +415,10 @@ mod tests {
Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap() Bytes::from_str("0x6982508145454Ce325dDbE47a25d4ec3d2311933").unwrap()
} }
fn ondo() -> Bytes {
Bytes::from_str("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3").unwrap()
}
// Fee and tick spacing information for this test is obtained by querying the // Fee and tick spacing information for this test is obtained by querying the
// USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e // USV4 Position Manager contract: 0xbd216513d74c8cf14cf4747e6aaa6420ff64ee9e
// Using the poolKeys function with the first 25 bytes of the pool id // Using the poolKeys function with the first 25 bytes of the pool id
@@ -2410,10 +2414,8 @@ mod tests {
// WETH ───(USV3)──> USDC ───(Bebop RFQ)──> ONDO // WETH ───(USV3)──> USDC ───(Bebop RFQ)──> ONDO
let weth = weth(); let weth = weth();
let usdc = let usdc = usdc();
Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap(); let ondo = ondo();
let ondo =
Bytes::from_str("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3").unwrap();
// First swap: WETH -> USDC via UniswapV3 // First swap: WETH -> USDC via UniswapV3
let swap_weth_usdc = Swap { let swap_weth_usdc = Swap {
@@ -3830,8 +3832,8 @@ mod tests {
fn test_single_encoding_strategy_bebop() { fn test_single_encoding_strategy_bebop() {
// Use the same mainnet data from Solidity tests // Use the same mainnet data from Solidity tests
// Transaction: https://etherscan.io/tx/0x6279bc970273b6e526e86d9b69133c2ca1277e697ba25375f5e6fc4df50c0c94 // Transaction: https://etherscan.io/tx/0x6279bc970273b6e526e86d9b69133c2ca1277e697ba25375f5e6fc4df50c0c94
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC let token_in = usdc();
let token_out = Bytes::from("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3"); // ONDO let token_out = ondo();
let amount_in = BigUint::from_str("200000000").unwrap(); // 200 USDC let amount_in = BigUint::from_str("200000000").unwrap(); // 200 USDC
let amount_out = BigUint::from_str("237212396774431060000").unwrap(); // 237.21 ONDO let amount_out = BigUint::from_str("237212396774431060000").unwrap(); // 237.21 ONDO
@@ -3935,55 +3937,55 @@ mod tests {
#[test] #[test]
fn test_single_encoding_strategy_bebop_aggregate() { fn test_single_encoding_strategy_bebop_aggregate() {
// For simplicity, let's use the same tokens as the Single test but with Aggregate // Use real mainnet aggregate order data from CLAUDE.md
// order // Transaction: https://etherscan.io/tx/0xec88410136c287280da87d0a37c1cb745f320406ca3ae55c678dec11996c1b1c
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC // For testing, use WETH directly to avoid delegatecall + native ETH complexities
let token_out = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // WETH let token_in = eth();
let amount_in = BigUint::from_str("1000_000000").unwrap(); // 1000 USDC let token_out = usdc();
let amount_out = BigUint::from_str("400000000000000000").unwrap(); // 0.4 WETH let amount_in = BigUint::from_str("9850000000000000").unwrap(); // 0.00985 WETH
let amount_out = BigUint::from_str("17969561").unwrap(); // 17.969561 USDC
// Create a valid Bebop Aggregate order struct // Create the exact aggregate order from mainnet
use std::time::{SystemTime, UNIX_EPOCH}; let expiry = 1746367285u64;
let expiry = SystemTime::now() let taker_address =
.duration_since(UNIX_EPOCH) Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap();
.unwrap() let receiver =
.as_secs() + Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap();
3600;
let taker_address = Address::ZERO;
// Aggregate order has multiple makers // Set up makers
let maker_addresses = vec![ let maker_addresses = vec![
Address::from_str("0x1111111111111111111111111111111111111111").unwrap(), Address::from_str("0x67336Cec42645F55059EfF241Cb02eA5cC52fF86").unwrap(),
Address::from_str("0x2222222222222222222222222222222222222222").unwrap(), Address::from_str("0xBF19CbF0256f19f39A016a86Ff3551ecC6f2aAFE").unwrap(),
]; ];
let maker_nonces = vec![U256::from(1u64), U256::from(2u64)]; // Array of nonces let maker_nonces = vec![U256::from(1746367197308u64), U256::from(15460096u64)];
// Each maker accepts the same taker token (USDC) // 2D arrays for tokens
// We use WETH as a taker token even when handling native ETH
let taker_tokens = vec![ let taker_tokens = vec![
vec![Address::from_slice(&token_in)], // USDC for maker 1 vec![Address::from_slice(&weth())],
vec![Address::from_slice(&token_in)], // USDC for maker 2 vec![Address::from_slice(&weth())],
]; ];
// Each maker gets a portion of the input
let taker_amounts = vec![
vec![U256::from_str("600000000").unwrap()], // 600 USDC for maker 1
vec![U256::from_str("400000000").unwrap()], // 400 USDC for maker 2
];
// Each maker provides WETH
let maker_tokens = vec![ let maker_tokens = vec![
vec![Address::from_slice(&token_out)], vec![Address::from_slice(&token_out)],
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![ let maker_amounts = vec![
vec![U256::from_str("240000000000000000").unwrap()], // 0.24 WETH from maker 1 vec![U256::from_str("10607211").unwrap()],
vec![U256::from_str("160000000000000000").unwrap()], // 0.16 WETH from maker 2 vec![U256::from_str("7362350").unwrap()],
]; ];
let receiver = // Commands and flags from the real transaction
Address::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(); // Alice
let commands = hex!("00040004").to_vec(); let commands = hex!("00040004").to_vec();
let flags = U256::ZERO; let flags = U256::from_str(
"95769172144825922628485191511070792431742484643425438763224908097896054784000",
)
.unwrap();
// Encode Aggregate order - must match IBebopSettlement.Aggregate struct exactly // Encode Aggregate order - must match IBebopSettlement.Aggregate struct exactly
let quote_data = ( let quote_data = (
@@ -4001,15 +4003,14 @@ mod tests {
) )
.abi_encode(); .abi_encode();
// For aggregate orders with multiple makers, we need multiple signatures // Use real signatures from the mainnet transaction
// This example has 2 makers, so 2 separate 65-byte signatures let sig1 = hex::decode("d5abb425f9bac1f44d48705f41a8ab9cae207517be8553d2c03b06a88995f2f351ab8ce7627a87048178d539dd64fd2380245531a0c8e43fdc614652b1f32fc71c").unwrap();
let sig1 = hex::decode("1b47a665f9a5e14b5208015d11f9143e27b93dc5a0d8c892ec5326eda1e5df3c42a987d0b2ea5b8be8f0e5c326bd4ec0321b10c6e9b4e5f8a0b8d5e6f7c8a9b01b").unwrap(); let sig2 = hex::decode("f38c698e48a3eac48f184bc324fef0b135ee13705ab38cc0bbf5a792f21002f051e445b9e7d57cf24c35e17629ea35b3263591c4abf8ca87ffa44b41301b89c41b").unwrap();
let sig2 = hex::decode("2c58b665f9a5e14b5208015d11f9143e27b93dc5a0d8c892ec5326eda1e5df3c53b987d0b2ea5b8be8f0e5c326bd4ec0321b10c6e9b4e5f8a0b8d5e6f7c8a9b01c").unwrap();
// Build user_data with multiple signatures for the aggregate order // Build user_data with ETH_SIGN flag (0) for both signatures
let signatures = vec![ let signatures = vec![
(sig1, 1u8), // EIP712 signature type for maker 1 (sig1, 0u8), // ETH_SIGN for maker 1
(sig2, 1u8), // EIP712 signature type for maker 2 (sig2, 0u8), // ETH_SIGN for maker 2
]; ];
let user_data = build_bebop_user_data( let user_data = build_bebop_user_data(
@@ -4034,18 +4035,19 @@ mod tests {
user_data: Some(user_data), user_data: Some(user_data),
}; };
// Use TransferFrom for WETH token transfer
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom); let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
let solution = Solution { let solution = Solution {
exact_out: false, exact_out: false,
given_token: token_in, given_token: token_in.clone(),
given_amount: amount_in, given_amount: amount_in,
checked_token: token_out, checked_token: token_out,
checked_amount: amount_out, checked_amount: amount_out,
// Alice // Use ALICE as sender but order receiver as receiver
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(), /* ALICE */
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2") receiver: Bytes::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6")
.unwrap(), .unwrap(), /* Order receiver */
swaps: vec![swap], swaps: vec![swap],
..Default::default() ..Default::default()
}; };
@@ -4059,7 +4061,7 @@ mod tests {
eth_chain().id, eth_chain().id,
encoded_solution, encoded_solution,
&solution, &solution,
UserTransferType::TransferFrom, UserTransferType::None,
eth(), eth(),
None, None,
) )