chore: refactor Aggregate orders, fix Single orders integration tests and calldata generation

This commit is contained in:
pedrobergamini
2025-06-16 23:08:54 -03:00
parent 689fdd6a58
commit 5418846619
9 changed files with 368 additions and 488 deletions

View File

@@ -1,4 +1,7 @@
use std::{collections::HashMap, str::FromStr};
use std::{
collections::{HashMap, HashSet},
str::FromStr,
};
use alloy::{
core::sol,
@@ -637,14 +640,14 @@ sol! {
struct BebopAggregate {
uint256 expiry;
address taker_address;
uint256 taker_nonce;
address[] taker_tokens;
uint256[] taker_amounts;
address[] maker_addresses;
uint256[] maker_nonces;
address[][] taker_tokens;
address[][] maker_tokens;
uint256[][] taker_amounts;
uint256[][] maker_amounts;
address receiver;
uint256 packed_commands;
bytes commands;
uint256 flags;
}
}
@@ -691,10 +694,17 @@ impl BebopSwapEncoder {
EncodingError::InvalidInput(format!("Failed to decode Bebop Aggregate order: {}", e))
})?;
// Validate that we only have one input token
if order.taker_tokens.len() != 1 {
// Validate that we only have one unique input token across all makers
let unique_taker_tokens: HashSet<_> = order
.taker_tokens
.iter()
.flat_map(|tokens| tokens.iter())
.collect();
if unique_taker_tokens.len() != 1 {
return Err(EncodingError::InvalidInput(
"Aggregate orders must have exactly one input token".to_string(),
"Aggregate orders must have exactly one unique input token across all makers"
.to_string(),
));
}
@@ -2195,24 +2205,26 @@ mod tests {
let aggregate_order = BebopAggregate {
expiry: U256::from(1234567890u64),
taker_address: Address::from([0x11; 20]),
taker_nonce: U256::from(12345u64),
taker_tokens: vec![
Address::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(), /* DAI */
],
taker_amounts: vec![
U256::from(1000000000000000000u64), // 1 DAI
],
maker_addresses: vec![Address::from([0x22; 20]), Address::from([0x33; 20])],
maker_nonces: vec![U256::from(12345u64), U256::from(12346u64)],
taker_tokens: vec![
vec![Address::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap()], /* DAI for maker 1 */
vec![Address::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap()], /* DAI for maker 2 */
],
maker_tokens: vec![
vec![Address::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599").unwrap()], /* WBTC from maker 1 */
vec![Address::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599").unwrap()], /* WBTC from maker 2 */
],
taker_amounts: vec![
vec![U256::from(500000000000000000u64)], // 0.5 DAI for maker 1
vec![U256::from(500000000000000000u64)], // 0.5 DAI for maker 2
],
maker_amounts: vec![
vec![U256::from(1250000u64)], // 0.0125 WBTC from maker 1
vec![U256::from(1250000u64)], // 0.0125 WBTC from maker 2
],
receiver: Address::from([0x44; 20]),
packed_commands: U256::from(0),
commands: hex::decode("00040004").unwrap().into(),
flags: U256::from(0),
};

View File

@@ -2046,7 +2046,6 @@ mod tests {
mod optimized_transfers {
// In this module we test the ability to chain swaps or not. Different protocols are
// tested. The encoded data is used for solidity tests as well
use std::time::{SystemTime, UNIX_EPOCH};
use super::*;
@@ -2405,16 +2404,16 @@ mod tests {
// 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 DAI through USDC using USV3 and Bebop
// RFQ
// Performs a sequential swap from WETH to ONDO through USDC using USV3 and
// Bebop RFQ
//
// WETH ───(USV3)──> USDC ───(Bebop RFQ)──> DAI
// WETH ───(USV3)──> USDC ───(Bebop RFQ)──> ONDO
let weth = weth();
let usdc =
Bytes::from_str("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48").unwrap();
let dai =
Bytes::from_str("0x6b175474e89094c44da98b954eedeac495271d0f").unwrap();
let ondo =
Bytes::from_str("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3").unwrap();
// First swap: WETH -> USDC via UniswapV3
let swap_weth_usdc = Swap {
@@ -2437,26 +2436,23 @@ mod tests {
user_data: None,
};
// Second swap: USDC -> DAI via Bebop RFQ
// Create a valid Bebop Single order struct
let expiry = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() +
3600; // Current time + 1 hour
let taker_address = Address::ZERO;
// 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("0x1234567890123456789012345678901234567890").unwrap(); // Use a proper maker address
let maker_nonce = 1u64;
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(&dai.to_string()).unwrap();
// For ~2021.75 USDC input (what 1 ETH gives us via USV3), expecting ~2021.75
// DAI output
let taker_amount = U256::from_str("2021750881").unwrap(); // 2021.75 USDC (6 decimals)
let maker_amount = U256::from_str("2021750881000000000000").unwrap(); // 2021.75 DAI (18 decimals)
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("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(); // Alice's address
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 = (
@@ -2470,18 +2466,19 @@ mod tests {
maker_amount,
receiver,
packed_commands,
U256::from(0u64), // flags as uint256
flags,
)
.abi_encode();
let signature = hex::decode("aabbccdd").unwrap();
// Real signature from the order
let signature = hex::decode("eb5419631614978da217532a40f02a8f2ece37d8cfb94aaa602baabbdefb56b474f4c2048a0f56502caff4ea7411d99eed6027cd67dc1088aaf4181dcb0df7051c").unwrap();
// Build user_data with the quote and signature
let user_data = build_bebop_user_data(
BebopOrderType::Single,
U256::from(0), // 0 means fill entire order
&quote_data,
vec![(signature, 1)], // EIP712 signature type
vec![(signature, 0)], // ETH_SIGN signature type (0)
);
let bebop_component = ProtocolComponent {
@@ -2491,10 +2488,10 @@ mod tests {
..Default::default()
};
let swap_usdc_dai = Swap {
let swap_usdc_ondo = Swap {
component: bebop_component,
token_in: usdc.clone(),
token_out: dai.clone(),
token_out: ondo.clone(),
split: 0f64,
user_data: Some(user_data),
};
@@ -2504,14 +2501,17 @@ mod tests {
let solution = Solution {
exact_out: false,
given_token: weth,
given_amount: BigUint::from_str("1_000000000000000000").unwrap(), // 1 WETH
checked_token: dai,
checked_amount: BigUint::from_str("2021750881000000000000").unwrap(), /* Expected ~2021.75 DAI */
// Use ~0.099 WETH to get approximately 200 USDC from UniswapV3
// This should leave only dust amount in the router after Bebop consumes 200
// USDC
given_amount: BigUint::from_str("99000000000000000").unwrap(), // 0.099 WETH
checked_token: ondo,
checked_amount: BigUint::from_str("237212396774431060000").unwrap(), /* Expected ONDO from Bebop order */
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
.unwrap(),
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
.unwrap(),
swaps: vec![swap_weth_usdc, swap_usdc_dai],
receiver: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670")
.unwrap(), // Using the real order receiver
swaps: vec![swap_weth_usdc, swap_usdc_ondo],
..Default::default()
};
@@ -3228,8 +3228,6 @@ mod tests {
mod protocol_integration {
// in this module we test protocol specific logic by creating the calldata that then is
// used in the solidity tests
use std::time::{SystemTime, UNIX_EPOCH};
use super::*;
#[test]
@@ -3840,7 +3838,7 @@ mod tests {
// Create the exact same order from mainnet
let expiry = 1749483840u64;
let taker_address =
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap();
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(); // Order receiver from mainnet
let maker_address =
Address::from_str("0xCe79b081c0c924cb67848723ed3057234d10FC6b").unwrap();
let maker_nonce = 1749483765992417u64;
@@ -3848,7 +3846,7 @@ mod tests {
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;
let receiver = taker_address; // Same as taker_address in this order
let packed_commands = U256::ZERO;
let flags = U256::from_str(
"51915842898789398998206002334703507894664330885127600393944965515693155942400",
@@ -3905,10 +3903,10 @@ mod tests {
given_amount: amount_in,
checked_token: token_out,
checked_amount: amount_out, // Expected output amount
// Use the original taker address
sender: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(),
// Use ALICE as sender but order receiver as receiver
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
receiver: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670")
.unwrap(),
.unwrap(), // Order receiver from mainnet
swaps: vec![swap],
..Default::default()
};
@@ -3945,6 +3943,7 @@ mod tests {
let amount_out = BigUint::from_str("400000000000000000").unwrap(); // 0.4 WETH
// Create a valid Bebop Aggregate order struct
use std::time::{SystemTime, UNIX_EPOCH};
let expiry = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
@@ -3957,11 +3956,21 @@ mod tests {
Address::from_str("0x1111111111111111111111111111111111111111").unwrap(),
Address::from_str("0x2222222222222222222222222222222222222222").unwrap(),
];
let taker_nonce = U256::from(1u64); // Single taker nonce, not array
let taker_tokens = vec![Address::from_slice(&token_in)];
let taker_amounts = vec![U256::from_str(&amount_in.to_string()).unwrap()];
let maker_nonces = vec![U256::from(1u64), U256::from(2u64)]; // Array of nonces
// Each maker provides different tokens
// Each maker accepts the same taker token (USDC)
let taker_tokens = vec![
vec![Address::from_slice(&token_in)], // USDC for maker 1
vec![Address::from_slice(&token_in)], // USDC for maker 2
];
// 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![
vec![Address::from_slice(&token_out)],
vec![Address::from_slice(&token_out)],
@@ -3973,21 +3982,21 @@ mod tests {
let receiver =
Address::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(); // Alice
let packed_commands = U256::ZERO;
let commands = hex!("00040004").to_vec();
let flags = U256::ZERO;
// Encode Aggregate order - must match IBebopSettlement.Aggregate struct exactly
let quote_data = (
U256::from(expiry), // expiry as U256
taker_address,
taker_nonce, // Single taker_nonce, not array
taker_tokens,
taker_amounts,
maker_addresses,
maker_nonces, // Array of maker nonces
taker_tokens, // 2D array
maker_tokens,
taker_amounts, // 2D array
maker_amounts,
receiver,
packed_commands,
commands,
flags,
)
.abi_encode();