use crate::{
error::CowError,
types::{OrderKind, SigningScheme, TokenBalance},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OrderFlags {
pub kind: OrderKind,
pub partially_fillable: bool,
pub sell_token_balance: TokenBalance,
pub buy_token_balance: TokenBalance,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TradeFlags {
pub order_flags: OrderFlags,
pub signing_scheme: SigningScheme,
}
const KIND_OFFSET: u8 = 0;
const PARTIALLY_FILLABLE_OFFSET: u8 = 1;
const SELL_TOKEN_BALANCE_OFFSET: u8 = 2;
const BUY_TOKEN_BALANCE_OFFSET: u8 = 4;
const SIGNING_SCHEME_OFFSET: u8 = 5;
#[must_use]
pub const fn encode_kind(kind: OrderKind) -> u8 {
match kind {
OrderKind::Sell => 0,
OrderKind::Buy => 1,
}
}
#[must_use]
pub const fn encode_partially_fillable(pf: bool) -> u8 {
(pf as u8) << PARTIALLY_FILLABLE_OFFSET
}
#[must_use]
pub const fn encode_sell_token_balance(balance: TokenBalance) -> u8 {
let index = match balance {
TokenBalance::Erc20 => 0u8,
TokenBalance::External => 2,
TokenBalance::Internal => 3,
};
index << SELL_TOKEN_BALANCE_OFFSET
}
#[must_use]
pub const fn encode_buy_token_balance(balance: TokenBalance) -> u8 {
let index = match balance {
TokenBalance::Erc20 | TokenBalance::External => 0u8,
TokenBalance::Internal => 1,
};
index << BUY_TOKEN_BALANCE_OFFSET
}
#[must_use]
pub const fn encode_signing_scheme(scheme: SigningScheme) -> u8 {
let index = match scheme {
SigningScheme::Eip712 => 0u8,
SigningScheme::EthSign => 1,
SigningScheme::Eip1271 => 2,
SigningScheme::PreSign => 3,
};
index << SIGNING_SCHEME_OFFSET
}
#[must_use]
pub const fn encode_order_flags(flags: &OrderFlags) -> u8 {
encode_kind(flags.kind) |
encode_partially_fillable(flags.partially_fillable) |
encode_sell_token_balance(flags.sell_token_balance) |
encode_buy_token_balance(flags.buy_token_balance)
}
pub fn decode_order_flags(bits: u8) -> Result<OrderFlags, CowError> {
let kind_index = (bits >> KIND_OFFSET) & 0x01;
let pf_index = (bits >> PARTIALLY_FILLABLE_OFFSET) & 0x01;
let sell_index = (bits >> SELL_TOKEN_BALANCE_OFFSET) & 0x03;
let buy_index = (bits >> BUY_TOKEN_BALANCE_OFFSET) & 0x01;
let kind = match kind_index {
0 => OrderKind::Sell,
1 => OrderKind::Buy,
_ => unreachable!(),
};
let partially_fillable = pf_index != 0;
let sell_token_balance = match sell_index {
0 => TokenBalance::Erc20,
2 => TokenBalance::External,
3 => TokenBalance::Internal,
other => {
return Err(CowError::Parse {
field: "sellTokenBalance",
reason: format!("invalid flag index: {other}"),
});
}
};
let buy_token_balance = match buy_index {
0 => TokenBalance::Erc20,
1 => TokenBalance::Internal,
_ => unreachable!(),
};
Ok(OrderFlags { kind, partially_fillable, sell_token_balance, buy_token_balance })
}
pub fn decode_signing_scheme(bits: u8) -> Result<SigningScheme, CowError> {
let index = (bits >> SIGNING_SCHEME_OFFSET) & 0x03;
match index {
0 => Ok(SigningScheme::Eip712),
1 => Ok(SigningScheme::EthSign),
2 => Ok(SigningScheme::Eip1271),
3 => Ok(SigningScheme::PreSign),
_ => unreachable!(),
}
}
#[must_use]
pub const fn encode_trade_flags(flags: &TradeFlags) -> u8 {
encode_order_flags(&flags.order_flags) | encode_signing_scheme(flags.signing_scheme)
}
pub fn decode_trade_flags(bits: u8) -> Result<TradeFlags, CowError> {
let order_flags = decode_order_flags(bits)?;
let signing_scheme = decode_signing_scheme(bits)?;
Ok(TradeFlags { order_flags, signing_scheme })
}
#[must_use]
pub const fn normalize_buy_token_balance(balance: TokenBalance) -> TokenBalance {
match balance {
TokenBalance::Erc20 | TokenBalance::External => TokenBalance::Erc20,
TokenBalance::Internal => TokenBalance::Internal,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_order_flags() {
let cases = [
OrderFlags {
kind: OrderKind::Sell,
partially_fillable: false,
sell_token_balance: TokenBalance::Erc20,
buy_token_balance: TokenBalance::Erc20,
},
OrderFlags {
kind: OrderKind::Buy,
partially_fillable: true,
sell_token_balance: TokenBalance::External,
buy_token_balance: TokenBalance::Internal,
},
OrderFlags {
kind: OrderKind::Sell,
partially_fillable: true,
sell_token_balance: TokenBalance::Internal,
buy_token_balance: TokenBalance::Erc20,
},
];
for flags in &cases {
let encoded = encode_order_flags(flags);
let decoded = decode_order_flags(encoded).unwrap();
assert_eq!(&decoded, flags, "roundtrip failed for {flags:?}");
}
}
#[test]
fn roundtrip_trade_flags() {
let flags = TradeFlags {
order_flags: OrderFlags {
kind: OrderKind::Buy,
partially_fillable: false,
sell_token_balance: TokenBalance::Erc20,
buy_token_balance: TokenBalance::Internal,
},
signing_scheme: SigningScheme::Eip1271,
};
let encoded = encode_trade_flags(&flags);
let decoded = decode_trade_flags(encoded).unwrap();
assert_eq!(decoded, flags);
}
#[test]
fn invalid_sell_token_balance_flag() {
let bits = 0b000_0100;
let result = decode_order_flags(bits);
assert!(result.is_err());
}
#[test]
fn signing_scheme_round_trip() {
for scheme in [
SigningScheme::Eip712,
SigningScheme::EthSign,
SigningScheme::Eip1271,
SigningScheme::PreSign,
] {
let encoded = encode_signing_scheme(scheme);
let decoded = decode_signing_scheme(encoded).unwrap();
assert_eq!(decoded, scheme);
}
}
}