rgbstd/interface/
rgb21.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 2019-2023 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-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::collections::BTreeMap;
25use std::fmt::{self, Debug, Formatter};
26use std::str::FromStr;
27
28use amplify::ascii::AsciiString;
29use amplify::confinement::{Confined, NonEmptyVec, SmallBlob};
30use bp::bc::stl::bp_tx_stl;
31use strict_encoding::stl::AsciiPrintable;
32use strict_encoding::{
33    InvalidIdent, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize, TypedWrite,
34};
35use strict_types::stl::std_stl;
36use strict_types::{CompileError, LibBuilder, TypeLib};
37
38use super::{
39    AssignIface, GenesisIface, GlobalIface, Iface, OwnedIface, Req, TransitionIface, VerNo,
40};
41use crate::interface::{ArgSpec, ContractIface};
42use crate::stl::{
43    rgb_contract_stl, Attachment, Details, DivisibleAssetSpec, MediaType, Name, ProofOfReserves,
44    StandardTypes, Ticker,
45};
46
47pub const LIB_NAME_RGB21: &str = "RGB21";
48/// Strict types id for the library providing data types for RGB21 interface.
49pub const LIB_ID_RGB21: &str =
50    "urn:ubideco:stl:3miGC5GTW58CeuGJgomApmdjm8N6Yu6YuuURS8N4WVBA#opera-cool-bread";
51
52#[derive(
53    Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From
54)]
55#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)]
56#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)]
57#[derive(StrictType, StrictEncode, StrictDecode)]
58#[strict_type(lib = LIB_NAME_RGB21)]
59#[cfg_attr(
60    feature = "serde",
61    derive(Serialize, Deserialize),
62    serde(crate = "serde_crate", transparent)
63)]
64pub struct ItemsCount(u32);
65
66#[derive(
67    Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From
68)]
69#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)]
70#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)]
71#[derive(StrictType, StrictEncode, StrictDecode)]
72#[strict_type(lib = LIB_NAME_RGB21)]
73#[cfg_attr(
74    feature = "serde",
75    derive(Serialize, Deserialize),
76    serde(crate = "serde_crate", transparent)
77)]
78pub struct TokenIndex(u32);
79
80#[derive(
81    Wrapper, WrapperMut, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Default, From
82)]
83#[wrapper(Display, FromStr, Add, Sub, Mul, Div, Rem)]
84#[wrapper_mut(AddAssign, SubAssign, MulAssign, DivAssign, RemAssign)]
85#[derive(StrictType, StrictEncode, StrictDecode)]
86#[strict_type(lib = LIB_NAME_RGB21)]
87#[cfg_attr(
88    feature = "serde",
89    derive(Serialize, Deserialize),
90    serde(crate = "serde_crate", transparent)
91)]
92pub struct OwnedFraction(u64);
93
94#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
95#[derive(StrictType, StrictEncode, StrictDecode)]
96#[strict_type(lib = LIB_NAME_RGB21)]
97#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
98pub struct Allocation(TokenIndex, OwnedFraction);
99
100impl Allocation {
101    pub fn with(index: TokenIndex, fraction: OwnedFraction) -> Allocation {
102        Allocation(index, fraction)
103    }
104}
105
106impl StrictSerialize for Allocation {}
107impl StrictDeserialize for Allocation {}
108
109#[derive(Clone, Eq, PartialEq, Hash, Debug)]
110#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
111#[strict_type(lib = LIB_NAME_RGB21)]
112#[cfg_attr(
113    feature = "serde",
114    derive(Serialize, Deserialize),
115    serde(crate = "serde_crate", rename_all = "camelCase")
116)]
117pub struct EngravingData {
118    pub applied_to: TokenIndex,
119    pub content: EmbeddedMedia,
120}
121
122#[derive(Clone, Eq, PartialEq, Hash, Debug)]
123#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
124#[strict_type(lib = LIB_NAME_RGB21)]
125#[cfg_attr(
126    feature = "serde",
127    derive(Serialize, Deserialize),
128    serde(crate = "serde_crate", rename_all = "camelCase")
129)]
130pub struct EmbeddedMedia {
131    #[strict_type(rename = "type")]
132    #[cfg_attr(feature = "serde", serde(rename = "type"))]
133    pub ty: MediaType,
134    pub data: SmallBlob,
135}
136
137#[derive(Clone, Eq, PartialEq, Hash, Debug)]
138#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
139#[strict_type(lib = LIB_NAME_RGB21, dumb = { AttachmentType::with(0, "dumb") })]
140#[cfg_attr(
141    feature = "serde",
142    derive(Serialize, Deserialize),
143    serde(crate = "serde_crate", rename_all = "camelCase")
144)]
145pub struct AttachmentType {
146    pub id: u8,
147    pub name: AttachmentName,
148}
149
150impl AttachmentType {
151    pub fn with(id: u8, name: &'static str) -> AttachmentType {
152        AttachmentType {
153            id,
154            name: AttachmentName::from(name),
155        }
156    }
157}
158
159#[derive(Wrapper, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, From)]
160#[wrapper(Deref, Display)]
161#[derive(StrictType, StrictDumb, StrictDecode)]
162#[strict_type(lib = LIB_NAME_RGB21, dumb = { AttachmentName::from("dumb") })]
163#[cfg_attr(
164    feature = "serde",
165    derive(Serialize, Deserialize),
166    serde(crate = "serde_crate", transparent)
167)]
168pub struct AttachmentName(Confined<AsciiString, 1, 20>);
169impl StrictEncode for AttachmentName {
170    fn strict_encode<W: TypedWrite>(&self, writer: W) -> std::io::Result<W> {
171        let iter = self
172            .0
173            .as_bytes()
174            .iter()
175            .map(|c| AsciiPrintable::try_from(*c).unwrap());
176        writer
177            .write_newtype::<Self>(&NonEmptyVec::<AsciiPrintable, 20>::try_from_iter(iter).unwrap())
178    }
179}
180
181// TODO: Ensure all constructors filter invalid characters
182impl FromStr for AttachmentName {
183    type Err = InvalidIdent;
184
185    fn from_str(s: &str) -> Result<Self, Self::Err> {
186        let s = AsciiString::from_ascii(s.as_bytes())?;
187        let s = Confined::try_from_iter(s.chars())?;
188        Ok(Self(s))
189    }
190}
191
192impl From<&'static str> for AttachmentName {
193    fn from(s: &'static str) -> Self { Self::from_str(s).expect("invalid attachment name") }
194}
195
196impl TryFrom<String> for AttachmentName {
197    type Error = InvalidIdent;
198
199    fn try_from(name: String) -> Result<Self, InvalidIdent> {
200        let name = AsciiString::from_ascii(name.as_bytes())?;
201        let s = Confined::try_from(name)?;
202        Ok(Self(s))
203    }
204}
205
206impl Debug for AttachmentName {
207    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
208        f.debug_tuple("AttachmentName")
209            .field(&self.as_str())
210            .finish()
211    }
212}
213
214#[derive(Clone, Eq, PartialEq, Hash, Debug, Default)]
215#[derive(StrictType, StrictEncode, StrictDecode)]
216#[strict_type(lib = LIB_NAME_RGB21)]
217#[cfg_attr(
218    feature = "serde",
219    derive(Serialize, Deserialize),
220    serde(crate = "serde_crate", rename_all = "camelCase")
221)]
222pub struct TokenData {
223    pub index: TokenIndex,
224    pub ticker: Option<Ticker>,
225    pub name: Option<Name>,
226    pub details: Option<Details>,
227    pub preview: Option<EmbeddedMedia>,
228    pub media: Option<Attachment>,
229    pub attachments: Confined<BTreeMap<u8, Attachment>, 0, 20>,
230    pub reserves: Option<ProofOfReserves>,
231}
232
233impl StrictSerialize for TokenData {}
234impl StrictDeserialize for TokenData {}
235
236const FRACTION_OVERFLOW: u8 = 1;
237const NON_EQUAL_VALUES: u8 = 2;
238const INVALID_PROOF: u8 = 3;
239const INSUFFICIENT_RESERVES: u8 = 4;
240const ISSUE_EXCEEDS_ALLOWANCE: u8 = 6;
241const NON_FRACTIONAL_TOKEN: u8 = 7;
242const NON_ENGRAVABLE_TOKEN: u8 = 8;
243const INVALID_ATTACHMENT_TYPE: u8 = 9;
244
245#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
246#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
247#[strict_type(lib = LIB_NAME_RGB21, tags = repr, into_u8, try_from_u8)]
248#[repr(u8)]
249pub enum Error {
250    #[strict_type(dumb)]
251    /// amount of token > 1
252    FractionOverflow = FRACTION_OVERFLOW,
253    NonEqualValues = NON_EQUAL_VALUES,
254    InvalidProof = INVALID_PROOF,
255    InsufficientReserves = INSUFFICIENT_RESERVES,
256    IssueExceedsAllowance = ISSUE_EXCEEDS_ALLOWANCE,
257    NonFractionalToken = NON_FRACTIONAL_TOKEN,
258    NonEngravableToken = NON_ENGRAVABLE_TOKEN,
259    InvalidAttachmentType = INVALID_ATTACHMENT_TYPE,
260}
261
262fn _rgb21_stl() -> Result<TypeLib, CompileError> {
263    LibBuilder::new(libname!(LIB_NAME_RGB21), tiny_bset! {
264        std_stl().to_dependency(),
265        bp_tx_stl().to_dependency(),
266        rgb_contract_stl().to_dependency()
267    })
268    .transpile::<TokenData>()
269    .transpile::<EngravingData>()
270    .transpile::<ItemsCount>()
271    .transpile::<Allocation>()
272    .transpile::<AttachmentType>()
273    .transpile::<Error>()
274    .compile()
275}
276
277/// Generates strict type library providing data types for RGB21 interface.
278pub fn rgb21_stl() -> TypeLib { _rgb21_stl().expect("invalid strict type RGB21 library") }
279
280pub fn rgb21() -> Iface {
281    let types = StandardTypes::with(rgb21_stl());
282
283    Iface {
284        version: VerNo::V1,
285        name: tn!("RGB21"),
286        global_state: tiny_bmap! {
287            fname!("spec") => GlobalIface::required(types.get("RGBContract.DivisibleAssetSpec")),
288            fname!("terms") => GlobalIface::required(types.get("RGBContract.RicardianContract")),
289            fname!("created") => GlobalIface::required(types.get("RGBContract.Timestamp")),
290            fname!("tokens") => GlobalIface::none_or_many(types.get("RGB21.TokenData")),
291            fname!("engravings") => GlobalIface::none_or_many(types.get("RGB21.EngravingData")),
292            fname!("attachmentTypes") => GlobalIface::none_or_many(types.get("RGB21.AttachmentType")),
293        },
294        assignments: tiny_bmap! {
295            fname!("inflationAllowance") => AssignIface::public(OwnedIface::Data(types.get("RGB21.ItemsCount")), Req::NoneOrMore),
296            fname!("updateRight") => AssignIface::public(OwnedIface::Rights, Req::Optional),
297            fname!("assetOwner") => AssignIface::private(OwnedIface::Data(types.get("RGB21.Allocation")), Req::NoneOrMore),
298        },
299        valencies: none!(),
300        genesis: GenesisIface {
301            metadata: Some(types.get("RGBContract.IssueMeta")),
302            global: tiny_bmap! {
303                fname!("spec") => ArgSpec::required(),
304                fname!("terms") => ArgSpec::required(),
305                fname!("created") => ArgSpec::required(),
306                fname!("tokens") => ArgSpec::many(),
307                fname!("attachmentTypes") => ArgSpec::many(),
308            },
309            assignments: tiny_bmap! {
310                fname!("assetOwner") => ArgSpec::many(),
311                fname!("inflationAllowance") => ArgSpec::many(),
312                fname!("updateRight") => ArgSpec::optional(),
313            },
314            valencies: none!(),
315            errors: tiny_bset! {
316                FRACTION_OVERFLOW,
317                INVALID_PROOF,
318                INSUFFICIENT_RESERVES,
319                INVALID_ATTACHMENT_TYPE
320            },
321        },
322        transitions: tiny_bmap! {
323            tn!("Transfer") => TransitionIface {
324                optional: false,
325                metadata: None,
326                globals: none!(),
327                inputs: tiny_bmap! {
328                    fname!("previous") => ArgSpec::from_non_empty("assetOwner"),
329                },
330                assignments: tiny_bmap! {
331                    fname!("beneficiary") => ArgSpec::from_non_empty("assetOwner"),
332                },
333                valencies: none!(),
334                errors: tiny_bset! {
335                    NON_EQUAL_VALUES,
336                    FRACTION_OVERFLOW,
337                    NON_FRACTIONAL_TOKEN
338                },
339                default_assignment: Some(fname!("beneficiary")),
340            },
341            tn!("Engrave") => TransitionIface {
342                optional: true,
343                metadata: None,
344                globals: tiny_bmap! {
345                    fname!("engravings") => ArgSpec::required(),
346                },
347                inputs: tiny_bmap! {
348                    fname!("previous") => ArgSpec::from_non_empty("assetOwner"),
349                },
350                assignments: tiny_bmap! {
351                    fname!("beneficiary") => ArgSpec::from_non_empty("assetOwner"),
352                },
353                valencies: none!(),
354                errors: tiny_bset! {
355                    NON_EQUAL_VALUES,
356                    FRACTION_OVERFLOW,
357                    NON_FRACTIONAL_TOKEN,
358                    NON_ENGRAVABLE_TOKEN
359                },
360                default_assignment: Some(fname!("beneficiary")),
361            },
362            tn!("Issue") => TransitionIface {
363                optional: true,
364                metadata: Some(types.get("RGBContract.IssueMeta")),
365                globals: tiny_bmap! {
366                    fname!("newTokens") => ArgSpec::from_many("tokens"),
367                    fname!("newAttachmentTypes") => ArgSpec::from_many("attachmentTypes"),
368                },
369                inputs: tiny_bmap! {
370                    fname!("used") => ArgSpec::from_non_empty("inflationAllowance"),
371                },
372                assignments: tiny_bmap! {
373                    fname!("beneficiary") => ArgSpec::from_many("assetOwner"),
374                    fname!("future") => ArgSpec::from_many("inflationAllowance"),
375                },
376                valencies: none!(),
377                errors: tiny_bset! {
378                    FRACTION_OVERFLOW,
379                    INVALID_PROOF,
380                    INSUFFICIENT_RESERVES,
381                    INVALID_ATTACHMENT_TYPE,
382                    ISSUE_EXCEEDS_ALLOWANCE,
383                },
384                default_assignment: Some(fname!("beneficiary")),
385            },
386            tn!("Rename") => TransitionIface {
387                optional: true,
388                metadata: None,
389                globals: tiny_bmap! {
390                    fname!("new") => ArgSpec::from_required("spec"),
391                },
392                inputs: tiny_bmap! {
393                    fname!("used") => ArgSpec::from_required("updateRight"),
394                },
395                assignments: tiny_bmap! {
396                    fname!("future") => ArgSpec::from_optional("updateRight"),
397                },
398                valencies: none!(),
399                errors: none!(),
400                default_assignment: Some(fname!("future")),
401            },
402        },
403        extensions: none!(),
404        error_type: types.get("RGB21.Error"),
405        default_operation: Some(tn!("Transfer")),
406        type_system: types.type_system(),
407    }
408}
409
410#[derive(Wrapper, WrapperMut, Clone, Eq, PartialEq, Debug)]
411#[wrapper(Deref)]
412#[wrapper_mut(DerefMut)]
413pub struct Rgb21(ContractIface);
414
415impl From<ContractIface> for Rgb21 {
416    fn from(iface: ContractIface) -> Self {
417        if iface.iface.iface_id != rgb21().iface_id() {
418            panic!("the provided interface is not RGB20 interface");
419        }
420        Self(iface)
421    }
422}
423
424impl Rgb21 {
425    pub fn spec(&self) -> DivisibleAssetSpec {
426        let strict_val = &self
427            .0
428            .global("spec")
429            .expect("RGB21 interface requires global `spec`")[0];
430        DivisibleAssetSpec::from_strict_val_unchecked(strict_val)
431    }
432}
433
434#[cfg(test)]
435mod test {
436    use super::*;
437    use crate::containers::BindleContent;
438
439    const RGB21: &str = include_str!("../../tests/data/rgb21.rgba");
440
441    #[test]
442    fn lib_id() {
443        let lib = rgb21_stl();
444        assert_eq!(lib.id().to_string(), LIB_ID_RGB21);
445    }
446
447    #[test]
448    fn iface_creation() { rgb21(); }
449
450    #[test]
451    fn iface_bindle() {
452        assert_eq!(format!("{}", rgb21().bindle()), RGB21);
453    }
454}