1use aluvm::isa::opcodes::INSTR_PUTA;
25use aluvm::isa::Instr;
26use aluvm::library::{Lib, LibSite};
27use amplify::confinement::Confined;
28use rgbstd::contract::{
29 AssignmentsFilter, ContractData, FungibleAllocation, IssuerWrapper, SchemaWrapper,
30};
31use rgbstd::persistence::{ContractStateRead, MemContract};
32use rgbstd::schema::{
33 AssignmentDetails, FungibleType, GenesisSchema, GlobalDetails, GlobalStateSchema, Occurrences,
34 OwnedStateSchema, Schema, TransitionSchema,
35};
36use rgbstd::stl::{rgb_contract_stl, AssetSpec, ContractTerms, StandardTypes};
37use rgbstd::validation::Scripts;
38use rgbstd::vm::opcodes::INSTR_SVS;
39use rgbstd::vm::RgbIsa;
40use rgbstd::{rgbasm, Amount, SchemaId, TransitionDetails};
41use strict_types::TypeSystem;
42
43use crate::{
44 ERRNO_ISSUED_MISMATCH, ERRNO_NON_EQUAL_IN_OUT, GS_ISSUED_SUPPLY, GS_NOMINAL, GS_TERMS,
45 OS_ASSET, TS_TRANSFER,
46};
47
48pub const NIA_SCHEMA_ID: SchemaId = SchemaId::from_array([
49 0x45, 0x68, 0x70, 0x51, 0xf4, 0xcc, 0xa6, 0xe3, 0xf6, 0x65, 0xfc, 0x75, 0xfe, 0x3e, 0x27, 0xb3,
50 0x00, 0x80, 0x34, 0x67, 0x89, 0xad, 0x83, 0xaa, 0x0d, 0xc2, 0x9e, 0x95, 0xa3, 0x15, 0xe3, 0x35,
51]);
52
53pub(crate) fn nia_lib() -> Lib {
54 let code = rgbasm! {
55 put a8[0],ERRNO_NON_EQUAL_IN_OUT;
58 svs OS_ASSET;
60 test;
61 ret;
62
63 put a8[0],ERRNO_ISSUED_MISMATCH;
67 put a8[1],0;
68 put a16[0],0;
69 ldg GS_ISSUED_SUPPLY,a8[1],s16[0];
71 extr s16[0],a64[0],a16[0];
74 sas OS_ASSET;
76 test;
77 ret;
78 };
79 Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code).expect("wrong non-inflatable asset script")
80}
81pub(crate) const FN_NIA_GENESIS_OFFSET: u16 = 4 + 3 + 2;
82pub(crate) const FN_NIA_TRANSFER_OFFSET: u16 = 0;
83
84fn nia_standard_types() -> StandardTypes { StandardTypes::with(rgb_contract_stl()) }
85
86fn nia_schema() -> Schema {
87 let types = nia_standard_types();
88
89 let alu_lib = nia_lib();
90 let alu_id = alu_lib.id();
91 assert_eq!(alu_lib.code.as_ref()[FN_NIA_TRANSFER_OFFSET as usize + 4], INSTR_SVS);
92 assert_eq!(alu_lib.code.as_ref()[FN_NIA_GENESIS_OFFSET as usize], INSTR_PUTA);
93 assert_eq!(alu_lib.code.as_ref()[FN_NIA_GENESIS_OFFSET as usize + 4], INSTR_PUTA);
94 assert_eq!(alu_lib.code.as_ref()[FN_NIA_GENESIS_OFFSET as usize + 8], INSTR_PUTA);
95
96 Schema {
97 ffv: zero!(),
98 name: tn!("NonInflatableAsset"),
99 meta_types: none!(),
100 global_types: tiny_bmap! {
101 GS_NOMINAL => GlobalDetails {
102 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.AssetSpec")),
103 name: fname!("spec"),
104 },
105 GS_TERMS => GlobalDetails {
106 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.ContractTerms")),
107 name: fname!("terms"),
108 },
109 GS_ISSUED_SUPPLY => GlobalDetails {
110 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.Amount")),
111 name: fname!("issuedSupply"),
112 },
113 },
114 owned_types: tiny_bmap! {
115 OS_ASSET => AssignmentDetails {
116 owned_state_schema: OwnedStateSchema::Fungible(FungibleType::Unsigned64Bit),
117 name: fname!("assetOwner"),
118 default_transition: TS_TRANSFER,
119 }
120 },
121 genesis: GenesisSchema {
122 metadata: none!(),
123 globals: tiny_bmap! {
124 GS_NOMINAL => Occurrences::Once,
125 GS_TERMS => Occurrences::Once,
126 GS_ISSUED_SUPPLY => Occurrences::Once,
127 },
128 assignments: tiny_bmap! {
129 OS_ASSET => Occurrences::OnceOrMore,
130 },
131 validator: Some(LibSite::with(FN_NIA_GENESIS_OFFSET, alu_id)),
132 },
133 transitions: tiny_bmap! {
134 TS_TRANSFER => TransitionDetails {
135 transition_schema: TransitionSchema {
136 metadata: none!(),
137 globals: none!(),
138 inputs: tiny_bmap! {
139 OS_ASSET => Occurrences::OnceOrMore
140 },
141 assignments: tiny_bmap! {
142 OS_ASSET => Occurrences::OnceOrMore
143 },
144 validator: Some(LibSite::with(FN_NIA_TRANSFER_OFFSET, alu_id))
145 },
146 name: fname!("transfer"),
147 }
148 },
149 default_assignment: Some(OS_ASSET),
150 }
151}
152
153#[derive(Default)]
154pub struct NonInflatableAsset;
155
156impl IssuerWrapper for NonInflatableAsset {
157 type Wrapper<S: ContractStateRead> = NiaWrapper<S>;
158
159 fn schema() -> Schema { nia_schema() }
160
161 fn types() -> TypeSystem { nia_standard_types().type_system(nia_schema()) }
162
163 fn scripts() -> Scripts {
164 let lib = nia_lib();
165 Confined::from_checked(bmap! { lib.id() => lib })
166 }
167}
168
169#[derive(Clone, Eq, PartialEq, Debug, From)]
170pub struct NiaWrapper<S: ContractStateRead>(ContractData<S>);
171
172impl<S: ContractStateRead> SchemaWrapper<S> for NiaWrapper<S> {
173 fn with(data: ContractData<S>) -> Self {
174 if data.schema.schema_id() != NIA_SCHEMA_ID {
175 panic!("the provided schema is not NIA");
176 }
177 Self(data)
178 }
179}
180
181impl<S: ContractStateRead> NiaWrapper<S> {
182 pub fn spec(&self) -> AssetSpec {
183 let strict_val = &self
184 .0
185 .global("spec")
186 .next()
187 .expect("NIA requires global state `spec` to have at least one item");
188 AssetSpec::from_strict_val_unchecked(strict_val)
189 }
190
191 pub fn contract_terms(&self) -> ContractTerms {
192 let strict_val = &self
193 .0
194 .global("terms")
195 .next()
196 .expect("NIA requires global state `terms` to have at least one item");
197 ContractTerms::from_strict_val_unchecked(strict_val)
198 }
199
200 pub fn total_issued_supply(&self) -> Amount {
201 self.0
202 .global("issuedSupply")
203 .map(|amount| Amount::from_strict_val_unchecked(&amount))
204 .sum()
205 }
206
207 pub fn allocations<'c>(
208 &'c self,
209 filter: impl AssignmentsFilter + 'c,
210 ) -> impl Iterator<Item = FungibleAllocation> + 'c {
211 self.0.fungible_raw(OS_ASSET, filter).unwrap()
212 }
213}
214
215#[cfg(test)]
216mod test {
217 use std::str::FromStr;
218
219 use rgbstd::containers::{BuilderSeal, ConsignmentExt};
220 use rgbstd::contract::*;
221 use rgbstd::invoice::Precision;
222 use rgbstd::stl::*;
223 use rgbstd::txout::BlindSeal;
224 use rgbstd::*;
225
226 use super::*;
227
228 #[test]
229 fn schema_id() {
230 let schema_id = nia_schema().schema_id();
231 eprintln!("{:#04x?}", schema_id.to_byte_array());
232 assert_eq!(NIA_SCHEMA_ID, schema_id);
233 }
234
235 #[test]
236 fn deterministic_contract_id() {
237 let created_at = 1713261744;
238 let terms = ContractTerms {
239 text: RicardianContract::default(),
240 media: None,
241 };
242 let spec = AssetSpec {
243 ticker: Ticker::from("TICKER"),
244 name: Name::from("NAME"),
245 details: None,
246 precision: Precision::try_from(2).unwrap(),
247 };
248 let issued_supply = 999u64;
249 let seal: BlindSeal<Txid> = GenesisSeal::from(BlindSeal::with_blinding(
250 Txid::from_str("8d54c98d4c29a1ec4fd90635f543f0f7a871a78eb6a6e706342f831d92e3ba19")
251 .unwrap(),
252 0,
253 654321,
254 ));
255
256 let builder = ContractBuilder::with(
257 Identity::default(),
258 NonInflatableAsset::schema(),
259 NonInflatableAsset::types(),
260 NonInflatableAsset::scripts(),
261 ChainNet::BitcoinTestnet4,
262 )
263 .add_global_state("spec", spec)
264 .unwrap()
265 .add_global_state("terms", terms)
266 .unwrap()
267 .add_global_state("issuedSupply", Amount::from(issued_supply))
268 .unwrap()
269 .add_fungible_state("assetOwner", BuilderSeal::from(seal), issued_supply)
270 .unwrap();
271
272 let contract = builder.issue_contract_raw(created_at).unwrap();
273
274 assert_eq!(
275 contract.contract_id().to_string(),
276 s!("rgb:663wqep~-0pVYnjS-ieA0N3r-58wUTIY-zgCGO_1-QQkuMMs")
277 );
278 }
279}