use alloy_primitives::{Address, B256, Bytes, U256};
use foldhash::HashMap;
use crate::{
error::CowError,
order_signing::{
flags::{
OrderFlags, TradeFlags, decode_order_flags, encode_trade_flags,
normalize_buy_token_balance,
},
types::UnsignedOrder,
},
types::SigningScheme,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncodedTrade {
pub sell_token_index: u64,
pub buy_token_index: u64,
pub receiver: Address,
pub sell_amount: U256,
pub buy_amount: U256,
pub valid_to: u32,
pub app_data: B256,
pub fee_amount: U256,
pub flags: u8,
pub executed_amount: U256,
pub signature: Bytes,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BatchSwapStep {
pub pool_id: B256,
pub asset_in_index: u64,
pub asset_out_index: u64,
pub amount: U256,
pub user_data: Bytes,
}
#[derive(Debug, Clone)]
pub struct Swap {
pub pool_id: B256,
pub asset_in: Address,
pub asset_out: Address,
pub amount: U256,
pub user_data: Option<Bytes>,
}
#[derive(Debug, Clone, Default)]
pub struct SettlementTokenRegistry {
tokens: Vec<Address>,
token_map: HashMap<Address, u64>,
}
impl SettlementTokenRegistry {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn addresses(&self) -> &[Address] {
&self.tokens
}
pub fn index(&mut self, token: Address) -> u64 {
if let Some(&idx) = self.token_map.get(&token) {
return idx;
}
let idx = self.tokens.len() as u64;
self.tokens.push(token);
self.token_map.insert(token, idx);
idx
}
}
#[derive(Debug, Clone)]
pub struct SignatureData {
pub scheme: SigningScheme,
pub data: Bytes,
}
#[must_use]
pub fn encode_trade(
tokens: &mut SettlementTokenRegistry,
order: &UnsignedOrder,
signature: &SignatureData,
executed_amount: U256,
) -> EncodedTrade {
let trade_flags = TradeFlags {
order_flags: OrderFlags {
kind: order.kind,
partially_fillable: order.partially_fillable,
sell_token_balance: order.sell_token_balance,
buy_token_balance: normalize_buy_token_balance(order.buy_token_balance),
},
signing_scheme: signature.scheme,
};
EncodedTrade {
sell_token_index: tokens.index(order.sell_token),
buy_token_index: tokens.index(order.buy_token),
receiver: order.receiver,
sell_amount: order.sell_amount,
buy_amount: order.buy_amount,
valid_to: order.valid_to,
app_data: order.app_data,
fee_amount: order.fee_amount,
flags: encode_trade_flags(&trade_flags),
executed_amount,
signature: signature.data.clone(),
}
}
pub fn decode_order(trade: &EncodedTrade, tokens: &[Address]) -> Result<UnsignedOrder, CowError> {
let sell_index = trade.sell_token_index as usize;
let buy_index = trade.buy_token_index as usize;
if sell_index >= tokens.len() || buy_index >= tokens.len() {
return Err(CowError::Parse {
field: "trade",
reason: format!(
"token index out of bounds: sell={sell_index}, buy={buy_index}, tokens={}",
tokens.len()
),
});
}
let flags = decode_order_flags(trade.flags)?;
Ok(UnsignedOrder {
sell_token: tokens[sell_index],
buy_token: tokens[buy_index],
receiver: trade.receiver,
sell_amount: trade.sell_amount,
buy_amount: trade.buy_amount,
valid_to: trade.valid_to,
app_data: trade.app_data,
fee_amount: trade.fee_amount,
kind: flags.kind,
partially_fillable: flags.partially_fillable,
sell_token_balance: flags.sell_token_balance,
buy_token_balance: flags.buy_token_balance,
})
}
#[must_use]
pub fn encode_swap_step(tokens: &mut SettlementTokenRegistry, swap: &Swap) -> BatchSwapStep {
BatchSwapStep {
pool_id: swap.pool_id,
asset_in_index: tokens.index(swap.asset_in),
asset_out_index: tokens.index(swap.asset_out),
amount: swap.amount,
user_data: swap.user_data.clone().unwrap_or_default(),
}
}
#[must_use]
pub fn encode_signature_data(signature: &SignatureData) -> Bytes {
signature.data.clone()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Eip1271SignatureData {
pub verifier: Address,
pub signature: Bytes,
}
#[must_use]
pub fn encode_eip1271_signature_data(data: &Eip1271SignatureData) -> Bytes {
let mut buf = Vec::with_capacity(20 + data.signature.len());
buf.extend_from_slice(data.verifier.as_slice());
buf.extend_from_slice(&data.signature);
Bytes::from(buf)
}
pub fn decode_signature_owner(data: &[u8]) -> Result<Address, CowError> {
if data.len() < 20 {
return Err(CowError::Parse {
field: "eip1271_signature",
reason: format!("data too short: {} bytes, need at least 20", data.len()),
});
}
Ok(Address::from_slice(&data[..20]))
}
pub fn decode_eip1271_signature_data(data: &[u8]) -> Result<Eip1271SignatureData, CowError> {
if data.len() < 20 {
return Err(CowError::Parse {
field: "eip1271_signature",
reason: format!("data too short: {} bytes, need at least 20", data.len()),
});
}
let verifier = Address::from_slice(&data[..20]);
let signature = Bytes::from(data[20..].to_vec());
Ok(Eip1271SignatureData { verifier, signature })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{OrderKind, TokenBalance};
use alloy_primitives::address;
#[test]
fn token_registry_basic() {
let mut reg = SettlementTokenRegistry::new();
let a = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
let b = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
assert_eq!(reg.index(a), 0);
assert_eq!(reg.index(b), 1);
assert_eq!(reg.index(a), 0);
assert_eq!(reg.addresses().len(), 2);
}
#[test]
fn encode_decode_trade_roundtrip() {
let sell = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
let buy = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
let mut tokens = SettlementTokenRegistry::new();
let order = UnsignedOrder {
sell_token: sell,
buy_token: buy,
receiver: Address::ZERO,
sell_amount: U256::from(1000),
buy_amount: U256::from(900),
valid_to: 1_000_000,
app_data: B256::ZERO,
fee_amount: U256::ZERO,
kind: OrderKind::Sell,
partially_fillable: false,
sell_token_balance: TokenBalance::Erc20,
buy_token_balance: TokenBalance::Erc20,
};
let signature =
SignatureData { scheme: SigningScheme::Eip712, data: Bytes::from(vec![0u8; 65]) };
let encoded = encode_trade(&mut tokens, &order, &signature, U256::ZERO);
let decoded = decode_order(&encoded, tokens.addresses()).unwrap();
assert_eq!(decoded.sell_token, sell);
assert_eq!(decoded.buy_token, buy);
assert_eq!(decoded.kind, OrderKind::Sell);
assert_eq!(decoded.sell_amount, U256::from(1000));
}
#[test]
fn decode_order_out_of_bounds() {
let trade = EncodedTrade {
sell_token_index: 5,
buy_token_index: 0,
receiver: Address::ZERO,
sell_amount: U256::ZERO,
buy_amount: U256::ZERO,
valid_to: 0,
app_data: B256::ZERO,
fee_amount: U256::ZERO,
flags: 0,
executed_amount: U256::ZERO,
signature: Bytes::new(),
};
let tokens = vec![Address::ZERO];
assert!(decode_order(&trade, &tokens).is_err());
}
#[test]
fn eip1271_roundtrip() {
let data = Eip1271SignatureData {
verifier: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
signature: Bytes::from(vec![1, 2, 3, 4, 5]),
};
let encoded = encode_eip1271_signature_data(&data);
let decoded = decode_eip1271_signature_data(&encoded).unwrap();
assert_eq!(decoded, data);
}
#[test]
fn eip1271_too_short() {
assert!(decode_eip1271_signature_data(&[0u8; 19]).is_err());
}
#[test]
fn encode_swap_step_basic() {
let mut tokens = SettlementTokenRegistry::new();
let swap = Swap {
pool_id: B256::ZERO,
asset_in: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
asset_out: address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
amount: U256::from(500),
user_data: None,
};
let step = encode_swap_step(&mut tokens, &swap);
assert_eq!(step.asset_in_index, 0);
assert_eq!(step.asset_out_index, 1);
assert_eq!(step.amount, U256::from(500));
assert!(step.user_data.is_empty());
}
#[test]
fn encode_swap_step_with_user_data() {
let mut tokens = SettlementTokenRegistry::new();
let swap = Swap {
pool_id: B256::ZERO,
asset_in: address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
asset_out: address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
amount: U256::from(500),
user_data: Some(Bytes::from(vec![0xDE, 0xAD])),
};
let step = encode_swap_step(&mut tokens, &swap);
assert_eq!(step.user_data, Bytes::from(vec![0xDE, 0xAD]));
}
#[test]
fn encode_signature_data_returns_clone() {
let sig = SignatureData { scheme: SigningScheme::Eip712, data: Bytes::from(vec![1, 2, 3]) };
let encoded = encode_signature_data(&sig);
assert_eq!(encoded, Bytes::from(vec![1, 2, 3]));
}
#[test]
fn decode_signature_owner_too_short() {
assert!(decode_signature_owner(&[0u8; 19]).is_err());
}
#[test]
fn decode_signature_owner_exact_20_bytes() {
let mut data = [0u8; 20];
data[19] = 0x42;
let owner = decode_signature_owner(&data).unwrap();
assert_eq!(owner.as_slice()[19], 0x42);
}
#[test]
fn decode_eip1271_exact_20_bytes_empty_sig() {
let data = [0u8; 20];
let result = decode_eip1271_signature_data(&data).unwrap();
assert_eq!(result.verifier, Address::ZERO);
assert!(result.signature.is_empty());
}
#[test]
fn encode_decode_trade_partially_fillable() {
let sell = address!("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
let buy = address!("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
let mut tokens = SettlementTokenRegistry::new();
let order = UnsignedOrder {
sell_token: sell,
buy_token: buy,
receiver: Address::ZERO,
sell_amount: U256::from(1000),
buy_amount: U256::from(900),
valid_to: 1_000_000,
app_data: B256::ZERO,
fee_amount: U256::ZERO,
kind: OrderKind::Buy,
partially_fillable: true,
sell_token_balance: TokenBalance::Erc20,
buy_token_balance: TokenBalance::Erc20,
};
let signature =
SignatureData { scheme: SigningScheme::Eip712, data: Bytes::from(vec![0u8; 65]) };
let encoded = encode_trade(&mut tokens, &order, &signature, U256::from(100u64));
assert_eq!(encoded.executed_amount, U256::from(100u64));
let decoded = decode_order(&encoded, tokens.addresses()).unwrap();
assert_eq!(decoded.kind, OrderKind::Buy);
assert!(decoded.partially_fillable);
}
}