1use std::str::FromStr;
23
24use rgb::RevealedData;
25#[cfg(feature = "serde")]
26use serde::{Deserialize, Serialize};
27use strict_encoding::{StrictDeserialize, StrictSerialize};
28use strict_types::encoding::DefaultBasedStrictDumb;
29use strict_types::StrictVal;
30
31use crate::LIB_NAME_RGB_CONTRACT;
32
33#[derive(Clone, Eq, PartialEq, Hash, Debug, Display)]
34#[cfg_attr(
35 feature = "serde",
36 derive(Serialize, Deserialize),
37 serde(crate = "serde_crate", rename_all = "camelCase")
38)]
39pub enum NonFungible {
40 #[display(inner)]
41 FractionedToken(Allocation),
42}
43
44impl FromStr for NonFungible {
45 type Err = AllocationParseError;
46 fn from_str(s: &str) -> Result<Self, Self::Err> {
47 let allocation = Allocation::from_str(s)?;
48 Ok(NonFungible::FractionedToken(allocation))
49 }
50}
51
52#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
53#[display(inner)]
54pub enum AllocationParseError {
55 #[display(doc_comments)]
56 InvalidIndex(String),
58
59 #[display(doc_comments)]
60 InvalidFraction(String),
62
63 #[display(doc_comments)]
64 WrongFormat,
66}
67
68#[derive(
69 Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From
70)]
71#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)]
72#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)]
73#[derive(StrictType, StrictEncode, StrictDecode)]
74#[strict_type(lib = LIB_NAME_RGB_CONTRACT)]
75#[cfg_attr(
76 feature = "serde",
77 derive(Serialize, Deserialize),
78 serde(crate = "serde_crate", transparent)
79)]
80pub struct TokenIndex(u32);
81
82impl DefaultBasedStrictDumb for TokenIndex {}
83
84#[derive(Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
85#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)]
86#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)]
87#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)]
88#[strict_type(lib = LIB_NAME_RGB_CONTRACT)]
89#[cfg_attr(
90 feature = "serde",
91 derive(Serialize, Deserialize),
92 serde(crate = "serde_crate", transparent)
93)]
94pub struct OwnedFraction(u64);
95
96impl OwnedFraction {
97 pub const ZERO: Self = OwnedFraction(0);
98
99 pub fn from_strict_val_unchecked(value: &StrictVal) -> Self {
100 value.unwrap_uint::<u64>().into()
101 }
102
103 pub fn value(self) -> u64 { self.0 }
104
105 pub fn saturating_add(&self, other: impl Into<Self>) -> Self {
106 self.0.saturating_add(other.into().0).into()
107 }
108 pub fn saturating_sub(&self, other: impl Into<Self>) -> Self {
109 self.0.saturating_sub(other.into().0).into()
110 }
111
112 pub fn saturating_add_assign(&mut self, other: impl Into<Self>) {
113 *self = self.0.saturating_add(other.into().0).into();
114 }
115 pub fn saturating_sub_assign(&mut self, other: impl Into<Self>) {
116 *self = self.0.saturating_sub(other.into().0).into();
117 }
118
119 #[must_use]
120 pub fn checked_add(&self, other: impl Into<Self>) -> Option<Self> {
121 self.0.checked_add(other.into().0).map(Self)
122 }
123 #[must_use]
124 pub fn checked_sub(&self, other: impl Into<Self>) -> Option<Self> {
125 self.0.checked_sub(other.into().0).map(Self)
126 }
127
128 #[must_use]
129 pub fn checked_add_assign(&mut self, other: impl Into<Self>) -> Option<()> {
130 *self = self.0.checked_add(other.into().0).map(Self)?;
131 Some(())
132 }
133 #[must_use]
134 pub fn checked_sub_assign(&mut self, other: impl Into<Self>) -> Option<()> {
135 *self = self.0.checked_sub(other.into().0).map(Self)?;
136 Some(())
137 }
138}
139
140#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)]
141#[display("{1}@{0}")]
142#[derive(StrictType, StrictEncode, StrictDecode, StrictDumb)]
143#[strict_type(lib = LIB_NAME_RGB_CONTRACT)]
144#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
145pub struct Allocation(TokenIndex, OwnedFraction);
146
147impl Allocation {
148 pub fn with(index: impl Into<TokenIndex>, fraction: impl Into<OwnedFraction>) -> Allocation {
149 Allocation(index.into(), fraction.into())
150 }
151
152 pub fn token_index(self) -> TokenIndex { self.0 }
153
154 pub fn fraction(self) -> OwnedFraction { self.1 }
155}
156
157impl StrictSerialize for Allocation {}
158impl StrictDeserialize for Allocation {}
159
160impl From<RevealedData> for Allocation {
161 fn from(data: RevealedData) -> Self {
162 Allocation::from_strict_serialized(data.into()).expect("invalid allocation data")
163 }
164}
165
166impl From<Allocation> for RevealedData {
167 fn from(allocation: Allocation) -> Self {
168 RevealedData::from(
169 allocation
170 .to_strict_serialized()
171 .expect("invalid allocation data"),
172 )
173 }
174}
175
176impl FromStr for Allocation {
177 type Err = AllocationParseError;
178
179 fn from_str(s: &str) -> Result<Self, Self::Err> {
180 if !s.contains('@') {
181 return Err(AllocationParseError::WrongFormat);
182 }
183
184 match s.split_once('@') {
185 Some((fraction, token_index)) => Ok(Allocation(
186 token_index
187 .parse()
188 .map_err(|_| AllocationParseError::InvalidIndex(token_index.to_owned()))?,
189 fraction
190 .parse()
191 .map_err(|_| AllocationParseError::InvalidFraction(fraction.to_lowercase()))?,
192 )),
193 None => Err(AllocationParseError::WrongFormat),
194 }
195 }
196}
197
198#[cfg(test)]
199mod test {
200 use strict_types::value::StrictNum;
201
202 use super::*;
203
204 #[test]
205 fn owned_fraction_from_str() {
206 let owned_fraction = match OwnedFraction::from_str("1") {
207 Ok(value) => value,
208 Err(_) => OwnedFraction::ZERO,
209 };
210
211 assert_eq!(owned_fraction.value(), 1);
212 assert_eq!(format!("{owned_fraction}"), "1");
213 }
214
215 #[test]
216 fn owned_fraction_from_strict_val() {
217 let owned_fraction =
219 OwnedFraction::from_strict_val_unchecked(&StrictVal::Number(StrictNum::Uint(1)));
220
221 assert_eq!(owned_fraction.value(), 1);
222 assert_eq!(format!("{owned_fraction}"), "1");
223 }
224
225 #[test]
226 fn owned_fraction_add_assign() {
227 let mut owned_fraction = match OwnedFraction::from_str("1") {
228 Ok(value) => value,
229 Err(_) => OwnedFraction::ZERO,
230 };
231
232 let _ = owned_fraction.checked_add_assign(OwnedFraction::ZERO);
233 assert_eq!(owned_fraction.value(), 1);
234 assert_eq!(format!("{owned_fraction}"), "1");
235 }
236
237 #[test]
238 fn owned_fraction_add() {
239 let owned_fraction = match OwnedFraction::from_str("1") {
240 Ok(value) => value,
241 Err(_) => OwnedFraction::ZERO,
242 };
243
244 let owned = match owned_fraction.checked_add(OwnedFraction::ZERO) {
245 Some(value) => value,
246 None => OwnedFraction::ZERO,
247 };
248 assert_eq!(owned.value(), 1);
249 assert_eq!(format!("{owned}"), "1");
250 }
251
252 #[test]
253 fn owned_fraction_sub() {
254 let owned_fraction = match OwnedFraction::from_str("1") {
255 Ok(value) => value,
256 Err(_) => OwnedFraction::ZERO,
257 };
258
259 let other_fraction = match OwnedFraction::from_str("1") {
260 Ok(value) => value,
261 Err(_) => OwnedFraction::ZERO,
262 };
263
264 let owned = match owned_fraction.checked_sub(other_fraction) {
265 Some(value) => value,
266 None => OwnedFraction::ZERO,
267 };
268 assert_eq!(owned.value(), 0);
269 assert_eq!(format!("{owned}"), "0");
270 }
271
272 #[test]
273 fn owned_fraction_sub_assign() {
274 let mut owned_fraction = match OwnedFraction::from_str("1") {
275 Ok(value) => value,
276 Err(_) => OwnedFraction::ZERO,
277 };
278
279 let other_fraction = match OwnedFraction::from_str("1") {
280 Ok(value) => value,
281 Err(_) => OwnedFraction::ZERO,
282 };
283
284 let _ = owned_fraction.checked_sub_assign(other_fraction);
285 assert_eq!(owned_fraction.value(), 0);
286 assert_eq!(format!("{owned_fraction}"), "0");
287 }
288}