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 std::{collections::HashMap, str::FromStr};
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
primitives::{Address, Bytes as AlloyBytes, U8},
|
core::sol,
|
||||||
|
primitives::{Address, Bytes as AlloyBytes, U256, U8},
|
||||||
sol_types::SolValue,
|
sol_types::SolValue,
|
||||||
};
|
};
|
||||||
use serde_json::from_str;
|
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.
|
/// 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
|
/// Bebop uses a Request-for-Quote model where quotes are obtained off-chain
|
||||||
@@ -646,6 +678,52 @@ impl BebopSwapEncoder {
|
|||||||
}
|
}
|
||||||
Ok(())
|
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 {
|
impl SwapEncoder for BebopSwapEncoder {
|
||||||
@@ -703,61 +781,200 @@ impl SwapEncoder for BebopSwapEncoder {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Parse user_data format:
|
// Parse user_data format:
|
||||||
// order_type (1 byte) | signature_type (1 byte) | quote_data_length (4 bytes) | quote_data
|
// order_type (1 byte) | filledTakerAmount (32 bytes) | quote_data_length (4 bytes) |
|
||||||
// | signature_length (4 bytes) | signature
|
// quote_data | abi_encoded_maker_signatures where abi_encoded_maker_signatures is
|
||||||
if user_data.len() < 10 {
|
// abi.encode(MakerSignature[])
|
||||||
|
if user_data.len() < 37 {
|
||||||
return Err(EncodingError::InvalidInput(
|
return Err(EncodingError::InvalidInput(
|
||||||
"User data too short to contain Bebop RFQ data".to_string(),
|
"User data too short to contain Bebop RFQ data".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let order_type = BebopOrderType::try_from(user_data[0])?;
|
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 =
|
let quote_data_len =
|
||||||
u32::from_be_bytes([user_data[2], user_data[3], user_data[4], user_data[5]]) as usize;
|
u32::from_be_bytes([user_data[33], user_data[34], user_data[35], user_data[36]])
|
||||||
if user_data.len() < 10 + quote_data_len {
|
as usize;
|
||||||
|
if user_data.len() < 37 + quote_data_len {
|
||||||
return Err(EncodingError::InvalidInput(
|
return Err(EncodingError::InvalidInput(
|
||||||
"User data too short to contain quote data".to_string(),
|
"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;
|
// Validate Aggregate orders have single token in/out
|
||||||
if user_data.len() < sig_len_start + 4 {
|
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(
|
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([
|
// Read offset to array (should be 0x20 for single parameter)
|
||||||
user_data[sig_len_start],
|
let array_offset: usize = U256::from_be_slice(&remaining_data[0..32])
|
||||||
user_data[sig_len_start + 1],
|
.try_into()
|
||||||
user_data[sig_len_start + 2],
|
.map_err(|_| EncodingError::InvalidInput("Array offset too large".to_string()))?;
|
||||||
user_data[sig_len_start + 3],
|
|
||||||
]) as usize;
|
|
||||||
|
|
||||||
if user_data.len() != sig_len_start + 4 + signature_len {
|
if array_offset != 32 {
|
||||||
return Err(EncodingError::InvalidInput("User data length mismatch".to_string()));
|
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
|
// Encode packed data for the executor
|
||||||
// Format: token_in | token_out | transfer_type | order_type |
|
// Format: token_in | token_out | transfer_type | order_type | filledTakerAmount |
|
||||||
// quote_data_length | quote_data | signature_type | signature_length | signature |
|
// quote_data_length | quote_data | maker_signatures_length |
|
||||||
// approval_needed
|
// abi_encoded_maker_signatures | approval_needed
|
||||||
let args = (
|
let args = (
|
||||||
token_in,
|
token_in,
|
||||||
token_out,
|
token_out,
|
||||||
(encoding_context.transfer_type as u8).to_be_bytes(),
|
(encoding_context.transfer_type as u8).to_be_bytes(),
|
||||||
(order_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(),
|
(quote_data.len() as u32).to_be_bytes(),
|
||||||
"e_data[..],
|
"e_data[..],
|
||||||
signature_type.to_be_bytes(),
|
(maker_sigs_encoded.len() as u32).to_be_bytes(),
|
||||||
(signature.len() as u32).to_be_bytes(),
|
&maker_sigs_encoded[..],
|
||||||
&signature[..],
|
|
||||||
(approval_needed as u8).to_be_bytes(),
|
(approval_needed as u8).to_be_bytes(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1826,25 +2043,92 @@ mod tests {
|
|||||||
|
|
||||||
mod bebop {
|
mod bebop {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::encoding::evm::utils::write_calldata_to_file;
|
use crate::encoding::{evm::utils::write_calldata_to_file, models::TransferType};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_bebop_single() {
|
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 order_type = BebopOrderType::Single as u8;
|
||||||
let signature_type = 1u8; // EIP712
|
|
||||||
let quote_data = hex::decode("1234567890abcdef").unwrap();
|
// Create the IBebopSettlement.Single struct data using the exact input data from the tx
|
||||||
let signature = hex::decode("aabbccdd").unwrap();
|
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();
|
let mut user_data = Vec::new();
|
||||||
user_data.push(order_type);
|
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(&(quote_data.len() as u32).to_be_bytes());
|
||||||
user_data.extend_from_slice("e_data);
|
user_data.extend_from_slice("e_data);
|
||||||
user_data.extend_from_slice(&(signature.len() as u32).to_be_bytes());
|
user_data.extend_from_slice(&encoded_maker_sigs);
|
||||||
user_data.extend_from_slice(&signature);
|
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
let bebop_component = ProtocolComponent {
|
||||||
id: String::from("bebop-rfq"),
|
id: String::from("bebop-rfq"),
|
||||||
@@ -1854,7 +2138,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||||
let token_out = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // WETH
|
let token_out = Bytes::from("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3"); // ONDO
|
||||||
let swap = Swap {
|
let swap = Swap {
|
||||||
component: bebop_component,
|
component: bebop_component,
|
||||||
token_in: token_in.clone(),
|
token_in: token_in.clone(),
|
||||||
@@ -1864,7 +2148,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let encoding_context = EncodingContext {
|
let encoding_context = EncodingContext {
|
||||||
receiver: Bytes::from("0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e"), // BOB
|
receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"), /* Original taker */
|
||||||
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(),
|
||||||
@@ -1887,123 +2171,17 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
assert_eq!(
|
// Verify the encoding contains the expected tokens
|
||||||
hex_swap,
|
assert!(hex_swap.contains("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")); // USDC
|
||||||
String::from(concat!(
|
assert!(hex_swap.contains("faba6f8e4a5e8ab82f62fe7c39859fa577269be3")); // ONDO
|
||||||
// token in
|
|
||||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
// Verify it includes the signature
|
||||||
// token out
|
let sig_hex = hex::encode(&signature);
|
||||||
"c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
|
assert!(hex_swap.contains(&sig_hex));
|
||||||
// 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"
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
write_calldata_to_file("test_encode_bebop_single", hex_swap.as_str());
|
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]
|
#[test]
|
||||||
fn test_encode_bebop_aggregate() {
|
fn test_encode_bebop_aggregate() {
|
||||||
use alloy::hex;
|
use alloy::hex;
|
||||||
@@ -2011,19 +2189,87 @@ 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 = 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 sig1 = hex::decode("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001").unwrap();
|
||||||
let sig2 = hex::decode("1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101").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();
|
let mut user_data = Vec::new();
|
||||||
user_data.push(order_type);
|
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(&(quote_data.len() as u32).to_be_bytes());
|
||||||
user_data.extend_from_slice("e_data);
|
user_data.extend_from_slice("e_data);
|
||||||
user_data.extend_from_slice(&(signature.len() as u32).to_be_bytes());
|
user_data.extend_from_slice(&encoded_maker_sigs);
|
||||||
user_data.extend_from_slice(&signature);
|
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
let bebop_component = ProtocolComponent {
|
||||||
id: String::from("bebop-rfq"),
|
id: String::from("bebop-rfq"),
|
||||||
@@ -2066,33 +2312,71 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
let hex_swap = encode(&encoded_swap);
|
let hex_swap = encode(&encoded_swap);
|
||||||
|
|
||||||
assert_eq!(
|
// Calculate expected hex for ABI-encoded maker signatures array with 2 signatures
|
||||||
hex_swap,
|
let _expected_abi_maker_sigs = concat!(
|
||||||
String::from(concat!(
|
// offset to array
|
||||||
// token in
|
"0000000000000000000000000000000000000000000000000000000000000020",
|
||||||
"6b175474e89094c44da98b954eedeac495271d0f",
|
// array length (2)
|
||||||
// token out
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"2260fac5e5542a773aa44fbcfedf7c193bc2c599",
|
// offset to first struct (relative to array data start)
|
||||||
// transfer type TransferFrom
|
"0000000000000000000000000000000000000000000000000000000000000040",
|
||||||
"00",
|
// offset to second struct (64 + 192 = 256 = 0x100)
|
||||||
// order type Aggregate (2)
|
"0000000000000000000000000000000000000000000000000000000000000100",
|
||||||
"02",
|
// First struct data:
|
||||||
// quote data length (4 bytes = 0x00000004)
|
// - offset to signatureBytes
|
||||||
"00000004",
|
"0000000000000000000000000000000000000000000000000000000000000040",
|
||||||
// quote data
|
// - flags
|
||||||
"deadbeef",
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
// signature type EIP712 (1)
|
// - signatureBytes length (65 = 0x41)
|
||||||
"01",
|
"0000000000000000000000000000000000000000000000000000000000000041",
|
||||||
// signature length (130 bytes = 2 * 65-byte EIP712 signatures)
|
// - signatureBytes (65 bytes padded to 96)
|
||||||
"00000082",
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
// concatenated EIP712 signatures (2 * 65 bytes)
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001",
|
"0100000000000000000000000000000000000000000000000000000000000000",
|
||||||
"1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111101",
|
// Second struct data:
|
||||||
// approval needed
|
// - offset to signatureBytes
|
||||||
"01"
|
"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());
|
write_calldata_to_file("test_encode_bebop_aggregate", hex_swap.as_str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1261,20 +1261,91 @@ mod tests {
|
|||||||
models::BebopOrderType,
|
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(
|
fn build_bebop_user_data(
|
||||||
order_type: BebopOrderType,
|
order_type: BebopOrderType,
|
||||||
signature_type: u8,
|
filled_taker_amount: U256,
|
||||||
quote_data: &[u8],
|
quote_data: &[u8],
|
||||||
signature: &[u8],
|
signatures: Vec<(Vec<u8>, u8)>, // (signature, signature_type)
|
||||||
) -> Bytes {
|
) -> 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();
|
let mut user_data = Vec::new();
|
||||||
user_data.push(order_type as u8);
|
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.len() as u32).to_be_bytes());
|
||||||
user_data.extend_from_slice(quote_data);
|
user_data.extend_from_slice(quote_data);
|
||||||
user_data.extend_from_slice(&(signature.len() as u32).to_be_bytes());
|
user_data.extend_from_slice(&encoded_maker_sigs);
|
||||||
user_data.extend_from_slice(signature);
|
|
||||||
Bytes::from(user_data)
|
Bytes::from(user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2375,7 +2446,7 @@ mod tests {
|
|||||||
3600; // Current time + 1 hour
|
3600; // Current time + 1 hour
|
||||||
let taker_address = Address::ZERO;
|
let taker_address = Address::ZERO;
|
||||||
let maker_address =
|
let maker_address =
|
||||||
Address::from_str("0xbbbbbBB520d69a9775E85b458C58c648259FAD5F").unwrap();
|
Address::from_str("0x1234567890123456789012345678901234567890").unwrap(); // Use a proper maker address
|
||||||
let maker_nonce = 1u64;
|
let maker_nonce = 1u64;
|
||||||
let taker_token = Address::from_str(&usdc.to_string()).unwrap();
|
let taker_token = Address::from_str(&usdc.to_string()).unwrap();
|
||||||
let maker_token = Address::from_str(&dai.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
|
// Build user_data with the quote and signature
|
||||||
let user_data = build_bebop_user_data(
|
let user_data = build_bebop_user_data(
|
||||||
BebopOrderType::Single,
|
BebopOrderType::Single,
|
||||||
1, // EIP712 signature type
|
U256::from(0), // 0 means fill entire order
|
||||||
"e_data,
|
"e_data,
|
||||||
&signature,
|
vec![(signature, 1)], // EIP712 signature type
|
||||||
);
|
);
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
let bebop_component = ProtocolComponent {
|
||||||
@@ -3759,30 +3830,30 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_single_encoding_strategy_bebop() {
|
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_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||||
let token_out = Bytes::from("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); // WETH
|
let token_out = Bytes::from("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3"); // ONDO
|
||||||
let amount_in = BigUint::from_str("1000_000000").unwrap(); // 1000 USDC
|
let amount_in = BigUint::from_str("200000000").unwrap(); // 200 USDC
|
||||||
let amount_out = BigUint::from_str("400000000000000000").unwrap(); // 0.4 WETH
|
let amount_out = BigUint::from_str("237212396774431060000").unwrap(); // 237.21 ONDO
|
||||||
|
|
||||||
// Create a valid Bebop Single order struct that matches IBebopSettlement.Single
|
// Create the exact same order from mainnet
|
||||||
// The mock settlement expects this exact structure
|
let expiry = 1749483840u64;
|
||||||
let expiry = SystemTime::now()
|
let taker_address =
|
||||||
.duration_since(UNIX_EPOCH)
|
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap();
|
||||||
.unwrap()
|
|
||||||
.as_secs() +
|
|
||||||
3600; // Current time + 1 hour
|
|
||||||
let taker_address = Address::ZERO;
|
|
||||||
let maker_address =
|
let maker_address =
|
||||||
Address::from_str("0xbbbbbBB520d69a9775E85b458C58c648259FAD5F").unwrap();
|
Address::from_str("0xCe79b081c0c924cb67848723ed3057234d10FC6b").unwrap();
|
||||||
let maker_nonce = 1u64;
|
let maker_nonce = 1749483765992417u64;
|
||||||
let taker_token = Address::from_str(&token_in.to_string()).unwrap();
|
let taker_token = Address::from_str(&token_in.to_string()).unwrap();
|
||||||
let maker_token = Address::from_str(&token_out.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 taker_amount = U256::from_str(&amount_in.to_string()).unwrap();
|
||||||
let maker_amount = U256::from_str(&amount_out.to_string()).unwrap();
|
let maker_amount = U256::from_str(&amount_out.to_string()).unwrap();
|
||||||
let receiver =
|
let receiver = taker_address;
|
||||||
Address::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(); // Alice's address - the actual receiver
|
|
||||||
let packed_commands = U256::ZERO;
|
let packed_commands = U256::ZERO;
|
||||||
|
let flags = U256::from_str(
|
||||||
|
"51915842898789398998206002334703507894664330885127600393944965515693155942400",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Encode using standard ABI encoding (not packed)
|
// Encode using standard ABI encoding (not packed)
|
||||||
let quote_data = (
|
let quote_data = (
|
||||||
@@ -3796,18 +3867,19 @@ mod tests {
|
|||||||
maker_amount,
|
maker_amount,
|
||||||
receiver,
|
receiver,
|
||||||
packed_commands,
|
packed_commands,
|
||||||
U256::from(0u64), // flags as uint256
|
flags,
|
||||||
)
|
)
|
||||||
.abi_encode();
|
.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
|
// Build user_data with the quote and signature
|
||||||
let user_data = build_bebop_user_data(
|
let user_data = build_bebop_user_data(
|
||||||
BebopOrderType::Single,
|
BebopOrderType::Single,
|
||||||
1, // EIP712 signature type
|
U256::ZERO, // 0 means fill entire order
|
||||||
"e_data,
|
"e_data,
|
||||||
&signature,
|
vec![(signature, 0)], // ETH_SIGN signature type
|
||||||
);
|
);
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
let bebop_component = ProtocolComponent {
|
||||||
@@ -3833,9 +3905,9 @@ mod tests {
|
|||||||
given_amount: amount_in,
|
given_amount: amount_in,
|
||||||
checked_token: token_out,
|
checked_token: token_out,
|
||||||
checked_amount: amount_out, // Expected output amount
|
checked_amount: amount_out, // Expected output amount
|
||||||
// Alice
|
// Use the original taker address
|
||||||
sender: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2").unwrap(),
|
sender: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap(),
|
||||||
receiver: Bytes::from_str("0xcd09f75E2BF2A4d11F3AB23f1389FcC1621c0cc2")
|
receiver: Bytes::from_str("0xc5564C13A157E6240659fb81882A28091add8670")
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
swaps: vec![swap],
|
swaps: vec![swap],
|
||||||
..Default::default()
|
..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]
|
#[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
|
// For simplicity, let's use the same tokens as the Single test but with Aggregate
|
||||||
@@ -4036,19 +3992,22 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.abi_encode();
|
.abi_encode();
|
||||||
|
|
||||||
// For aggregate orders with ECDSA signatures, use concatenated 65-byte signatures
|
// For aggregate orders with multiple makers, we need multiple signatures
|
||||||
// This example has 2 makers, so 2x 65-byte signatures = 130 bytes
|
// This example has 2 makers, so 2 separate 65-byte signatures
|
||||||
let sig1 = hex::decode("1b47a665f9a5e14b5208015d11f9143e27b93dc5a0d8c892ec5326eda1e5df3c42a987d0b2ea5b8be8f0e5c326bd4ec0321b10c6e9b4e5f8a0b8d5e6f7c8a9b01b").unwrap();
|
let sig1 = hex::decode("1b47a665f9a5e14b5208015d11f9143e27b93dc5a0d8c892ec5326eda1e5df3c42a987d0b2ea5b8be8f0e5c326bd4ec0321b10c6e9b4e5f8a0b8d5e6f7c8a9b01b").unwrap();
|
||||||
let sig2 = hex::decode("2c58b665f9a5e14b5208015d11f9143e27b93dc5a0d8c892ec5326eda1e5df3c53b987d0b2ea5b8be8f0e5c326bd4ec0321b10c6e9b4e5f8a0b8d5e6f7c8a9b01c").unwrap();
|
let sig2 = hex::decode("2c58b665f9a5e14b5208015d11f9143e27b93dc5a0d8c892ec5326eda1e5df3c53b987d0b2ea5b8be8f0e5c326bd4ec0321b10c6e9b4e5f8a0b8d5e6f7c8a9b01c").unwrap();
|
||||||
let mut signature = Vec::new();
|
|
||||||
signature.extend_from_slice(&sig1);
|
// Build user_data with multiple signatures for the aggregate order
|
||||||
signature.extend_from_slice(&sig2);
|
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(
|
let user_data = build_bebop_user_data(
|
||||||
BebopOrderType::Aggregate,
|
BebopOrderType::Aggregate,
|
||||||
1, // EIP712 signature type
|
U256::from(0), // 0 means fill entire aggregate order
|
||||||
"e_data,
|
"e_data,
|
||||||
&signature,
|
signatures,
|
||||||
);
|
);
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
let bebop_component = ProtocolComponent {
|
||||||
|
|||||||
@@ -214,8 +214,7 @@ pub enum TransferType {
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum BebopOrderType {
|
pub enum BebopOrderType {
|
||||||
Single = 0,
|
Single = 0,
|
||||||
Multi = 1,
|
Aggregate = 1,
|
||||||
Aggregate = 2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<u8> for BebopOrderType {
|
impl TryFrom<u8> for BebopOrderType {
|
||||||
@@ -224,8 +223,7 @@ impl TryFrom<u8> for BebopOrderType {
|
|||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||||
match value {
|
match value {
|
||||||
0 => Ok(BebopOrderType::Single),
|
0 => Ok(BebopOrderType::Single),
|
||||||
1 => Ok(BebopOrderType::Multi),
|
1 => Ok(BebopOrderType::Aggregate),
|
||||||
2 => Ok(BebopOrderType::Aggregate),
|
|
||||||
_ => Err(EncodingError::InvalidInput(format!("Invalid Bebop order type: {}", value))),
|
_ => Err(EncodingError::InvalidInput(format!("Invalid Bebop order type: {}", value))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user