refactor: update Rust encoding to remove Multi order type
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use alloy::{
|
||||
primitives::{Address, Bytes as AlloyBytes, U8},
|
||||
core::sol,
|
||||
primitives::{Address, Bytes as AlloyBytes, U256, U8},
|
||||
sol_types::SolValue,
|
||||
};
|
||||
use serde_json::from_str;
|
||||
@@ -617,6 +618,37 @@ impl SwapEncoder for BalancerV3SwapEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
// Define Bebop order structures for ABI decoding
|
||||
sol! {
|
||||
struct BebopSingle {
|
||||
uint256 expiry;
|
||||
address taker_address;
|
||||
address maker_address;
|
||||
uint256 maker_nonce;
|
||||
address taker_token;
|
||||
address maker_token;
|
||||
uint256 taker_amount;
|
||||
uint256 maker_amount;
|
||||
address receiver;
|
||||
uint256 packed_commands;
|
||||
uint256 flags;
|
||||
}
|
||||
|
||||
struct BebopAggregate {
|
||||
uint256 expiry;
|
||||
address taker_address;
|
||||
uint256 taker_nonce;
|
||||
address[] taker_tokens;
|
||||
uint256[] taker_amounts;
|
||||
address[] maker_addresses;
|
||||
address[][] maker_tokens;
|
||||
uint256[][] maker_amounts;
|
||||
address receiver;
|
||||
uint256 packed_commands;
|
||||
uint256 flags;
|
||||
}
|
||||
}
|
||||
|
||||
/// Encodes a swap on Bebop (PMM RFQ) through the given executor address.
|
||||
///
|
||||
/// Bebop uses a Request-for-Quote model where quotes are obtained off-chain
|
||||
@@ -646,6 +678,52 @@ impl BebopSwapEncoder {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Analyzes the quote to determine token input/output characteristics
|
||||
/// Returns (has_many_inputs, has_many_outputs)
|
||||
fn validate_aggregate_order(
|
||||
&self,
|
||||
quote_data: &[u8],
|
||||
expected_token_out: &Address,
|
||||
) -> Result<(), EncodingError> {
|
||||
// Decode the Aggregate order to validate
|
||||
let order = BebopAggregate::abi_decode(quote_data).map_err(|e| {
|
||||
EncodingError::InvalidInput(format!("Failed to decode Bebop Aggregate order: {}", e))
|
||||
})?;
|
||||
|
||||
// Validate that we only have one input token
|
||||
if order.taker_tokens.len() != 1 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"Aggregate orders must have exactly one input token".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Validate that all makers provide exactly one output token
|
||||
for (i, maker_tokens) in order.maker_tokens.iter().enumerate() {
|
||||
if maker_tokens.len() != 1 {
|
||||
return Err(EncodingError::InvalidInput(format!(
|
||||
"Maker {} must provide exactly one output token, found {}",
|
||||
i,
|
||||
maker_tokens.len()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that all makers provide the same output token
|
||||
let all_same_output = order
|
||||
.maker_tokens
|
||||
.iter()
|
||||
.flat_map(|maker_tokens| maker_tokens.iter())
|
||||
.all(|token| token == expected_token_out);
|
||||
|
||||
if !all_same_output {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"All makers must provide the same output token".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SwapEncoder for BebopSwapEncoder {
|
||||
@@ -703,61 +781,200 @@ impl SwapEncoder for BebopSwapEncoder {
|
||||
})?;
|
||||
|
||||
// Parse user_data format:
|
||||
// order_type (1 byte) | signature_type (1 byte) | quote_data_length (4 bytes) | quote_data
|
||||
// | signature_length (4 bytes) | signature
|
||||
if user_data.len() < 10 {
|
||||
// order_type (1 byte) | filledTakerAmount (32 bytes) | quote_data_length (4 bytes) |
|
||||
// quote_data | abi_encoded_maker_signatures where abi_encoded_maker_signatures is
|
||||
// abi.encode(MakerSignature[])
|
||||
if user_data.len() < 37 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain Bebop RFQ data".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let order_type = BebopOrderType::try_from(user_data[0])?;
|
||||
let signature_type = user_data[1];
|
||||
|
||||
// Extract filledTakerAmount (32 bytes)
|
||||
let filled_taker_amount = U256::from_be_slice(&user_data[1..33]);
|
||||
|
||||
let quote_data_len =
|
||||
u32::from_be_bytes([user_data[2], user_data[3], user_data[4], user_data[5]]) as usize;
|
||||
if user_data.len() < 10 + quote_data_len {
|
||||
u32::from_be_bytes([user_data[33], user_data[34], user_data[35], user_data[36]])
|
||||
as usize;
|
||||
if user_data.len() < 37 + quote_data_len {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain quote data".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let quote_data = user_data[6..6 + quote_data_len].to_vec();
|
||||
let quote_data = user_data[37..37 + quote_data_len].to_vec();
|
||||
|
||||
let sig_len_start = 6 + quote_data_len;
|
||||
if user_data.len() < sig_len_start + 4 {
|
||||
// Validate Aggregate orders have single token in/out
|
||||
if order_type == BebopOrderType::Aggregate {
|
||||
self.validate_aggregate_order("e_data, &token_out)?;
|
||||
}
|
||||
|
||||
// Decode the ABI-encoded MakerSignature[] array
|
||||
let maker_sigs_start = 37 + quote_data_len;
|
||||
let remaining_data = &user_data[maker_sigs_start..];
|
||||
|
||||
// Need at least 64 bytes for offset (32) and array length (32)
|
||||
if remaining_data.len() < 64 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain signature length".to_string(),
|
||||
"User data too short to contain MakerSignature array".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let signature_len = u32::from_be_bytes([
|
||||
user_data[sig_len_start],
|
||||
user_data[sig_len_start + 1],
|
||||
user_data[sig_len_start + 2],
|
||||
user_data[sig_len_start + 3],
|
||||
]) as usize;
|
||||
// Read offset to array (should be 0x20 for single parameter)
|
||||
let array_offset: usize = U256::from_be_slice(&remaining_data[0..32])
|
||||
.try_into()
|
||||
.map_err(|_| EncodingError::InvalidInput("Array offset too large".to_string()))?;
|
||||
|
||||
if user_data.len() != sig_len_start + 4 + signature_len {
|
||||
return Err(EncodingError::InvalidInput("User data length mismatch".to_string()));
|
||||
if array_offset != 32 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"Invalid array offset in ABI encoding".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let signature = user_data[sig_len_start + 4..].to_vec();
|
||||
// Read array length
|
||||
let array_length: usize = U256::from_be_slice(&remaining_data[32..64])
|
||||
.try_into()
|
||||
.map_err(|_| EncodingError::InvalidInput("Array length too large".to_string()))?;
|
||||
|
||||
// Validate signature count based on order type
|
||||
match order_type {
|
||||
BebopOrderType::Single => {
|
||||
if array_length != 1 {
|
||||
return Err(EncodingError::InvalidInput(format!(
|
||||
"Expected 1 signature for Single order, got {}",
|
||||
array_length
|
||||
)));
|
||||
}
|
||||
}
|
||||
BebopOrderType::Aggregate => {
|
||||
if array_length == 0 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"Aggregate order requires at least one signature".to_string(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For aggregate orders, we just pass through the entire ABI-encoded array
|
||||
// For single orders, we validate and re-encode
|
||||
let maker_sigs_encoded = if order_type == BebopOrderType::Aggregate {
|
||||
// For aggregate orders, pass through the entire encoded array
|
||||
remaining_data.to_vec()
|
||||
} else {
|
||||
// For single orders, validate and re-encode the single signature
|
||||
// Read offset to first (and only) struct
|
||||
if remaining_data.len() < 96 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain struct offset".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let struct_offset: usize = U256::from_be_slice(&remaining_data[64..96])
|
||||
.try_into()
|
||||
.map_err(|_| EncodingError::InvalidInput("Struct offset too large".to_string()))?;
|
||||
|
||||
// The struct data starts at the struct_offset
|
||||
// struct_offset is relative to the start of array data (after the array length)
|
||||
// Array data starts at position 64 (after offset[32] and length[32])
|
||||
// Absolute position = 64 + struct_offset
|
||||
let struct_start = 64 + struct_offset;
|
||||
|
||||
if remaining_data.len() < struct_start + 64 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain MakerSignature struct".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Read the struct fields
|
||||
// - offset to signatureBytes (32 bytes) - should be 64
|
||||
// - flags (32 bytes)
|
||||
let sig_offset: usize =
|
||||
U256::from_be_slice(&remaining_data[struct_start..struct_start + 32])
|
||||
.try_into()
|
||||
.map_err(|_| {
|
||||
EncodingError::InvalidInput("Signature offset too large".to_string())
|
||||
})?;
|
||||
|
||||
if sig_offset != 64 {
|
||||
return Err(EncodingError::InvalidInput(format!(
|
||||
"Invalid signature offset in struct: expected 64, got {}",
|
||||
sig_offset
|
||||
)));
|
||||
}
|
||||
|
||||
let flags = U256::from_be_slice(&remaining_data[struct_start + 32..struct_start + 64]);
|
||||
|
||||
// Read signature data
|
||||
let sig_data_start = struct_start + sig_offset;
|
||||
if remaining_data.len() < sig_data_start + 32 {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain signature length".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let signature_len: usize =
|
||||
U256::from_be_slice(&remaining_data[sig_data_start..sig_data_start + 32])
|
||||
.try_into()
|
||||
.map_err(|_| {
|
||||
EncodingError::InvalidInput("Signature length too large".to_string())
|
||||
})?;
|
||||
|
||||
if remaining_data.len() < sig_data_start + 32 + signature_len {
|
||||
return Err(EncodingError::InvalidInput(
|
||||
"User data too short to contain signature data".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let signature =
|
||||
remaining_data[sig_data_start + 32..sig_data_start + 32 + signature_len].to_vec();
|
||||
|
||||
// Re-encode the MakerSignature[] array for the executor
|
||||
let mut encoded = Vec::new();
|
||||
|
||||
// Offset to array (always 0x20 for single parameter)
|
||||
encoded.extend_from_slice(&U256::from(32).to_be_bytes::<32>());
|
||||
|
||||
// Array length (1 for non-aggregate orders)
|
||||
encoded.extend_from_slice(&U256::from(1).to_be_bytes::<32>());
|
||||
|
||||
// Offset to first struct (relative to array data start)
|
||||
let struct_offset = 32; // After the array length
|
||||
encoded.extend_from_slice(&U256::from(struct_offset).to_be_bytes::<32>());
|
||||
|
||||
// Struct data
|
||||
// - offset to signatureBytes (always 64)
|
||||
encoded.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
|
||||
|
||||
// - flags
|
||||
encoded.extend_from_slice(&flags.to_be_bytes::<32>());
|
||||
|
||||
// - signatureBytes length
|
||||
encoded.extend_from_slice(&U256::from(signature.len()).to_be_bytes::<32>());
|
||||
|
||||
// - signatureBytes data (padded)
|
||||
encoded.extend_from_slice(&signature);
|
||||
let padding = (32 - (signature.len() % 32)) % 32;
|
||||
encoded.extend_from_slice(&vec![0u8; padding]);
|
||||
|
||||
encoded
|
||||
};
|
||||
|
||||
// Encode packed data for the executor
|
||||
// Format: token_in | token_out | transfer_type | order_type |
|
||||
// quote_data_length | quote_data | signature_type | signature_length | signature |
|
||||
// approval_needed
|
||||
// Format: token_in | token_out | transfer_type | order_type | filledTakerAmount |
|
||||
// quote_data_length | quote_data | maker_signatures_length |
|
||||
// abi_encoded_maker_signatures | approval_needed
|
||||
let args = (
|
||||
token_in,
|
||||
token_out,
|
||||
(encoding_context.transfer_type as u8).to_be_bytes(),
|
||||
(order_type as u8).to_be_bytes(),
|
||||
filled_taker_amount.to_be_bytes::<32>(),
|
||||
(quote_data.len() as u32).to_be_bytes(),
|
||||
"e_data[..],
|
||||
signature_type.to_be_bytes(),
|
||||
(signature.len() as u32).to_be_bytes(),
|
||||
&signature[..],
|
||||
(maker_sigs_encoded.len() as u32).to_be_bytes(),
|
||||
&maker_sigs_encoded[..],
|
||||
(approval_needed as u8).to_be_bytes(),
|
||||
);
|
||||
|
||||
@@ -1826,25 +2043,92 @@ mod tests {
|
||||
|
||||
mod bebop {
|
||||
use super::*;
|
||||
use crate::encoding::evm::utils::write_calldata_to_file;
|
||||
use crate::encoding::{evm::utils::write_calldata_to_file, models::TransferType};
|
||||
|
||||
#[test]
|
||||
fn test_encode_bebop_single() {
|
||||
use alloy::hex;
|
||||
use alloy::{hex, primitives::Address};
|
||||
|
||||
// Create user_data with quote and signature
|
||||
// Use mainnet data
|
||||
// Transaction: https://etherscan.io/tx/0x6279bc970273b6e526e86d9b69133c2ca1277e697ba25375f5e6fc4df50c0c94
|
||||
let order_type = BebopOrderType::Single as u8;
|
||||
let signature_type = 1u8; // EIP712
|
||||
let quote_data = hex::decode("1234567890abcdef").unwrap();
|
||||
let signature = hex::decode("aabbccdd").unwrap();
|
||||
|
||||
// Create the IBebopSettlement.Single struct data using the exact input data from the tx
|
||||
let expiry = U256::from(1749483840u64);
|
||||
let taker_address =
|
||||
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap();
|
||||
let maker_address =
|
||||
Address::from_str("0xCe79b081c0c924cb67848723ed3057234d10FC6b").unwrap();
|
||||
let maker_nonce = U256::from(1749483765992417u64);
|
||||
let taker_token =
|
||||
Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap(); // USDC
|
||||
let maker_token =
|
||||
Address::from_str("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3").unwrap(); // ONDO
|
||||
let taker_amount = U256::from(200000000u64); // 200 USDC
|
||||
let maker_amount = U256::from_str("237212396774431060000").unwrap(); // 237.21 ONDO
|
||||
let receiver = taker_address;
|
||||
let packed_commands = U256::ZERO;
|
||||
let flags = U256::from_str(
|
||||
"51915842898789398998206002334703507894664330885127600393944965515693155942400",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// ABI encode the order struct
|
||||
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 signature_type = 0u8; // ETH_SIGN
|
||||
|
||||
// Build ABI-encoded MakerSignature[] array
|
||||
let flags = U256::from(signature_type);
|
||||
let mut encoded_maker_sigs = Vec::new();
|
||||
|
||||
// Offset to array (always 0x20 for single parameter)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(32).to_be_bytes::<32>());
|
||||
|
||||
// Array length (1 for Single order)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(1).to_be_bytes::<32>());
|
||||
|
||||
// Offset to first struct (relative to array data start)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(32).to_be_bytes::<32>());
|
||||
|
||||
// Struct data (MakerSignature has signatureBytes first, then flags)
|
||||
// - offset to signatureBytes (always 64 since it comes after offset and flags)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
|
||||
|
||||
// - flags (AFTER the offset, as per struct order)
|
||||
encoded_maker_sigs.extend_from_slice(&flags.to_be_bytes::<32>());
|
||||
|
||||
// - signatureBytes length
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(signature.len()).to_be_bytes::<32>());
|
||||
|
||||
// - signatureBytes data (padded)
|
||||
encoded_maker_sigs.extend_from_slice(&signature);
|
||||
let padding = (32 - (signature.len() % 32)) % 32;
|
||||
encoded_maker_sigs.extend_from_slice(&vec![0u8; padding]);
|
||||
|
||||
let filled_taker_amount = U256::ZERO; // 0 means fill entire order
|
||||
|
||||
let mut user_data = Vec::new();
|
||||
user_data.push(order_type);
|
||||
user_data.push(signature_type);
|
||||
user_data.extend_from_slice(&filled_taker_amount.to_be_bytes::<32>());
|
||||
user_data.extend_from_slice(&(quote_data.len() as u32).to_be_bytes());
|
||||
user_data.extend_from_slice("e_data);
|
||||
user_data.extend_from_slice(&(signature.len() as u32).to_be_bytes());
|
||||
user_data.extend_from_slice(&signature);
|
||||
user_data.extend_from_slice(&encoded_maker_sigs);
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
id: String::from("bebop-rfq"),
|
||||
@@ -1854,7 +2138,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let token_out = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // WETH
|
||||
let token_out = Bytes::from("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3"); // ONDO
|
||||
let swap = Swap {
|
||||
component: bebop_component,
|
||||
token_in: token_in.clone(),
|
||||
@@ -1864,7 +2148,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let encoding_context = EncodingContext {
|
||||
receiver: Bytes::from("0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e"), // BOB
|
||||
receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"), /* Original taker */
|
||||
exact_out: false,
|
||||
router_address: Some(Bytes::zero(20)),
|
||||
group_token_in: token_in.clone(),
|
||||
@@ -1887,123 +2171,17 @@ mod tests {
|
||||
.unwrap();
|
||||
let hex_swap = encode(&encoded_swap);
|
||||
|
||||
assert_eq!(
|
||||
hex_swap,
|
||||
String::from(concat!(
|
||||
// token in
|
||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
// token out
|
||||
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||
// transfer type Transfer
|
||||
"01",
|
||||
// order type Single (0)
|
||||
"00",
|
||||
// quote data length (8 bytes = 0x00000008)
|
||||
"00000008",
|
||||
// quote data
|
||||
"1234567890abcdef",
|
||||
// signature type EIP712 (1)
|
||||
"01",
|
||||
// signature length (4 bytes = 0x00000004)
|
||||
"00000004",
|
||||
// signature
|
||||
"aabbccdd",
|
||||
// approval needed
|
||||
"01"
|
||||
))
|
||||
);
|
||||
// Verify the encoding contains the expected tokens
|
||||
assert!(hex_swap.contains("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")); // USDC
|
||||
assert!(hex_swap.contains("faba6f8e4a5e8ab82f62fe7c39859fa577269be3")); // ONDO
|
||||
|
||||
// Verify it includes the signature
|
||||
let sig_hex = hex::encode(&signature);
|
||||
assert!(hex_swap.contains(&sig_hex));
|
||||
|
||||
write_calldata_to_file("test_encode_bebop_single", hex_swap.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_bebop_multi() {
|
||||
use alloy::hex;
|
||||
|
||||
// Create user_data for a Bebop Multi RFQ quote
|
||||
let order_type = BebopOrderType::Multi as u8;
|
||||
let signature_type = 1u8; // EIP712
|
||||
let quote_data = hex::decode("abcdef1234567890").unwrap();
|
||||
let signature = hex::decode("11223344").unwrap();
|
||||
|
||||
let mut user_data = Vec::new();
|
||||
user_data.push(order_type);
|
||||
user_data.push(signature_type);
|
||||
user_data.extend_from_slice(&(quote_data.len() as u32).to_be_bytes());
|
||||
user_data.extend_from_slice("e_data);
|
||||
user_data.extend_from_slice(&(signature.len() as u32).to_be_bytes());
|
||||
user_data.extend_from_slice(&signature);
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
id: String::from("bebop-rfq"),
|
||||
protocol_system: String::from("rfq:bebop"),
|
||||
static_attributes: HashMap::new(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let token_in = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // WETH
|
||||
let token_out = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let swap = Swap {
|
||||
component: bebop_component,
|
||||
token_in: token_in.clone(),
|
||||
token_out: token_out.clone(),
|
||||
split: 0f64,
|
||||
user_data: Some(Bytes::from(user_data)),
|
||||
};
|
||||
|
||||
let encoding_context = EncodingContext {
|
||||
receiver: Bytes::from("0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e"), // BOB
|
||||
exact_out: false,
|
||||
router_address: Some(Bytes::zero(20)),
|
||||
group_token_in: token_in.clone(),
|
||||
group_token_out: token_out.clone(),
|
||||
transfer_type: TransferType::Transfer,
|
||||
};
|
||||
|
||||
let encoder = BebopSwapEncoder::new(
|
||||
String::from("0x543778987b293C7E8Cf0722BB2e935ba6f4068D4"),
|
||||
TychoCoreChain::Ethereum.into(),
|
||||
Some(HashMap::from([(
|
||||
"bebop_settlement_address".to_string(),
|
||||
"0xbbbbbBB520d69a9775E85b458C58c648259FAD5F".to_string(),
|
||||
)])),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let encoded_swap = encoder
|
||||
.encode_swap(swap, encoding_context)
|
||||
.unwrap();
|
||||
let hex_swap = encode(&encoded_swap);
|
||||
|
||||
assert_eq!(
|
||||
hex_swap,
|
||||
String::from(concat!(
|
||||
// token in
|
||||
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
||||
// token out
|
||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
||||
// transfer type Transfer
|
||||
"01",
|
||||
// order type Multi (1)
|
||||
"01",
|
||||
// quote data length (8 bytes = 0x00000008)
|
||||
"00000008",
|
||||
// quote data
|
||||
"abcdef1234567890",
|
||||
// signature type EIP712 (1)
|
||||
"01",
|
||||
// signature length (4 bytes = 0x00000004)
|
||||
"00000004",
|
||||
// signature
|
||||
"11223344",
|
||||
// approval needed
|
||||
"01"
|
||||
))
|
||||
);
|
||||
|
||||
write_calldata_to_file("test_encode_bebop_multi", hex_swap.as_str());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_encode_bebop_aggregate() {
|
||||
use alloy::hex;
|
||||
@@ -2011,19 +2189,87 @@ mod tests {
|
||||
// Create user_data for a Bebop Aggregate RFQ quote
|
||||
let order_type = BebopOrderType::Aggregate as u8;
|
||||
let signature_type = 1u8; // EIP712
|
||||
let quote_data = hex::decode("deadbeef").unwrap();
|
||||
// For aggregate orders with ECDSA, use concatenated 65-byte signatures (2 makers)
|
||||
|
||||
// Create a valid ABI-encoded Aggregate order
|
||||
// For this test: single input (DAI) -> single output (WBTC) to match the test setup
|
||||
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_tokens: vec![
|
||||
vec![Address::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599").unwrap()], /* WBTC from maker 1 */
|
||||
vec![Address::from_str("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599").unwrap()], /* WBTC from 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),
|
||||
flags: U256::from(0),
|
||||
};
|
||||
|
||||
let quote_data = aggregate_order.abi_encode();
|
||||
// For aggregate orders with ECDSA, use 65-byte signatures (2 makers)
|
||||
let sig1 = hex::decode("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001").unwrap();
|
||||
let sig2 = hex::decode("1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101").unwrap();
|
||||
let signature = [sig1, sig2].concat();
|
||||
|
||||
// Build ABI-encoded MakerSignature[] array with 2 signatures
|
||||
let flags = U256::from(signature_type);
|
||||
let mut encoded_maker_sigs = Vec::new();
|
||||
|
||||
// Offset to array (always 0x20 for single parameter)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(32).to_be_bytes::<32>());
|
||||
|
||||
// Array length (2 for Aggregate order with 2 makers)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(2).to_be_bytes::<32>());
|
||||
|
||||
// Offsets to structs (relative to array data start)
|
||||
// First struct at offset 64 (after the 2 offset values)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
|
||||
// Second struct will be after first struct's data
|
||||
// First struct size: 64 (fixed) + 32 (length) + 96 (65 bytes padded) = 192
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(64 + 192).to_be_bytes::<32>());
|
||||
|
||||
// First struct data (MakerSignature has signatureBytes first, then flags)
|
||||
// - offset to signatureBytes (always 64)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
|
||||
// - flags (AFTER the offset)
|
||||
encoded_maker_sigs.extend_from_slice(&flags.to_be_bytes::<32>());
|
||||
// - signatureBytes length
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(sig1.len()).to_be_bytes::<32>());
|
||||
// - signatureBytes data (padded)
|
||||
encoded_maker_sigs.extend_from_slice(&sig1);
|
||||
let padding1 = (32 - (sig1.len() % 32)) % 32;
|
||||
encoded_maker_sigs.extend_from_slice(&vec![0u8; padding1]);
|
||||
|
||||
// Second struct data (MakerSignature has signatureBytes first, then flags)
|
||||
// - offset to signatureBytes (always 64)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
|
||||
// - flags (AFTER the offset)
|
||||
encoded_maker_sigs.extend_from_slice(&flags.to_be_bytes::<32>());
|
||||
// - signatureBytes length
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(sig2.len()).to_be_bytes::<32>());
|
||||
// - signatureBytes data (padded)
|
||||
encoded_maker_sigs.extend_from_slice(&sig2);
|
||||
let padding2 = (32 - (sig2.len() % 32)) % 32;
|
||||
encoded_maker_sigs.extend_from_slice(&vec![0u8; padding2]);
|
||||
|
||||
let filled_taker_amount = U256::from(1000000u64); // 1 USDC (6 decimals)
|
||||
|
||||
let mut user_data = Vec::new();
|
||||
user_data.push(order_type);
|
||||
user_data.push(signature_type);
|
||||
user_data.extend_from_slice(&filled_taker_amount.to_be_bytes::<32>());
|
||||
user_data.extend_from_slice(&(quote_data.len() as u32).to_be_bytes());
|
||||
user_data.extend_from_slice("e_data);
|
||||
user_data.extend_from_slice(&(signature.len() as u32).to_be_bytes());
|
||||
user_data.extend_from_slice(&signature);
|
||||
user_data.extend_from_slice(&encoded_maker_sigs);
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
id: String::from("bebop-rfq"),
|
||||
@@ -2066,33 +2312,71 @@ mod tests {
|
||||
.unwrap();
|
||||
let hex_swap = encode(&encoded_swap);
|
||||
|
||||
assert_eq!(
|
||||
hex_swap,
|
||||
String::from(concat!(
|
||||
// token in
|
||||
"6b175474e89094c44da98b954eedeac495271d0f",
|
||||
// token out
|
||||
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
|
||||
// transfer type TransferFrom
|
||||
"00",
|
||||
// order type Aggregate (2)
|
||||
"02",
|
||||
// quote data length (4 bytes = 0x00000004)
|
||||
"00000004",
|
||||
// quote data
|
||||
"deadbeef",
|
||||
// signature type EIP712 (1)
|
||||
"01",
|
||||
// signature length (130 bytes = 2 * 65-byte EIP712 signatures)
|
||||
"00000082",
|
||||
// concatenated EIP712 signatures (2 * 65 bytes)
|
||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
|
||||
"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101",
|
||||
// approval needed
|
||||
"01"
|
||||
))
|
||||
// Calculate expected hex for ABI-encoded maker signatures array with 2 signatures
|
||||
let _expected_abi_maker_sigs = concat!(
|
||||
// offset to array
|
||||
"0000000000000000000000000000000000000000000000000000000000000020",
|
||||
// array length (2)
|
||||
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||
// offset to first struct (relative to array data start)
|
||||
"0000000000000000000000000000000000000000000000000000000000000040",
|
||||
// offset to second struct (64 + 192 = 256 = 0x100)
|
||||
"0000000000000000000000000000000000000000000000000000000000000100",
|
||||
// First struct data:
|
||||
// - offset to signatureBytes
|
||||
"0000000000000000000000000000000000000000000000000000000000000040",
|
||||
// - flags
|
||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||
// - signatureBytes length (65 = 0x41)
|
||||
"0000000000000000000000000000000000000000000000000000000000000041",
|
||||
// - signatureBytes (65 bytes padded to 96)
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||
// Second struct data:
|
||||
// - offset to signatureBytes
|
||||
"0000000000000000000000000000000000000000000000000000000000000040",
|
||||
// - flags
|
||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||
// - signatureBytes length (65 = 0x41)
|
||||
"0000000000000000000000000000000000000000000000000000000000000041",
|
||||
// - signatureBytes (65 bytes padded to 96)
|
||||
"1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"0100000000000000000000000000000000000000000000000000000000000000"
|
||||
);
|
||||
|
||||
// The quote data is now an ABI-encoded BebopAggregate struct
|
||||
// Calculate its actual length
|
||||
let quote_data_hex = hex::encode("e_data);
|
||||
let quote_data_length = format!("{:08x}", quote_data.len());
|
||||
|
||||
let expected_hex = format!(
|
||||
"{}{}{}{}{}{}{}{}{}{}",
|
||||
// token in
|
||||
"6b175474e89094c44da98b954eedeac495271d0f",
|
||||
// token out
|
||||
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
|
||||
// transfer type TransferFrom
|
||||
"00",
|
||||
// order type Aggregate (1)
|
||||
"01",
|
||||
// filledTakerAmount (1000000 = 0x0f4240)
|
||||
"00000000000000000000000000000000000000000000000000000000000f4240",
|
||||
// quote data length
|
||||
"e_data_length,
|
||||
// quote data
|
||||
"e_data_hex,
|
||||
// maker signatures length (0x200 = 512 bytes)
|
||||
"00000200",
|
||||
// abi-encoded maker signatures
|
||||
&encode(&encoded_maker_sigs),
|
||||
// approval needed
|
||||
"01"
|
||||
);
|
||||
|
||||
assert_eq!(hex_swap, expected_hex);
|
||||
|
||||
write_calldata_to_file("test_encode_bebop_aggregate", hex_swap.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1261,20 +1261,91 @@ mod tests {
|
||||
models::BebopOrderType,
|
||||
};
|
||||
|
||||
/// Helper function to build Bebop user_data
|
||||
/// Builds Bebop user_data with support for single or multiple signatures
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `order_type` - The type of Bebop order (Single or Aggregate)
|
||||
/// * `filled_taker_amount` - Amount to fill (0 means fill entire order)
|
||||
/// * `quote_data` - The ABI-encoded order data
|
||||
/// * `signatures` - Vector of (signature_bytes, signature_type) tuples
|
||||
/// - For Single orders: expects exactly 1 signature
|
||||
/// - For Aggregate orders: expects 1 or more signatures (one per maker)
|
||||
fn build_bebop_user_data(
|
||||
order_type: BebopOrderType,
|
||||
signature_type: u8,
|
||||
filled_taker_amount: U256,
|
||||
quote_data: &[u8],
|
||||
signature: &[u8],
|
||||
signatures: Vec<(Vec<u8>, u8)>, // (signature, signature_type)
|
||||
) -> Bytes {
|
||||
// ABI encode MakerSignature[] array
|
||||
// Format: offset_to_array | array_length | [offset_to_struct_i]... | [struct_i_data]...
|
||||
let mut encoded_maker_sigs = Vec::new();
|
||||
|
||||
// Calculate total size needed
|
||||
let array_offset = 32; // offset to array start
|
||||
let array_length_size = 32;
|
||||
let struct_offsets_size = 32 * signatures.len();
|
||||
let _header_size = array_length_size + struct_offsets_size;
|
||||
|
||||
// Build each struct's data and calculate offsets
|
||||
let mut struct_data = Vec::new();
|
||||
let mut struct_offsets = Vec::new();
|
||||
// Offsets are relative to the start of array data, not the absolute position
|
||||
// Array data starts after array length, so first offset is after all offset values
|
||||
let mut current_offset = struct_offsets_size; // Just the space for offsets, not including array length
|
||||
|
||||
for (signature, signature_type) in &signatures {
|
||||
struct_offsets.push(current_offset);
|
||||
|
||||
// Each struct contains:
|
||||
// - offset to signatureBytes (32 bytes) - always 0x40 (64)
|
||||
// - flags (32 bytes)
|
||||
// - signatureBytes length (32 bytes)
|
||||
// - signatureBytes data (padded to 32 bytes)
|
||||
let mut struct_bytes = Vec::new();
|
||||
|
||||
// Offset to signatureBytes within this struct
|
||||
struct_bytes.extend_from_slice(&U256::from(64).to_be_bytes::<32>());
|
||||
|
||||
// Flags (contains signature type) - AFTER the offset, not before!
|
||||
let flags = U256::from(*signature_type);
|
||||
struct_bytes.extend_from_slice(&flags.to_be_bytes::<32>());
|
||||
|
||||
// SignatureBytes length
|
||||
struct_bytes.extend_from_slice(&U256::from(signature.len()).to_be_bytes::<32>());
|
||||
|
||||
// SignatureBytes data (padded to 32 byte boundary)
|
||||
struct_bytes.extend_from_slice(signature);
|
||||
let padding = (32 - (signature.len() % 32)) % 32;
|
||||
struct_bytes.extend_from_slice(&vec![0u8; padding]);
|
||||
|
||||
current_offset += struct_bytes.len();
|
||||
struct_data.push(struct_bytes);
|
||||
}
|
||||
|
||||
// Build the complete ABI encoded array
|
||||
// Offset to array (always 0x20 for a single parameter)
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(array_offset).to_be_bytes::<32>());
|
||||
|
||||
// Array length
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(signatures.len()).to_be_bytes::<32>());
|
||||
|
||||
// Struct offsets (relative to start of array data)
|
||||
for offset in struct_offsets {
|
||||
encoded_maker_sigs.extend_from_slice(&U256::from(offset).to_be_bytes::<32>());
|
||||
}
|
||||
|
||||
// Struct data
|
||||
for data in struct_data {
|
||||
encoded_maker_sigs.extend_from_slice(&data);
|
||||
}
|
||||
|
||||
// Build complete user_data
|
||||
let mut user_data = Vec::new();
|
||||
user_data.push(order_type as u8);
|
||||
user_data.push(signature_type);
|
||||
user_data.extend_from_slice(&filled_taker_amount.to_be_bytes::<32>());
|
||||
user_data.extend_from_slice(&(quote_data.len() as u32).to_be_bytes());
|
||||
user_data.extend_from_slice(quote_data);
|
||||
user_data.extend_from_slice(&(signature.len() as u32).to_be_bytes());
|
||||
user_data.extend_from_slice(signature);
|
||||
user_data.extend_from_slice(&encoded_maker_sigs);
|
||||
Bytes::from(user_data)
|
||||
}
|
||||
|
||||
@@ -2375,7 +2446,7 @@ mod tests {
|
||||
3600; // Current time + 1 hour
|
||||
let taker_address = Address::ZERO;
|
||||
let maker_address =
|
||||
Address::from_str("0xbbbbbBB520d69a9775E85b458C58c648259FAD5F").unwrap();
|
||||
Address::from_str("0x1234567890123456789012345678901234567890").unwrap(); // Use a proper maker address
|
||||
let maker_nonce = 1u64;
|
||||
let taker_token = Address::from_str(&usdc.to_string()).unwrap();
|
||||
let maker_token = Address::from_str(&dai.to_string()).unwrap();
|
||||
@@ -2408,9 +2479,9 @@ mod tests {
|
||||
// Build user_data with the quote and signature
|
||||
let user_data = build_bebop_user_data(
|
||||
BebopOrderType::Single,
|
||||
1, // EIP712 signature type
|
||||
U256::from(0), // 0 means fill entire order
|
||||
"e_data,
|
||||
&signature,
|
||||
vec![(signature, 1)], // EIP712 signature type
|
||||
);
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
@@ -3759,30 +3830,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_bebop() {
|
||||
// USDC -> (Bebop RFQ) -> WETH
|
||||
// Use the same mainnet data from Solidity tests
|
||||
// Transaction: https://etherscan.io/tx/0x6279bc970273b6e526e86d9b69133c2ca1277e697ba25375f5e6fc4df50c0c94
|
||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let token_out = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // WETH
|
||||
let amount_in = BigUint::from_str("1000_000000").unwrap(); // 1000 USDC
|
||||
let amount_out = BigUint::from_str("400000000000000000").unwrap(); // 0.4 WETH
|
||||
let token_out = Bytes::from("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3"); // ONDO
|
||||
let amount_in = BigUint::from_str("200000000").unwrap(); // 200 USDC
|
||||
let amount_out = BigUint::from_str("237212396774431060000").unwrap(); // 237.21 ONDO
|
||||
|
||||
// Create a valid Bebop Single order struct that matches IBebopSettlement.Single
|
||||
// The mock settlement expects this exact structure
|
||||
let expiry = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() +
|
||||
3600; // Current time + 1 hour
|
||||
let taker_address = Address::ZERO;
|
||||
// Create the exact same order from mainnet
|
||||
let expiry = 1749483840u64;
|
||||
let taker_address =
|
||||
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap();
|
||||
let maker_address =
|
||||
Address::from_str("0xbbbbbBB520d69a9775E85b458C58c648259FAD5F").unwrap();
|
||||
let maker_nonce = 1u64;
|
||||
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 =
|
||||
Address::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(); // Alice's address - the actual receiver
|
||||
let receiver = taker_address;
|
||||
let packed_commands = U256::ZERO;
|
||||
let flags = U256::from_str(
|
||||
"51915842898789398998206002334703507894664330885127600393944965515693155942400",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Encode using standard ABI encoding (not packed)
|
||||
let quote_data = (
|
||||
@@ -3796,18 +3867,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 mainnet
|
||||
let signature = hex::decode("eb5419631614978da217532a40f02a8f2ece37d8cfb94aaa602baabbdefb56b474f4c2048a0f56502caff4ea7411d99eed6027cd67dc1088aaf4181dcb0df7051c").unwrap();
|
||||
|
||||
// Build user_data with the quote and signature
|
||||
let user_data = build_bebop_user_data(
|
||||
BebopOrderType::Single,
|
||||
1, // EIP712 signature type
|
||||
U256::ZERO, // 0 means fill entire order
|
||||
"e_data,
|
||||
&signature,
|
||||
vec![(signature, 0)], // ETH_SIGN signature type
|
||||
);
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
@@ -3833,9 +3905,9 @@ mod tests {
|
||||
given_amount: amount_in,
|
||||
checked_token: token_out,
|
||||
checked_amount: amount_out, // Expected output amount
|
||||
// Alice
|
||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
|
||||
// Use the original taker address
|
||||
sender: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(),
|
||||
receiver: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670")
|
||||
.unwrap(),
|
||||
swaps: vec![swap],
|
||||
..Default::default()
|
||||
@@ -3863,122 +3935,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_bebop_multi() {
|
||||
// For Multi orders, we'll demonstrate a single-token-in, multi-token-out scenario
|
||||
// WETH -> USDC + WBTC
|
||||
let token_in = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // WETH
|
||||
let token_out_1 = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||
let token_out_2 = Bytes::from("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"); // WBTC
|
||||
|
||||
let amount_in = BigUint::from_str("1000000000000000000").unwrap(); // 1 WETH
|
||||
let amount_out = BigUint::from_str("3000000000").unwrap(); // 3000 USDC (primary output)
|
||||
|
||||
// Create a valid Bebop Multi order struct
|
||||
let expiry = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() +
|
||||
3600;
|
||||
let taker_address = Address::ZERO;
|
||||
let maker_address =
|
||||
Address::from_str("0xbbbbbBB520d69a9775E85b458C58c648259FAD5F").unwrap();
|
||||
let maker_nonce = 2u64;
|
||||
|
||||
// Multi order: single taker token, multiple maker tokens
|
||||
let taker_tokens = vec![Address::from_str(&token_in.to_string()).unwrap()];
|
||||
let maker_tokens = vec![
|
||||
Address::from_str(&token_out_1.to_string()).unwrap(),
|
||||
Address::from_str(&token_out_2.to_string()).unwrap(),
|
||||
];
|
||||
let taker_amounts = vec![U256::from_str(&amount_in.to_string()).unwrap()];
|
||||
let maker_amounts = vec![
|
||||
U256::from_str(&amount_out.to_string()).unwrap(),
|
||||
U256::from_str("10000000").unwrap(), // 0.1 WBTC
|
||||
];
|
||||
let receiver =
|
||||
Address::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(); // Alice
|
||||
let packed_commands = U256::ZERO;
|
||||
|
||||
// Encode Multi order
|
||||
let quote_data = (
|
||||
expiry,
|
||||
taker_address,
|
||||
maker_address,
|
||||
maker_nonce,
|
||||
taker_tokens,
|
||||
maker_tokens,
|
||||
taker_amounts,
|
||||
maker_amounts,
|
||||
receiver,
|
||||
packed_commands,
|
||||
U256::from(0u64), // flags
|
||||
)
|
||||
.abi_encode();
|
||||
|
||||
let signature = hex::decode("11223344").unwrap();
|
||||
|
||||
let user_data = build_bebop_user_data(
|
||||
BebopOrderType::Multi,
|
||||
1, // EIP712 signature type
|
||||
"e_data,
|
||||
&signature,
|
||||
);
|
||||
|
||||
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_1.clone(), // Primary output token
|
||||
split: 0f64,
|
||||
user_data: Some(user_data),
|
||||
};
|
||||
|
||||
let encoder = get_tycho_router_encoder(UserTransferType::TransferFrom);
|
||||
|
||||
let solution = Solution {
|
||||
exact_out: false,
|
||||
given_token: token_in,
|
||||
given_amount: amount_in,
|
||||
checked_token: token_out_1,
|
||||
checked_amount: amount_out,
|
||||
// Alice
|
||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
||||
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
|
||||
.unwrap(),
|
||||
swaps: vec![swap],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let encoded_solution = encoder
|
||||
.encode_solutions(vec![solution.clone()])
|
||||
.unwrap()[0]
|
||||
.clone();
|
||||
|
||||
let calldata = encode_tycho_router_call(
|
||||
eth_chain().id,
|
||||
encoded_solution,
|
||||
&solution,
|
||||
UserTransferType::TransferFrom,
|
||||
eth(),
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.data;
|
||||
let hex_calldata = hex::encode(&calldata);
|
||||
|
||||
write_calldata_to_file(
|
||||
"test_single_encoding_strategy_bebop_multi",
|
||||
hex_calldata.as_str(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_encoding_strategy_bebop_aggregate() {
|
||||
// For simplicity, let's use the same tokens as the Single test but with Aggregate
|
||||
@@ -4036,19 +3992,22 @@ mod tests {
|
||||
)
|
||||
.abi_encode();
|
||||
|
||||
// For aggregate orders with ECDSA signatures, use concatenated 65-byte signatures
|
||||
// This example has 2 makers, so 2x 65-byte signatures = 130 bytes
|
||||
// For aggregate orders with multiple makers, we need multiple signatures
|
||||
// This example has 2 makers, so 2 separate 65-byte signatures
|
||||
let sig1 = hex::decode("1b47a665f9a5e14b5208015d11f9143e27b93dc5a0d8c892ec5326eda1e5df3c42a987d0b2ea5b8be8f0e5c326bd4ec0321b10c6e9b4e5f8a0b8d5e6f7c8a9b01b").unwrap();
|
||||
let sig2 = hex::decode("2c58b665f9a5e14b5208015d11f9143e27b93dc5a0d8c892ec5326eda1e5df3c53b987d0b2ea5b8be8f0e5c326bd4ec0321b10c6e9b4e5f8a0b8d5e6f7c8a9b01c").unwrap();
|
||||
let mut signature = Vec::new();
|
||||
signature.extend_from_slice(&sig1);
|
||||
signature.extend_from_slice(&sig2);
|
||||
|
||||
// Build user_data with multiple signatures for the aggregate order
|
||||
let signatures = vec![
|
||||
(sig1, 1u8), // EIP712 signature type for maker 1
|
||||
(sig2, 1u8), // EIP712 signature type for maker 2
|
||||
];
|
||||
|
||||
let user_data = build_bebop_user_data(
|
||||
BebopOrderType::Aggregate,
|
||||
1, // EIP712 signature type
|
||||
U256::from(0), // 0 means fill entire aggregate order
|
||||
"e_data,
|
||||
&signature,
|
||||
signatures,
|
||||
);
|
||||
|
||||
let bebop_component = ProtocolComponent {
|
||||
|
||||
@@ -214,8 +214,7 @@ pub enum TransferType {
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum BebopOrderType {
|
||||
Single = 0,
|
||||
Multi = 1,
|
||||
Aggregate = 2,
|
||||
Aggregate = 1,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for BebopOrderType {
|
||||
@@ -224,8 +223,7 @@ impl TryFrom<u8> for BebopOrderType {
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
0 => Ok(BebopOrderType::Single),
|
||||
1 => Ok(BebopOrderType::Multi),
|
||||
2 => Ok(BebopOrderType::Aggregate),
|
||||
1 => Ok(BebopOrderType::Aggregate),
|
||||
_ => Err(EncodingError::InvalidInput(format!("Invalid Bebop order type: {}", value))),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user