rgbstd/interface/
rgb25.rs

1// RGB standard library for working with smart contracts on Bitcoin & Lightning
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2023 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2023 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
22#![allow(unused_braces)]
23
24use std::fmt::Debug;
25
26use bp::bc::stl::bp_tx_stl;
27use strict_encoding::{StrictDumb, StrictEncode};
28use strict_types::stl::std_stl;
29use strict_types::{CompileError, LibBuilder, TypeLib};
30
31use super::{
32    AssignIface, GenesisIface, GlobalIface, Iface, OwnedIface, Req, TransitionIface, VerNo,
33};
34use crate::interface::{ArgSpec, ContractIface};
35use crate::stl::{rgb_contract_stl, Amount, ContractData, Details, Name, Precision, StandardTypes};
36
37pub const LIB_NAME_RGB25: &str = "RGB25";
38/// Strict types id for the library providing data types for RGB25 interface.
39pub const LIB_ID_RGB25: &str =
40    "urn:ubideco:stl:4JmGrg7oTgwuCQtyC4ezC38ToHMzgMCVS5kMSDPwo2ee#camera-betty-bank";
41
42const SUPPLY_MISMATCH: u8 = 1;
43const NON_EQUAL_AMOUNTS: u8 = 2;
44const INVALID_PROOF: u8 = 3;
45const INSUFFICIENT_RESERVES: u8 = 4;
46const INSUFFICIENT_COVERAGE: u8 = 5;
47
48#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
49#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
50#[strict_type(lib = LIB_NAME_RGB25, tags = repr, into_u8, try_from_u8)]
51#[repr(u8)]
52pub enum Error {
53    #[strict_type(dumb)]
54    SupplyMismatch = SUPPLY_MISMATCH,
55    NonEqualAmounts = NON_EQUAL_AMOUNTS,
56    InvalidProof = INVALID_PROOF,
57    InsufficientReserves = INSUFFICIENT_RESERVES,
58    InsufficientCoverage = INSUFFICIENT_COVERAGE,
59}
60
61fn _rgb25_stl() -> Result<TypeLib, CompileError> {
62    LibBuilder::new(libname!(LIB_NAME_RGB25), tiny_bset! {
63        std_stl().to_dependency(),
64        bp_tx_stl().to_dependency(),
65        rgb_contract_stl().to_dependency(),
66    })
67    .transpile::<Error>()
68    .compile()
69}
70
71/// Generates strict type library providing data types for RGB25 interface.
72pub fn rgb25_stl() -> TypeLib { _rgb25_stl().expect("invalid strict type RGB25 library") }
73
74pub fn rgb25() -> Iface {
75    let types = StandardTypes::with(rgb25_stl());
76
77    Iface {
78        version: VerNo::V1,
79        name: tn!("RGB25"),
80        global_state: tiny_bmap! {
81            fname!("name") => GlobalIface::required(types.get("RGBContract.Name")),
82            fname!("details") => GlobalIface::optional(types.get("RGBContract.Details")),
83            fname!("precision") => GlobalIface::required(types.get("RGBContract.Precision")),
84            fname!("data") => GlobalIface::required(types.get("RGBContract.ContractData")),
85            fname!("created") => GlobalIface::required(types.get("RGBContract.Timestamp")),
86            fname!("issuedSupply") => GlobalIface::required(types.get("RGBContract.Amount")),
87            fname!("burnedSupply") => GlobalIface::none_or_many(types.get("RGBContract.Amount")),
88        },
89        assignments: tiny_bmap! {
90            fname!("assetOwner") => AssignIface::private(OwnedIface::Amount, Req::OneOrMore),
91            fname!("burnRight") => AssignIface::public(OwnedIface::Rights, Req::NoneOrMore),
92        },
93        valencies: none!(),
94        genesis: GenesisIface {
95            metadata: Some(types.get("RGBContract.IssueMeta")),
96            global: tiny_bmap! {
97                fname!("name") => ArgSpec::required(),
98                fname!("details") => ArgSpec::optional(),
99                fname!("precision") => ArgSpec::required(),
100                fname!("data") => ArgSpec::required(),
101                fname!("created") => ArgSpec::required(),
102                fname!("issuedSupply") => ArgSpec::required(),
103            },
104            assignments: tiny_bmap! {
105                fname!("assetOwner") => ArgSpec::non_empty(),
106            },
107            valencies: none!(),
108            errors: tiny_bset! {
109                SUPPLY_MISMATCH,
110                INVALID_PROOF,
111                INSUFFICIENT_RESERVES
112            },
113        },
114        transitions: tiny_bmap! {
115            tn!("Transfer") => TransitionIface {
116                optional: false,
117                metadata: None,
118                globals: none!(),
119                inputs: tiny_bmap! {
120                    fname!("previous") => ArgSpec::from_non_empty("assetOwner"),
121                },
122                assignments: tiny_bmap! {
123                    fname!("beneficiary") => ArgSpec::from_non_empty("assetOwner"),
124                },
125                valencies: none!(),
126                errors: tiny_bset! {
127                    NON_EQUAL_AMOUNTS
128                },
129                default_assignment: Some(fname!("beneficiary")),
130            },
131            tn!("Burn") => TransitionIface {
132                optional: true,
133                metadata: Some(types.get("RGBContract.BurnMeta")),
134                globals: tiny_bmap! {
135                    fname!("burnedSupply") => ArgSpec::required(),
136                },
137                inputs: tiny_bmap! {
138                    fname!("used") => ArgSpec::from_required("burnRight"),
139                },
140                assignments: tiny_bmap! {
141                    fname!("future") => ArgSpec::from_optional("burnRight"),
142                },
143                valencies: none!(),
144                errors: tiny_bset! {
145                    SUPPLY_MISMATCH,
146                    INVALID_PROOF,
147                    INSUFFICIENT_COVERAGE
148                },
149                default_assignment: None,
150            },
151        },
152        extensions: none!(),
153        error_type: types.get("RGB25.Error"),
154        default_operation: Some(tn!("Transfer")),
155        type_system: types.type_system(),
156    }
157}
158
159#[derive(Wrapper, WrapperMut, Clone, Eq, PartialEq, Debug)]
160#[wrapper(Deref)]
161#[wrapper_mut(DerefMut)]
162pub struct Rgb25(ContractIface);
163
164impl From<ContractIface> for Rgb25 {
165    fn from(iface: ContractIface) -> Self {
166        if iface.iface.iface_id != rgb25().iface_id() {
167            panic!("the provided interface is not RGB25 interface");
168        }
169        Self(iface)
170    }
171}
172
173impl Rgb25 {
174    pub fn name(&self) -> Name {
175        let strict_val = &self
176            .0
177            .global("name")
178            .expect("RGB25 interface requires global `name`")[0];
179        Name::from_strict_val_unchecked(strict_val)
180    }
181
182    pub fn details(&self) -> Option<Details> {
183        let strict_val = &self
184            .0
185            .global("details")
186            .expect("RGB25 interface requires global `details`");
187        if strict_val.len() == 0 {
188            None
189        } else {
190            Some(Details::from_strict_val_unchecked(&strict_val[0]))
191        }
192    }
193
194    pub fn precision(&self) -> Precision {
195        let strict_val = &self
196            .0
197            .global("precision")
198            .expect("RGB25 interface requires global `precision`")[0];
199        Precision::from_strict_val_unchecked(strict_val)
200    }
201
202    pub fn total_issued_supply(&self) -> Amount {
203        self.0
204            .global("issuedSupply")
205            .expect("RGB25 interface requires global `issuedSupply`")
206            .iter()
207            .map(Amount::from_strict_val_unchecked)
208            .sum()
209    }
210
211    pub fn total_burned_supply(&self) -> Amount {
212        self.0
213            .global("burnedSupply")
214            .unwrap_or_default()
215            .iter()
216            .map(Amount::from_strict_val_unchecked)
217            .sum()
218    }
219
220    pub fn contract_data(&self) -> ContractData {
221        let strict_val = &self
222            .0
223            .global("data")
224            .expect("RGB25 interface requires global `data`")[0];
225        ContractData::from_strict_val_unchecked(strict_val)
226    }
227}
228
229#[cfg(test)]
230mod test {
231    use super::*;
232    use crate::containers::BindleContent;
233
234    const RGB25: &str = include_str!("../../tests/data/rgb25.rgba");
235
236    #[test]
237    fn lib_id() {
238        let lib = rgb25_stl();
239        assert_eq!(lib.id().to_string(), LIB_ID_RGB25);
240    }
241
242    #[test]
243    fn iface_creation() { rgb25(); }
244
245    #[test]
246    fn iface_bindle() {
247        assert_eq!(format!("{}", rgb25().bindle()), RGB25);
248    }
249}