use std::fmt;
use alloy_primitives::{Address, U256};
use crate::{
order_book::types::OrderQuoteResponse,
order_signing::types::{OrderTypedData, UnsignedOrder},
types::OrderKind,
};
#[derive(Debug, Clone, Copy, Default)]
pub struct Amounts {
pub sell_amount: U256,
pub buy_amount: U256,
}
impl Amounts {
#[must_use]
pub const fn new(sell_amount: U256, buy_amount: U256) -> Self {
Self { sell_amount, buy_amount }
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.sell_amount.is_zero() && self.buy_amount.is_zero()
}
#[must_use]
pub const fn total(&self) -> U256 {
self.sell_amount.saturating_add(self.buy_amount)
}
}
impl fmt::Display for Amounts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sell {} → buy {}", self.sell_amount, self.buy_amount)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct NetworkFee {
pub amount_in_sell_currency: U256,
pub amount_in_buy_currency: U256,
}
impl NetworkFee {
#[must_use]
pub const fn new(amount_in_sell_currency: U256, amount_in_buy_currency: U256) -> Self {
Self { amount_in_sell_currency, amount_in_buy_currency }
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.amount_in_sell_currency.is_zero() && self.amount_in_buy_currency.is_zero()
}
#[must_use]
pub const fn total_atoms(&self) -> U256 {
self.amount_in_sell_currency.saturating_add(self.amount_in_buy_currency)
}
}
impl fmt::Display for NetworkFee {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"network-fee sell={} buy={}",
self.amount_in_sell_currency, self.amount_in_buy_currency,
)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct PartnerFeeCost {
pub amount: U256,
pub bps: u32,
}
impl PartnerFeeCost {
#[must_use]
pub const fn new(amount: U256, bps: u32) -> Self {
Self { amount, bps }
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.amount.is_zero() && self.bps == 0
}
#[must_use]
pub const fn has_bps(&self) -> bool {
self.bps > 0
}
}
impl fmt::Display for PartnerFeeCost {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "partner-fee {}bps {}", self.bps, self.amount)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct ProtocolFeeCost {
pub amount: U256,
pub bps: u32,
}
impl ProtocolFeeCost {
#[must_use]
pub const fn new(amount: U256, bps: u32) -> Self {
Self { amount, bps }
}
#[must_use]
pub fn is_zero(&self) -> bool {
self.amount.is_zero() && self.bps == 0
}
#[must_use]
pub const fn has_bps(&self) -> bool {
self.bps > 0
}
}
impl fmt::Display for ProtocolFeeCost {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "protocol-fee {}bps {}", self.bps, self.amount)
}
}
#[derive(Debug, Clone, Copy)]
pub struct QuoteAmountsAndCosts {
pub is_sell: bool,
pub before_all_fees: Amounts,
pub before_network_costs: Amounts,
pub after_network_costs: Amounts,
pub after_partner_fees: Amounts,
pub after_slippage: Amounts,
pub network_fee: NetworkFee,
pub partner_fee: PartnerFeeCost,
pub protocol_fee: ProtocolFeeCost,
}
#[derive(Debug, Clone)]
pub struct TradingAppDataInfo {
pub full_app_data: String,
pub app_data_keccak256: String,
}
impl TradingAppDataInfo {
#[must_use]
pub fn new(full_app_data: impl Into<String>, app_data_keccak256: impl Into<String>) -> Self {
Self { full_app_data: full_app_data.into(), app_data_keccak256: app_data_keccak256.into() }
}
#[must_use]
pub const fn has_full_app_data(&self) -> bool {
!self.full_app_data.is_empty()
}
#[must_use]
pub fn full_app_data_ref(&self) -> &str {
&self.full_app_data
}
#[must_use]
pub fn keccak256_ref(&self) -> &str {
&self.app_data_keccak256
}
}
impl fmt::Display for TradingAppDataInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "app-data({})", self.app_data_keccak256)
}
}
#[derive(Debug, Clone)]
pub struct TradingTransactionParams {
pub data: Vec<u8>,
pub to: Address,
pub gas_limit: u64,
pub value: U256,
}
impl TradingTransactionParams {
#[must_use]
pub const fn new(data: Vec<u8>, to: Address, gas_limit: u64, value: U256) -> Self {
Self { data, to, gas_limit, value }
}
#[must_use]
pub fn with_data(mut self, data: Vec<u8>) -> Self {
self.data = data;
self
}
#[must_use]
pub const fn with_to(mut self, to: Address) -> Self {
self.to = to;
self
}
#[must_use]
pub const fn with_gas_limit(mut self, gas_limit: u64) -> Self {
self.gas_limit = gas_limit;
self
}
#[must_use]
pub const fn with_value(mut self, value: U256) -> Self {
self.value = value;
self
}
#[must_use]
pub const fn data_len(&self) -> usize {
self.data.len()
}
#[must_use]
pub fn has_value(&self) -> bool {
!self.value.is_zero()
}
}
impl fmt::Display for TradingTransactionParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "tx to={:#x} gas={}", self.to, self.gas_limit)
}
}
#[derive(Debug, Clone, Default)]
pub struct PostTradeAdditionalParams {
pub signing_scheme: Option<crate::types::SigningScheme>,
pub network_costs_amount: Option<String>,
pub apply_costs_slippage_and_fees: Option<bool>,
}
impl PostTradeAdditionalParams {
#[must_use]
pub const fn with_signing_scheme(mut self, scheme: crate::types::SigningScheme) -> Self {
self.signing_scheme = Some(scheme);
self
}
#[must_use]
pub fn with_network_costs_amount(mut self, amount: impl Into<String>) -> Self {
self.network_costs_amount = Some(amount.into());
self
}
#[must_use]
pub const fn with_apply_costs_slippage_and_fees(mut self, apply: bool) -> Self {
self.apply_costs_slippage_and_fees = Some(apply);
self
}
#[must_use]
pub const fn has_signing_scheme(&self) -> bool {
self.signing_scheme.is_some()
}
#[must_use]
pub const fn has_network_costs(&self) -> bool {
self.network_costs_amount.is_some()
}
#[must_use]
pub const fn should_apply_costs(&self) -> bool {
matches!(self.apply_costs_slippage_and_fees, Some(true))
}
}
impl fmt::Display for PostTradeAdditionalParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("post-trade-params")
}
}
#[derive(Debug, Clone, Default)]
pub struct SwapAdvancedSettings {
pub app_data: Option<serde_json::Value>,
pub slippage_bps: Option<u32>,
pub partner_fee: Option<crate::app_data::types::PartnerFee>,
}
impl SwapAdvancedSettings {
#[must_use]
pub fn with_app_data(mut self, app_data: serde_json::Value) -> Self {
self.app_data = Some(app_data);
self
}
#[must_use]
pub const fn with_slippage_bps(mut self, bps: u32) -> Self {
self.slippage_bps = Some(bps);
self
}
#[must_use]
pub fn with_partner_fee(mut self, fee: crate::app_data::types::PartnerFee) -> Self {
self.partner_fee = Some(fee);
self
}
#[must_use]
pub const fn has_app_data(&self) -> bool {
self.app_data.is_some()
}
#[must_use]
pub const fn has_slippage_bps(&self) -> bool {
self.slippage_bps.is_some()
}
#[must_use]
pub const fn has_partner_fee(&self) -> bool {
self.partner_fee.is_some()
}
}
impl fmt::Display for SwapAdvancedSettings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("swap-settings")
}
}
#[derive(Debug, Clone, Default)]
pub struct LimitOrderAdvancedSettings {
pub receiver: Option<Address>,
pub valid_to: Option<u32>,
pub partner_fee: Option<crate::app_data::types::PartnerFee>,
pub partially_fillable: Option<bool>,
pub app_data: Option<String>,
}
impl LimitOrderAdvancedSettings {
#[must_use]
pub const fn with_receiver(mut self, receiver: Address) -> Self {
self.receiver = Some(receiver);
self
}
#[must_use]
pub const fn with_valid_to(mut self, valid_to: u32) -> Self {
self.valid_to = Some(valid_to);
self
}
#[must_use]
pub fn with_partner_fee(mut self, fee: crate::app_data::types::PartnerFee) -> Self {
self.partner_fee = Some(fee);
self
}
#[must_use]
pub const fn with_partially_fillable(mut self, partially_fillable: bool) -> Self {
self.partially_fillable = Some(partially_fillable);
self
}
#[must_use]
pub fn with_app_data(mut self, app_data: impl Into<String>) -> Self {
self.app_data = Some(app_data.into());
self
}
#[must_use]
pub const fn has_receiver(&self) -> bool {
self.receiver.is_some()
}
#[must_use]
pub const fn has_valid_to(&self) -> bool {
self.valid_to.is_some()
}
#[must_use]
pub const fn has_partner_fee(&self) -> bool {
self.partner_fee.is_some()
}
#[must_use]
pub const fn has_partially_fillable(&self) -> bool {
self.partially_fillable.is_some()
}
#[must_use]
pub const fn has_app_data(&self) -> bool {
self.app_data.is_some()
}
}
impl fmt::Display for LimitOrderAdvancedSettings {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("limit-settings")
}
}
#[must_use]
pub fn apply_settings_to_limit_trade_parameters(
mut params: LimitTradeParameters,
settings: Option<&LimitOrderAdvancedSettings>,
) -> LimitTradeParameters {
let Some(s) = settings else {
return params;
};
if s.receiver.is_some() {
params.receiver = s.receiver;
}
if s.valid_to.is_some() {
params.valid_to = s.valid_to;
}
if s.partner_fee.is_some() {
params.partner_fee = s.partner_fee.clone();
}
if let Some(pf) = s.partially_fillable {
params.partially_fillable = pf;
}
if s.app_data.is_some() {
params.app_data = s.app_data.clone();
}
params
}
#[derive(Debug, Clone)]
pub struct LimitTradeParametersFromQuote {
pub sell_token: Address,
pub buy_token: Address,
pub sell_amount: U256,
pub buy_amount: U256,
pub quote_id: Option<i64>,
}
impl LimitTradeParametersFromQuote {
#[must_use]
pub const fn new(
sell_token: Address,
buy_token: Address,
sell_amount: U256,
buy_amount: U256,
) -> Self {
Self { sell_token, buy_token, sell_amount, buy_amount, quote_id: None }
}
#[must_use]
pub const fn with_quote_id(mut self, quote_id: i64) -> Self {
self.quote_id = Some(quote_id);
self
}
#[must_use]
pub const fn has_quote_id(&self) -> bool {
self.quote_id.is_some()
}
}
impl fmt::Display for LimitTradeParametersFromQuote {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"limit-from-quote {:#x} sell={} buy\u{2265}{}",
self.sell_token, self.sell_amount, self.buy_amount
)
}
}
#[derive(Debug, Clone)]
pub struct TradeParameters {
pub kind: OrderKind,
pub sell_token: Address,
pub sell_token_decimals: u8,
pub buy_token: Address,
pub buy_token_decimals: u8,
pub amount: U256,
pub slippage_bps: Option<u32>,
pub receiver: Option<Address>,
pub valid_for: Option<u32>,
pub valid_to: Option<u32>,
pub partially_fillable: Option<bool>,
pub partner_fee: Option<crate::app_data::types::PartnerFee>,
}
impl TradeParameters {
#[must_use]
pub const fn sell(
sell_token: Address,
sell_token_decimals: u8,
buy_token: Address,
buy_token_decimals: u8,
amount: U256,
) -> Self {
Self {
kind: OrderKind::Sell,
sell_token,
sell_token_decimals,
buy_token,
buy_token_decimals,
amount,
slippage_bps: None,
receiver: None,
valid_for: None,
valid_to: None,
partially_fillable: None,
partner_fee: None,
}
}
#[must_use]
pub const fn buy(
sell_token: Address,
sell_token_decimals: u8,
buy_token: Address,
buy_token_decimals: u8,
amount: U256,
) -> Self {
Self {
kind: OrderKind::Buy,
sell_token,
sell_token_decimals,
buy_token,
buy_token_decimals,
amount,
slippage_bps: None,
receiver: None,
valid_for: None,
valid_to: None,
partially_fillable: None,
partner_fee: None,
}
}
#[must_use]
pub const fn with_slippage_bps(mut self, bps: u32) -> Self {
self.slippage_bps = Some(bps);
self
}
#[must_use]
pub const fn with_receiver(mut self, receiver: Address) -> Self {
self.receiver = Some(receiver);
self
}
#[must_use]
pub const fn with_valid_for(mut self, secs: u32) -> Self {
self.valid_for = Some(secs);
self
}
#[must_use]
pub const fn with_valid_to(mut self, ts: u32) -> Self {
self.valid_to = Some(ts);
self
}
#[must_use]
pub const fn with_partially_fillable(mut self) -> Self {
self.partially_fillable = Some(true);
self
}
#[must_use]
pub const fn is_sell(&self) -> bool {
self.kind.is_sell()
}
#[must_use]
pub const fn is_buy(&self) -> bool {
self.kind.is_buy()
}
#[must_use]
pub const fn has_slippage_bps(&self) -> bool {
self.slippage_bps.is_some()
}
#[must_use]
pub const fn has_receiver(&self) -> bool {
self.receiver.is_some()
}
#[must_use]
pub const fn has_partner_fee(&self) -> bool {
self.partner_fee.is_some()
}
}
impl fmt::Display for TradeParameters {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} {:#x} \u{2192} {:#x} amt={}",
self.kind, self.sell_token, self.buy_token, self.amount
)
}
}
impl LimitTradeParameters {
#[must_use]
pub const fn sell(
sell_token: Address,
buy_token: Address,
sell_amount: U256,
buy_amount: U256,
) -> Self {
Self {
kind: crate::types::OrderKind::Sell,
sell_token,
buy_token,
sell_amount,
buy_amount,
receiver: None,
valid_for: None,
valid_to: None,
partially_fillable: false,
app_data: None,
partner_fee: None,
}
}
#[must_use]
pub const fn buy(
sell_token: Address,
buy_token: Address,
sell_amount: U256,
buy_amount: U256,
) -> Self {
Self {
kind: crate::types::OrderKind::Buy,
sell_token,
buy_token,
sell_amount,
buy_amount,
receiver: None,
valid_for: None,
valid_to: None,
partially_fillable: false,
app_data: None,
partner_fee: None,
}
}
#[must_use]
pub const fn with_receiver(mut self, receiver: Address) -> Self {
self.receiver = Some(receiver);
self
}
#[must_use]
pub const fn with_valid_for(mut self, secs: u32) -> Self {
self.valid_for = Some(secs);
self
}
#[must_use]
pub const fn with_valid_to(mut self, ts: u32) -> Self {
self.valid_to = Some(ts);
self
}
#[must_use]
pub const fn with_partially_fillable(mut self) -> Self {
self.partially_fillable = true;
self
}
#[must_use]
pub const fn is_sell(&self) -> bool {
self.kind.is_sell()
}
#[must_use]
pub const fn is_buy(&self) -> bool {
self.kind.is_buy()
}
#[must_use]
pub const fn has_receiver(&self) -> bool {
self.receiver.is_some()
}
#[must_use]
pub const fn has_valid_to(&self) -> bool {
self.valid_to.is_some()
}
#[must_use]
pub const fn has_valid_for(&self) -> bool {
self.valid_for.is_some()
}
#[must_use]
pub const fn has_app_data(&self) -> bool {
self.app_data.is_some()
}
#[must_use]
pub const fn has_partner_fee(&self) -> bool {
self.partner_fee.is_some()
}
}
impl fmt::Display for LimitTradeParameters {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"limit {} {:#x} sell={} buy\u{2265}{}",
self.kind, self.sell_token, self.sell_amount, self.buy_amount
)
}
}
impl QuoteAmountsAndCosts {
#[must_use]
pub const fn is_buy(&self) -> bool {
!self.is_sell
}
#[must_use]
pub const fn max_slippage_atoms(&self) -> U256 {
self.after_partner_fees.buy_amount.saturating_sub(self.after_slippage.buy_amount)
}
#[must_use]
pub const fn total_fees_atoms(&self) -> U256 {
self.network_fee
.amount_in_sell_currency
.saturating_add(self.partner_fee.amount)
.saturating_add(self.protocol_fee.amount)
}
#[must_use]
pub fn has_network_fee(&self) -> bool {
!self.network_fee.is_zero()
}
#[must_use]
pub fn has_partner_fee(&self) -> bool {
!self.partner_fee.is_zero()
}
#[must_use]
pub fn has_protocol_fee(&self) -> bool {
!self.protocol_fee.is_zero()
}
}
impl fmt::Display for QuoteAmountsAndCosts {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let dir = if self.is_sell { "sell" } else { "buy" };
write!(
f,
"{dir} gross={} after-slippage={} [{} / {} / {}]",
self.before_all_fees,
self.after_slippage,
self.network_fee,
self.partner_fee,
self.protocol_fee,
)
}
}
#[must_use]
pub fn map_quote_amounts_and_costs<F>(
costs: &QuoteAmountsAndCosts,
mut f: F,
) -> QuoteAmountsAndCosts
where
F: FnMut(U256) -> U256,
{
QuoteAmountsAndCosts {
is_sell: costs.is_sell,
before_all_fees: Amounts {
sell_amount: f(costs.before_all_fees.sell_amount),
buy_amount: f(costs.before_all_fees.buy_amount),
},
before_network_costs: Amounts {
sell_amount: f(costs.before_network_costs.sell_amount),
buy_amount: f(costs.before_network_costs.buy_amount),
},
after_network_costs: Amounts {
sell_amount: f(costs.after_network_costs.sell_amount),
buy_amount: f(costs.after_network_costs.buy_amount),
},
after_partner_fees: Amounts {
sell_amount: f(costs.after_partner_fees.sell_amount),
buy_amount: f(costs.after_partner_fees.buy_amount),
},
after_slippage: Amounts {
sell_amount: f(costs.after_slippage.sell_amount),
buy_amount: f(costs.after_slippage.buy_amount),
},
network_fee: NetworkFee {
amount_in_sell_currency: f(costs.network_fee.amount_in_sell_currency),
amount_in_buy_currency: f(costs.network_fee.amount_in_buy_currency),
},
partner_fee: PartnerFeeCost {
amount: f(costs.partner_fee.amount),
bps: costs.partner_fee.bps,
},
protocol_fee: ProtocolFeeCost {
amount: f(costs.protocol_fee.amount),
bps: costs.protocol_fee.bps,
},
}
}
#[derive(Debug, Clone)]
pub struct LimitTradeParameters {
pub kind: crate::types::OrderKind,
pub sell_token: Address,
pub buy_token: Address,
pub sell_amount: U256,
pub buy_amount: U256,
pub receiver: Option<Address>,
pub valid_for: Option<u32>,
pub valid_to: Option<u32>,
pub partially_fillable: bool,
pub app_data: Option<String>,
pub partner_fee: Option<crate::app_data::types::PartnerFee>,
}
#[derive(Debug, Clone)]
pub struct OrderPostingResult {
pub order_id: String,
pub signing_scheme: crate::types::SigningScheme,
pub signature: String,
pub order_to_sign: UnsignedOrder,
}
impl OrderPostingResult {
#[must_use]
pub fn new(
order_id: impl Into<String>,
signing_scheme: crate::types::SigningScheme,
signature: impl Into<String>,
order_to_sign: UnsignedOrder,
) -> Self {
Self {
order_id: order_id.into(),
signing_scheme,
signature: signature.into(),
order_to_sign,
}
}
#[must_use]
pub const fn is_eip712(&self) -> bool {
matches!(self.signing_scheme, crate::types::SigningScheme::Eip712)
}
#[must_use]
pub const fn is_eth_sign(&self) -> bool {
matches!(self.signing_scheme, crate::types::SigningScheme::EthSign)
}
#[must_use]
pub const fn is_eip1271(&self) -> bool {
matches!(self.signing_scheme, crate::types::SigningScheme::Eip1271)
}
#[must_use]
pub const fn is_presign(&self) -> bool {
matches!(self.signing_scheme, crate::types::SigningScheme::PreSign)
}
#[must_use]
pub fn order_id_ref(&self) -> &str {
&self.order_id
}
#[must_use]
pub fn signature_ref(&self) -> &str {
&self.signature
}
}
impl fmt::Display for OrderPostingResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "order({})", self.order_id)
}
}
impl fmt::Display for QuoteResults {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "quote slippage={}bps {}", self.suggested_slippage_bps, self.amounts_and_costs)
}
}
#[derive(Debug, Clone)]
pub struct QuoteResults {
pub order_to_sign: UnsignedOrder,
pub order_typed_data: OrderTypedData,
pub quote_response: OrderQuoteResponse,
pub amounts_and_costs: QuoteAmountsAndCosts,
pub suggested_slippage_bps: u32,
pub app_data_info: TradingAppDataInfo,
}
impl QuoteResults {
#[must_use]
pub const fn order_ref(&self) -> &UnsignedOrder {
&self.order_to_sign
}
#[must_use]
pub const fn quote_ref(&self) -> &OrderQuoteResponse {
&self.quote_response
}
}
#[derive(Debug, Clone)]
pub struct BuildAppDataParams {
pub app_code: String,
pub slippage_bps: u32,
pub order_class: crate::app_data::types::OrderClassKind,
pub partner_fee: Option<crate::app_data::types::PartnerFee>,
}
impl BuildAppDataParams {
#[must_use]
pub fn new(
app_code: impl Into<String>,
slippage_bps: u32,
order_class: crate::app_data::types::OrderClassKind,
) -> Self {
Self { app_code: app_code.into(), slippage_bps, order_class, partner_fee: None }
}
#[must_use]
pub fn with_partner_fee(mut self, fee: crate::app_data::types::PartnerFee) -> Self {
self.partner_fee = Some(fee);
self
}
#[must_use]
pub const fn has_partner_fee(&self) -> bool {
self.partner_fee.is_some()
}
}
impl fmt::Display for BuildAppDataParams {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"build-app-data({}, {}bps, {})",
self.app_code, self.slippage_bps, self.order_class
)
}
}
#[derive(Debug, Clone)]
pub struct SlippageToleranceRequest {
pub chain_id: u64,
pub sell_token: Address,
pub buy_token: Address,
pub sell_amount: Option<U256>,
pub buy_amount: Option<U256>,
}
impl SlippageToleranceRequest {
#[must_use]
pub const fn new(chain_id: u64, sell_token: Address, buy_token: Address) -> Self {
Self { chain_id, sell_token, buy_token, sell_amount: None, buy_amount: None }
}
#[must_use]
pub const fn with_sell_amount(mut self, amount: U256) -> Self {
self.sell_amount = Some(amount);
self
}
#[must_use]
pub const fn with_buy_amount(mut self, amount: U256) -> Self {
self.buy_amount = Some(amount);
self
}
}
impl fmt::Display for SlippageToleranceRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"slippage-req(chain={}, {:#x} -> {:#x})",
self.chain_id, self.sell_token, self.buy_token
)
}
}
#[derive(Debug, Clone)]
pub struct SlippageToleranceResponse {
pub slippage_bps: Option<u32>,
}
impl SlippageToleranceResponse {
#[must_use]
pub const fn new(slippage_bps: Option<u32>) -> Self {
Self { slippage_bps }
}
#[must_use]
pub const fn has_suggestion(&self) -> bool {
self.slippage_bps.is_some()
}
}
impl fmt::Display for SlippageToleranceResponse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.slippage_bps {
Some(bps) => write!(f, "slippage-resp({bps}bps)"),
None => f.write_str("slippage-resp(none)"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::B256;
use crate::{
app_data::types::{OrderClassKind, PartnerFee, PartnerFeeEntry},
types::{SigningScheme, TokenBalance},
};
#[test]
fn amounts_new_stores_fields() {
let a = Amounts::new(U256::from(100u32), U256::from(200u32));
assert_eq!(a.sell_amount, U256::from(100u32));
assert_eq!(a.buy_amount, U256::from(200u32));
}
#[test]
fn amounts_is_zero() {
assert!(Amounts::default().is_zero());
assert!(Amounts::new(U256::ZERO, U256::ZERO).is_zero());
assert!(!Amounts::new(U256::from(1u32), U256::ZERO).is_zero());
assert!(!Amounts::new(U256::ZERO, U256::from(1u32)).is_zero());
}
#[test]
fn amounts_total() {
let a = Amounts::new(U256::from(100u32), U256::from(90u32));
assert_eq!(a.total(), U256::from(190u32));
}
#[test]
fn amounts_total_saturates() {
let a = Amounts::new(U256::MAX, U256::from(1u32));
assert_eq!(a.total(), U256::MAX);
}
#[test]
fn amounts_display() {
let a = Amounts::new(U256::from(42u32), U256::from(7u32));
let s = format!("{a}");
assert!(s.contains("sell"));
assert!(s.contains("buy"));
assert!(s.contains("42"));
assert!(s.contains('7'));
}
#[test]
fn amounts_default() {
let a = Amounts::default();
assert_eq!(a.sell_amount, U256::ZERO);
assert_eq!(a.buy_amount, U256::ZERO);
}
#[test]
fn network_fee_new_stores_fields() {
let nf = NetworkFee::new(U256::from(10u32), U256::from(20u32));
assert_eq!(nf.amount_in_sell_currency, U256::from(10u32));
assert_eq!(nf.amount_in_buy_currency, U256::from(20u32));
}
#[test]
fn network_fee_is_zero() {
assert!(NetworkFee::default().is_zero());
assert!(!NetworkFee::new(U256::from(1u32), U256::ZERO).is_zero());
assert!(!NetworkFee::new(U256::ZERO, U256::from(1u32)).is_zero());
}
#[test]
fn network_fee_total_atoms() {
let nf = NetworkFee::new(U256::from(5u32), U256::from(3u32));
assert_eq!(nf.total_atoms(), U256::from(8u32));
}
#[test]
fn network_fee_total_atoms_saturates() {
let nf = NetworkFee::new(U256::MAX, U256::from(1u32));
assert_eq!(nf.total_atoms(), U256::MAX);
}
#[test]
fn network_fee_display() {
let nf = NetworkFee::new(U256::from(10u32), U256::from(20u32));
let s = format!("{nf}");
assert!(s.contains("network-fee"));
assert!(s.contains("10"));
assert!(s.contains("20"));
}
#[test]
fn network_fee_default() {
let nf = NetworkFee::default();
assert_eq!(nf.amount_in_sell_currency, U256::ZERO);
assert_eq!(nf.amount_in_buy_currency, U256::ZERO);
}
#[test]
fn partner_fee_cost_new() {
let pf = PartnerFeeCost::new(U256::from(500u32), 50);
assert_eq!(pf.amount, U256::from(500u32));
assert_eq!(pf.bps, 50);
}
#[test]
fn partner_fee_cost_is_zero() {
assert!(PartnerFeeCost::default().is_zero());
assert!(!PartnerFeeCost::new(U256::from(1u32), 0).is_zero());
assert!(!PartnerFeeCost::new(U256::ZERO, 1).is_zero());
}
#[test]
fn partner_fee_cost_has_bps() {
assert!(!PartnerFeeCost::default().has_bps());
assert!(PartnerFeeCost::new(U256::ZERO, 10).has_bps());
}
#[test]
fn partner_fee_cost_display() {
let pf = PartnerFeeCost::new(U256::from(99u32), 25);
let s = format!("{pf}");
assert!(s.contains("partner-fee"));
assert!(s.contains("25bps"));
}
#[test]
fn partner_fee_cost_default() {
let pf = PartnerFeeCost::default();
assert_eq!(pf.amount, U256::ZERO);
assert_eq!(pf.bps, 0);
}
#[test]
fn protocol_fee_cost_new() {
let pf = ProtocolFeeCost::new(U256::from(300u32), 15);
assert_eq!(pf.amount, U256::from(300u32));
assert_eq!(pf.bps, 15);
}
#[test]
fn protocol_fee_cost_is_zero() {
assert!(ProtocolFeeCost::default().is_zero());
assert!(!ProtocolFeeCost::new(U256::from(1u32), 0).is_zero());
assert!(!ProtocolFeeCost::new(U256::ZERO, 1).is_zero());
}
#[test]
fn protocol_fee_cost_has_bps() {
assert!(!ProtocolFeeCost::default().has_bps());
assert!(ProtocolFeeCost::new(U256::ZERO, 5).has_bps());
}
#[test]
fn protocol_fee_cost_display() {
let pf = ProtocolFeeCost::new(U256::from(77u32), 10);
let s = format!("{pf}");
assert!(s.contains("protocol-fee"));
assert!(s.contains("10bps"));
}
#[test]
fn protocol_fee_cost_default() {
let pf = ProtocolFeeCost::default();
assert_eq!(pf.amount, U256::ZERO);
assert_eq!(pf.bps, 0);
}
#[test]
fn trading_app_data_info_new() {
let info = TradingAppDataInfo::new("{\"v\":1}", "0xabc");
assert_eq!(info.full_app_data, "{\"v\":1}");
assert_eq!(info.app_data_keccak256, "0xabc");
}
#[test]
fn trading_app_data_info_has_full_app_data() {
let with = TradingAppDataInfo::new("{}", "0x1");
assert!(with.has_full_app_data());
let without = TradingAppDataInfo::new("", "0x1");
assert!(!without.has_full_app_data());
}
#[test]
fn trading_app_data_info_refs() {
let info = TradingAppDataInfo::new("doc", "0xhash");
assert_eq!(info.full_app_data_ref(), "doc");
assert_eq!(info.keccak256_ref(), "0xhash");
}
#[test]
fn trading_app_data_info_display() {
let info = TradingAppDataInfo::new("{}", "0xdeadbeef");
let s = format!("{info}");
assert!(s.contains("app-data"));
assert!(s.contains("0xdeadbeef"));
}
#[test]
fn trading_tx_params_new() {
let data = vec![0xAA, 0xBB];
let to = Address::ZERO;
let tx = TradingTransactionParams::new(data.clone(), to, 21_000, U256::from(1u32));
assert_eq!(tx.data, data);
assert_eq!(tx.to, to);
assert_eq!(tx.gas_limit, 21_000);
assert_eq!(tx.value, U256::from(1u32));
}
#[test]
fn trading_tx_params_builders() {
let tx = TradingTransactionParams::new(vec![], Address::ZERO, 0, U256::ZERO)
.with_data(vec![1, 2, 3])
.with_to(Address::with_last_byte(0x01))
.with_gas_limit(50_000)
.with_value(U256::from(999u32));
assert_eq!(tx.data, vec![1, 2, 3]);
assert_eq!(tx.to, Address::with_last_byte(0x01));
assert_eq!(tx.gas_limit, 50_000);
assert_eq!(tx.value, U256::from(999u32));
}
#[test]
fn trading_tx_params_data_len() {
let tx = TradingTransactionParams::new(vec![0; 64], Address::ZERO, 0, U256::ZERO);
assert_eq!(tx.data_len(), 64);
}
#[test]
fn trading_tx_params_has_value() {
let no_val = TradingTransactionParams::new(vec![], Address::ZERO, 0, U256::ZERO);
assert!(!no_val.has_value());
let with_val = no_val.with_value(U256::from(1u32));
assert!(with_val.has_value());
}
#[test]
fn trading_tx_params_display() {
let tx = TradingTransactionParams::new(vec![], Address::ZERO, 21_000, U256::ZERO);
let s = format!("{tx}");
assert!(s.contains("tx"));
assert!(s.contains("21000"));
}
#[test]
fn post_trade_default() {
let p = PostTradeAdditionalParams::default();
assert!(!p.has_signing_scheme());
assert!(!p.has_network_costs());
assert!(!p.should_apply_costs());
}
#[test]
fn post_trade_with_signing_scheme() {
let p = PostTradeAdditionalParams::default().with_signing_scheme(SigningScheme::PreSign);
assert!(p.has_signing_scheme());
assert!(matches!(p.signing_scheme, Some(SigningScheme::PreSign)));
}
#[test]
fn post_trade_with_network_costs_amount() {
let p = PostTradeAdditionalParams::default().with_network_costs_amount("12345");
assert!(p.has_network_costs());
assert_eq!(p.network_costs_amount.as_deref(), Some("12345"));
}
#[test]
fn post_trade_with_apply_costs() {
let p = PostTradeAdditionalParams::default().with_apply_costs_slippage_and_fees(true);
assert!(p.should_apply_costs());
let p2 = PostTradeAdditionalParams::default().with_apply_costs_slippage_and_fees(false);
assert!(!p2.should_apply_costs());
}
#[test]
fn post_trade_display() {
let p = PostTradeAdditionalParams::default();
assert_eq!(format!("{p}"), "post-trade-params");
}
#[test]
fn swap_settings_default() {
let s = SwapAdvancedSettings::default();
assert!(!s.has_app_data());
assert!(!s.has_slippage_bps());
assert!(!s.has_partner_fee());
}
#[test]
fn swap_settings_with_app_data() {
let s = SwapAdvancedSettings::default().with_app_data(serde_json::json!({"k": "v"}));
assert!(s.has_app_data());
}
#[test]
fn swap_settings_with_slippage_bps() {
let s = SwapAdvancedSettings::default().with_slippage_bps(100);
assert!(s.has_slippage_bps());
assert_eq!(s.slippage_bps, Some(100));
}
#[test]
fn swap_settings_with_partner_fee() {
let fee = PartnerFee::single(PartnerFeeEntry::volume(50, "0xRecipient"));
let s = SwapAdvancedSettings::default().with_partner_fee(fee);
assert!(s.has_partner_fee());
}
#[test]
fn swap_settings_display() {
let s = SwapAdvancedSettings::default();
assert_eq!(format!("{s}"), "swap-settings");
}
#[test]
fn limit_settings_default() {
let s = LimitOrderAdvancedSettings::default();
assert!(!s.has_receiver());
assert!(!s.has_valid_to());
assert!(!s.has_partner_fee());
assert!(!s.has_partially_fillable());
assert!(!s.has_app_data());
}
#[test]
fn limit_settings_with_receiver() {
let addr = Address::with_last_byte(0x42);
let s = LimitOrderAdvancedSettings::default().with_receiver(addr);
assert!(s.has_receiver());
assert_eq!(s.receiver, Some(addr));
}
#[test]
fn limit_settings_with_valid_to() {
let s = LimitOrderAdvancedSettings::default().with_valid_to(1_700_000_000);
assert!(s.has_valid_to());
assert_eq!(s.valid_to, Some(1_700_000_000));
}
#[test]
fn limit_settings_with_partner_fee() {
let fee = PartnerFee::single(PartnerFeeEntry::volume(25, "0xAddr"));
let s = LimitOrderAdvancedSettings::default().with_partner_fee(fee);
assert!(s.has_partner_fee());
}
#[test]
fn limit_settings_with_partially_fillable() {
let s = LimitOrderAdvancedSettings::default().with_partially_fillable(true);
assert!(s.has_partially_fillable());
assert_eq!(s.partially_fillable, Some(true));
}
#[test]
fn limit_settings_with_app_data() {
let s = LimitOrderAdvancedSettings::default().with_app_data("0xabc123");
assert!(s.has_app_data());
assert_eq!(s.app_data.as_deref(), Some("0xabc123"));
}
#[test]
fn limit_settings_display() {
let s = LimitOrderAdvancedSettings::default();
assert_eq!(format!("{s}"), "limit-settings");
}
#[test]
fn apply_settings_none_returns_unchanged() {
let params = LimitTradeParameters::sell(
Address::ZERO,
Address::ZERO,
U256::from(1000u32),
U256::from(900u32),
);
let result = apply_settings_to_limit_trade_parameters(params, None);
assert_eq!(result.sell_amount, U256::from(1000u32));
assert!(!result.partially_fillable);
}
#[test]
fn apply_settings_overrides_fields() {
let params = LimitTradeParameters::sell(
Address::ZERO,
Address::ZERO,
U256::from(1000u32),
U256::from(900u32),
);
let settings = LimitOrderAdvancedSettings::default()
.with_receiver(Address::with_last_byte(0x01))
.with_valid_to(9999)
.with_partially_fillable(true)
.with_app_data("0xbeef");
let result = apply_settings_to_limit_trade_parameters(params, Some(&settings));
assert_eq!(result.receiver, Some(Address::with_last_byte(0x01)));
assert_eq!(result.valid_to, Some(9999));
assert!(result.partially_fillable);
assert_eq!(result.app_data.as_deref(), Some("0xbeef"));
}
#[test]
fn limit_from_quote_new() {
let p = LimitTradeParametersFromQuote::new(
Address::ZERO,
Address::with_last_byte(1),
U256::from(100u32),
U256::from(90u32),
);
assert_eq!(p.sell_token, Address::ZERO);
assert_eq!(p.buy_token, Address::with_last_byte(1));
assert_eq!(p.sell_amount, U256::from(100u32));
assert_eq!(p.buy_amount, U256::from(90u32));
assert!(!p.has_quote_id());
}
#[test]
fn limit_from_quote_with_quote_id() {
let p = LimitTradeParametersFromQuote::new(
Address::ZERO,
Address::ZERO,
U256::from(1u32),
U256::from(1u32),
)
.with_quote_id(42);
assert!(p.has_quote_id());
assert_eq!(p.quote_id, Some(42));
}
#[test]
fn limit_from_quote_display() {
let p = LimitTradeParametersFromQuote::new(
Address::ZERO,
Address::ZERO,
U256::from(100u32),
U256::from(90u32),
);
let s = format!("{p}");
assert!(s.contains("limit-from-quote"));
}
#[test]
fn trade_params_sell() {
let p = TradeParameters::sell(
Address::ZERO,
18,
Address::with_last_byte(1),
6,
U256::from(1000u32),
);
assert!(p.is_sell());
assert!(!p.is_buy());
assert_eq!(p.sell_token_decimals, 18);
assert_eq!(p.buy_token_decimals, 6);
assert_eq!(p.amount, U256::from(1000u32));
assert!(!p.has_slippage_bps());
assert!(!p.has_receiver());
assert!(!p.has_partner_fee());
}
#[test]
fn trade_params_buy() {
let p = TradeParameters::buy(
Address::ZERO,
18,
Address::with_last_byte(1),
6,
U256::from(500u32),
);
assert!(p.is_buy());
assert!(!p.is_sell());
}
#[test]
fn trade_params_builders() {
let recv = Address::with_last_byte(0x99);
let p = TradeParameters::sell(Address::ZERO, 18, Address::ZERO, 18, U256::from(1u32))
.with_slippage_bps(50)
.with_receiver(recv)
.with_valid_for(600)
.with_valid_to(1_700_000_000)
.with_partially_fillable();
assert!(p.has_slippage_bps());
assert_eq!(p.slippage_bps, Some(50));
assert!(p.has_receiver());
assert_eq!(p.receiver, Some(recv));
assert_eq!(p.valid_for, Some(600));
assert_eq!(p.valid_to, Some(1_700_000_000));
assert_eq!(p.partially_fillable, Some(true));
}
#[test]
fn trade_params_display() {
let p = TradeParameters::sell(Address::ZERO, 18, Address::ZERO, 18, U256::from(1u32));
let s = format!("{p}");
assert!(s.contains("sell"));
}
#[test]
fn limit_trade_params_sell_and_buy() {
let sell = LimitTradeParameters::sell(
Address::ZERO,
Address::with_last_byte(1),
U256::from(100u32),
U256::from(90u32),
);
assert!(sell.is_sell());
assert!(!sell.is_buy());
assert!(!sell.partially_fillable);
let buy = LimitTradeParameters::buy(
Address::ZERO,
Address::with_last_byte(1),
U256::from(100u32),
U256::from(90u32),
);
assert!(buy.is_buy());
assert!(!buy.is_sell());
}
#[test]
fn limit_trade_params_builders() {
let recv = Address::with_last_byte(0x42);
let p = LimitTradeParameters::sell(
Address::ZERO,
Address::ZERO,
U256::from(1u32),
U256::from(1u32),
)
.with_receiver(recv)
.with_valid_for(300)
.with_valid_to(9999)
.with_partially_fillable();
assert!(p.has_receiver());
assert_eq!(p.receiver, Some(recv));
assert!(p.has_valid_for());
assert_eq!(p.valid_for, Some(300));
assert!(p.has_valid_to());
assert_eq!(p.valid_to, Some(9999));
assert!(p.partially_fillable);
}
#[test]
fn limit_trade_params_has_app_data_and_partner_fee() {
let p = LimitTradeParameters::sell(
Address::ZERO,
Address::ZERO,
U256::from(1u32),
U256::from(1u32),
);
assert!(!p.has_app_data());
assert!(!p.has_partner_fee());
}
#[test]
fn limit_trade_params_display() {
let p = LimitTradeParameters::sell(
Address::ZERO,
Address::ZERO,
U256::from(100u32),
U256::from(90u32),
);
let s = format!("{p}");
assert!(s.contains("limit"));
assert!(s.contains("sell"));
}
fn sample_quote_costs() -> QuoteAmountsAndCosts {
QuoteAmountsAndCosts {
is_sell: true,
before_all_fees: Amounts::new(U256::from(200u32), U256::from(110u32)),
before_network_costs: Amounts::new(U256::from(200u32), U256::from(100u32)),
after_network_costs: Amounts::new(U256::from(190u32), U256::from(100u32)),
after_partner_fees: Amounts::new(U256::from(190u32), U256::from(95u32)),
after_slippage: Amounts::new(U256::from(190u32), U256::from(90u32)),
network_fee: NetworkFee::new(U256::from(10u32), U256::ZERO),
partner_fee: PartnerFeeCost::new(U256::from(5u32), 50),
protocol_fee: ProtocolFeeCost::new(U256::from(3u32), 30),
}
}
#[test]
fn quote_costs_is_buy() {
let sell = sample_quote_costs();
assert!(!sell.is_buy());
let mut buy = sample_quote_costs();
buy.is_sell = false;
assert!(buy.is_buy());
}
#[test]
fn quote_costs_max_slippage_atoms() {
let q = sample_quote_costs();
assert_eq!(q.max_slippage_atoms(), U256::from(5u32));
}
#[test]
fn quote_costs_total_fees_atoms() {
let q = sample_quote_costs();
assert_eq!(q.total_fees_atoms(), U256::from(18u32));
}
#[test]
fn quote_costs_has_fees() {
let q = sample_quote_costs();
assert!(q.has_network_fee());
assert!(q.has_partner_fee());
assert!(q.has_protocol_fee());
let zero_q = QuoteAmountsAndCosts {
is_sell: true,
before_all_fees: Amounts::default(),
before_network_costs: Amounts::default(),
after_network_costs: Amounts::default(),
after_partner_fees: Amounts::default(),
after_slippage: Amounts::default(),
network_fee: NetworkFee::default(),
partner_fee: PartnerFeeCost::default(),
protocol_fee: ProtocolFeeCost::default(),
};
assert!(!zero_q.has_network_fee());
assert!(!zero_q.has_partner_fee());
assert!(!zero_q.has_protocol_fee());
}
#[test]
fn quote_costs_display() {
let q = sample_quote_costs();
let s = format!("{q}");
assert!(s.contains("sell"));
assert!(s.contains("network-fee"));
assert!(s.contains("partner-fee"));
assert!(s.contains("protocol-fee"));
}
#[test]
fn map_doubles_amounts_preserves_bps() {
let q = sample_quote_costs();
let doubled = map_quote_amounts_and_costs(&q, |a| a * U256::from(2u32));
assert_eq!(doubled.before_all_fees.sell_amount, U256::from(400u32));
assert_eq!(doubled.network_fee.amount_in_sell_currency, U256::from(20u32));
assert_eq!(doubled.partner_fee.amount, U256::from(10u32));
assert_eq!(doubled.protocol_fee.amount, U256::from(6u32));
assert_eq!(doubled.partner_fee.bps, 50);
assert_eq!(doubled.protocol_fee.bps, 30);
assert!(doubled.is_sell);
}
fn sample_unsigned_order() -> UnsignedOrder {
UnsignedOrder {
sell_token: Address::ZERO,
buy_token: Address::ZERO,
receiver: Address::ZERO,
sell_amount: U256::from(100u32),
buy_amount: U256::from(90u32),
valid_to: 0,
app_data: B256::ZERO,
fee_amount: U256::ZERO,
kind: OrderKind::Sell,
partially_fillable: false,
sell_token_balance: TokenBalance::Erc20,
buy_token_balance: TokenBalance::Erc20,
}
}
#[test]
fn order_posting_result_new() {
let r = OrderPostingResult::new(
"uid123",
SigningScheme::Eip712,
"0xsig",
sample_unsigned_order(),
);
assert_eq!(r.order_id_ref(), "uid123");
assert_eq!(r.signature_ref(), "0xsig");
}
#[test]
fn order_posting_result_signing_scheme_predicates() {
let eip712 =
OrderPostingResult::new("a", SigningScheme::Eip712, "", sample_unsigned_order());
assert!(eip712.is_eip712());
assert!(!eip712.is_eth_sign());
assert!(!eip712.is_eip1271());
assert!(!eip712.is_presign());
let eth_sign =
OrderPostingResult::new("b", SigningScheme::EthSign, "", sample_unsigned_order());
assert!(eth_sign.is_eth_sign());
let eip1271 =
OrderPostingResult::new("c", SigningScheme::Eip1271, "", sample_unsigned_order());
assert!(eip1271.is_eip1271());
let presign =
OrderPostingResult::new("d", SigningScheme::PreSign, "", sample_unsigned_order());
assert!(presign.is_presign());
}
#[test]
fn order_posting_result_display() {
let r =
OrderPostingResult::new("uid-xyz", SigningScheme::Eip712, "", sample_unsigned_order());
let s = format!("{r}");
assert!(s.contains("order"));
assert!(s.contains("uid-xyz"));
}
#[test]
fn build_app_data_params_new() {
let p = BuildAppDataParams::new("CoW Swap", 50, OrderClassKind::Market);
assert_eq!(p.app_code, "CoW Swap");
assert_eq!(p.slippage_bps, 50);
assert!(!p.has_partner_fee());
}
#[test]
fn build_app_data_params_with_partner_fee() {
let fee = PartnerFee::single(PartnerFeeEntry::volume(50, "0xRecip"));
let p = BuildAppDataParams::new("App", 25, OrderClassKind::Limit).with_partner_fee(fee);
assert!(p.has_partner_fee());
}
#[test]
fn build_app_data_params_display() {
let p = BuildAppDataParams::new("MyApp", 100, OrderClassKind::Market);
let s = format!("{p}");
assert!(s.contains("build-app-data"));
assert!(s.contains("MyApp"));
assert!(s.contains("100bps"));
}
#[test]
fn slippage_request_new() {
let r = SlippageToleranceRequest::new(1, Address::ZERO, Address::with_last_byte(1));
assert_eq!(r.chain_id, 1);
assert_eq!(r.sell_token, Address::ZERO);
assert_eq!(r.buy_token, Address::with_last_byte(1));
assert!(r.sell_amount.is_none());
assert!(r.buy_amount.is_none());
}
#[test]
fn slippage_request_with_amounts() {
let r = SlippageToleranceRequest::new(1, Address::ZERO, Address::ZERO)
.with_sell_amount(U256::from(100u32))
.with_buy_amount(U256::from(90u32));
assert_eq!(r.sell_amount, Some(U256::from(100u32)));
assert_eq!(r.buy_amount, Some(U256::from(90u32)));
}
#[test]
fn slippage_request_display() {
let r = SlippageToleranceRequest::new(1, Address::ZERO, Address::ZERO);
let s = format!("{r}");
assert!(s.contains("slippage-req"));
assert!(s.contains("chain=1"));
}
#[test]
fn slippage_response_new() {
let with = SlippageToleranceResponse::new(Some(50));
assert!(with.has_suggestion());
assert_eq!(with.slippage_bps, Some(50));
let without = SlippageToleranceResponse::new(None);
assert!(!without.has_suggestion());
}
#[test]
fn slippage_response_display() {
let with = SlippageToleranceResponse::new(Some(100));
assert_eq!(format!("{with}"), "slippage-resp(100bps)");
let without = SlippageToleranceResponse::new(None);
assert_eq!(format!("{without}"), "slippage-resp(none)");
}
}