use std::fmt;
use alloy_primitives::{Address, B256, U256};
use crate::{OrderKind, TokenBalance};
#[derive(Debug, Clone)]
pub struct UnsignedOrder {
pub sell_token: Address,
pub buy_token: Address,
pub receiver: Address,
pub sell_amount: U256,
pub buy_amount: U256,
pub valid_to: u32,
pub app_data: B256,
pub fee_amount: U256,
pub kind: OrderKind,
pub partially_fillable: bool,
pub sell_token_balance: TokenBalance,
pub buy_token_balance: TokenBalance,
}
impl UnsignedOrder {
#[must_use]
pub const fn sell(
sell_token: Address,
buy_token: Address,
sell_amount: U256,
buy_amount: U256,
) -> Self {
Self {
sell_token,
buy_token,
receiver: Address::ZERO,
sell_amount,
buy_amount,
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,
}
}
#[must_use]
pub const fn buy(
sell_token: Address,
buy_token: Address,
sell_amount: U256,
buy_amount: U256,
) -> Self {
Self {
sell_token,
buy_token,
receiver: Address::ZERO,
sell_amount,
buy_amount,
valid_to: 0,
app_data: B256::ZERO,
fee_amount: U256::ZERO,
kind: OrderKind::Buy,
partially_fillable: false,
sell_token_balance: TokenBalance::Erc20,
buy_token_balance: TokenBalance::Erc20,
}
}
#[must_use]
pub const fn with_receiver(mut self, receiver: Address) -> Self {
self.receiver = receiver;
self
}
#[must_use]
pub const fn with_valid_to(mut self, valid_to: u32) -> Self {
self.valid_to = valid_to;
self
}
#[must_use]
pub const fn with_app_data(mut self, app_data: B256) -> Self {
self.app_data = app_data;
self
}
#[must_use]
pub const fn with_fee_amount(mut self, fee_amount: U256) -> Self {
self.fee_amount = fee_amount;
self
}
#[must_use]
pub const fn with_partially_fillable(mut self) -> Self {
self.partially_fillable = true;
self
}
#[must_use]
pub const fn with_sell_token_balance(mut self, balance: TokenBalance) -> Self {
self.sell_token_balance = balance;
self
}
#[must_use]
pub const fn with_buy_token_balance(mut self, balance: TokenBalance) -> Self {
self.buy_token_balance = balance;
self
}
#[must_use]
pub const fn is_expired(&self, timestamp: u64) -> bool {
timestamp > self.valid_to as u64
}
#[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 fn has_custom_receiver(&self) -> bool {
!self.receiver.is_zero()
}
#[must_use]
pub fn has_app_data(&self) -> bool {
!self.app_data.is_zero()
}
#[must_use]
pub fn has_fee(&self) -> bool {
!self.fee_amount.is_zero()
}
#[must_use]
pub const fn is_partially_fillable(&self) -> bool {
self.partially_fillable
}
#[must_use]
pub const fn total_amount(&self) -> U256 {
self.sell_amount.saturating_add(self.buy_amount)
}
}
impl fmt::Display for UnsignedOrder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {:#x} → {:#x}", self.kind, self.sell_token, self.buy_token)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn base() -> UnsignedOrder {
UnsignedOrder::sell(Address::ZERO, Address::ZERO, U256::from(1_000u64), U256::from(500u64))
}
#[test]
fn with_partially_fillable_sets_flag() {
let order = base().with_partially_fillable();
assert!(order.is_partially_fillable());
}
#[test]
fn with_sell_token_balance_overrides_default() {
let order = base().with_sell_token_balance(TokenBalance::External);
assert_eq!(order.sell_token_balance, TokenBalance::External);
}
#[test]
fn with_buy_token_balance_overrides_default() {
let order = base().with_buy_token_balance(TokenBalance::Internal);
assert_eq!(order.buy_token_balance, TokenBalance::Internal);
}
#[test]
fn total_amount_saturates_on_overflow() {
let order = UnsignedOrder::sell(Address::ZERO, Address::ZERO, U256::MAX, U256::from(1u64));
assert_eq!(order.total_amount(), U256::MAX);
}
#[test]
fn total_amount_sums_sell_and_buy() {
let order =
UnsignedOrder::sell(Address::ZERO, Address::ZERO, U256::from(7u64), U256::from(11u64));
assert_eq!(order.total_amount(), U256::from(18u64));
}
#[test]
fn display_renders_kind_and_token_addresses() {
let order = base();
let rendered = format!("{order}");
assert!(rendered.contains("sell"));
assert!(rendered.contains("0x0000000000000000000000000000000000000000"));
}
}