rgbinvoice/
data.rs

1// RGB wallet library for smart contracts on Bitcoin & Lightning network
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use 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    /// invalid token index {0}.
57    InvalidIndex(String),
58
59    #[display(doc_comments)]
60    /// invalid fraction {0}.
61    InvalidFraction(String),
62
63    #[display(doc_comments)]
64    /// allocation must have format <fraction>@<token_index>.
65    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        // note that the strict number is u128 but not u64
218        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}