use {
crate::{
app_data::AppDataHash,
error::{Error, Result},
order::{BuyTokenDestination, OrderData, OrderKind, SellTokenSource},
},
alloy_primitives::{Address, U256, address},
};
pub const ETH_FLOW_PRODUCTION: Address = address!("bA3cB449bD2B4ADddBc894D8697F5170800EAdeC");
pub const ETH_FLOW_STAGING: Address = address!("04501b9b1D52e67f6862d157E00D13419D2D6E95");
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct EthFlowOrder {
pub buy_token: Address,
pub receiver: Address,
pub sell_amount: U256,
pub buy_amount: U256,
pub app_data: AppDataHash,
pub fee_amount: U256,
pub valid_to: u32,
pub partially_fillable: bool,
pub quote_id: i64,
}
impl EthFlowOrder {
pub fn to_order_data(&self, wrapped_native_token: Address) -> Result<OrderData> {
if self.receiver == Address::ZERO {
return Err(Error::OrderCreationInvalid {
field: "receiver",
reason: "EthFlow orders require a non-zero receiver; the EthFlow contract \
is the EIP-1271 order owner, so address(0) would route proceeds to it",
});
}
Ok(OrderData {
sell_token: wrapped_native_token,
buy_token: self.buy_token,
receiver: Some(self.receiver),
sell_amount: self.sell_amount,
buy_amount: self.buy_amount,
valid_to: u32::MAX,
app_data: self.app_data,
fee_amount: self.fee_amount,
kind: OrderKind::Sell,
partially_fillable: self.partially_fillable,
sell_token_balance: SellTokenSource::Erc20,
buy_token_balance: BuyTokenDestination::Erc20,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
const WETH_MAINNET: Address = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
const SAMPLE_RECEIVER: Address = address!("70997970C51812dc3A010C7d01b50e0d17dc79C8");
#[test]
fn deployment_addresses_match_canonical_hex_literals() {
assert_eq!(
ETH_FLOW_PRODUCTION,
address!("bA3cB449bD2B4ADddBc894D8697F5170800EAdeC")
);
assert_eq!(
ETH_FLOW_STAGING,
address!("04501b9b1D52e67f6862d157E00D13419D2D6E95")
);
assert_ne!(ETH_FLOW_PRODUCTION, ETH_FLOW_STAGING);
}
#[test]
fn to_order_data_projects_canonical_sell_order() {
let eth_flow = EthFlowOrder {
buy_token: address!("6B175474E89094C44Da98b954EedeAC495271d0F"), receiver: SAMPLE_RECEIVER,
sell_amount: U256::from(1_000_000_000_000_000_000_u128), buy_amount: U256::from(3_500_000_000_000_000_000_000_u128), app_data: AppDataHash([0xab; 32]),
fee_amount: U256::from(1_500_000_000_000_000_u128), valid_to: 1_700_000_000,
partially_fillable: false,
quote_id: 42,
};
let order = eth_flow.to_order_data(WETH_MAINNET).unwrap();
assert_eq!(order.sell_token, WETH_MAINNET);
assert_eq!(order.buy_token, eth_flow.buy_token);
assert_eq!(order.receiver, Some(SAMPLE_RECEIVER));
assert_eq!(order.sell_amount, eth_flow.sell_amount);
assert_eq!(order.buy_amount, eth_flow.buy_amount);
assert_eq!(order.fee_amount, eth_flow.fee_amount);
assert_eq!(order.app_data, eth_flow.app_data);
assert_eq!(order.valid_to, u32::MAX);
assert_eq!(order.kind, OrderKind::Sell);
assert!(!order.partially_fillable);
assert_eq!(order.sell_token_balance, SellTokenSource::Erc20);
assert_eq!(order.buy_token_balance, BuyTokenDestination::Erc20);
}
#[test]
fn to_order_data_rejects_zero_receiver() {
let eth_flow = EthFlowOrder {
buy_token: address!("6B175474E89094C44Da98b954EedeAC495271d0F"),
receiver: Address::ZERO,
sell_amount: U256::from(1_u8),
buy_amount: U256::from(1_u8),
app_data: AppDataHash::default(),
fee_amount: U256::ZERO,
valid_to: 0,
partially_fillable: true,
quote_id: 0,
};
let err = eth_flow.to_order_data(WETH_MAINNET).unwrap_err();
match err {
crate::Error::OrderCreationInvalid { field, .. } => assert_eq!(field, "receiver"),
other => panic!("expected OrderCreationInvalid, got {other:?}"),
}
}
#[test]
fn to_order_data_hash_binds_concrete_receiver() {
let eth_flow = EthFlowOrder {
buy_token: address!("6B175474E89094C44Da98b954EedeAC495271d0F"),
receiver: SAMPLE_RECEIVER,
sell_amount: U256::from(1_u8),
buy_amount: U256::from(1_u8),
app_data: AppDataHash::default(),
fee_amount: U256::ZERO,
valid_to: 0,
partially_fillable: false,
quote_id: 0,
};
let order = eth_flow.to_order_data(WETH_MAINNET).unwrap();
assert_eq!(order.receiver, Some(SAMPLE_RECEIVER));
let mut owner_fallback = order;
owner_fallback.receiver = None;
assert_ne!(order.hash_struct(), owner_fallback.hash_struct());
}
}