use std::fmt;
use alloy_primitives::{Address, U256};
use serde::{Deserialize, Serialize};
pub const ZERO_ADDRESS: Address = Address::ZERO;
pub const ZERO_HASH: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
pub const ZERO: U256 = U256::ZERO;
pub const ONE: U256 = U256::from_limbs([1, 0, 0, 0]);
pub const MAX_UINT32: U256 = U256::from_limbs([u32::MAX as u64, 0, 0, 0]);
pub const MAX_UINT256: U256 = U256::MAX;
pub const HUNDRED_THOUSANDS: u64 = 100_000;
pub const ONE_HUNDRED_BPS: u64 = 10_000;
pub const LIMIT_CONCURRENT_REQUESTS: u32 = 5;
pub const ATTESTATION_PREFIX_CONST: &str = "0x0a773570";
pub const ATTESTION_VERSION_BYTE: &str = "0x00";
pub const ATTESTATOR_ADDRESS: Address = Address::new([
0x00, 0x73, 0xdd, 0x10, 0x0b, 0x51, 0xc5, 0x55, 0xe4, 0x1b, 0x2a, 0x45, 0x2e, 0x59, 0x33, 0xef,
0x76, 0xf4, 0x27, 0x90,
]);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OrderKind {
Sell,
Buy,
}
impl OrderKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Sell => "sell",
Self::Buy => "buy",
}
}
#[must_use]
pub const fn is_sell(self) -> bool {
matches!(self, Self::Sell)
}
#[must_use]
pub const fn is_buy(self) -> bool {
matches!(self, Self::Buy)
}
}
impl fmt::Display for OrderKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum TokenBalance {
#[default]
Erc20,
External,
Internal,
}
impl TokenBalance {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Erc20 => "erc20",
Self::External => "external",
Self::Internal => "internal",
}
}
#[must_use]
pub fn eip712_hash(self) -> alloy_primitives::B256 {
alloy_primitives::keccak256(self.as_str().as_bytes())
}
#[must_use]
pub const fn is_erc20(self) -> bool {
matches!(self, Self::Erc20)
}
#[must_use]
pub const fn is_external(self) -> bool {
matches!(self, Self::External)
}
#[must_use]
pub const fn is_internal(self) -> bool {
matches!(self, Self::Internal)
}
}
impl fmt::Display for TokenBalance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SigningScheme {
Eip712,
EthSign,
Eip1271,
PreSign,
}
impl SigningScheme {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Eip712 => "eip712",
Self::EthSign => "ethsign",
Self::Eip1271 => "eip1271",
Self::PreSign => "presign",
}
}
#[must_use]
pub const fn is_eip712(self) -> bool {
matches!(self, Self::Eip712)
}
#[must_use]
pub const fn is_eth_sign(self) -> bool {
matches!(self, Self::EthSign)
}
#[must_use]
pub const fn is_eip1271(self) -> bool {
matches!(self, Self::Eip1271)
}
#[must_use]
pub const fn is_presign(self) -> bool {
matches!(self, Self::PreSign)
}
}
impl fmt::Display for SigningScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum EcdsaSigningScheme {
#[default]
Eip712,
EthSign,
}
impl EcdsaSigningScheme {
#[must_use]
pub const fn into_signing_scheme(self) -> SigningScheme {
match self {
Self::Eip712 => SigningScheme::Eip712,
Self::EthSign => SigningScheme::EthSign,
}
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Eip712 => "eip712",
Self::EthSign => "ethsign",
}
}
#[must_use]
pub const fn is_eip712(self) -> bool {
matches!(self, Self::Eip712)
}
#[must_use]
pub const fn is_eth_sign(self) -> bool {
matches!(self, Self::EthSign)
}
}
impl fmt::Display for EcdsaSigningScheme {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl From<EcdsaSigningScheme> for SigningScheme {
fn from(s: EcdsaSigningScheme) -> Self {
s.into_signing_scheme()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum PriceQuality {
Fast,
#[default]
Optimal,
Verified,
}
impl PriceQuality {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Fast => "fast",
Self::Optimal => "optimal",
Self::Verified => "verified",
}
}
#[must_use]
pub const fn is_fast(self) -> bool {
matches!(self, Self::Fast)
}
#[must_use]
pub const fn is_optimal(self) -> bool {
matches!(self, Self::Optimal)
}
#[must_use]
pub const fn is_verified(self) -> bool {
matches!(self, Self::Verified)
}
}
impl fmt::Display for PriceQuality {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl TryFrom<&str> for OrderKind {
type Error = crate::error::CowError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"sell" => Ok(Self::Sell),
"buy" => Ok(Self::Buy),
other => Err(crate::error::CowError::Parse {
field: "OrderKind",
reason: format!("unknown value: {other}"),
}),
}
}
}
impl TryFrom<&str> for TokenBalance {
type Error = crate::error::CowError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"erc20" => Ok(Self::Erc20),
"external" => Ok(Self::External),
"internal" => Ok(Self::Internal),
other => Err(crate::error::CowError::Parse {
field: "TokenBalance",
reason: format!("unknown value: {other}"),
}),
}
}
}
impl TryFrom<&str> for SigningScheme {
type Error = crate::error::CowError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"eip712" => Ok(Self::Eip712),
"ethsign" => Ok(Self::EthSign),
"eip1271" => Ok(Self::Eip1271),
"presign" => Ok(Self::PreSign),
other => Err(crate::error::CowError::Parse {
field: "SigningScheme",
reason: format!("unknown value: {other}"),
}),
}
}
}
impl TryFrom<&str> for EcdsaSigningScheme {
type Error = crate::error::CowError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"eip712" => Ok(Self::Eip712),
"ethsign" => Ok(Self::EthSign),
other => Err(crate::error::CowError::Parse {
field: "EcdsaSigningScheme",
reason: format!("unknown value: {other}"),
}),
}
}
}
impl TryFrom<&str> for PriceQuality {
type Error = crate::error::CowError;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"fast" => Ok(Self::Fast),
"optimal" => Ok(Self::Optimal),
"verified" => Ok(Self::Verified),
other => Err(crate::error::CowError::Parse {
field: "PriceQuality",
reason: format!("unknown value: {other}"),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn order_kind_as_str() {
assert_eq!(OrderKind::Sell.as_str(), "sell");
assert_eq!(OrderKind::Buy.as_str(), "buy");
}
#[test]
fn order_kind_predicates() {
assert!(OrderKind::Sell.is_sell());
assert!(!OrderKind::Sell.is_buy());
assert!(OrderKind::Buy.is_buy());
assert!(!OrderKind::Buy.is_sell());
}
#[test]
fn order_kind_display() {
assert_eq!(format!("{}", OrderKind::Sell), "sell");
assert_eq!(format!("{}", OrderKind::Buy), "buy");
}
#[test]
fn order_kind_roundtrip() {
for kind in [OrderKind::Sell, OrderKind::Buy] {
let parsed = OrderKind::try_from(kind.as_str());
assert!(parsed.is_ok());
assert_eq!(parsed.unwrap_or_else(|_| OrderKind::Sell), kind);
}
}
#[test]
fn order_kind_invalid() {
assert!(OrderKind::try_from("invalid").is_err());
assert!(OrderKind::try_from("").is_err());
assert!(OrderKind::try_from("SELL").is_err());
}
#[test]
fn order_kind_serde_roundtrip() {
let json = serde_json::to_string(&OrderKind::Sell).unwrap_or_default();
assert_eq!(json, "\"sell\"");
let back: OrderKind = serde_json::from_str(&json).unwrap_or_else(|_| OrderKind::Buy);
assert_eq!(back, OrderKind::Sell);
}
#[test]
fn token_balance_as_str() {
assert_eq!(TokenBalance::Erc20.as_str(), "erc20");
assert_eq!(TokenBalance::External.as_str(), "external");
assert_eq!(TokenBalance::Internal.as_str(), "internal");
}
#[test]
fn token_balance_predicates() {
assert!(TokenBalance::Erc20.is_erc20());
assert!(!TokenBalance::Erc20.is_external());
assert!(!TokenBalance::Erc20.is_internal());
assert!(TokenBalance::External.is_external());
assert!(TokenBalance::Internal.is_internal());
}
#[test]
fn token_balance_default() {
assert_eq!(TokenBalance::default(), TokenBalance::Erc20);
}
#[test]
fn token_balance_roundtrip() {
for bal in [TokenBalance::Erc20, TokenBalance::External, TokenBalance::Internal] {
let parsed = TokenBalance::try_from(bal.as_str());
assert!(parsed.is_ok());
assert_eq!(parsed.unwrap_or_else(|_| TokenBalance::Erc20), bal);
}
}
#[test]
fn token_balance_invalid() {
assert!(TokenBalance::try_from("ERC20").is_err());
assert!(TokenBalance::try_from("").is_err());
}
#[test]
fn token_balance_eip712_hash_deterministic() {
let h1 = TokenBalance::Erc20.eip712_hash();
let h2 = TokenBalance::Erc20.eip712_hash();
assert_eq!(h1, h2);
assert_ne!(TokenBalance::Erc20.eip712_hash(), TokenBalance::External.eip712_hash());
assert_ne!(TokenBalance::External.eip712_hash(), TokenBalance::Internal.eip712_hash());
}
#[test]
fn token_balance_display() {
assert_eq!(format!("{}", TokenBalance::External), "external");
}
#[test]
fn signing_scheme_as_str() {
assert_eq!(SigningScheme::Eip712.as_str(), "eip712");
assert_eq!(SigningScheme::EthSign.as_str(), "ethsign");
assert_eq!(SigningScheme::Eip1271.as_str(), "eip1271");
assert_eq!(SigningScheme::PreSign.as_str(), "presign");
}
#[test]
fn signing_scheme_predicates() {
assert!(SigningScheme::Eip712.is_eip712());
assert!(SigningScheme::EthSign.is_eth_sign());
assert!(SigningScheme::Eip1271.is_eip1271());
assert!(SigningScheme::PreSign.is_presign());
assert!(!SigningScheme::Eip712.is_presign());
}
#[test]
fn signing_scheme_roundtrip() {
for s in [
SigningScheme::Eip712,
SigningScheme::EthSign,
SigningScheme::Eip1271,
SigningScheme::PreSign,
] {
assert_eq!(
SigningScheme::try_from(s.as_str()).unwrap_or_else(|_| SigningScheme::Eip712),
s
);
}
}
#[test]
fn signing_scheme_invalid() {
assert!(SigningScheme::try_from("eip-712").is_err());
assert!(SigningScheme::try_from("").is_err());
}
#[test]
fn signing_scheme_display() {
assert_eq!(format!("{}", SigningScheme::PreSign), "presign");
}
#[test]
fn ecdsa_scheme_default() {
assert_eq!(EcdsaSigningScheme::default(), EcdsaSigningScheme::Eip712);
}
#[test]
fn ecdsa_scheme_into_signing_scheme() {
assert_eq!(EcdsaSigningScheme::Eip712.into_signing_scheme(), SigningScheme::Eip712);
assert_eq!(EcdsaSigningScheme::EthSign.into_signing_scheme(), SigningScheme::EthSign);
}
#[test]
fn ecdsa_scheme_from_conversion() {
let full: SigningScheme = EcdsaSigningScheme::EthSign.into();
assert_eq!(full, SigningScheme::EthSign);
}
#[test]
fn ecdsa_scheme_predicates() {
assert!(EcdsaSigningScheme::Eip712.is_eip712());
assert!(!EcdsaSigningScheme::Eip712.is_eth_sign());
assert!(EcdsaSigningScheme::EthSign.is_eth_sign());
}
#[test]
fn ecdsa_scheme_roundtrip() {
for s in [EcdsaSigningScheme::Eip712, EcdsaSigningScheme::EthSign] {
assert_eq!(
EcdsaSigningScheme::try_from(s.as_str())
.unwrap_or_else(|_| EcdsaSigningScheme::Eip712),
s
);
}
}
#[test]
fn ecdsa_scheme_invalid() {
assert!(EcdsaSigningScheme::try_from("eip1271").is_err());
}
#[test]
fn ecdsa_scheme_display() {
assert_eq!(format!("{}", EcdsaSigningScheme::EthSign), "ethsign");
}
#[test]
fn price_quality_default() {
assert_eq!(PriceQuality::default(), PriceQuality::Optimal);
}
#[test]
fn price_quality_as_str() {
assert_eq!(PriceQuality::Fast.as_str(), "fast");
assert_eq!(PriceQuality::Optimal.as_str(), "optimal");
assert_eq!(PriceQuality::Verified.as_str(), "verified");
}
#[test]
fn price_quality_predicates() {
assert!(PriceQuality::Fast.is_fast());
assert!(PriceQuality::Optimal.is_optimal());
assert!(PriceQuality::Verified.is_verified());
assert!(!PriceQuality::Fast.is_optimal());
}
#[test]
fn price_quality_roundtrip() {
for q in [PriceQuality::Fast, PriceQuality::Optimal, PriceQuality::Verified] {
assert_eq!(
PriceQuality::try_from(q.as_str()).unwrap_or_else(|_| PriceQuality::Fast),
q
);
}
}
#[test]
fn price_quality_invalid() {
assert!(PriceQuality::try_from("slow").is_err());
}
#[test]
fn price_quality_display() {
assert_eq!(format!("{}", PriceQuality::Verified), "verified");
}
#[test]
fn constants_are_correct() {
assert!(ZERO_ADDRESS.is_zero());
assert_eq!(ZERO, U256::ZERO);
assert_eq!(ONE, U256::from(1));
assert_eq!(MAX_UINT32, U256::from(u32::MAX));
assert_eq!(MAX_UINT256, U256::MAX);
assert_eq!(ONE_HUNDRED_BPS, 10_000);
assert_eq!(HUNDRED_THOUSANDS, 100_000);
assert_eq!(LIMIT_CONCURRENT_REQUESTS, 5);
}
#[test]
fn zero_hash_is_correct_length() {
assert_eq!(ZERO_HASH.len(), 66); assert!(ZERO_HASH.starts_with("0x"));
}
#[test]
fn attestator_address_is_nonzero() {
assert!(!ATTESTATOR_ADDRESS.is_zero());
}
}