1use 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 put a16[0],0;
63 ldp OS_ASSET,a16[0],s16[0];
65 jmp FN_SHARED_OFFSET;
67
68 put a16[0],0x00;
71 put a8[1],0x00;
73 ldg GS_TOKENS,a8[1],s16[0];
75
76 put a8[0],ERRNO_NON_EQUAL_IN_OUT;
79 extr s16[0],a32[0],a16[0];
81 put a16[1],0x00;
83 lds OS_ASSET,a16[1],s16[1];
85 extr s16[1],a32[1],a16[0];
87 eq.n a32[0],a32[1];
89 test;
91
92 put a8[0],ERRNO_NON_FRACTIONAL;
94 put a16[2],4;
96 extr s16[1],a64[0],a16[2];
98 put a64[1],1;
100 eq.n a64[0],a64[1];
101 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}