schemata/
uda.rs

1// RGB schemas
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2023-2024 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2023-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
22//! Unique digital asset (UDA) schema.
23
24use aluvm::isa::opcodes::{INSTR_EXTR, INSTR_PUTA};
25use aluvm::isa::Instr;
26use aluvm::library::{Lib, LibSite};
27use amplify::confinement::Confined;
28use rgbstd::contract::{
29    AssignmentsFilter, ContractData, DataAllocation, IssuerWrapper, SchemaWrapper,
30};
31use rgbstd::persistence::{ContractStateRead, MemContract};
32use rgbstd::schema::{
33    AssignmentDetails, GenesisSchema, GlobalStateSchema, Occurrences, Schema, TransitionSchema,
34};
35use rgbstd::stl::{rgb_contract_stl, AssetSpec, ContractTerms, StandardTypes, TokenData};
36use rgbstd::validation::Scripts;
37use rgbstd::vm::opcodes::INSTR_LDG;
38use rgbstd::vm::RgbIsa;
39use rgbstd::{rgbasm, GlobalDetails, OwnedStateSchema, SchemaId, TransitionDetails};
40use strict_types::TypeSystem;
41
42use crate::{
43    ERRNO_NON_EQUAL_IN_OUT, ERRNO_NON_FRACTIONAL, GS_ATTACH, GS_NOMINAL, GS_TERMS, GS_TOKENS,
44    OS_ASSET, TS_TRANSFER,
45};
46
47pub const UDA_SCHEMA_ID: SchemaId = SchemaId::from_array([
48    0xff, 0xaa, 0xe3, 0xca, 0x67, 0xf7, 0x19, 0x31, 0x3c, 0xe3, 0x49, 0x5b, 0xe4, 0x9a, 0x17, 0x9b,
49    0x66, 0x85, 0xc0, 0x4f, 0x1e, 0x58, 0x29, 0x37, 0x98, 0x28, 0xce, 0x7f, 0xe9, 0x94, 0xce, 0xd1,
50]);
51
52pub const FN_GENESIS_OFFSET: u16 = 4 + 4 + 3;
53pub const FN_TRANSFER_OFFSET: u16 = 0;
54pub const FN_SHARED_OFFSET: u16 = FN_GENESIS_OFFSET + 4 + 4 + 4;
55
56fn uda_standard_types() -> StandardTypes { StandardTypes::with(rgb_contract_stl()) }
57
58fn uda_lib() -> Lib {
59    let code = rgbasm! {
60        // SUBROUTINE 2: Transfer validation
61        // Put 0 to a16[0]
62        put     a16[0],0;
63        // Read previous state into s16[0]
64        ldp     OS_ASSET,a16[0],s16[0];
65        // jump into SUBROUTINE 3 to reuse the code
66        jmp     FN_SHARED_OFFSET;
67
68        // SUBROUTINE 1: Genesis validation
69        // Set offset to read state from strings
70        put     a16[0],0x00;
71        // Set which state index to read
72        put     a8[1],0x00;
73        // Read global state into s16[0]
74        ldg     GS_TOKENS,a8[1],s16[0];
75
76        // SUBROUTINE 3: Shared code
77        // Set errno
78        put     a8[0],ERRNO_NON_EQUAL_IN_OUT;
79        // Extract 128 bits from the beginning of s16[0] into a32[0]
80        extr    s16[0],a32[0],a16[0];
81        // Set which state index to read
82        put     a16[1],0x00;
83        // Read owned state into s16[1]
84        lds     OS_ASSET,a16[1],s16[1];
85        // Extract 128 bits from the beginning of s16[1] into a32[1]
86        extr    s16[1],a32[1],a16[0];
87        // Check that token indexes match
88        eq.n    a32[0],a32[1];
89        // Fail if they don't
90        test;
91
92        // Set errno
93        put     a8[0],ERRNO_NON_FRACTIONAL;
94        // Put offset for the data into a16[2]
95        put     a16[2],4;
96        // Extract 128 bits starting from the fifth byte of s16[1] into a64[0]
97        extr    s16[1],a64[0],a16[2];
98        // Check that owned fraction == 1
99        put     a64[1],1;
100        eq.n    a64[0],a64[1];
101        // Fail if not
102        test;
103    };
104    Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code).expect("wrong unique digital asset script")
105}
106
107fn uda_schema() -> Schema {
108    let types = uda_standard_types();
109
110    let alu_lib = uda_lib();
111    let alu_id = alu_lib.id();
112    let code = alu_lib.code.as_ref();
113    assert_eq!(code[FN_GENESIS_OFFSET as usize], INSTR_PUTA);
114    assert_eq!(code[FN_GENESIS_OFFSET as usize + 8], INSTR_LDG);
115    assert_eq!(code[FN_TRANSFER_OFFSET as usize], INSTR_PUTA);
116    assert_eq!(code[FN_SHARED_OFFSET as usize], INSTR_PUTA);
117    assert_eq!(code[FN_SHARED_OFFSET as usize + 4], INSTR_EXTR);
118
119    Schema {
120        ffv: zero!(),
121        name: tn!("UniqueDigitalAsset"),
122        meta_types: none!(),
123        global_types: tiny_bmap! {
124            GS_NOMINAL => GlobalDetails {
125                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.AssetSpec")),
126                name: fname!("spec"),
127            },
128            GS_TERMS => GlobalDetails {
129                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.ContractTerms")),
130                name: fname!("terms"),
131            },
132            GS_TOKENS => GlobalDetails {
133                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.TokenData")),
134                name: fname!("tokens"),
135            },
136            GS_ATTACH => GlobalDetails {
137                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.AttachmentType")),
138                name: fname!("attachmentTypes"),
139            },
140        },
141        owned_types: tiny_bmap! {
142            OS_ASSET => AssignmentDetails {
143                owned_state_schema: OwnedStateSchema::Structured(types.get("RGBContract.Allocation")),
144                name: fname!("assetOwner"),
145                default_transition: TS_TRANSFER,
146            }
147        },
148        genesis: GenesisSchema {
149            metadata: none!(),
150            globals: tiny_bmap! {
151                GS_NOMINAL => Occurrences::Once,
152                GS_TERMS => Occurrences::Once,
153                GS_TOKENS => Occurrences::Once,
154                GS_ATTACH => Occurrences::NoneOrOnce,
155            },
156            assignments: tiny_bmap! {
157                OS_ASSET => Occurrences::Once,
158            },
159            validator: Some(LibSite::with(FN_GENESIS_OFFSET, alu_id)),
160        },
161        transitions: tiny_bmap! {
162            TS_TRANSFER => TransitionDetails {
163                transition_schema: TransitionSchema {
164                    metadata: none!(),
165                    globals: none!(),
166                    inputs: tiny_bmap! {
167                        OS_ASSET => Occurrences::Once
168                    },
169                    assignments: tiny_bmap! {
170                        OS_ASSET => Occurrences::Once
171                    },
172                    validator: Some(LibSite::with(FN_TRANSFER_OFFSET, alu_id)),
173                },
174                name: fname!("transfer"),
175            }
176        },
177        default_assignment: Some(OS_ASSET),
178    }
179}
180
181#[derive(Default)]
182pub struct UniqueDigitalAsset;
183
184#[derive(Clone, Eq, PartialEq, Debug, From)]
185pub struct UdaWrapper<S: ContractStateRead>(ContractData<S>);
186
187impl IssuerWrapper for UniqueDigitalAsset {
188    type Wrapper<S: ContractStateRead> = UdaWrapper<S>;
189
190    fn schema() -> Schema { uda_schema() }
191
192    fn types() -> TypeSystem { uda_standard_types().type_system(uda_schema()) }
193
194    fn scripts() -> Scripts {
195        let lib = uda_lib();
196        Confined::from_checked(bmap! { lib.id() => lib })
197    }
198}
199
200impl<S: ContractStateRead> SchemaWrapper<S> for UdaWrapper<S> {
201    fn with(data: ContractData<S>) -> Self {
202        if data.schema.schema_id() != UDA_SCHEMA_ID {
203            panic!("the provided schema is not UDA");
204        }
205        Self(data)
206    }
207}
208
209impl<S: ContractStateRead> UdaWrapper<S> {
210    pub fn spec(&self) -> AssetSpec {
211        let strict_val = &self
212            .0
213            .global("spec")
214            .next()
215            .expect("UDA requires global state `spec` to have at least one item");
216        AssetSpec::from_strict_val_unchecked(strict_val)
217    }
218
219    pub fn contract_terms(&self) -> ContractTerms {
220        let strict_val = &self
221            .0
222            .global("terms")
223            .next()
224            .expect("UDA requires global state `terms` to have at least one item");
225        ContractTerms::from_strict_val_unchecked(strict_val)
226    }
227
228    pub fn token_data(&self) -> TokenData {
229        let strict_val = &self
230            .0
231            .global("tokens")
232            .next()
233            .expect("UDA requires global state `tokens` to have at least one item");
234        TokenData::from_strict_val_unchecked(strict_val)
235    }
236
237    pub fn allocations<'c>(
238        &'c self,
239        filter: impl AssignmentsFilter + 'c,
240    ) -> impl Iterator<Item = DataAllocation> + 'c {
241        self.0.data_raw(OS_ASSET, filter).unwrap()
242    }
243}
244
245#[cfg(test)]
246mod test {
247    use super::*;
248
249    #[test]
250    fn schema_id() {
251        let schema_id = uda_schema().schema_id();
252        eprintln!("{:#04x?}", schema_id.to_byte_array());
253        assert_eq!(UDA_SCHEMA_ID, schema_id);
254    }
255}