oasis_contract_sdk_types/
token.rs1use std::{convert::TryFrom, fmt};
3
4#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, cbor::Encode)]
6#[cbor(transparent)]
7pub struct Denomination(Vec<u8>);
8
9impl Denomination {
10 pub const MAX_LENGTH: usize = 32;
12 pub const NATIVE: Denomination = Denomination(Vec::new());
14
15 pub fn is_native(&self) -> bool {
17 self.0.is_empty()
18 }
19
20 pub fn into_vec(self) -> Vec<u8> {
22 self.0
23 }
24}
25
26impl AsRef<[u8]> for Denomination {
27 fn as_ref(&self) -> &[u8] {
28 &self.0
29 }
30}
31
32impl fmt::Display for Denomination {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 if self.is_native() {
35 write!(f, "<native>")?;
36 } else {
37 write!(f, "{}", String::from_utf8_lossy(&self.0))?;
38 }
39 Ok(())
40 }
41}
42
43impl std::str::FromStr for Denomination {
44 type Err = Error;
45
46 fn from_str(v: &str) -> Result<Self, Self::Err> {
47 Self::try_from(v.as_bytes())
48 }
49}
50
51impl TryFrom<&[u8]> for Denomination {
52 type Error = Error;
53
54 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
55 if bytes.len() > Self::MAX_LENGTH {
56 return Err(Error::NameTooLong {
57 length: bytes.len(),
58 });
59 }
60 Ok(Self(bytes.to_vec()))
61 }
62}
63
64impl cbor::Decode for Denomination {
65 fn try_default() -> Result<Self, cbor::DecodeError> {
66 Ok(Default::default())
67 }
68
69 fn try_from_cbor_value(value: cbor::Value) -> Result<Self, cbor::DecodeError> {
70 match value {
71 cbor::Value::ByteString(data) => {
72 Self::try_from(data.as_ref()).map_err(|_| cbor::DecodeError::UnexpectedType)
73 }
74 _ => Err(cbor::DecodeError::UnexpectedType),
75 }
76 }
77}
78
79#[cfg(feature = "oasis-runtime-sdk")]
80impl From<oasis_runtime_sdk::types::token::Denomination> for Denomination {
81 fn from(d: oasis_runtime_sdk::types::token::Denomination) -> Self {
82 Self(d.into_vec())
83 }
84}
85
86#[cfg(feature = "oasis-runtime-sdk")]
87impl From<Denomination> for oasis_runtime_sdk::types::token::Denomination {
88 fn from(d: Denomination) -> Self {
89 oasis_runtime_sdk::types::token::Denomination::try_from(d.0.as_ref()).unwrap()
90 }
91}
92
93#[derive(Debug, thiserror::Error)]
94pub enum Error {
95 #[error(
96 "denomination name too long. received length {length} exceeded maximum of {}",
97 Denomination::MAX_LENGTH
98 )]
99 NameTooLong { length: usize },
100}
101
102#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, cbor::Encode, cbor::Decode)]
104pub struct BaseUnits(pub u128, pub Denomination);
105
106impl BaseUnits {
107 pub const fn new(amount: u128, denomination: Denomination) -> Self {
109 BaseUnits(amount, denomination)
110 }
111
112 pub const fn native(amount: u128) -> Self {
114 BaseUnits(amount, Denomination::NATIVE)
115 }
116
117 pub fn amount(&self) -> u128 {
119 self.0
120 }
121
122 pub fn amount_mut(&mut self) -> &mut u128 {
124 &mut self.0
125 }
126
127 pub fn denomination(&self) -> &Denomination {
129 &self.1
130 }
131}
132
133impl fmt::Display for BaseUnits {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 write!(f, "{} {}", self.0, self.1)?;
136 Ok(())
137 }
138}
139
140#[cfg(feature = "oasis-runtime-sdk")]
141impl From<oasis_runtime_sdk::types::token::BaseUnits> for BaseUnits {
142 fn from(a: oasis_runtime_sdk::types::token::BaseUnits) -> Self {
143 Self(a.0, a.1.into())
144 }
145}
146
147#[cfg(feature = "oasis-runtime-sdk")]
148impl From<&oasis_runtime_sdk::types::token::BaseUnits> for BaseUnits {
149 fn from(a: &oasis_runtime_sdk::types::token::BaseUnits) -> Self {
150 Self(a.0, a.1.clone().into())
151 }
152}
153
154#[cfg(test)]
155mod test {
156 use super::*;
157
158 #[test]
159 fn test_basic() {
160 let cases = vec![
161 (0, Denomination::NATIVE, "824040"),
163 (1, Denomination::NATIVE, "82410140"),
164 (1000, Denomination::NATIVE, "824203e840"),
165 (0, "test".parse().unwrap(), "82404474657374"),
167 (1, "test".parse().unwrap(), "8241014474657374"),
168 (1000, "test".parse().unwrap(), "824203e84474657374"),
169 ];
170
171 for tc in cases {
172 let token = BaseUnits::new(tc.0, tc.1);
173 let enc = cbor::to_vec(token.clone());
174 assert_eq!(hex::encode(&enc), tc.2, "serialization should match");
175
176 let dec: BaseUnits = cbor::from_slice(&enc).expect("deserialization should succeed");
177 assert_eq!(dec, token, "serialization should round-trip");
178 }
179 }
180
181 #[test]
182 fn test_decoding_denomination() {
183 macro_rules! assert_rountrip_ok {
184 ($bytes:expr) => {
185 let enc = cbor::to_vec($bytes.to_vec());
186 let dec: Denomination = cbor::from_slice(&enc).unwrap();
187 assert_eq!(dec, Denomination::try_from($bytes).unwrap());
188 assert_eq!(dec.0, $bytes);
189 };
190 }
191
192 let bytes_fixture = vec![42u8; Denomination::MAX_LENGTH + 1];
193
194 assert_rountrip_ok!(&bytes_fixture[0..0]);
195 assert_rountrip_ok!(&bytes_fixture[0..1]);
196 assert_rountrip_ok!(&bytes_fixture[0..Denomination::MAX_LENGTH]);
197
198 let dec_result: Result<Denomination, _> = cbor::from_slice(&cbor::to_vec(bytes_fixture));
200 assert!(dec_result.is_err());
201 }
202}