Skip to main content

df_hyperliquid_core/deploy/outcomes/actions/
bounded.rs

1//! The two decimals an outcome action carries, each in its range and sent in normal form: a
2//! deployer's [`FeeScale`] and a settlement's [`Fraction`].
3
4use std::{fmt, str::FromStr};
5
6use crate::deploy::outcomes::actions::error::DeployError;
7use crate::utils::Dec;
8
9/// `text` as a decimal from 0 to `most`, written with no sign and no
10/// exponent. It goes on the wire in normal form: `1.0` as `1`.
11fn bounded(text: &str, most: Dec) -> Option<Dec> {
12    let text = text.trim();
13    if text.starts_with(['+', '-']) {
14        return None;
15    }
16    let value = Dec::parse(text).ok()?;
17    (!value.is_negative() && value <= most).then_some(value)
18}
19
20/// What a deployer takes of the fees on its outcomes, from 0 to 10.
21/// `outcomeMeta` reads a scale back as `1.0`; it is sent as `1`. Only a
22/// standalone outcome and a question carry one: a named outcome added later
23/// takes its question's.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub struct FeeScale(Dec);
26
27impl FeeScale {
28    pub const ZERO: FeeScale = FeeScale(Dec::ZERO);
29    pub const ONE: FeeScale = FeeScale(Dec::ONE);
30
31    #[must_use]
32    pub const fn value(self) -> Dec {
33        self.0
34    }
35
36    /// The string hashed: no trailing zeros.
37    #[must_use]
38    pub fn to_wire(self) -> String {
39        self.0.to_wire()
40    }
41}
42
43impl FromStr for FeeScale {
44    type Err = DeployError;
45
46    /// Takes `1.0`, `01` or `.50`, and keeps what the venue hashes: `1`, `1`,
47    /// `0.5`.
48    fn from_str(text: &str) -> Result<Self, Self::Err> {
49        bounded(text, Dec::from_int(10)).map(FeeScale).ok_or_else(|| DeployError::FeeScale(text.trim().to_owned()))
50    }
51}
52
53impl fmt::Display for FeeScale {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        write!(f, "{}", self.0)
56    }
57}
58
59/// The share of an outcome's pool its first side is paid when it settles,
60/// from 0 to 1: `1` pays the first side in full and `0` the second. A
61/// standalone outcome settles to any fraction; a question's only to 0 or 1.
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct Fraction(Dec);
64
65impl Fraction {
66    pub const ZERO: Fraction = Fraction(Dec::ZERO);
67    pub const ONE: Fraction = Fraction(Dec::ONE);
68
69    #[must_use]
70    pub const fn value(self) -> Dec {
71        self.0
72    }
73
74    #[must_use]
75    pub fn to_wire(self) -> String {
76        self.0.to_wire()
77    }
78
79    /// Exactly 0 or exactly 1, the only fractions a question's outcome takes.
80    #[must_use]
81    pub fn is_whole(self) -> bool {
82        self == Fraction::ZERO || self == Fraction::ONE
83    }
84}
85
86impl FromStr for Fraction {
87    type Err = DeployError;
88
89    /// As [`FeeScale`]'s: `1.0` is kept as `1` and `.50` as `0.5`.
90    fn from_str(text: &str) -> Result<Self, Self::Err> {
91        bounded(text, Dec::ONE).map(Fraction).ok_or_else(|| DeployError::Fraction(text.trim().to_owned()))
92    }
93}
94
95impl fmt::Display for Fraction {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        write!(f, "{}", self.0)
98    }
99}
100
101#[cfg(test)]
102#[path = "tests/bounded.rs"]
103mod tests;