chore: update swap_encoders.rs
This commit is contained in:
@@ -1,10 +1,6 @@
|
|||||||
use std::{
|
use std::{collections::HashMap, str::FromStr};
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
str::FromStr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use alloy::{
|
use alloy::{
|
||||||
core::sol,
|
|
||||||
primitives::{Address, Bytes as AlloyBytes, U256, U8},
|
primitives::{Address, Bytes as AlloyBytes, U256, U8},
|
||||||
sol_types::SolValue,
|
sol_types::SolValue,
|
||||||
};
|
};
|
||||||
@@ -17,7 +13,7 @@ use crate::encoding::{
|
|||||||
approvals::protocol_approvals_manager::ProtocolApprovalsManager,
|
approvals::protocol_approvals_manager::ProtocolApprovalsManager,
|
||||||
utils::{bytes_to_address, get_static_attribute, pad_to_fixed_size},
|
utils::{bytes_to_address, get_static_attribute, pad_to_fixed_size},
|
||||||
},
|
},
|
||||||
models::{BebopOrderType, Chain, EncodingContext, Swap},
|
models::{Chain, EncodingContext, Swap},
|
||||||
swap_encoder::SwapEncoder,
|
swap_encoder::SwapEncoder,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -641,37 +637,6 @@ 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;
|
|
||||||
address[] maker_addresses;
|
|
||||||
uint256[] maker_nonces;
|
|
||||||
address[][] taker_tokens;
|
|
||||||
address[][] maker_tokens;
|
|
||||||
uint256[][] taker_amounts;
|
|
||||||
uint256[][] maker_amounts;
|
|
||||||
address receiver;
|
|
||||||
bytes 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
|
||||||
@@ -701,59 +666,6 @@ 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 unique input token across all makers
|
|
||||||
let unique_taker_tokens: HashSet<_> = order
|
|
||||||
.taker_tokens
|
|
||||||
.iter()
|
|
||||||
.flat_map(|tokens| tokens.iter())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
if unique_taker_tokens.len() != 1 {
|
|
||||||
return Err(EncodingError::InvalidInput(
|
|
||||||
"Aggregate orders must have exactly one unique input token across all makers"
|
|
||||||
.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 {
|
||||||
@@ -808,208 +720,44 @@ impl SwapEncoder for BebopSwapEncoder {
|
|||||||
// Validate component ID
|
// Validate component ID
|
||||||
Self::validate_component_id(&swap.component.id)?;
|
Self::validate_component_id(&swap.component.id)?;
|
||||||
|
|
||||||
// Extract data from user_data (required for Bebop)
|
// Extract bebop calldata from user_data (required for Bebop)
|
||||||
let user_data = swap.user_data.clone().ok_or_else(|| {
|
let user_data = swap.user_data.clone().ok_or_else(|| {
|
||||||
EncodingError::InvalidInput(
|
EncodingError::InvalidInput("Bebop swaps require user_data with calldata".to_string())
|
||||||
"Bebop swaps require user_data with quote and signature".to_string(),
|
|
||||||
)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Parse user_data format:
|
// User data should contain the complete Bebop calldata
|
||||||
// order_type (1 byte) | filledTakerAmount (32 bytes) | quote_data_length (4 bytes) |
|
if user_data.len() < 4 {
|
||||||
// quote_data | abi_encoded_maker_signatures where abi_encoded_maker_signatures is
|
|
||||||
// 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 calldata".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let order_type = BebopOrderType::try_from(user_data[0])?;
|
// The calldata should be for either swapSingle or swapAggregate
|
||||||
|
let bebop_calldata = user_data.to_vec();
|
||||||
|
|
||||||
// Extract filledTakerAmount (32 bytes)
|
// Extract the original filledTakerAmount from the calldata
|
||||||
let filled_taker_amount = U256::from_be_slice(&user_data[1..33]);
|
// Both swapSingle and swapAggregate have filledTakerAmount as the last parameter
|
||||||
|
// We need at least 4 bytes selector + 32 bytes for filledTakerAmount at the end
|
||||||
let quote_data_len =
|
if bebop_calldata.len() < 36 {
|
||||||
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(
|
return Err(EncodingError::InvalidInput(
|
||||||
"User data too short to contain quote data".to_string(),
|
"Bebop calldata too short to contain filledTakerAmount".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let quote_data = user_data[37..37 + quote_data_len].to_vec();
|
// Extract the last 32 bytes as the original filledTakerAmount
|
||||||
|
let original_filled_taker_amount =
|
||||||
// Validate Aggregate orders have single token in/out
|
U256::from_be_slice(&bebop_calldata[bebop_calldata.len() - 32..]);
|
||||||
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 MakerSignature array".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 array_offset != 32 {
|
|
||||||
return Err(EncodingError::InvalidInput(
|
|
||||||
"Invalid array offset in ABI encoding".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 | filledTakerAmount |
|
// Format: token_in | token_out | transfer_type | bebop_calldata_length |
|
||||||
// quote_data_length | quote_data | maker_signatures_length |
|
// bebop_calldata | original_filled_taker_amount | 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(),
|
(bebop_calldata.len() as u32).to_be_bytes(),
|
||||||
filled_taker_amount.to_be_bytes::<32>(),
|
&bebop_calldata[..],
|
||||||
(quote_data.len() as u32).to_be_bytes(),
|
original_filled_taker_amount.to_be_bytes::<32>(),
|
||||||
"e_data[..],
|
|
||||||
(maker_sigs_encoded.len() as u32).to_be_bytes(),
|
|
||||||
&maker_sigs_encoded[..],
|
|
||||||
(approval_needed as u8).to_be_bytes(),
|
(approval_needed as u8).to_be_bytes(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2081,89 +1829,17 @@ mod tests {
|
|||||||
use crate::encoding::{evm::utils::write_calldata_to_file, models::TransferType};
|
use crate::encoding::{evm::utils::write_calldata_to_file, models::TransferType};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_encode_bebop_single() {
|
fn test_encode_bebop_calldata_forwarding() {
|
||||||
use alloy::{hex, primitives::Address};
|
use alloy::hex;
|
||||||
|
|
||||||
// Use mainnet data
|
// Create mock Bebop calldata for swapSingle
|
||||||
// Transaction: https://etherscan.io/tx/0x6279bc970273b6e526e86d9b69133c2ca1277e697ba25375f5e6fc4df50c0c94
|
// This would normally come from the Bebop API response
|
||||||
let order_type = BebopOrderType::Single as u8;
|
let bebop_calldata = hex::decode(
|
||||||
|
"47fb5891".to_owned() // swapSingle selector
|
||||||
// Create the IBebopSettlement.Single struct data using the exact input data from the tx
|
+ "0000000000000000000000000000000000000000000000000000000000000001" // mock order data
|
||||||
let expiry = U256::from(1749483840u64);
|
+ "0000000000000000000000000000000000000000000000000000000000000002" // mock signature data
|
||||||
let taker_address =
|
+ "0000000000000000000000000000000000000000000000000000000bebc200" // filledTakerAmount = 200000000
|
||||||
Address::from_str("0xc5564C13A157E6240659fb81882A28091add8670").unwrap();
|
).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.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(&encoded_maker_sigs);
|
|
||||||
|
|
||||||
let bebop_component = ProtocolComponent {
|
let bebop_component = ProtocolComponent {
|
||||||
id: String::from("bebop-rfq"),
|
id: String::from("bebop-rfq"),
|
||||||
@@ -2174,16 +1850,17 @@ mod tests {
|
|||||||
|
|
||||||
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
let token_in = Bytes::from("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); // USDC
|
||||||
let token_out = Bytes::from("0xfAbA6f8e4a5E8Ab82F62fe7C39859FA577269BE3"); // ONDO
|
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(),
|
||||||
token_out: token_out.clone(),
|
token_out: token_out.clone(),
|
||||||
split: 0f64,
|
split: 0f64,
|
||||||
user_data: Some(Bytes::from(user_data)),
|
user_data: Some(Bytes::from(bebop_calldata.clone())),
|
||||||
};
|
};
|
||||||
|
|
||||||
let encoding_context = EncodingContext {
|
let encoding_context = EncodingContext {
|
||||||
receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"), /* Taker address from tx */
|
receiver: Bytes::from("0xc5564C13A157E6240659fb81882A28091add8670"),
|
||||||
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(),
|
||||||
@@ -2210,218 +1887,16 @@ mod tests {
|
|||||||
assert!(hex_swap.contains("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")); // USDC
|
assert!(hex_swap.contains("a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48")); // USDC
|
||||||
assert!(hex_swap.contains("faba6f8e4a5e8ab82f62fe7c39859fa577269be3")); // ONDO
|
assert!(hex_swap.contains("faba6f8e4a5e8ab82f62fe7c39859fa577269be3")); // ONDO
|
||||||
|
|
||||||
// Verify it includes the signature
|
// Verify it includes the bebop calldata
|
||||||
let sig_hex = hex::encode(&signature);
|
let calldata_hex = hex::encode(&bebop_calldata);
|
||||||
assert!(hex_swap.contains(&sig_hex));
|
assert!(hex_swap.contains(&calldata_hex));
|
||||||
|
|
||||||
write_calldata_to_file("test_encode_bebop_single", hex_swap.as_str());
|
// Verify the original amount matches the filledTakerAmount from calldata
|
||||||
}
|
assert!(
|
||||||
|
hex_swap.contains("0000000000000000000000000000000000000000000000000000000bebc200")
|
||||||
|
); // 200000000 in hex
|
||||||
|
|
||||||
#[test]
|
write_calldata_to_file("test_encode_bebop_calldata_forwarding", hex_swap.as_str());
|
||||||
fn test_encode_bebop_aggregate() {
|
|
||||||
use alloy::hex;
|
|
||||||
|
|
||||||
// Create user_data for a Bebop Aggregate RFQ quote
|
|
||||||
let order_type = BebopOrderType::Aggregate as u8;
|
|
||||||
let signature_type = 0u8; // ETH_SIGN
|
|
||||||
|
|
||||||
// Use real mainnet data from https://etherscan.io/tx/0xec88410136c287280da87d0a37c1cb745f320406ca3ae55c678dec11996c1b1c
|
|
||||||
// Swap: 0.00985 ETH → 17.969561 USDC
|
|
||||||
let aggregate_order = BebopAggregate {
|
|
||||||
expiry: U256::from(1746367285u64),
|
|
||||||
taker_address: Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6")
|
|
||||||
.unwrap(),
|
|
||||||
maker_addresses: vec![
|
|
||||||
Address::from_str("0x67336Cec42645F55059EfF241Cb02eA5cC52fF86").unwrap(),
|
|
||||||
Address::from_str("0xBF19CbF0256f19f39A016a86Ff3551ecC6f2aAFE").unwrap(),
|
|
||||||
],
|
|
||||||
maker_nonces: vec![U256::from(1746367197308u64), U256::from(15460096u64)],
|
|
||||||
taker_tokens: vec![
|
|
||||||
vec![Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap()], /* WETH for maker 1 */
|
|
||||||
vec![Address::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap()], /* WETH for maker 2 */
|
|
||||||
],
|
|
||||||
maker_tokens: vec![
|
|
||||||
vec![Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap()], /* USDC from maker 1 */
|
|
||||||
vec![Address::from_str("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48").unwrap()], /* USDC from maker 2 */
|
|
||||||
],
|
|
||||||
taker_amounts: vec![
|
|
||||||
vec![U256::from(5812106401997138u64)], // First maker takes ~0.0058 ETH
|
|
||||||
vec![U256::from(4037893598002862u64)], // Second maker takes ~0.0040 ETH
|
|
||||||
],
|
|
||||||
maker_amounts: vec![
|
|
||||||
vec![U256::from(10607211u64)], // First maker gives ~10.6 USDC
|
|
||||||
vec![U256::from(7362350u64)], // Second maker gives ~7.36 USDC
|
|
||||||
],
|
|
||||||
receiver: Address::from_str("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6").unwrap(),
|
|
||||||
commands: hex::decode("00040004").unwrap().into(),
|
|
||||||
flags: U256::from_str(
|
|
||||||
"95769172144825922628485191511070792431742484643425438763224908097896054784000",
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let quote_data = aggregate_order.abi_encode();
|
|
||||||
// Real signatures from mainnet transaction
|
|
||||||
let sig1 = hex::decode("d5abb425f9bac1f44d48705f41a8ab9cae207517be8553d2c03b06a88995f2f351ab8ce7627a87048178d539dd64fd2380245531a0c8e43fdc614652b1f32fc71c").unwrap();
|
|
||||||
let sig2 = hex::decode("f38c698e48a3eac48f184bc324fef0b135ee13705ab38cc0bbf5a792f21002f051e445b9e7d57cf24c35e17629ea35b3263591c4abf8ca87ffa44b41301b89c41b").unwrap();
|
|
||||||
|
|
||||||
// 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(0u64); // 0 means fill entire order
|
|
||||||
|
|
||||||
let mut user_data = Vec::new();
|
|
||||||
user_data.push(order_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(&encoded_maker_sigs);
|
|
||||||
|
|
||||||
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(Address::ZERO.as_slice()); // Native ETH
|
|
||||||
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("0x7078B12Ca5B294d95e9aC16D90B7D38238d8F4E6"), /* Taker address from tx */
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 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
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
// - signatureBytes length (66 = 0x42, actual length of signatures)
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000042",
|
|
||||||
// - signatureBytes (66 bytes padded to 96)
|
|
||||||
"d5abb425f9bac1f44d48705f41a8ab9cae207517be8553d2c03b06a88995f2f3",
|
|
||||||
"51ab8ce7627a87048178d539dd64fd2380245531a0c8e43fdc614652b1f32fc7",
|
|
||||||
"1c00000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
// Second struct data:
|
|
||||||
// - offset to signatureBytes
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000040",
|
|
||||||
// - flags
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
// - signatureBytes length (66 = 0x42)
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000042",
|
|
||||||
// - signatureBytes (66 bytes padded to 96)
|
|
||||||
"f38c698e48a3eac48f184bc324fef0b135ee13705ab38cc0bbf5a792f21002f0",
|
|
||||||
"51e445b9e7d57cf24c35e17629ea35b3263591c4abf8ca87ffa44b41301b89c4",
|
|
||||||
"1b00000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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
|
|
||||||
"0000000000000000000000000000000000000000",
|
|
||||||
// token out
|
|
||||||
"a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
|
|
||||||
// transfer type Transfer
|
|
||||||
"01",
|
|
||||||
// order type Aggregate (1)
|
|
||||||
"01",
|
|
||||||
// filledTakerAmount (0 for full fill)
|
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
// 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
|
|
||||||
"00"
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(hex_swap, expected_hex);
|
|
||||||
|
|
||||||
write_calldata_to_file("test_encode_bebop_aggregate", hex_swap.as_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user