use alloy_primitives::{Address, B256, U256, keccak256};
use crate::error::CowError;
use super::types::ConditionalOrderParams;
pub const STOP_LOSS_HANDLER_ADDRESS: Address = Address::new([
0xe8, 0x21, 0x2f, 0x30, 0xc2, 0x8b, 0x4a, 0xab, 0x46, 0x7d, 0xf3, 0x72, 0x5c, 0x14, 0xd6, 0xe8,
0x9c, 0x2e, 0xb9, 0x72,
]);
#[derive(Debug, Clone)]
pub struct StopLossData {
pub sell_token: Address,
pub buy_token: Address,
pub sell_amount: U256,
pub buy_amount: U256,
pub app_data: B256,
pub receiver: Address,
pub is_sell_order: bool,
pub is_partially_fillable: bool,
pub valid_to: u32,
pub strike_price: U256,
pub sell_token_price_oracle: Address,
pub buy_token_price_oracle: Address,
pub token_amount_in_eth: bool,
}
#[derive(Debug, Clone)]
pub struct StopLossOrder {
pub data: StopLossData,
pub salt: B256,
}
impl StopLossOrder {
#[must_use]
pub fn new(data: StopLossData) -> Self {
let salt = deterministic_salt(&data);
Self { data, salt }
}
#[must_use]
pub const fn with_salt(data: StopLossData, salt: B256) -> Self {
Self { data, salt }
}
#[must_use]
pub fn is_valid(&self) -> bool {
let d = &self.data;
!d.sell_amount.is_zero() && !d.buy_amount.is_zero() && d.sell_token != d.buy_token
}
pub fn to_params(&self) -> Result<ConditionalOrderParams, CowError> {
Ok(ConditionalOrderParams {
handler: STOP_LOSS_HANDLER_ADDRESS,
salt: self.salt,
static_input: encode_stop_loss_struct(&self.data),
})
}
#[must_use]
pub const fn data_ref(&self) -> &StopLossData {
&self.data
}
#[must_use]
pub const fn salt_ref(&self) -> &B256 {
&self.salt
}
}
#[must_use]
pub fn encode_stop_loss_struct(d: &StopLossData) -> Vec<u8> {
let mut buf = Vec::with_capacity(13 * 32);
buf.extend_from_slice(&pad_address(d.sell_token.as_slice()));
buf.extend_from_slice(&pad_address(d.buy_token.as_slice()));
buf.extend_from_slice(&u256_bytes(d.sell_amount));
buf.extend_from_slice(&u256_bytes(d.buy_amount));
buf.extend_from_slice(d.app_data.as_slice());
buf.extend_from_slice(&pad_address(d.receiver.as_slice()));
buf.extend_from_slice(&bool_word(d.is_sell_order));
buf.extend_from_slice(&bool_word(d.is_partially_fillable));
buf.extend_from_slice(&u256_be(u64::from(d.valid_to)));
buf.extend_from_slice(&u256_bytes(d.strike_price));
buf.extend_from_slice(&pad_address(d.sell_token_price_oracle.as_slice()));
buf.extend_from_slice(&pad_address(d.buy_token_price_oracle.as_slice()));
buf.extend_from_slice(&bool_word(d.token_amount_in_eth));
buf
}
pub fn decode_stop_loss_static_input(bytes: &[u8]) -> Result<StopLossData, CowError> {
if bytes.len() < 13 * 32 {
return Err(CowError::AppData(format!(
"StopLoss static input too short: {} bytes (need 416)",
bytes.len()
)));
}
let addr = |off: usize| -> Address {
let mut a = [0u8; 20];
a.copy_from_slice(&bytes[off + 12..off + 32]);
Address::new(a)
};
let u256 = |off: usize| -> U256 { U256::from_be_slice(&bytes[off..off + 32]) };
let u32v = |off: usize| -> u32 {
u32::from_be_bytes([bytes[off + 28], bytes[off + 29], bytes[off + 30], bytes[off + 31]])
};
let bool_v = |off: usize| -> bool { bytes[off + 31] != 0 };
let mut app_data_bytes = [0u8; 32];
app_data_bytes.copy_from_slice(&bytes[4 * 32..5 * 32]);
Ok(StopLossData {
sell_token: addr(0),
buy_token: addr(32),
sell_amount: u256(64),
buy_amount: u256(96),
app_data: B256::new(app_data_bytes),
receiver: addr(5 * 32),
is_sell_order: bool_v(6 * 32),
is_partially_fillable: bool_v(7 * 32),
valid_to: u32v(8 * 32),
strike_price: u256(9 * 32),
sell_token_price_oracle: addr(10 * 32),
buy_token_price_oracle: addr(11 * 32),
token_amount_in_eth: bool_v(12 * 32),
})
}
fn pad_address(bytes: &[u8]) -> [u8; 32] {
let mut out = [0u8; 32];
out[12..].copy_from_slice(bytes);
out
}
const fn u256_bytes(v: U256) -> [u8; 32] {
v.to_be_bytes()
}
fn u256_be(v: u64) -> [u8; 32] {
let mut out = [0u8; 32];
out[24..].copy_from_slice(&v.to_be_bytes());
out
}
const fn bool_word(v: bool) -> [u8; 32] {
let mut out = [0u8; 32];
out[31] = if v { 1 } else { 0 };
out
}
fn deterministic_salt(d: &StopLossData) -> B256 {
let mut buf = Vec::with_capacity(20 + 20 + 32 + 32);
buf.extend_from_slice(d.sell_token.as_slice());
buf.extend_from_slice(d.buy_token.as_slice());
buf.extend_from_slice(&u256_bytes(d.sell_amount));
buf.extend_from_slice(&u256_bytes(d.strike_price));
keccak256(&buf)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_data() -> StopLossData {
StopLossData {
sell_token: Address::repeat_byte(0x01),
buy_token: Address::repeat_byte(0x02),
sell_amount: U256::from(1_000u64),
buy_amount: U256::from(900u64),
app_data: B256::ZERO,
receiver: Address::ZERO,
is_sell_order: true,
is_partially_fillable: false,
valid_to: 9_999_999,
strike_price: U256::from(1_000_000_000_000_000_000u64),
sell_token_price_oracle: Address::repeat_byte(0x03),
buy_token_price_oracle: Address::repeat_byte(0x04),
token_amount_in_eth: false,
}
}
#[test]
fn encode_is_416_bytes() {
let data = make_data();
let encoded = encode_stop_loss_struct(&data);
assert_eq!(encoded.len(), 416);
}
#[test]
fn encode_decode_roundtrip() {
let data = make_data();
let encoded = encode_stop_loss_struct(&data);
let decoded = decode_stop_loss_static_input(&encoded).unwrap();
assert_eq!(decoded.sell_token, data.sell_token);
assert_eq!(decoded.buy_token, data.buy_token);
assert_eq!(decoded.sell_amount, data.sell_amount);
assert_eq!(decoded.buy_amount, data.buy_amount);
assert_eq!(decoded.app_data, data.app_data);
assert_eq!(decoded.receiver, data.receiver);
assert_eq!(decoded.is_sell_order, data.is_sell_order);
assert_eq!(decoded.is_partially_fillable, data.is_partially_fillable);
assert_eq!(decoded.valid_to, data.valid_to);
assert_eq!(decoded.strike_price, data.strike_price);
assert_eq!(decoded.sell_token_price_oracle, data.sell_token_price_oracle);
assert_eq!(decoded.buy_token_price_oracle, data.buy_token_price_oracle);
assert_eq!(decoded.token_amount_in_eth, data.token_amount_in_eth);
}
#[test]
fn is_valid_returns_false_when_same_token() {
let mut data = make_data();
data.buy_token = data.sell_token;
let order = StopLossOrder::new(data);
assert!(!order.is_valid());
}
#[test]
fn is_valid_returns_false_when_zero_amounts() {
let mut data = make_data();
data.sell_amount = U256::ZERO;
let order = StopLossOrder::new(data);
assert!(!order.is_valid());
}
#[test]
fn is_valid_returns_true_for_valid_order() {
let order = StopLossOrder::new(make_data());
assert!(order.is_valid());
}
#[test]
fn to_params_sets_correct_handler() {
let order = StopLossOrder::new(make_data());
let params = order.to_params().unwrap();
assert_eq!(params.handler, STOP_LOSS_HANDLER_ADDRESS);
}
#[test]
fn decode_too_short_returns_error() {
let result = decode_stop_loss_static_input(&[0u8; 100]);
assert!(result.is_err());
}
}