1use aluvm::isa::Instr;
26use aluvm::library::{Lib, LibSite};
27use amplify::confinement::Confined;
28use rgbstd::contract::{
29 AssignmentsFilter, ContractData, FungibleAllocation, IssuerWrapper, RightsAllocation,
30 SchemaWrapper,
31};
32use rgbstd::persistence::{ContractStateRead, MemContract};
33use rgbstd::schema::{
34 AssignmentDetails, FungibleType, GenesisSchema, GlobalStateSchema, Occurrences,
35 OwnedStateSchema, Schema, TransitionSchema,
36};
37use rgbstd::stl::{rgb_contract_stl, AssetSpec, ContractTerms, RejectListUrl, StandardTypes};
38use rgbstd::validation::Scripts;
39use rgbstd::vm::RgbIsa;
40use rgbstd::{rgbasm, Amount, GlobalDetails, MetaDetails, SchemaId, TransitionDetails};
41use strict_types::TypeSystem;
42
43use crate::{
44 ERRNO_INFLATION_EXCEEDS_ALLOWANCE, ERRNO_INFLATION_MISMATCH, ERRNO_ISSUED_MISMATCH,
45 ERRNO_NON_EQUAL_IN_OUT, ERRNO_REPLACE_HIDDEN_BURN, ERRNO_REPLACE_NO_INPUT, GS_ISSUED_SUPPLY,
46 GS_MAX_SUPPLY, GS_NOMINAL, GS_REJECT_LIST_URL, GS_TERMS, MS_ALLOWED_INFLATION, OS_ASSET,
47 OS_INFLATION, OS_REPLACE, TS_BURN, TS_INFLATION, TS_REPLACE, TS_TRANSFER,
48};
49
50pub const IFA_SCHEMA_ID: SchemaId = SchemaId::from_array([
51 0x82, 0x65, 0x7f, 0x89, 0x08, 0x2f, 0x06, 0x27, 0x64, 0xdc, 0x04, 0x7c, 0xbb, 0xff, 0xad, 0x94,
52 0x2a, 0x82, 0x30, 0xc0, 0x41, 0xbc, 0xa3, 0x16, 0x43, 0x05, 0xba, 0x24, 0xc5, 0x95, 0xb4, 0x60,
53]);
54
55pub(crate) fn ifa_lib_genesis() -> Lib {
56 #[allow(clippy::diverging_sub_expression)]
57 let code = rgbasm! {
58 put a8[1],0;
60 put a16[0],0;
61
62 put a8[0],ERRNO_ISSUED_MISMATCH; ldg GS_ISSUED_SUPPLY,a8[1],s16[0]; extr s16[0],a64[0],a16[0]; sas OS_ASSET; test;
68
69 put a8[0],ERRNO_INFLATION_MISMATCH; ldg GS_MAX_SUPPLY,a8[1],s16[1]; extr s16[1],a64[1],a16[0]; sub.uc a64[1],a64[0]; test; sas OS_INFLATION; test;
77
78 ret;
79 };
80 Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code)
81 .expect("wrong inflatable asset genesis valdiation script")
82}
83
84pub(crate) fn ifa_lib_transfer() -> Lib {
85 let code = rgbasm! {
86 put a8[0],ERRNO_NON_EQUAL_IN_OUT; svs OS_ASSET; test; svs OS_INFLATION; test; cnp OS_REPLACE,a16[0]; cns OS_REPLACE,a16[1]; put a16[2],0; eq.n a16[0],a16[2]; jif 40; put a8[0],ERRNO_REPLACE_HIDDEN_BURN; lt.u a16[1],a16[0]; inv st0; test; ret; put a8[0],ERRNO_REPLACE_NO_INPUT; eq.n a16[1],a16[0]; test; ret; };
113 Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code).expect("wrong transfer validation script")
114}
115
116pub(crate) fn ifa_lib_inflation() -> Lib {
117 #[allow(clippy::diverging_sub_expression)]
118 let code = rgbasm! {
119 put a8[1],0;
121 put a16[0],0;
122
123 put a8[0],ERRNO_ISSUED_MISMATCH; ldg GS_ISSUED_SUPPLY,a8[1],s16[0]; extr s16[0],a64[0],a16[0]; sas OS_ASSET; test;
129 cpy a64[0],a64[1]; put a8[0],ERRNO_INFLATION_MISMATCH; ldm MS_ALLOWED_INFLATION,s16[0]; extr s16[0],a64[0],a16[0]; sas OS_INFLATION; test;
137
138 put a8[0],ERRNO_INFLATION_EXCEEDS_ALLOWANCE;
140 add.uc a64[1],a64[0]; test; sps OS_INFLATION; test;
144
145 ret;
146 };
147 Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code).expect("wrong inflation validation script")
148}
149
150fn ifa_standard_types() -> StandardTypes { StandardTypes::with(rgb_contract_stl()) }
151
152fn ifa_schema() -> Schema {
153 let types = ifa_standard_types();
154
155 let alu_id_transfer = ifa_lib_transfer().id();
156
157 Schema {
158 ffv: zero!(),
159 name: tn!("InflatableFungibleAsset"),
160 meta_types: tiny_bmap! {
161 MS_ALLOWED_INFLATION => MetaDetails {
162 sem_id: types.get("RGBContract.Amount"),
163 name: fname!("allowedInflation"),
164 }
165 },
166 global_types: tiny_bmap! {
167 GS_NOMINAL => GlobalDetails {
168 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.AssetSpec")),
169 name: fname!("spec"),
170 },
171 GS_TERMS => GlobalDetails {
172 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.ContractTerms")),
173 name: fname!("terms"),
174 },
175 GS_ISSUED_SUPPLY => GlobalDetails {
176 global_state_schema: GlobalStateSchema::many(types.get("RGBContract.Amount")),
177 name: fname!("issuedSupply"),
178 },
179 GS_MAX_SUPPLY => GlobalDetails {
180 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.Amount")),
181 name: fname!("maxSupply"),
182 },
183 GS_REJECT_LIST_URL => GlobalDetails {
184 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.RejectListUrl")),
185 name: fname!("rejectListUrl"),
186 },
187 },
188 owned_types: tiny_bmap! {
189 OS_ASSET => AssignmentDetails {
190 owned_state_schema: OwnedStateSchema::Fungible(FungibleType::Unsigned64Bit),
191 name: fname!("assetOwner"),
192 default_transition: TS_TRANSFER,
193 },
194 OS_INFLATION => AssignmentDetails {
195 owned_state_schema: OwnedStateSchema::Fungible(FungibleType::Unsigned64Bit),
196 name: fname!("inflationAllowance"),
197 default_transition: TS_TRANSFER
198 },
199 OS_REPLACE => AssignmentDetails {
200 owned_state_schema: OwnedStateSchema::Declarative,
201 name: fname!("replaceRight"),
202 default_transition: TS_TRANSFER,
203 }
204 },
205 genesis: GenesisSchema {
206 metadata: none!(),
207 globals: tiny_bmap! {
208 GS_NOMINAL => Occurrences::Once,
209 GS_TERMS => Occurrences::Once,
210 GS_ISSUED_SUPPLY => Occurrences::Once,
211 GS_MAX_SUPPLY => Occurrences::Once,
212 GS_REJECT_LIST_URL => Occurrences::NoneOrOnce,
213 },
214 assignments: tiny_bmap! {
215 OS_ASSET => Occurrences::NoneOrMore,
216 OS_INFLATION => Occurrences::NoneOrMore,
217 OS_REPLACE => Occurrences::NoneOrMore,
218 },
219 validator: Some(LibSite::with(0, ifa_lib_genesis().id())),
220 },
221 transitions: tiny_bmap! {
222 TS_TRANSFER => TransitionDetails {
223 transition_schema: TransitionSchema {
224 metadata: none!(),
225 globals: none!(),
226 inputs: tiny_bmap! {
227 OS_ASSET => Occurrences::NoneOrMore,
228 OS_INFLATION => Occurrences::NoneOrMore,
229 OS_REPLACE => Occurrences::NoneOrMore
230 },
231 assignments: tiny_bmap! {
232 OS_ASSET => Occurrences::NoneOrMore,
233 OS_INFLATION => Occurrences::NoneOrMore,
234 OS_REPLACE => Occurrences::NoneOrMore
235 },
236 validator: Some(LibSite::with(0, alu_id_transfer))
237 },
238 name: fname!("transfer"),
239 },
240 TS_INFLATION => TransitionDetails {
241 transition_schema: TransitionSchema {
242 metadata: tiny_bset![MS_ALLOWED_INFLATION],
243 globals: tiny_bmap! {
244 GS_ISSUED_SUPPLY => Occurrences::Once,
245 },
246 inputs: tiny_bmap! {
247 OS_INFLATION => Occurrences::OnceOrMore
248 },
249 assignments: tiny_bmap! {
250 OS_ASSET => Occurrences::OnceOrMore,
251 OS_INFLATION => Occurrences::NoneOrMore
252 },
253 validator: Some(LibSite::with(0, ifa_lib_inflation().id()))
254 },
255 name: fname!("inflate"),
256 },
257 TS_BURN => TransitionDetails {
258 transition_schema: TransitionSchema {
259 metadata: none!(),
260 globals: none!(),
261 inputs: tiny_bmap! {
262 OS_ASSET => Occurrences::NoneOrMore,
263 OS_REPLACE => Occurrences::NoneOrMore,
264 OS_INFLATION => Occurrences::NoneOrMore,
265 },
266 assignments: none!(),
267 validator: None
268 },
269 name: fname!("burn"),
270 },
271 TS_REPLACE => TransitionDetails {
272 transition_schema: TransitionSchema {
273 metadata: none!(),
274 globals: none!(),
275 inputs: tiny_bmap! {
276 OS_ASSET => Occurrences::OnceOrMore,
277 OS_REPLACE => Occurrences::OnceOrMore,
278 },
279 assignments: tiny_bmap! {
280 OS_ASSET => Occurrences::OnceOrMore,
281 OS_REPLACE => Occurrences::OnceOrMore,
282 },
283 validator: Some(LibSite::with(0, alu_id_transfer))
284 },
285 name: fname!("replace"),
286 },
287 },
288 default_assignment: Some(OS_ASSET),
289 }
290}
291
292#[derive(Default)]
293pub struct InflatableFungibleAsset;
294
295impl IssuerWrapper for InflatableFungibleAsset {
296 type Wrapper<S: ContractStateRead> = IfaWrapper<S>;
297
298 fn schema() -> Schema { ifa_schema() }
299
300 fn types() -> TypeSystem { ifa_standard_types().type_system(ifa_schema()) }
301
302 fn scripts() -> Scripts {
303 let alu_lib_genesis = ifa_lib_genesis();
304 let alu_id_genesis = alu_lib_genesis.id();
305
306 let alu_lib_transfer = ifa_lib_transfer();
307 let alu_id_transfer = alu_lib_transfer.id();
308
309 let alu_lib_inflation = ifa_lib_inflation();
310 let alu_id_inflation = alu_lib_inflation.id();
311
312 Confined::from_checked(bmap! {
313 alu_id_genesis => alu_lib_genesis,
314 alu_id_transfer => alu_lib_transfer,
315 alu_id_inflation => alu_lib_inflation,
316 })
317 }
318}
319#[derive(Clone, Eq, PartialEq, Debug, From)]
320pub struct IfaWrapper<S: ContractStateRead>(ContractData<S>);
321
322impl<S: ContractStateRead> SchemaWrapper<S> for IfaWrapper<S> {
323 fn with(data: ContractData<S>) -> Self {
324 if data.schema.schema_id() != IFA_SCHEMA_ID {
325 panic!("the provided schema is not IFA");
326 }
327 Self(data)
328 }
329}
330
331impl<S: ContractStateRead> IfaWrapper<S> {
332 pub fn spec(&self) -> AssetSpec {
333 let strict_val = &self
334 .0
335 .global("spec")
336 .next()
337 .expect("IFA requires global state `spec` to have at least one item");
338 AssetSpec::from_strict_val_unchecked(strict_val)
339 }
340
341 pub fn contract_terms(&self) -> ContractTerms {
342 let strict_val = &self
343 .0
344 .global("terms")
345 .next()
346 .expect("IFA requires global state `terms` to have at least one item");
347 ContractTerms::from_strict_val_unchecked(strict_val)
348 }
349
350 pub fn reject_list_url(&self) -> Option<RejectListUrl> {
351 self.0
352 .global("rejectListUrl")
353 .next()
354 .map(|strict_val| RejectListUrl::from_strict_val_unchecked(&strict_val))
355 }
356
357 fn issued_supply(&self) -> impl Iterator<Item = Amount> + '_ {
358 self.0
359 .global("issuedSupply")
360 .map(|amount| Amount::from_strict_val_unchecked(&amount))
361 }
362
363 pub fn total_issued_supply(&self) -> Amount { self.issued_supply().sum() }
364
365 pub fn issuance_amounts(&self) -> Vec<Amount> { self.issued_supply().collect::<Vec<_>>() }
366
367 pub fn max_supply(&self) -> Amount {
368 self.0
369 .global("maxSupply")
370 .map(|amount| Amount::from_strict_val_unchecked(&amount))
371 .sum()
372 }
373
374 pub fn allocations<'c>(
375 &'c self,
376 filter: impl AssignmentsFilter + 'c,
377 ) -> impl Iterator<Item = FungibleAllocation> + 'c {
378 self.0.fungible_raw(OS_ASSET, filter).unwrap()
379 }
380
381 pub fn inflation_allocations<'c>(
382 &'c self,
383 filter: impl AssignmentsFilter + 'c,
384 ) -> impl Iterator<Item = FungibleAllocation> + 'c {
385 self.0.fungible_raw(OS_INFLATION, filter).unwrap()
386 }
387
388 pub fn replace_rights<'c>(
389 &'c self,
390 filter: impl AssignmentsFilter + 'c,
391 ) -> impl Iterator<Item = RightsAllocation> + 'c {
392 self.0.rights_raw(OS_REPLACE, filter).unwrap()
393 }
394}
395
396#[cfg(test)]
397mod test {
398 use crate::ifa::ifa_schema;
399 use crate::IFA_SCHEMA_ID;
400
401 #[test]
402 fn schema_id() {
403 let schema_id = ifa_schema().schema_id();
404 eprintln!("{:#04x?}", schema_id.to_byte_array());
405 assert_eq!(IFA_SCHEMA_ID, schema_id);
406 }
407}