1#![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";
48pub 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
181impl 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 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
277pub 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}