nash_protocol/types/
exchange.rs

1//! Types used for protocol request inputs (arguments) and outputs. Types
2//! specific to a single protocol request will live within the respetitive
3//! module. For example `protocol::place_order`.
4
5use crate::errors::{ProtocolError, Result};
6use bigdecimal::BigDecimal;
7use std::str::FromStr;
8use chrono::{DateTime, Utc};
9use serde::{Deserialize, Serialize};
10use super::blockchain::bigdecimal_to_nash_prec;
11use lazy_static::lazy_static;
12
13/// Representation of blockchains to help navigate encoding issues
14
15#[derive(Clone, Debug, Copy, PartialEq, Hash, Eq)]
16pub enum Blockchain {
17    NEO,
18    Ethereum,
19    Bitcoin,
20}
21
22lazy_static! {
23    static ref BLOCKCHAINS: Vec<Blockchain> = {
24        vec![Blockchain::Bitcoin, Blockchain::Ethereum, Blockchain::NEO]
25    };
26}
27
28impl Blockchain {
29    pub fn all() -> &'static Vec<Blockchain> {
30        &BLOCKCHAINS
31    }
32}
33
34/// Assets are the units of value that can be traded in a market
35/// We also use this type as a root for encodings for smart contract
36/// operations defined in super::sc_payloads
37#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
38pub enum Asset {
39    ETH,
40    BAT,
41    OMG,
42    USDC,
43    USDT,
44    ZRX,
45    LINK,
46    QNT,
47    RLC,
48    ANT,
49    BTC,
50    NEO,
51    GAS,
52    TRAC,
53    GUNTHY,
54    NNN,
55    NOIA
56}
57
58impl Asset {
59    /// Get blockchain associated with a specific asset
60    pub fn blockchain(&self) -> Blockchain {
61        match self {
62            Self::ETH => Blockchain::Ethereum,
63            Self::USDC => Blockchain::Ethereum,
64            Self::USDT => Blockchain::Ethereum,
65            Self::BAT => Blockchain::Ethereum,
66            Self::OMG => Blockchain::Ethereum,
67            Self::ZRX => Blockchain::Ethereum,
68            Self::LINK => Blockchain::Ethereum,
69            Self::QNT => Blockchain::Ethereum,
70            Self::RLC => Blockchain::Ethereum,
71            Self::ANT => Blockchain::Ethereum,
72            Self::TRAC => Blockchain::Ethereum,
73            Self::GUNTHY => Blockchain::Ethereum,
74            Self::BTC => Blockchain::Bitcoin,
75            Self::NEO => Blockchain::NEO,
76            Self::GAS => Blockchain::NEO,
77            Self::NNN => Blockchain::NEO,
78            Self::NOIA => Blockchain::Ethereum
79        }
80    }
81
82    /// Get asset name as an `&str`. This name will be compatible with the
83    /// what Nash's GraphQL protocol wants
84    // FIXME: This can probably be more cleverly automated
85    pub fn name(&self) -> &'static str {
86        match self {
87            Self::ETH => "eth",
88            Self::USDC => "usdc",
89            Self::USDT => "usdt",
90            Self::BAT => "bat",
91            Self::OMG => "omg",
92            Self::ZRX => "zrx",
93            Self::LINK => "link",
94            Self::QNT => "qnt",
95            Self::RLC => "rlc",
96            Self::ANT => "ant",
97            Self::BTC => "btc",
98            Self::NEO => "neo",
99            Self::GAS => "gas",
100            Self::TRAC => "trac",
101            Self::GUNTHY => "gunthy",
102            Self::NNN => "nnn",
103            Self::NOIA => "noia"
104        }
105    }
106
107    pub fn from_str(asset_str: &str) -> Result<Self> {
108        match asset_str {
109            "eth" => Ok(Self::ETH),
110            "usdc" => Ok(Self::USDC),
111            "usdt" => Ok(Self::USDT),
112            "bat" => Ok(Self::BAT),
113            "omg" => Ok(Self::OMG),
114            "zrx" => Ok(Self::ZRX),
115            "link" => Ok(Self::LINK),
116            "qnt" => Ok(Self::QNT),
117            "rlc" => Ok(Self::RLC),
118            "ant" => Ok(Self::ANT),
119            "btc" => Ok(Self::BTC),
120            "neo" => Ok(Self::NEO),
121            "gas" => Ok(Self::GAS),
122            "trac" => Ok(Self::TRAC),
123            "gunthy" => Ok(Self::GUNTHY),
124            "nnn" => Ok(Self::NNN),
125            "noia" => Ok(Self::NOIA),
126            _ => Err(ProtocolError("Asset not known")),
127        }
128    }
129
130    // FIXME: can this be more cleverly automated?
131    /// Return list of all supported `Asset` types
132    pub fn assets() -> Vec<Self> {
133        vec![
134            Self::ETH,
135            Self::USDC,
136            Self::USDT,
137            Self::BAT,
138            Self::OMG,
139            Self::ZRX,
140            Self::LINK,
141            Self::QNT,
142            Self::ANT,
143            Self::BTC,
144            Self::NEO,
145            Self::GAS,
146            Self::TRAC,
147            Self::GUNTHY,
148            Self::NNN,
149        ]
150    }
151}
152
153/// Assets can potentially have different precisions across markets.
154/// This keeps track of what precision we are dealing with
155#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
156pub struct AssetofPrecision {
157    pub asset: Asset,
158    pub precision: u32,
159}
160
161/// Convert AssetOfPrecision back into asset, e.g., for encoding
162impl Into<Asset> for AssetofPrecision {
163    fn into(self) -> Asset {
164        self.asset
165    }
166}
167
168impl AssetofPrecision {
169    /// Starting with an asset of some precision, create a new asset that holds
170    /// a specific amount of value
171    pub fn with_amount(&self, amount_str: &str) -> Result<AssetAmount> {
172        let amount = Amount::new(amount_str, self.precision)?;
173        Ok(AssetAmount {
174            asset: *self,
175            amount,
176        })
177    }
178}
179
180// Extending Asset here with helper to convert with precision
181impl Asset {
182    /// Starting with an asset, create a new asset of desired precision.
183    pub fn with_precision(&self, precision: u32) -> AssetofPrecision {
184        AssetofPrecision {
185            asset: *self,
186            precision,
187        }
188    }
189}
190
191/// A specific amount of an asset being traded
192#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
193pub struct AssetAmount {
194    pub asset: AssetofPrecision,
195    pub amount: Amount,
196}
197
198impl AssetAmount {
199    /// Create a new amount based on an exchange of `self` into `into_asset` at `rate`
200    pub fn exchange_at(&self, rate: &Rate, into_asset: AssetofPrecision) -> Result<AssetAmount> {
201        let new_amount = self.amount.to_bigdecimal() * rate.to_bigdecimal()?;
202        Ok(AssetAmount {
203            asset: into_asset,
204            amount: Amount::from_bigdecimal(new_amount, into_asset.precision),
205        })
206    }
207}
208
209/// This type encodes all the information necessary for a client operating
210/// over the protocol to understand a market.
211#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
212pub struct Market {
213    // FIXME: probably more things here?
214    pub asset_a: AssetofPrecision,
215    pub asset_b: AssetofPrecision,
216    pub min_trade_size_a: AssetAmount,
217    pub min_trade_size_b: AssetAmount,
218}
219
220impl Market {
221    /// Create a new market from assets with precision
222    /// ```
223    /// use nash_protocol::types::{Market, Asset};
224    /// Market::new(Asset::ETH.with_precision(4), Asset::USDC.with_precision(2));
225    /// ```
226    pub fn new(
227        asset_a: AssetofPrecision,
228        asset_b: AssetofPrecision,
229        min_trade_size_a: AssetAmount,
230        min_trade_size_b: AssetAmount
231    ) -> Self {
232        Self {
233            asset_a,
234            asset_b,
235            min_trade_size_a,
236            min_trade_size_b
237        }
238    }
239
240    /// Return name of A/B market as "A_B"
241    pub fn market_name(&self) -> String {
242        format!(
243            "{}_{}",
244            self.asset_a.asset.name(),
245            self.asset_b.asset.name()
246        )
247    }
248
249    /// Get list of blockchains associated with this market. There will always be
250    /// either one or two blockchains associated with a market.
251    pub fn blockchains(&self) -> Vec<Blockchain> {
252        let chain_a = self.asset_a.asset.blockchain();
253        let chain_b = self.asset_b.asset.blockchain();
254        if chain_a == chain_b {
255            vec![chain_a]
256        } else {
257            vec![chain_a, chain_b]
258        }
259    }
260
261    /// Get market asset by string name
262    pub fn get_asset(&self, asset_name: &str) -> Result<AssetofPrecision> {
263        if asset_name == self.asset_a.asset.name() {
264            Ok(self.asset_a.clone())
265        } else if asset_name == self.asset_b.asset.name() {
266            Ok(self.asset_b.clone())
267        } else {
268            Err(ProtocolError("Asset not associated with market"))
269        }
270    }
271
272    pub fn invert(&self) -> Market {
273        Market::new(
274            self.asset_b.clone(),
275            self.asset_a.clone(),
276            self.min_trade_size_b.clone(),
277            self.min_trade_size_a.clone(),
278        )
279    }
280}
281
282/// Buy or sell type for Nash protocol. We don't use the one generated automatically
283/// from the GraphQL schema as it does not implement necessary traits like Clone
284#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
285#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
286pub enum BuyOrSell {
287    Buy,
288    Sell,
289}
290
291/// Type of order execution in Nash ME
292#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
293#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
294pub enum OrderType {
295    Market,
296    Limit,
297    StopMarket,
298    StopLimit,
299}
300
301impl std::fmt::Display for OrderType {
302    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
303        write!(f, "{:?}", self)
304    }
305}
306
307/// The Rate enum describes behavior common to rates/prices.
308
309#[derive(Clone, Debug, PartialEq)]
310pub enum Rate {
311    OrderRate(OrderRate),
312    MaxOrderRate,
313    MinOrderRate,
314    FeeRate(FeeRate),
315    MaxFeeRate,
316    MinFeeRate,
317}
318
319/// Enum wrappers are kind of annoying. This allows us to use .into()
320/// on a value to generate a wrapped value. For example:
321/// `OrderRate::new("1").unwrap().into() => Rate::OrderRate(...)`
322
323impl From<OrderRate> for Rate {
324    fn from(rate: OrderRate) -> Self {
325        Self::OrderRate(rate)
326    }
327}
328
329impl Rate {
330    /// Return new bigdecimal inner value based on Rate
331    pub fn to_bigdecimal(&self) -> Result<BigDecimal> {
332        let num = match self {
333            Self::FeeRate(rate) | Self::OrderRate(rate) => rate.inner.clone(),
334            // FIXME: this could be wrong
335            Self::MaxOrderRate | Self::MaxFeeRate => {
336                // FIXME: be bytes could be wrong for NEO
337                BigDecimal::from_str("0.0025").unwrap()
338            }
339            Self::MinOrderRate | Self::MinFeeRate => 0.into(),
340        };
341        Ok(num)
342    }
343
344    /// Round in the specified precision.
345    pub fn round(&self, precision: i64) -> Result<Self> {
346        match self {
347            Self::OrderRate(rate) => Ok(Self::OrderRate(rate.round(precision))),
348            _ => Err(ProtocolError(
349                "Cannot round a Rate that is not an OrderRate"
350            ))
351        }
352    }
353
354    pub fn invert_rate(&self, precision: Option<u32>) -> Result<Self> {
355        match self {
356            Self::OrderRate(rate) => Ok(Self::OrderRate(rate.invert_rate(precision))),
357            _ => Err(ProtocolError(
358                "Cannot invert a Rate that is not an OrderRate",
359            )),
360        }
361    }
362
363    /// Subtract fee from user by adjusting the order rate downwards
364    pub fn subtract_fee(&self, fee: BigDecimal) -> Result<OrderRate> {
365        let as_order_rate = OrderRate {
366            inner: self.to_bigdecimal()?,
367        };
368        Ok(as_order_rate.subtract_fee(fee))
369    }
370}
371
372/// Order rates impose limits on what trades the smart contract is allowed to
373/// execute. For example, the smart contract will reject a payload that requests
374/// a less favorable amount of asset_to than is imposed by the minimum order rate.
375/// For Sell orders, an order rate represented by currency B an in A/B market. For
376/// Buy orders, an order rate is represented in the protocol by currency A. Note
377/// that OrderRates are always created w.r.t. currency B, then potentially inverted
378/// during payload creation. This is because, unlike the smart contract, the ME
379/// always wants order prices expressed in currency B. In either case, these rates
380/// are encoded as 64 bit integers which take the initial price ratio, multiply
381/// by 10^8, and then drop all precision after the decimal.
382
383#[derive(Clone, Debug, PartialEq)]
384pub struct OrderRate {
385    inner: BigDecimal
386}
387
388impl OrderRate {
389    /// Construct a new OrderRate from a numerical string
390    pub fn new(str_num: &str) -> Result<Self> {
391        BigDecimal::from_str(str_num)
392            .map_err(|_| ProtocolError("String to BigDecimal failed in creating OrderRate"))
393            .map(|inner| Self { inner })
394    }
395
396    /// Create new OrderRate from bigdecimal
397    pub fn from_bigdecimal(decimal: BigDecimal) -> Self {
398        Self { inner: decimal }
399    }
400
401    /// Round the price in the specified precision.
402    pub fn round(&self, precision: i64) -> Self {
403        let inner = self.inner.round(precision);
404        Self { inner }
405    }
406
407    /// Invert the price to units of the other market pair. For example, if price is
408    /// in terms of ETH in an ETH/USDC market, this will convert it to terms of USDC
409    pub fn invert_rate(&self, precision: Option<u32>) -> Self {
410        let mut inverse = self.inner.inverse();
411        if let Some(precision) = precision {
412            let scale_num = BigDecimal::from(u64::pow(10, precision));
413            inverse = (&self.inner * &scale_num).with_scale(0) / scale_num;
414        }
415        Self { inner: inverse }
416    }
417
418    /// Return a new `BigDecimal` based on `OrderRate`
419    pub fn to_bigdecimal(&self) -> BigDecimal {
420        self.inner.clone()
421    }
422
423    /// Subtract fee from user by adjusting the order rate downwards. This will keep track of as
424    /// much precision as BigDecimal is capable of. However, this method is exclusively used by
425    /// the smart contract and will be reduced to an integer in scale of 10^8 before encoding
426    pub fn subtract_fee(&self, fee: BigDecimal) -> Self {
427        let fee_multiplier = BigDecimal::from(1) - fee;
428        let inner = &self.inner * &fee_multiplier;
429        OrderRate { inner }
430    }
431}
432
433type FeeRate = OrderRate;
434
435/// Amount encodes the amount of asset being bought or sold in an order
436/// It is encoded with a precision that depends on the market and asset
437/// being traded. For example, in the ETH/USD market, ETH has a precision
438/// of 4. In an A/B market, amount is always in units of A.
439
440#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
441pub struct Amount {
442    pub precision: u32,
443    pub value: BigDecimal,
444}
445
446impl Amount {
447    /// Construct a new Amount from string and precision
448    pub fn new(str_num: &str, precision: u32) -> Result<Self> {
449        let value = BigDecimal::from_str(str_num)
450            .map_err(|_| ProtocolError("String to BigDecimal failed in creating Amount"))?;
451        let adjust_precision = bigdecimal_to_nash_prec(&value, precision);
452        Ok(Self { value: adjust_precision, precision })
453    }
454
455    pub fn from_bigdecimal(value: BigDecimal, precision: u32) -> Self {
456        Self { value, precision }
457    }
458
459    // FIXME: this is a helper for rate conversion. Can be improved
460    pub fn to_bigdecimal(&self) -> BigDecimal {
461        self.value.clone()
462    }
463}
464
465/// Nonces are 32 bit integers. They increment over time such that data
466/// with lower nonces that has already been observed are rejected by the
467/// matching engine and smart contract.
468
469#[derive(Clone, Debug, Copy, PartialEq, Eq, Hash)]
470pub enum Nonce {
471    Value(u32),
472    Crosschain,
473}
474
475impl Nonce {
476    pub fn crosschain() -> u32 {
477        0xffff_ffff
478    }
479}
480
481// GraphQL wants this as i64
482impl Into<i64> for Nonce {
483    fn into(self) -> i64 {
484        match self {
485            Self::Value(value) => value as i64,
486            Self::Crosschain => Nonce::crosschain() as i64,
487        }
488    }
489}
490
491impl Into<u32> for Nonce {
492    fn into(self) -> u32 {
493        match self {
494            Self::Value(value) => value as u32,
495            Self::Crosschain => Nonce::crosschain() as u32,
496        }
497    }
498}
499
500impl From<u32> for Nonce {
501    fn from(val: u32) -> Self {
502        if val == Nonce::crosschain() {
503            Self::Crosschain
504        } else {
505            Self::Value(val)
506        }
507    }
508}
509
510impl From<&u32> for Nonce {
511    fn from(val: &u32) -> Self {
512        if val == &Nonce::crosschain() {
513            Self::Crosschain
514        } else {
515            Self::Value(*val)
516        }
517    }
518}
519
520#[derive(Clone, Debug, PartialEq)]
521pub enum CandleInterval {
522    FifteenMinute,
523    FiveMinute,
524    FourHour,
525    OneDay,
526    OneHour,
527    OneMinute,
528    OneMonth,
529    OneWeek,
530    SixHour,
531    ThirtyMinute,
532    ThreeHour,
533    TwelveHour,
534}
535
536#[derive(Debug)]
537pub struct Candle {
538    pub a_volume: BigDecimal,
539    pub b_volume: BigDecimal,
540    pub close_price: BigDecimal,
541    pub high_price: BigDecimal,
542    pub low_price: BigDecimal,
543    pub open_price: BigDecimal,
544    pub interval: CandleInterval,
545    pub interval_start: DateTime<Utc>,
546}
547#[derive(Clone, Copy, Debug)]
548pub struct DateTimeRange {
549    pub start: DateTime<Utc>,
550    pub stop: DateTime<Utc>,
551}
552
553/// Status of an order on Nash
554#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq)]
555#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
556pub enum OrderStatus {
557    Pending,
558    Open,
559    Filled,
560    Canceled,
561}
562
563/// Relation of an account to a trade, whether maker, taker, or not related (none)
564#[derive(Clone, Debug, PartialEq)]
565pub enum AccountTradeSide {
566    Maker,
567    Taker,
568    // account played no role in this trade
569    None,
570}
571
572#[derive(Clone, Debug)]
573pub struct Trade {
574    pub id: String,
575    pub taker_order_id: String,
576    pub maker_order_id: String,
577    pub amount: BigDecimal,
578    pub executed_at: DateTime<Utc>,
579    pub account_side: AccountTradeSide,
580    pub maker_fee: BigDecimal,
581    pub taker_fee: BigDecimal,
582    pub maker_recieved: BigDecimal,
583    pub taker_recieved: BigDecimal,
584    pub market: String,
585    pub direction: BuyOrSell,
586    pub limit_price: BigDecimal,
587}
588
589#[derive(Clone, Copy, Debug, PartialEq)]
590pub enum OrderCancellationPolicy {
591    FillOrKill,
592    GoodTilCancelled,
593    GoodTilTime(DateTime<Utc>),
594    ImmediateOrCancel,
595}
596
597#[derive(Clone, Copy, Debug, PartialEq)]
598pub enum OrderCancellationReason {
599    AdminCancelled,
600    Expiration,
601    InvalidForOrderbookState,
602    NoFill,
603    User,
604}
605
606#[derive(Clone, Debug)]
607pub struct Order {
608    pub id: String,
609    pub client_order_id: Option<String>,
610    // Amount the order was placed for
611    pub amount_placed: BigDecimal,
612    // Amount remaining in order
613    pub amount_remaining: BigDecimal,
614    // Amount executed in order
615    pub amount_executed: BigDecimal,
616    pub limit_price: Option<BigDecimal>,
617    pub stop_price: Option<BigDecimal>,
618    pub placed_at: DateTime<Utc>,
619    pub buy_or_sell: BuyOrSell,
620    pub cancellation_policy: Option<OrderCancellationPolicy>,
621    pub cancellation_reason: Option<OrderCancellationReason>,
622    pub market: String,
623    pub order_type: OrderType,
624    pub status: OrderStatus,
625    pub trades: Vec<Trade>,
626}
627
628/// Compressed representation for Order as returned by Orderbook queries and subscriptions
629#[derive(Clone, Debug, Serialize, Deserialize)]
630pub struct OrderbookOrder {
631    pub price: String,
632    pub amount: BigDecimal,
633}
634
635#[cfg(test)]
636mod tests {
637    use super::{BigDecimal, FromStr, OrderRate};
638    use std::convert::TryInto;
639
640    #[test]
641    fn fee_rate_conversion_precision() {
642        let rate = OrderRate::new("150").unwrap();
643        let inverted_rate = rate.invert_rate(None);
644        let minus_fee = inverted_rate.subtract_fee(BigDecimal::from_str("0.0025").unwrap());
645        let payload = minus_fee.to_be_bytes(8).unwrap();
646        assert_eq!(665000, u64::from_be_bytes(payload.try_into().unwrap()));
647    }
648
649    #[test]
650    fn round() {
651        let n = OrderRate::new("26.249999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999996325").expect("Couldn't create OrderRate.");
652        assert_eq!(n.round(3), OrderRate::new("26.25").unwrap());
653        assert_eq!(n.round(2), OrderRate::new("26.25").unwrap());
654        assert_eq!(n.round(1), OrderRate::new("26.2").unwrap());
655        assert_eq!(n.round(0), OrderRate::new("26.0").unwrap());
656        let n = OrderRate::new("14.45652173").unwrap();
657        assert_eq!(n.round(7), OrderRate::new("14.4565217").unwrap());
658        assert_eq!(n.round(6), OrderRate::new("14.456522").unwrap());
659        assert_eq!(n.round(0), OrderRate::new("14.0").unwrap());
660    }
661}