rgbcore/schema/
schema.rs

1// RGB Core Library: consensus layer for RGB smart contracts.
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// Copyright (C) 2019-2024 Dr Maxim Orlovsky. All rights reserved.
10//
11// Licensed under the Apache License, Version 2.0 (the "License");
12// you may not use this file except in compliance with the License.
13// You may obtain a copy of the License at
14//
15//     http://www.apache.org/licenses/LICENSE-2.0
16//
17// Unless required by applicable law or agreed to in writing, software
18// distributed under the License is distributed on an "AS IS" BASIS,
19// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20// See the License for the specific language governing permissions and
21// limitations under the License.
22
23use std::cmp::Ordering;
24use std::fmt::{self, Display, Formatter};
25use std::str::FromStr;
26
27use aluvm::library::LibId;
28use amplify::confinement::TinyOrdMap;
29use amplify::{ByteArray, Bytes32};
30use baid64::{Baid64ParseError, DisplayBaid64, FromBaid64Str};
31use commit_verify::{CommitEncode, CommitEngine, CommitId, CommitmentId, DigestExt, Sha256};
32use strict_encoding::{
33    StrictDecode, StrictDeserialize, StrictEncode, StrictSerialize, StrictType, TypeName,
34};
35use strict_types::{FieldName, SemId};
36
37use super::{AssignmentType, GenesisSchema, OwnedStateSchema, TransitionSchema};
38use crate::{impl_serde_baid64, Ffv, GlobalStateSchema, StateType, LIB_NAME_RGB_COMMIT};
39
40#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Display)]
41#[wrapper(FromStr, LowerHex, UpperHex)]
42#[display("0x{0:04X}")]
43#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
44#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
45#[cfg_attr(
46    feature = "serde",
47    derive(Serialize, Deserialize),
48    serde(crate = "serde_crate", rename_all = "camelCase")
49)]
50pub struct MetaType(u16);
51impl MetaType {
52    pub const fn with(ty: u16) -> Self { Self(ty) }
53}
54
55#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Display)]
56#[wrapper(FromStr, LowerHex, UpperHex)]
57#[display("0x{0:04X}")]
58#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
59#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
60#[cfg_attr(
61    feature = "serde",
62    derive(Serialize, Deserialize),
63    serde(crate = "serde_crate", rename_all = "camelCase")
64)]
65pub struct GlobalStateType(u16);
66impl GlobalStateType {
67    pub const fn with(ty: u16) -> Self { Self(ty) }
68}
69
70#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From, Display)]
71#[wrapper(FromStr, LowerHex, UpperHex)]
72#[display("0x{0:04X}")]
73#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
74#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
75#[cfg_attr(
76    feature = "serde",
77    derive(Serialize, Deserialize),
78    serde(crate = "serde_crate", rename_all = "camelCase")
79)]
80pub struct TransitionType(u16);
81impl TransitionType {
82    pub const fn with(ty: u16) -> Self { Self(ty) }
83}
84
85#[derive(Clone, PartialEq, Eq, Debug)]
86#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
87#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = order)]
88#[cfg_attr(
89    feature = "serde",
90    derive(Serialize, Deserialize),
91    serde(crate = "serde_crate", rename_all = "camelCase")
92)]
93pub struct AssignmentDetails {
94    pub owned_state_schema: OwnedStateSchema,
95    pub name: FieldName,
96    pub default_transition: TransitionType,
97}
98
99#[derive(Clone, PartialEq, Eq, Debug)]
100#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
101#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = order)]
102#[cfg_attr(
103    feature = "serde",
104    derive(Serialize, Deserialize),
105    serde(crate = "serde_crate", rename_all = "camelCase")
106)]
107pub struct GlobalDetails {
108    pub global_state_schema: GlobalStateSchema,
109    pub name: FieldName,
110}
111
112#[derive(Clone, PartialEq, Eq, Debug)]
113#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
114#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = order)]
115#[cfg_attr(
116    feature = "serde",
117    derive(Serialize, Deserialize),
118    serde(crate = "serde_crate", rename_all = "camelCase")
119)]
120pub struct MetaDetails {
121    pub sem_id: SemId,
122    pub name: FieldName,
123}
124
125#[derive(Clone, PartialEq, Eq, Debug)]
126#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
127#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = order)]
128#[cfg_attr(
129    feature = "serde",
130    derive(Serialize, Deserialize),
131    serde(crate = "serde_crate", rename_all = "camelCase")
132)]
133pub struct TransitionDetails {
134    pub transition_schema: TransitionSchema,
135    pub name: FieldName,
136}
137
138impl TransitionType {
139    pub const REPLACE: Self = TransitionType(8011);
140    pub fn is_replace(self) -> bool { self == Self::REPLACE }
141}
142
143/// Schema identifier.
144///
145/// Schema identifier commits to all the schema data.
146#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
147#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
148#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
149#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
150pub struct SchemaId(
151    #[from]
152    #[from([u8; 32])]
153    Bytes32,
154);
155
156impl SchemaId {
157    pub const fn from_array(id: [u8; 32]) -> Self { SchemaId(Bytes32::from_array(id)) }
158}
159
160impl From<Sha256> for SchemaId {
161    fn from(hasher: Sha256) -> Self { hasher.finish().into() }
162}
163
164impl CommitmentId for SchemaId {
165    const TAG: &'static str = "urn:lnp-bp:rgb:schema#2024-02-03";
166}
167
168impl DisplayBaid64 for SchemaId {
169    const HRI: &'static str = "rgb:sch";
170    const CHUNKING: bool = false;
171    const PREFIX: bool = true;
172    const EMBED_CHECKSUM: bool = false;
173    const MNEMONIC: bool = true;
174    fn to_baid64_payload(&self) -> [u8; 32] { self.to_byte_array() }
175}
176impl FromBaid64Str for SchemaId {}
177impl FromStr for SchemaId {
178    type Err = Baid64ParseError;
179    fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_baid64_str(s) }
180}
181impl Display for SchemaId {
182    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { self.fmt_baid64(f) }
183}
184
185impl_serde_baid64!(SchemaId);
186
187#[derive(Clone, Eq, Debug)]
188#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
189#[strict_type(lib = LIB_NAME_RGB_COMMIT)]
190#[cfg_attr(
191    feature = "serde",
192    derive(Serialize, Deserialize),
193    serde(crate = "serde_crate", rename_all = "camelCase")
194)]
195pub struct Schema {
196    pub ffv: Ffv,
197
198    pub name: TypeName,
199
200    pub meta_types: TinyOrdMap<MetaType, MetaDetails>,
201    pub global_types: TinyOrdMap<GlobalStateType, GlobalDetails>,
202    pub owned_types: TinyOrdMap<AssignmentType, AssignmentDetails>,
203    pub genesis: GenesisSchema,
204    pub transitions: TinyOrdMap<TransitionType, TransitionDetails>,
205
206    pub default_assignment: Option<AssignmentType>,
207}
208
209impl CommitEncode for Schema {
210    type CommitmentId = SchemaId;
211
212    fn commit_encode(&self, e: &mut CommitEngine) {
213        e.commit_to_serialized(&self.ffv);
214
215        e.commit_to_serialized(&self.name);
216
217        e.commit_to_map(&self.meta_types);
218        e.commit_to_map(&self.global_types);
219        e.commit_to_map(&self.owned_types);
220        e.commit_to_serialized(&self.genesis);
221        e.commit_to_map(&self.transitions);
222
223        e.commit_to_option(&self.default_assignment);
224    }
225}
226
227impl PartialEq for Schema {
228    fn eq(&self, other: &Self) -> bool { self.schema_id() == other.schema_id() }
229}
230
231impl Ord for Schema {
232    fn cmp(&self, other: &Self) -> Ordering { self.schema_id().cmp(&other.schema_id()) }
233}
234
235impl PartialOrd for Schema {
236    fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
237}
238
239impl StrictSerialize for Schema {}
240impl StrictDeserialize for Schema {}
241
242impl Schema {
243    #[inline]
244    pub fn schema_id(&self) -> SchemaId { self.commit_id() }
245
246    pub fn types(&self) -> impl Iterator<Item = SemId> + '_ {
247        self.meta_types
248            .values()
249            .map(|i| &i.sem_id)
250            .cloned()
251            .chain(
252                self.global_types
253                    .values()
254                    .map(|i| i.global_state_schema.sem_id),
255            )
256            .chain(
257                self.owned_types
258                    .values()
259                    .filter_map(|ai| OwnedStateSchema::sem_id(&ai.owned_state_schema)),
260            )
261    }
262
263    pub fn libs(&self) -> impl Iterator<Item = LibId> + '_ {
264        self.genesis
265            .validator
266            .iter()
267            .copied()
268            .chain(
269                self.transitions
270                    .values()
271                    .filter_map(|i| i.transition_schema.validator),
272            )
273            .map(|site| site.lib)
274    }
275
276    pub fn default_transition_for_assignment(
277        &self,
278        assignment_type: &AssignmentType,
279    ) -> TransitionType {
280        self.owned_types
281            .get(assignment_type)
282            .expect("invalid schema")
283            .default_transition
284    }
285
286    pub fn assignment(&self, name: impl Into<FieldName>) -> (&AssignmentType, &AssignmentDetails) {
287        let name = name.into();
288        self.owned_types
289            .iter()
290            .find(|(_, i)| i.name == name)
291            .expect("cannot find assignment with the given name")
292    }
293
294    pub fn assignment_type(&self, name: impl Into<FieldName>) -> AssignmentType {
295        *self.assignment(name).0
296    }
297
298    pub fn assignment_name(&self, type_id: AssignmentType) -> &FieldName {
299        &self
300            .owned_types
301            .iter()
302            .find(|(id, _)| *id == &type_id)
303            .expect("cannot find assignment with the given type ID")
304            .1
305            .name
306    }
307
308    pub fn assignment_types_for_state(&self, state_type: StateType) -> Vec<&AssignmentType> {
309        self.owned_types
310            .iter()
311            .filter_map(|(at, ai)| {
312                if ai.owned_state_schema.state_type() == state_type {
313                    Some(at)
314                } else {
315                    None
316                }
317            })
318            .collect()
319    }
320
321    pub fn global(&self, name: impl Into<FieldName>) -> (&GlobalStateType, &GlobalDetails) {
322        let name = name.into();
323        self.global_types
324            .iter()
325            .find(|(_, i)| i.name == name)
326            .expect("cannot find global with the given name")
327    }
328
329    pub fn global_type(&self, name: impl Into<FieldName>) -> GlobalStateType {
330        *self.global(name).0
331    }
332
333    pub fn meta(&self, name: impl Into<FieldName>) -> (&MetaType, &MetaDetails) {
334        let name = name.into();
335        self.meta_types
336            .iter()
337            .find(|(_, i)| i.name == name)
338            .expect("cannot find meta with the given name")
339    }
340
341    pub fn meta_type(&self, name: impl Into<FieldName>) -> MetaType { *self.meta(name).0 }
342
343    pub fn meta_name(&self, type_id: MetaType) -> &FieldName {
344        &self
345            .meta_types
346            .iter()
347            .find(|(id, _)| *id == &type_id)
348            .expect("cannot find meta with the given type ID")
349            .1
350            .name
351    }
352
353    pub fn transition(&self, name: impl Into<FieldName>) -> (&TransitionType, &TransitionDetails) {
354        let name = name.into();
355        self.transitions
356            .iter()
357            .find(|(_, i)| i.name == name)
358            .expect("cannot find transition with the given name")
359    }
360
361    pub fn transition_type(&self, name: impl Into<FieldName>) -> TransitionType {
362        *self.transition(name).0
363    }
364}
365
366#[cfg(test)]
367mod test {
368    use strict_encoding::StrictDumb;
369
370    use super::*;
371
372    #[test]
373    fn display() {
374        let dumb = SchemaId::strict_dumb();
375        assert_eq!(
376            dumb.to_string(),
377            "rgb:sch:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#distant-history-exotic"
378        );
379        assert_eq!(
380            &format!("{dumb:-}"),
381            "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA#distant-history-exotic"
382        );
383
384        let less_dumb = SchemaId::from_byte_array(*b"EV4350-'4vwj'4;v-w94w'e'vFVVDhpq");
385        assert_eq!(
386            less_dumb.to_string(),
387            "rgb:sch:RVY0MzUwLSc0dndqJzQ7di13OTR3J2UndkZWVkRocHE#lemon-diamond-cartoon"
388        );
389        assert_eq!(
390            &format!("{less_dumb:-}"),
391            "RVY0MzUwLSc0dndqJzQ7di13OTR3J2UndkZWVkRocHE#lemon-diamond-cartoon"
392        );
393        assert_eq!(
394            &format!("{less_dumb:#}"),
395            "rgb:sch:RVY0MzUwLSc0dndqJzQ7di13OTR3J2UndkZWVkRocHE"
396        );
397        assert_eq!(&format!("{less_dumb:-#}"), "RVY0MzUwLSc0dndqJzQ7di13OTR3J2UndkZWVkRocHE");
398    }
399}