use alloy_primitives::U256;
use crate::{
error::CowError,
order_book::types::OrderQuoteResponse,
trading::types::{Amounts, NetworkFee, PartnerFeeCost, ProtocolFeeCost, QuoteAmountsAndCosts},
types::OrderKind,
};
fn parse_u256(s: &str, field: &'static str) -> Result<U256, CowError> {
s.parse::<U256>().map_err(|_e| CowError::Api { status: 0, body: format!("invalid {field}") })
}
fn apply_slippage_sell(buy_amount: U256, slippage_bps: u32) -> U256 {
buy_amount * U256::from(10_000u32 - slippage_bps) / U256::from(10_000u32)
}
fn apply_slippage_buy(sell_amount: U256, slippage_bps: u32) -> U256 {
sell_amount * U256::from(10_000u32 + slippage_bps) / U256::from(10_000u32)
}
pub(crate) fn compute_quote_amounts_and_costs(
quote: &OrderQuoteResponse,
slippage_bps: u32,
) -> Result<QuoteAmountsAndCosts, CowError> {
let is_sell = matches!(quote.quote.kind, OrderKind::Sell);
let sell_amount = parse_u256("e.quote.sell_amount, "sellAmount")?;
let buy_amount = parse_u256("e.quote.buy_amount, "buyAmount")?;
let fee_amount = parse_u256("e.quote.fee_amount, "feeAmount")?;
let (gross_sell, gross_buy) =
if is_sell { (sell_amount + fee_amount, buy_amount) } else { (sell_amount, buy_amount) };
let before_network_costs = Amounts { sell_amount: gross_sell, buy_amount: gross_buy };
let after_network_costs = Amounts { sell_amount, buy_amount };
let after_slippage = if is_sell {
Amounts { sell_amount, buy_amount: apply_slippage_sell(buy_amount, slippage_bps) }
} else {
Amounts { sell_amount: apply_slippage_buy(sell_amount, slippage_bps), buy_amount }
};
let fee_in_buy =
if gross_sell.is_zero() { U256::ZERO } else { fee_amount * buy_amount / gross_sell };
Ok(QuoteAmountsAndCosts {
is_sell,
before_all_fees: before_network_costs,
before_network_costs,
after_network_costs,
after_partner_fees: after_network_costs,
after_slippage,
network_fee: NetworkFee {
amount_in_sell_currency: fee_amount,
amount_in_buy_currency: fee_in_buy,
},
partner_fee: PartnerFeeCost::default(),
protocol_fee: ProtocolFeeCost::default(),
})
}
#[cfg(test)]
mod tests {
use alloy_primitives::Address;
use crate::{order_book::types::QuoteData, types::TokenBalance};
use super::*;
fn make_sell_quote(sell: &str, buy: &str, fee: &str) -> OrderQuoteResponse {
OrderQuoteResponse {
quote: QuoteData {
sell_token: Address::repeat_byte(0x11),
buy_token: Address::repeat_byte(0x22),
receiver: None,
sell_amount: sell.to_owned(),
buy_amount: buy.to_owned(),
valid_to: 1_700_000_000,
app_data: "0x00".to_owned(),
fee_amount: fee.to_owned(),
kind: OrderKind::Sell,
partially_fillable: false,
sell_token_balance: TokenBalance::Erc20,
buy_token_balance: TokenBalance::Erc20,
},
from: Address::ZERO,
expiration: "2025-01-01T00:00:00Z".to_owned(),
id: Some(1),
verified: true,
protocol_fee_bps: None,
}
}
fn make_buy_quote(sell: &str, buy: &str, fee: &str) -> OrderQuoteResponse {
let mut q = make_sell_quote(sell, buy, fee);
q.quote.kind = OrderKind::Buy;
q
}
#[test]
fn sell_order_basic() {
let quote = make_sell_quote("1000", "2000", "100");
let result = compute_quote_amounts_and_costs("e, 50).unwrap();
assert!(result.is_sell);
assert_eq!(result.before_network_costs.sell_amount, U256::from(1100u64));
assert_eq!(result.before_network_costs.buy_amount, U256::from(2000u64));
assert_eq!(result.after_network_costs.sell_amount, U256::from(1000u64));
assert_eq!(result.after_network_costs.buy_amount, U256::from(2000u64));
assert_eq!(result.after_slippage.sell_amount, U256::from(1000u64));
assert_eq!(result.after_slippage.buy_amount, U256::from(1990u64));
assert_eq!(result.network_fee.amount_in_sell_currency, U256::from(100u64));
}
#[test]
fn buy_order_basic() {
let quote = make_buy_quote("1000", "2000", "100");
let result = compute_quote_amounts_and_costs("e, 50).unwrap();
assert!(!result.is_sell);
assert_eq!(result.before_network_costs.sell_amount, U256::from(1000u64));
assert_eq!(result.before_network_costs.buy_amount, U256::from(2000u64));
assert_eq!(result.after_slippage.sell_amount, U256::from(1005u64));
assert_eq!(result.after_slippage.buy_amount, U256::from(2000u64));
}
#[test]
fn zero_fee() {
let quote = make_sell_quote("1000", "2000", "0");
let result = compute_quote_amounts_and_costs("e, 0).unwrap();
assert_eq!(result.network_fee.amount_in_sell_currency, U256::ZERO);
assert_eq!(result.network_fee.amount_in_buy_currency, U256::ZERO);
assert_eq!(result.after_slippage.buy_amount, U256::from(2000u64));
}
#[test]
fn zero_gross_sell_does_not_panic() {
let quote = make_sell_quote("0", "1000", "0");
let result = compute_quote_amounts_and_costs("e, 50).unwrap();
assert_eq!(result.network_fee.amount_in_buy_currency, U256::ZERO);
}
#[test]
fn invalid_amount_returns_error() {
let mut quote = make_sell_quote("1000", "2000", "100");
quote.quote.sell_amount = "not_a_number".to_owned();
assert!(compute_quote_amounts_and_costs("e, 50).is_err());
}
#[test]
fn partner_and_protocol_fees_default_to_zero() {
let quote = make_sell_quote("1000", "2000", "100");
let result = compute_quote_amounts_and_costs("e, 50).unwrap();
assert_eq!(result.after_partner_fees.sell_amount, result.after_network_costs.sell_amount);
assert_eq!(result.after_partner_fees.buy_amount, result.after_network_costs.buy_amount);
assert_eq!(result.before_all_fees.sell_amount, result.before_network_costs.sell_amount);
assert_eq!(result.before_all_fees.buy_amount, result.before_network_costs.buy_amount);
}
}