schemata/
cfa.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//! Collectible Fungible Assets (CFA) schema.
23
24use aluvm::library::LibSite;
25use amplify::confinement::Confined;
26use rgbstd::contract::{
27    AssignmentsFilter, ContractData, FungibleAllocation, IssuerWrapper, SchemaWrapper,
28};
29use rgbstd::persistence::ContractStateRead;
30use rgbstd::schema::{
31    AssignmentDetails, FungibleType, GenesisSchema, GlobalDetails, GlobalStateSchema, Occurrences,
32    Schema, TransitionDetails, TransitionSchema,
33};
34use rgbstd::stl::{rgb_contract_stl, ContractTerms, Details, Name, StandardTypes};
35use rgbstd::validation::Scripts;
36use rgbstd::{Amount, OwnedStateSchema, Precision, SchemaId};
37use strict_types::TypeSystem;
38
39use crate::nia::{nia_lib, FN_NIA_GENESIS_OFFSET, FN_NIA_TRANSFER_OFFSET};
40use crate::{
41    GS_ART, GS_DETAILS, GS_ISSUED_SUPPLY, GS_NAME, GS_PRECISION, GS_TERMS, OS_ASSET, TS_TRANSFER,
42};
43
44pub const CFA_SCHEMA_ID: SchemaId = SchemaId::from_array([
45    0x26, 0x0a, 0x8a, 0xe6, 0x12, 0x57, 0xf5, 0x80, 0x53, 0xe2, 0x8b, 0x02, 0x57, 0xb5, 0x5c, 0x5b,
46    0xe8, 0x8b, 0x4d, 0xc0, 0x39, 0x72, 0xc5, 0x02, 0x9c, 0xbc, 0xef, 0x68, 0xa4, 0xd3, 0xac, 0xd6,
47]);
48
49fn cfa_standard_types() -> StandardTypes { StandardTypes::with(rgb_contract_stl()) }
50
51pub fn cfa_schema() -> Schema {
52    let types = cfa_standard_types();
53
54    let nia_id = nia_lib().id();
55
56    Schema {
57        ffv: zero!(),
58        name: tn!("CollectibleFungibleAsset"),
59        meta_types: none!(),
60        global_types: tiny_bmap! {
61            GS_ART => GlobalDetails {
62                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.Article")),
63                name: fname!("art"),
64            },
65            GS_NAME => GlobalDetails {
66                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.Name")),
67                name: fname!("name"),
68            },
69            GS_DETAILS => GlobalDetails {
70                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.Details")),
71                name: fname!("details"),
72            },
73            GS_PRECISION => GlobalDetails {
74                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.Precision")),
75                name: fname!("precision"),
76            },
77            GS_TERMS => GlobalDetails {
78                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.ContractTerms")),
79                name: fname!("terms"),
80            },
81            GS_ISSUED_SUPPLY => GlobalDetails {
82                global_state_schema: GlobalStateSchema::once(types.get("RGBContract.Amount")),
83                name: fname!("issuedSupply"),
84            },
85        },
86        owned_types: tiny_bmap! {
87            OS_ASSET => AssignmentDetails {
88                owned_state_schema: OwnedStateSchema::Fungible(FungibleType::Unsigned64Bit),
89                name: fname!("assetOwner"),
90                default_transition: TS_TRANSFER,
91            }
92        },
93        genesis: GenesisSchema {
94            metadata: none!(),
95            globals: tiny_bmap! {
96                GS_ART => Occurrences::NoneOrOnce,
97                GS_NAME => Occurrences::Once,
98                GS_DETAILS => Occurrences::NoneOrOnce,
99                GS_PRECISION => Occurrences::Once,
100                GS_TERMS => Occurrences::Once,
101                GS_ISSUED_SUPPLY => Occurrences::Once,
102            },
103            assignments: tiny_bmap! {
104                OS_ASSET => Occurrences::OnceOrMore,
105            },
106            validator: Some(LibSite::with(FN_NIA_GENESIS_OFFSET, nia_id)),
107        },
108        transitions: tiny_bmap! {
109            TS_TRANSFER => TransitionDetails {
110                transition_schema: TransitionSchema {
111                    metadata: none!(),
112                    globals: none!(),
113                    inputs: tiny_bmap! {
114                        OS_ASSET => Occurrences::OnceOrMore
115                    },
116                    assignments: tiny_bmap! {
117                        OS_ASSET => Occurrences::OnceOrMore
118                    },
119                    validator: Some(LibSite::with(FN_NIA_TRANSFER_OFFSET, nia_id))
120                },
121                name: fname!("transfer"),
122            }
123        },
124        default_assignment: Some(OS_ASSET),
125    }
126}
127
128#[derive(Default)]
129pub struct CollectibleFungibleAsset;
130
131#[derive(Clone, Eq, PartialEq, Debug, From)]
132pub struct CfaWrapper<S: ContractStateRead>(ContractData<S>);
133
134impl IssuerWrapper for CollectibleFungibleAsset {
135    type Wrapper<S: ContractStateRead> = CfaWrapper<S>;
136
137    fn schema() -> Schema { cfa_schema() }
138
139    fn types() -> TypeSystem { cfa_standard_types().type_system(cfa_schema()) }
140
141    fn scripts() -> Scripts {
142        let lib = nia_lib();
143        Confined::from_checked(bmap! { lib.id() => lib })
144    }
145}
146
147impl<S: ContractStateRead> SchemaWrapper<S> for CfaWrapper<S> {
148    fn with(data: ContractData<S>) -> Self {
149        if data.schema.schema_id() != CFA_SCHEMA_ID {
150            panic!("the provided schema is not CFA");
151        }
152        Self(data)
153    }
154}
155
156impl<S: ContractStateRead> CfaWrapper<S> {
157    pub fn name(&self) -> Name {
158        let strict_val = &self
159            .0
160            .global("name")
161            .next()
162            .expect("CFA requires global state `name` to have at least one item");
163        Name::from_strict_val_unchecked(strict_val)
164    }
165
166    pub fn details(&self) -> Option<Details> {
167        self.0
168            .global("details")
169            .next()
170            .map(|strict_val| Details::from_strict_val_unchecked(&strict_val))
171    }
172
173    pub fn precision(&self) -> Precision {
174        let strict_val = &self
175            .0
176            .global("precision")
177            .next()
178            .expect("CFA requires global state `precision` to have at least one item");
179        Precision::from_strict_val_unchecked(strict_val)
180    }
181
182    pub fn total_issued_supply(&self) -> Amount {
183        self.0
184            .global("issuedSupply")
185            .map(|amount| Amount::from_strict_val_unchecked(&amount))
186            .sum()
187    }
188
189    pub fn contract_terms(&self) -> ContractTerms {
190        let strict_val = &self
191            .0
192            .global("terms")
193            .next()
194            .expect("CFA requires global state `terms` to have at least one item");
195        ContractTerms::from_strict_val_unchecked(strict_val)
196    }
197
198    pub fn allocations<'c>(
199        &'c self,
200        filter: impl AssignmentsFilter + 'c,
201    ) -> impl Iterator<Item = FungibleAllocation> + 'c {
202        self.0.fungible_raw(OS_ASSET, filter).unwrap()
203    }
204}
205
206#[cfg(test)]
207mod test {
208    use super::*;
209
210    #[test]
211    fn schema_id() {
212        let schema_id = cfa_schema().schema_id();
213        eprintln!("{:#04x?}", schema_id.to_byte_array());
214        assert_eq!(CFA_SCHEMA_ID, schema_id);
215    }
216}