ergotree_ir/chain/
token.rs

1//! Token related types
2
3use crate::serialization::SigmaSerializeResult;
4use crate::serialization::{
5    sigma_byte_reader::SigmaByteRead, sigma_byte_writer::SigmaByteWrite, SigmaParsingError,
6    SigmaSerializable,
7};
8use std::convert::TryFrom;
9
10use super::ergo_box::BoxId;
11use derive_more::From;
12use derive_more::Into;
13use ergo_chain_types::{Digest32, DigestNError};
14use sigma_ser::ScorexSerializable;
15use thiserror::Error;
16
17/// newtype for token id
18#[derive(PartialEq, Eq, Hash, Debug, Copy, Clone, From, Into)]
19#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
20pub struct TokenId(Digest32);
21
22impl TokenId {
23    /// token id size in bytes
24    pub const SIZE: usize = Digest32::SIZE;
25
26    /// Parse TokenId from base64 encoded string
27    pub fn from_base64(s: &str) -> Result<TokenId, DigestNError> {
28        Digest32::from_base64(s).map(Into::into)
29    }
30}
31
32impl From<BoxId> for TokenId {
33    fn from(i: BoxId) -> Self {
34        TokenId(i.into())
35    }
36}
37
38impl From<TokenId> for Vec<i8> {
39    fn from(v: TokenId) -> Self {
40        v.0.into()
41    }
42}
43
44impl From<TokenId> for Vec<u8> {
45    fn from(v: TokenId) -> Self {
46        v.0.into()
47    }
48}
49
50impl AsRef<[u8]> for TokenId {
51    fn as_ref(&self) -> &[u8] {
52        self.0.as_ref()
53    }
54}
55
56impl From<TokenId> for String {
57    fn from(v: TokenId) -> Self {
58        v.0.into()
59    }
60}
61
62impl SigmaSerializable for TokenId {
63    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
64        Ok(self.0.scorex_serialize(w)?)
65    }
66    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
67        Ok(Self(Digest32::scorex_parse(r)?))
68    }
69}
70
71/// Token amount with bound checks
72#[cfg(not(feature = "json"))]
73#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
74pub struct TokenAmount(u64);
75
76/// Token amount with bound checks
77#[cfg(feature = "json")]
78#[derive(
79    serde::Serialize, serde::Deserialize, PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord,
80)]
81#[serde(
82    try_from = "crate::chain::json::token::TokenAmountJson",
83    into = "crate::chain::json::token::TokenAmountJson"
84)]
85pub struct TokenAmount(pub(crate) u64);
86
87impl TokenAmount {
88    /// minimal allowed value
89    pub const MIN_RAW: u64 = 1;
90    /// maximal allowed value
91    pub const MAX_RAW: u64 = i64::MAX as u64;
92
93    /// minimal allowed value
94    pub const MIN: TokenAmount = TokenAmount(Self::MIN_RAW);
95
96    /// Addition with overflow check
97    pub fn checked_add(&self, rhs: &Self) -> Result<Self, TokenAmountError> {
98        let raw = self
99            .0
100            .checked_add(rhs.0)
101            .ok_or(TokenAmountError::Overflow)?;
102        if raw > Self::MAX_RAW {
103            Err(TokenAmountError::OutOfBounds(raw as i64))
104        } else {
105            Ok(Self(raw))
106        }
107    }
108
109    /// Subtraction with overflow and bounds check
110    pub fn checked_sub(&self, rhs: &Self) -> Result<Self, TokenAmountError> {
111        let raw = self
112            .0
113            .checked_sub(rhs.0)
114            .ok_or(TokenAmountError::Overflow)?;
115        if raw < Self::MIN_RAW {
116            Err(TokenAmountError::OutOfBounds(raw as i64))
117        } else {
118            Ok(Self(raw))
119        }
120    }
121
122    /// Get the value as u64
123    pub fn as_u64(&self) -> &u64 {
124        &self.0
125    }
126}
127
128/// BoxValue errors
129#[derive(Error, Eq, PartialEq, Debug, Clone)]
130pub enum TokenAmountError {
131    /// Value is out of bounds
132    #[error("Token amount is out of bounds: {0}")]
133    OutOfBounds(i64),
134    /// Overflow
135    #[error("Overflow")]
136    Overflow,
137}
138
139impl TryFrom<u64> for TokenAmount {
140    type Error = TokenAmountError;
141
142    fn try_from(v: u64) -> Result<Self, Self::Error> {
143        if (TokenAmount::MIN_RAW..=TokenAmount::MAX_RAW).contains(&v) {
144            Ok(TokenAmount(v))
145        } else {
146            Err(TokenAmountError::OutOfBounds(v as i64))
147        }
148    }
149}
150
151impl From<TokenAmount> for u64 {
152    fn from(ta: TokenAmount) -> Self {
153        ta.0
154    }
155}
156
157impl From<TokenAmount> for i64 {
158    fn from(ta: TokenAmount) -> Self {
159        ta.0 as i64
160    }
161}
162
163impl From<Token> for (Vec<i8>, i64) {
164    fn from(t: Token) -> Self {
165        (t.token_id.into(), t.amount.into())
166    }
167}
168
169/// Token represented with token id paired with it's amount
170#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
171#[derive(PartialEq, Eq, Debug, Clone)]
172pub struct Token {
173    /// token id
174    #[cfg_attr(feature = "json", serde(rename = "tokenId"))]
175    pub token_id: TokenId,
176    /// token amount
177    #[cfg_attr(feature = "json", serde(rename = "amount"))]
178    pub amount: TokenAmount,
179}
180
181impl From<(TokenId, TokenAmount)> for Token {
182    fn from(token_pair: (TokenId, TokenAmount)) -> Self {
183        Token {
184            token_id: token_pair.0,
185            amount: token_pair.1,
186        }
187    }
188}
189
190/// Arbitrary
191#[allow(clippy::unwrap_used)]
192#[cfg(feature = "arbitrary")]
193pub mod arbitrary {
194    use ergo_chain_types::Digest32;
195    use proptest::prelude::*;
196
197    use super::Token;
198    use super::TokenAmount;
199    use super::TokenId;
200
201    use std::convert::TryFrom;
202
203    /// How to generate a token id
204    #[derive(Default)]
205    pub enum ArbTokenIdParam {
206        /// From a list of predefined token ids
207        #[default]
208        Predef,
209        /// Arbitrary token id
210        Arbitrary,
211    }
212
213    impl Arbitrary for TokenId {
214        type Parameters = ArbTokenIdParam;
215
216        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
217            match args {
218                ArbTokenIdParam::Predef => prop_oneof![
219                    Just(TokenId::from(
220                        Digest32::try_from(
221                            "3130a82e45842aebb888742868e055e2f554ab7d92f233f2c828ed4a43793710"
222                                .to_string()
223                        )
224                        .unwrap()
225                    )),
226                    Just(TokenId::from(
227                        Digest32::try_from(
228                            "e7321ffb4ec5d71deb3110eb1ac09612b9cf57445acab1e0e3b1222d5b5a6c60"
229                                .to_string()
230                        )
231                        .unwrap()
232                    )),
233                    Just(TokenId::from(
234                        Digest32::try_from(
235                            "ad62f6dd92e7dc850bc406770dfac9a943dd221a7fb440b7b2bcc7d3149c1792"
236                                .to_string()
237                        )
238                        .unwrap()
239                    ))
240                ]
241                .boxed(),
242                ArbTokenIdParam::Arbitrary => (any::<Digest32>()).prop_map_into().boxed(),
243            }
244        }
245
246        type Strategy = BoxedStrategy<Self>;
247    }
248
249    impl Arbitrary for TokenAmount {
250        type Parameters = ();
251
252        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
253            (TokenAmount::MIN_RAW..=TokenAmount::MAX_RAW / 100000)
254                .prop_map(Self)
255                .boxed()
256        }
257        type Strategy = BoxedStrategy<Self>;
258    }
259
260    impl Default for TokenAmount {
261        fn default() -> Self {
262            TokenAmount::MIN
263        }
264    }
265
266    impl Arbitrary for Token {
267        type Parameters = ArbTokenIdParam;
268
269        fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
270            (any_with::<TokenId>(args), any::<TokenAmount>())
271                .prop_map(Self::from)
272                .boxed()
273        }
274        type Strategy = BoxedStrategy<Self>;
275    }
276}
277
278#[allow(clippy::panic)]
279#[cfg(test)]
280mod tests {
281
282    use crate::chain::token::TokenId;
283    use crate::serialization::sigma_serialize_roundtrip;
284    use proptest::prelude::*;
285
286    proptest! {
287
288        #[test]
289        fn token_id_roundtrip(v in any::<TokenId>()) {
290            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
291        }
292    }
293}