use std::fmt;
use crate::trade::builder_fee::{BuilderFee, OUTCOME_BUILDER_CAP, PERP_BUILDER_CAP, SPOT_BUILDER_CAP};
use crate::utils::sign::msgpack::Value;
pub const SPOT_ASSET_BASE: u32 = 10_000;
pub const HIP3_ASSET_BASE: u32 = 100_000;
pub const HIP3_DEX_SPAN: u32 = 10_000;
pub const OUTCOME_ASSET_BASE: u32 = 100_000_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum OutcomeSide {
Yes,
No,
}
impl OutcomeSide {
#[must_use]
pub const fn digit(self) -> u32 {
match self {
OutcomeSide::Yes => 0,
OutcomeSide::No => 1,
}
}
#[must_use]
pub const fn other(self) -> OutcomeSide {
match self {
OutcomeSide::Yes => OutcomeSide::No,
OutcomeSide::No => OutcomeSide::Yes,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AssetKind {
Perp,
Spot,
Hip3Perp,
Outcome,
}
impl AssetKind {
pub const ALL: [AssetKind; 4] = [AssetKind::Perp, AssetKind::Spot, AssetKind::Hip3Perp, AssetKind::Outcome];
#[must_use]
pub const fn of(id: u32) -> AssetKind {
if id >= OUTCOME_ASSET_BASE {
AssetKind::Outcome
} else if id >= HIP3_ASSET_BASE {
AssetKind::Hip3Perp
} else if id >= SPOT_ASSET_BASE {
AssetKind::Spot
} else {
AssetKind::Perp
}
}
#[must_use]
pub const fn builder_cap(self) -> BuilderFee {
match self {
AssetKind::Perp | AssetKind::Hip3Perp => PERP_BUILDER_CAP,
AssetKind::Spot => SPOT_BUILDER_CAP,
AssetKind::Outcome => OUTCOME_BUILDER_CAP,
}
}
#[must_use]
pub fn strictest(assets: impl IntoIterator<Item = Asset>) -> Option<AssetKind> {
assets.into_iter().map(Asset::kind).reduce(|kept, next| match next.builder_cap() < kept.builder_cap() {
true => next,
false => kept,
})
}
#[must_use]
pub const fn one(self) -> &'static str {
match self {
AssetKind::Perp => "a perp",
AssetKind::Spot => "a spot",
AssetKind::Hip3Perp => "a HIP-3 perp",
AssetKind::Outcome => "an outcome",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Asset(u32);
impl Asset {
#[must_use]
pub const fn from_id(id: u32) -> Asset {
Asset(id)
}
#[must_use]
pub const fn id(self) -> u32 {
self.0
}
#[must_use]
pub const fn kind(self) -> AssetKind {
AssetKind::of(self.0)
}
#[must_use]
pub const fn perp(index: u32) -> Option<Asset> {
match index < SPOT_ASSET_BASE {
true => Some(Asset(index)),
false => None,
}
}
#[must_use]
pub const fn spot(pair: u32) -> Option<Asset> {
match pair < HIP3_ASSET_BASE - SPOT_ASSET_BASE {
true => Some(Asset(SPOT_ASSET_BASE + pair)),
false => None,
}
}
#[must_use]
pub const fn hip3(dex: u32, index: u32) -> Option<Asset> {
if dex == 0 || index >= HIP3_DEX_SPAN {
return None;
}
let Some(offset) = dex.checked_mul(HIP3_DEX_SPAN) else {
return None;
};
let Some(start) = HIP3_ASSET_BASE.checked_add(offset) else {
return None;
};
match start.checked_add(index) {
Some(id) if id < OUTCOME_ASSET_BASE => Some(Asset(id)),
_ => None,
}
}
#[must_use]
pub const fn hip3_parts(self) -> Option<(u32, u32)> {
if !matches!(self.kind(), AssetKind::Hip3Perp) {
return None;
}
let offset = self.0 - HIP3_ASSET_BASE;
match offset / HIP3_DEX_SPAN {
0 => None,
place => Some((place, offset % HIP3_DEX_SPAN)),
}
}
#[must_use]
pub const fn perp_on(dex: u32, index: u32) -> Option<Asset> {
match dex {
0 => Asset::perp(index),
dex => Asset::hip3(dex, index),
}
}
#[must_use]
pub const fn outcome(outcome: u32, side: OutcomeSide) -> Option<Asset> {
let Some(tens) = outcome.checked_mul(10) else {
return None;
};
match OUTCOME_ASSET_BASE.checked_add(tens + side.digit()) {
Some(id) => Some(Asset(id)),
None => None,
}
}
#[must_use]
pub const fn outcome_side(self) -> Option<(u32, OutcomeSide)> {
if self.0 < OUTCOME_ASSET_BASE {
return None;
}
let encoding = self.0 - OUTCOME_ASSET_BASE;
let side = match encoding % 10 {
0 => OutcomeSide::Yes,
1 => OutcomeSide::No,
_ => return None,
};
Some((encoding / 10, side))
}
}
impl fmt::Display for Asset {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl From<Asset> for Value {
fn from(asset: Asset) -> Value {
Value::Uint(u64::from(asset.0))
}
}
#[cfg(test)]
#[path = "tests/asset.rs"]
mod tests;