df_hyperliquid_core/utils/
asset.rs1use std::fmt;
7
8use crate::trade::builder_fee::{BuilderFee, OUTCOME_BUILDER_CAP, PERP_BUILDER_CAP, SPOT_BUILDER_CAP};
9use crate::utils::sign::msgpack::Value;
10
11pub const SPOT_ASSET_BASE: u32 = 10_000;
13
14pub const HIP3_ASSET_BASE: u32 = 100_000;
17
18pub const HIP3_DEX_SPAN: u32 = 10_000;
20
21pub const OUTCOME_ASSET_BASE: u32 = 100_000_000;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub enum OutcomeSide {
27 Yes,
29 No,
30}
31
32impl OutcomeSide {
33 #[must_use]
34 pub const fn digit(self) -> u32 {
35 match self {
36 OutcomeSide::Yes => 0,
37 OutcomeSide::No => 1,
38 }
39 }
40
41 #[must_use]
42 pub const fn other(self) -> OutcomeSide {
43 match self {
44 OutcomeSide::Yes => OutcomeSide::No,
45 OutcomeSide::No => OutcomeSide::Yes,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum AssetKind {
53 Perp,
55 Spot,
57 Hip3Perp,
59 Outcome,
61}
62
63impl AssetKind {
64 pub const ALL: [AssetKind; 4] = [AssetKind::Perp, AssetKind::Spot, AssetKind::Hip3Perp, AssetKind::Outcome];
65
66 #[must_use]
67 pub const fn of(id: u32) -> AssetKind {
68 if id >= OUTCOME_ASSET_BASE {
69 AssetKind::Outcome
70 } else if id >= HIP3_ASSET_BASE {
71 AssetKind::Hip3Perp
72 } else if id >= SPOT_ASSET_BASE {
73 AssetKind::Spot
74 } else {
75 AssetKind::Perp
76 }
77 }
78
79 #[must_use]
83 pub const fn builder_cap(self) -> BuilderFee {
84 match self {
85 AssetKind::Perp | AssetKind::Hip3Perp => PERP_BUILDER_CAP,
86 AssetKind::Spot => SPOT_BUILDER_CAP,
87 AssetKind::Outcome => OUTCOME_BUILDER_CAP,
88 }
89 }
90
91 #[must_use]
95 pub fn strictest(assets: impl IntoIterator<Item = Asset>) -> Option<AssetKind> {
96 assets.into_iter().map(Asset::kind).reduce(|kept, next| match next.builder_cap() < kept.builder_cap() {
97 true => next,
98 false => kept,
99 })
100 }
101
102 #[must_use]
104 pub const fn one(self) -> &'static str {
105 match self {
106 AssetKind::Perp => "a perp",
107 AssetKind::Spot => "a spot",
108 AssetKind::Hip3Perp => "a HIP-3 perp",
109 AssetKind::Outcome => "an outcome",
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
118pub struct Asset(u32);
119
120impl Asset {
121 #[must_use]
123 pub const fn from_id(id: u32) -> Asset {
124 Asset(id)
125 }
126
127 #[must_use]
128 pub const fn id(self) -> u32 {
129 self.0
130 }
131
132 #[must_use]
133 pub const fn kind(self) -> AssetKind {
134 AssetKind::of(self.0)
135 }
136
137 #[must_use]
139 pub const fn perp(index: u32) -> Option<Asset> {
140 match index < SPOT_ASSET_BASE {
141 true => Some(Asset(index)),
142 false => None,
143 }
144 }
145
146 #[must_use]
149 pub const fn spot(pair: u32) -> Option<Asset> {
150 match pair < HIP3_ASSET_BASE - SPOT_ASSET_BASE {
151 true => Some(Asset(SPOT_ASSET_BASE + pair)),
152 false => None,
153 }
154 }
155
156 #[must_use]
160 pub const fn hip3(dex: u32, index: u32) -> Option<Asset> {
161 if dex == 0 || index >= HIP3_DEX_SPAN {
162 return None;
163 }
164 let Some(offset) = dex.checked_mul(HIP3_DEX_SPAN) else {
165 return None;
166 };
167 let Some(start) = HIP3_ASSET_BASE.checked_add(offset) else {
168 return None;
169 };
170 match start.checked_add(index) {
171 Some(id) if id < OUTCOME_ASSET_BASE => Some(Asset(id)),
172 _ => None,
173 }
174 }
175
176 #[must_use]
179 pub const fn hip3_parts(self) -> Option<(u32, u32)> {
180 if !matches!(self.kind(), AssetKind::Hip3Perp) {
181 return None;
182 }
183 let offset = self.0 - HIP3_ASSET_BASE;
184 match offset / HIP3_DEX_SPAN {
185 0 => None,
186 place => Some((place, offset % HIP3_DEX_SPAN)),
187 }
188 }
189
190 #[must_use]
192 pub const fn perp_on(dex: u32, index: u32) -> Option<Asset> {
193 match dex {
194 0 => Asset::perp(index),
195 dex => Asset::hip3(dex, index),
196 }
197 }
198
199 #[must_use]
201 pub const fn outcome(outcome: u32, side: OutcomeSide) -> Option<Asset> {
202 let Some(tens) = outcome.checked_mul(10) else {
203 return None;
204 };
205 match OUTCOME_ASSET_BASE.checked_add(tens + side.digit()) {
206 Some(id) => Some(Asset(id)),
207 None => None,
208 }
209 }
210
211 #[must_use]
213 pub const fn outcome_side(self) -> Option<(u32, OutcomeSide)> {
214 if self.0 < OUTCOME_ASSET_BASE {
215 return None;
216 }
217 let encoding = self.0 - OUTCOME_ASSET_BASE;
218 let side = match encoding % 10 {
219 0 => OutcomeSide::Yes,
220 1 => OutcomeSide::No,
221 _ => return None,
222 };
223 Some((encoding / 10, side))
224 }
225}
226
227impl fmt::Display for Asset {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 write!(f, "{}", self.0)
230 }
231}
232
233impl From<Asset> for Value {
235 fn from(asset: Asset) -> Value {
236 Value::Uint(u64::from(asset.0))
237 }
238}
239
240#[cfg(test)]
241#[path = "tests/asset.rs"]
242mod tests;