ergotree_ir/chain/
token.rs1use 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#[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 pub const SIZE: usize = Digest32::SIZE;
25
26 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#[cfg(not(feature = "json"))]
73#[derive(PartialEq, Eq, Hash, Debug, Clone, Copy, PartialOrd, Ord)]
74pub struct TokenAmount(u64);
75
76#[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 pub const MIN_RAW: u64 = 1;
90 pub const MAX_RAW: u64 = i64::MAX as u64;
92
93 pub const MIN: TokenAmount = TokenAmount(Self::MIN_RAW);
95
96 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 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 pub fn as_u64(&self) -> &u64 {
124 &self.0
125 }
126}
127
128#[derive(Error, Eq, PartialEq, Debug, Clone)]
130pub enum TokenAmountError {
131 #[error("Token amount is out of bounds: {0}")]
133 OutOfBounds(i64),
134 #[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#[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
171#[derive(PartialEq, Eq, Debug, Clone)]
172pub struct Token {
173 #[cfg_attr(feature = "json", serde(rename = "tokenId"))]
175 pub token_id: TokenId,
176 #[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#[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 #[derive(Default)]
205 pub enum ArbTokenIdParam {
206 #[default]
208 Predef,
209 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}