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 #[must_use]
51 pub const fn as_str(self) -> &'static str {
52 match self {
53 OutcomeSide::Yes => "yes",
54 OutcomeSide::No => "no",
55 }
56 }
57}
58
59impl serde::Serialize for OutcomeSide {
60 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
61 serializer.serialize_str(self.as_str())
62 }
63}
64
65impl<'de> serde::Deserialize<'de> for OutcomeSide {
66 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
67 match String::deserialize(deserializer)?.as_str() {
68 "yes" => Ok(OutcomeSide::Yes),
69 "no" => Ok(OutcomeSide::No),
70 other => Err(serde::de::Error::custom(format!("`{other}` is not an outcome side; expected yes or no"))),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
77pub enum AssetKind {
78 Perp,
80 Spot,
82 Hip3Perp,
84 Outcome,
86}
87
88impl AssetKind {
89 pub const ALL: [AssetKind; 4] = [AssetKind::Perp, AssetKind::Spot, AssetKind::Hip3Perp, AssetKind::Outcome];
90
91 #[must_use]
92 pub const fn of(id: u32) -> AssetKind {
93 if id >= OUTCOME_ASSET_BASE {
94 AssetKind::Outcome
95 } else if id >= HIP3_ASSET_BASE {
96 AssetKind::Hip3Perp
97 } else if id >= SPOT_ASSET_BASE {
98 AssetKind::Spot
99 } else {
100 AssetKind::Perp
101 }
102 }
103
104 #[must_use]
108 pub const fn builder_cap(self) -> BuilderFee {
109 match self {
110 AssetKind::Perp | AssetKind::Hip3Perp => PERP_BUILDER_CAP,
111 AssetKind::Spot => SPOT_BUILDER_CAP,
112 AssetKind::Outcome => OUTCOME_BUILDER_CAP,
113 }
114 }
115
116 #[must_use]
120 pub fn strictest(assets: impl IntoIterator<Item = Asset>) -> Option<AssetKind> {
121 assets.into_iter().map(Asset::kind).reduce(|kept, next| match next.builder_cap() < kept.builder_cap() {
122 true => next,
123 false => kept,
124 })
125 }
126
127 #[must_use]
129 pub const fn one(self) -> &'static str {
130 match self {
131 AssetKind::Perp => "a perp",
132 AssetKind::Spot => "a spot",
133 AssetKind::Hip3Perp => "a HIP-3 perp",
134 AssetKind::Outcome => "an outcome",
135 }
136 }
137}
138
139#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
143pub struct Asset(u32);
144
145impl Asset {
146 #[must_use]
148 pub const fn from_id(id: u32) -> Asset {
149 Asset(id)
150 }
151
152 #[must_use]
153 pub const fn id(self) -> u32 {
154 self.0
155 }
156
157 #[must_use]
158 pub const fn kind(self) -> AssetKind {
159 AssetKind::of(self.0)
160 }
161
162 #[must_use]
164 pub const fn perp(index: u32) -> Option<Asset> {
165 match index < SPOT_ASSET_BASE {
166 true => Some(Asset(index)),
167 false => None,
168 }
169 }
170
171 #[must_use]
174 pub const fn spot(pair: u32) -> Option<Asset> {
175 match pair < HIP3_ASSET_BASE - SPOT_ASSET_BASE {
176 true => Some(Asset(SPOT_ASSET_BASE + pair)),
177 false => None,
178 }
179 }
180
181 #[must_use]
185 pub const fn hip3(dex: u32, index: u32) -> Option<Asset> {
186 if dex == 0 || index >= HIP3_DEX_SPAN {
187 return None;
188 }
189 let Some(offset) = dex.checked_mul(HIP3_DEX_SPAN) else {
190 return None;
191 };
192 let Some(start) = HIP3_ASSET_BASE.checked_add(offset) else {
193 return None;
194 };
195 match start.checked_add(index) {
196 Some(id) if id < OUTCOME_ASSET_BASE => Some(Asset(id)),
197 _ => None,
198 }
199 }
200
201 #[must_use]
204 pub const fn hip3_parts(self) -> Option<(u32, u32)> {
205 if !matches!(self.kind(), AssetKind::Hip3Perp) {
206 return None;
207 }
208 let offset = self.0 - HIP3_ASSET_BASE;
209 match offset / HIP3_DEX_SPAN {
210 0 => None,
211 place => Some((place, offset % HIP3_DEX_SPAN)),
212 }
213 }
214
215 #[must_use]
217 pub const fn perp_on(dex: u32, index: u32) -> Option<Asset> {
218 match dex {
219 0 => Asset::perp(index),
220 dex => Asset::hip3(dex, index),
221 }
222 }
223
224 #[must_use]
226 pub const fn outcome(outcome: u32, side: OutcomeSide) -> Option<Asset> {
227 let Some(tens) = outcome.checked_mul(10) else {
228 return None;
229 };
230 match OUTCOME_ASSET_BASE.checked_add(tens + side.digit()) {
231 Some(id) => Some(Asset(id)),
232 None => None,
233 }
234 }
235
236 #[must_use]
238 pub const fn outcome_side(self) -> Option<(u32, OutcomeSide)> {
239 if self.0 < OUTCOME_ASSET_BASE {
240 return None;
241 }
242 let encoding = self.0 - OUTCOME_ASSET_BASE;
243 let side = match encoding % 10 {
244 0 => OutcomeSide::Yes,
245 1 => OutcomeSide::No,
246 _ => return None,
247 };
248 Some((encoding / 10, side))
249 }
250}
251
252impl fmt::Display for Asset {
253 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
254 write!(f, "{}", self.0)
255 }
256}
257
258impl From<Asset> for Value {
260 fn from(asset: Asset) -> Value {
261 Value::Uint(u64::from(asset.0))
262 }
263}
264
265#[cfg(test)]
266#[path = "tests/asset.rs"]
267mod tests;