Skip to main content

solana_exchange_program/
exchange_state.rs

1use {
2    serde_derive::{Deserialize, Serialize},
3    solana_sdk::pubkey::Pubkey,
4    std::{error, fmt},
5};
6
7/// Fixed-point scaler, 10 = one base 10 digit to the right of the decimal, 100 = 2, ...
8/// Used by both price and amount in their fixed point representation
9pub const SCALER: u64 = 1000;
10
11#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
12pub enum ExchangeError {
13    InvalidTrade(String),
14}
15impl error::Error for ExchangeError {}
16impl fmt::Display for ExchangeError {
17    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18        match self {
19            ExchangeError::InvalidTrade(s) => write!(f, "{}", s),
20        }
21    }
22}
23
24/// Supported token types
25#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
26pub enum Token {
27    A,
28    B,
29    C,
30    D,
31}
32impl Default for Token {
33    fn default() -> Self {
34        Token::A
35    }
36}
37
38// Values of tokens, could be quantities, prices, etc...
39#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
40#[allow(non_snake_case)]
41pub struct Tokens {
42    pub A: u64,
43    pub B: u64,
44    pub C: u64,
45    pub D: u64,
46}
47impl Tokens {
48    pub fn new(a: u64, b: u64, c: u64, d: u64) -> Self {
49        Self {
50            A: a,
51            B: b,
52            C: c,
53            D: d,
54        }
55    }
56}
57impl std::ops::Index<Token> for Tokens {
58    type Output = u64;
59    fn index(&self, t: Token) -> &u64 {
60        match t {
61            Token::A => &self.A,
62            Token::B => &self.B,
63            Token::C => &self.C,
64            Token::D => &self.D,
65        }
66    }
67}
68impl std::ops::IndexMut<Token> for Tokens {
69    fn index_mut(&mut self, t: Token) -> &mut u64 {
70        match t {
71            Token::A => &mut self.A,
72            Token::B => &mut self.B,
73            Token::C => &mut self.C,
74            Token::D => &mut self.D,
75        }
76    }
77}
78
79#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
80#[allow(non_snake_case)]
81pub struct AssetPair {
82    // represents a pair of two token enums that defines a market
83    pub Base: Token,
84    // "primary" token and numerator for pricing purposes
85    pub Quote: Token,
86    // "secondary" token and denominator for pricing purposes
87}
88
89impl Default for AssetPair {
90    fn default() -> AssetPair {
91        AssetPair {
92            Base: Token::A,
93            Quote: Token::B,
94        }
95    }
96}
97
98/// Token accounts are populated with this structure
99#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
100pub struct TokenAccountInfo {
101    /// Investor who owns this account
102    pub owner: Pubkey,
103    /// Current number of tokens this account holds
104    pub tokens: Tokens,
105}
106
107impl TokenAccountInfo {
108    pub fn owner(mut self, owner: &Pubkey) -> Self {
109        self.owner = *owner;
110        self
111    }
112    pub fn tokens(mut self, a: u64, b: u64, c: u64, d: u64) -> Self {
113        self.tokens = Tokens {
114            A: a,
115            B: b,
116            C: c,
117            D: d,
118        };
119        self
120    }
121}
122
123/// side of the exchange between two tokens in a pair
124#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
125pub enum OrderSide {
126    /// Offer the Base asset and Accept the Quote asset
127    Ask, // to
128    /// Offer the Quote asset and Accept the Base asset
129    Bid, // from
130}
131impl fmt::Display for OrderSide {
132    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133        match self {
134            OrderSide::Ask => write!(f, "A")?,
135            OrderSide::Bid => write!(f, "B")?,
136        }
137        Ok(())
138    }
139}
140
141/// Trade accounts are populated with this structure
142#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
143pub struct OrderInfo {
144    /// Owner of the trade order
145    pub owner: Pubkey,
146    /// side of the order in the market (bid/ask)
147    pub side: OrderSide,
148    /// Token pair indicating two tokens to exchange, first is primary
149    pub pair: AssetPair,
150    /// Number of tokens to exchange; primary or secondary depending on side.  Once
151    /// this number goes to zero this trade order will be converted into a regular token account
152    pub tokens: u64,
153    /// Scaled price of the secondary token given the primary is equal to the scale value
154    /// If scale is 1 and price is 2 then ratio is 1:2 or 1 primary token for 2 secondary tokens
155    pub price: u64,
156    /// Number of tokens that have been settled so far.  These nay be transferred to another
157    /// token account by the owner.
158    pub tokens_settled: u64,
159}
160impl Default for OrderInfo {
161    fn default() -> Self {
162        Self {
163            owner: Pubkey::default(),
164            pair: AssetPair::default(),
165            side: OrderSide::Ask,
166            tokens: 0,
167            price: 0,
168            tokens_settled: 0,
169        }
170    }
171}
172impl OrderInfo {
173    pub fn pair(mut self, pair: AssetPair) -> Self {
174        self.pair = pair;
175        self
176    }
177    pub fn side(mut self, side: OrderSide) -> Self {
178        self.side = side;
179        self
180    }
181    pub fn tokens(mut self, tokens: u64) -> Self {
182        self.tokens = tokens;
183        self
184    }
185    pub fn price(mut self, price: u64) -> Self {
186        self.price = price;
187        self
188    }
189}
190
191pub fn check_trade(side: OrderSide, tokens: u64, price: u64) -> Result<(), ExchangeError> {
192    match side {
193        OrderSide::Ask => {
194            if tokens * price / SCALER == 0 {
195                return Err(ExchangeError::InvalidTrade(format!(
196                    "To trade of {} for {}/{} results in 0 tradeable tokens",
197                    tokens, SCALER, price
198                )));
199            }
200        }
201        OrderSide::Bid => {
202            if tokens * SCALER / price == 0 {
203                return Err(ExchangeError::InvalidTrade(format!(
204                    "From trade of {} for {}?{} results in 0 tradeable tokens",
205                    tokens, SCALER, price
206                )));
207            }
208        }
209    }
210    Ok(())
211}
212
213/// Type of exchange account, account's user data is populated with this enum
214#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
215pub enum ExchangeState {
216    /// Account's data is unallocated
217    Unallocated,
218    // Token account
219    Account(TokenAccountInfo),
220    // Trade order account
221    Trade(OrderInfo),
222    Invalid,
223}
224impl Default for ExchangeState {
225    fn default() -> Self {
226        ExchangeState::Unallocated
227    }
228}