1use 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::RgbIsa;
39use rgbstd::{rgbasm, Amount, SchemaId, TransitionDetails};
40use strict_types::TypeSystem;
41
42use crate::{
43 ERRNO_INVALID_SIGNATURE, ERRNO_ISSUED_MISMATCH, ERRNO_MISSING_PUBKEY, ERRNO_NON_EQUAL_IN_OUT,
44 GS_ISSUED_SUPPLY, GS_NOMINAL, GS_PUBKEY, GS_TERMS, OS_ASSET, TS_TRANSFER,
45};
46
47pub const PFA_SCHEMA_ID: SchemaId = SchemaId::from_array([
48 0x62, 0xfb, 0xef, 0x43, 0x85, 0x2c, 0x1e, 0xe3, 0xd0, 0x0d, 0x3d, 0xe7, 0x21, 0x0f, 0x66, 0x9e,
49 0x9b, 0x2a, 0x31, 0xba, 0xec, 0xe6, 0x56, 0x19, 0x45, 0xbc, 0xb2, 0x98, 0x75, 0x6b, 0x91, 0x8f,
50]);
51
52pub(crate) fn pfa_lib_transition() -> Lib {
53 let code = rgbasm! {
54 put a8[0],ERRNO_NON_EQUAL_IN_OUT; svs OS_ASSET; test; put a8[0],ERRNO_MISSING_PUBKEY; put a32[0],0; ldc GS_PUBKEY,a32[0],s16[0]; put a8[0],ERRNO_INVALID_SIGNATURE; vts s16[0]; test; ret; };
68 Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code).expect("wrong non-inflatable asset script")
69}
70
71pub(crate) fn pfa_lib_genesis() -> Lib {
72 let code = rgbasm! {
73 put a8[0],ERRNO_ISSUED_MISMATCH; put a8[1],0; put a16[0],0; ldg GS_ISSUED_SUPPLY,a8[1],s16[0]; extr s16[0],a64[0],a16[0]; sas OS_ASSET; test; ret; };
84 Lib::assemble::<Instr<RgbIsa<MemContract>>>(&code).expect("wrong non-inflatable asset script")
85}
86
87fn pfa_standard_types() -> StandardTypes { StandardTypes::with(rgb_contract_stl()) }
88
89fn pfa_schema() -> Schema {
90 let types = pfa_standard_types();
91
92 let alu_lib_genesis = pfa_lib_genesis();
93 let alu_id_genesis = alu_lib_genesis.id();
94
95 let alu_lib_transition = pfa_lib_transition();
96 let alu_id_transition = alu_lib_transition.id();
97
98 Schema {
99 ffv: zero!(),
100 name: tn!("PermissionedFungibleAsset"),
101 meta_types: none!(),
102 global_types: tiny_bmap! {
103 GS_NOMINAL => GlobalDetails {
104 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.AssetSpec")),
105 name: fname!("spec"),
106 },
107 GS_TERMS => GlobalDetails {
108 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.ContractTerms")),
109 name: fname!("terms"),
110 },
111 GS_ISSUED_SUPPLY => GlobalDetails {
112 global_state_schema: GlobalStateSchema::once(types.get("RGBContract.Amount")),
113 name: fname!("issuedSupply"),
114 },
115 GS_PUBKEY => GlobalDetails {
116 global_state_schema: GlobalStateSchema::once(types.get("Bitcoin.CompressedPk")),
117 name: fname!("pubkey"),
118 },
119 },
120 owned_types: tiny_bmap! {
121 OS_ASSET => AssignmentDetails {
122 owned_state_schema: OwnedStateSchema::Fungible(FungibleType::Unsigned64Bit),
123 name: fname!("assetOwner"),
124 default_transition: TS_TRANSFER,
125 }
126 },
127 genesis: GenesisSchema {
128 metadata: none!(),
129 globals: tiny_bmap! {
130 GS_NOMINAL => Occurrences::Once,
131 GS_TERMS => Occurrences::Once,
132 GS_ISSUED_SUPPLY => Occurrences::Once,
133 GS_PUBKEY => Occurrences::Once,
134 },
135 assignments: tiny_bmap! {
136 OS_ASSET => Occurrences::OnceOrMore,
137 },
138 validator: Some(LibSite::with(0, alu_id_genesis)),
139 },
140 transitions: tiny_bmap! {
141 TS_TRANSFER => TransitionDetails {
142 transition_schema: TransitionSchema {
143 metadata: none!(),
144 globals: none!(),
145 inputs: tiny_bmap! {
146 OS_ASSET => Occurrences::OnceOrMore
147 },
148 assignments: tiny_bmap! {
149 OS_ASSET => Occurrences::OnceOrMore
150 },
151 validator: Some(LibSite::with(0, alu_id_transition))
152 },
153 name: fname!("transfer"),
154 }
155 },
156 default_assignment: Some(OS_ASSET),
157 }
158}
159
160#[derive(Default)]
161pub struct PermissionedFungibleAsset;
162
163impl IssuerWrapper for PermissionedFungibleAsset {
164 type Wrapper<S: ContractStateRead> = PfaWrapper<S>;
165
166 fn schema() -> Schema { pfa_schema() }
167
168 fn types() -> TypeSystem { pfa_standard_types().type_system(pfa_schema()) }
169
170 fn scripts() -> Scripts {
171 let alu_lib_genesis = pfa_lib_genesis();
172 let alu_id_genesis = alu_lib_genesis.id();
173
174 let alu_lib_transition = pfa_lib_transition();
175 let alu_id_transition = alu_lib_transition.id();
176
177 Confined::from_checked(bmap! {
178 alu_id_genesis => alu_lib_genesis,
179 alu_id_transition => alu_lib_transition,
180 })
181 }
182}
183
184#[derive(Clone, Eq, PartialEq, Debug, From)]
185pub struct PfaWrapper<S: ContractStateRead>(ContractData<S>);
186
187impl<S: ContractStateRead> SchemaWrapper<S> for PfaWrapper<S> {
188 fn with(data: ContractData<S>) -> Self {
189 if data.schema.schema_id() != PFA_SCHEMA_ID {
190 panic!("the provided schema is not PFA");
191 }
192 Self(data)
193 }
194}
195
196impl<S: ContractStateRead> PfaWrapper<S> {
197 pub fn spec(&self) -> AssetSpec {
198 let strict_val = &self
199 .0
200 .global("spec")
201 .next()
202 .expect("PFA requires global state `spec` to have at least one item");
203 AssetSpec::from_strict_val_unchecked(strict_val)
204 }
205
206 pub fn contract_terms(&self) -> ContractTerms {
207 let strict_val = &self
208 .0
209 .global("terms")
210 .next()
211 .expect("PFA requires global state `terms` to have at least one item");
212 ContractTerms::from_strict_val_unchecked(strict_val)
213 }
214
215 pub fn total_issued_supply(&self) -> Amount {
216 self.0
217 .global("issuedSupply")
218 .map(|amount| Amount::from_strict_val_unchecked(&amount))
219 .sum()
220 }
221
222 pub fn allocations<'c>(
223 &'c self,
224 filter: impl AssignmentsFilter + 'c,
225 ) -> impl Iterator<Item = FungibleAllocation> + 'c {
226 self.0.fungible_raw(OS_ASSET, filter).unwrap()
227 }
228}
229
230#[cfg(test)]
231mod test {
232 use super::*;
233
234 #[test]
235 fn schema_id() {
236 let schema_id = pfa_schema().schema_id();
237 eprintln!("{:#04x?}", schema_id.to_byte_array());
238 assert_eq!(PFA_SCHEMA_ID, schema_id);
239 }
240}