1use crate::blueprints::resource::*;
2use crate::errors::{ApplicationError, RuntimeError};
3use crate::internal_prelude::*;
4use crate::kernel::kernel_api::KernelSubstateApi;
5use crate::system::system_callback::SystemLockData;
6use crate::system::system_substates::FieldSubstate;
7use radix_engine_interface::api::LockFlags;
8use radix_engine_interface::api::SystemApi;
9use radix_engine_interface::blueprints::resource::*;
10use radix_native_sdk::resource::ResourceManager;
11
12use super::AuthZoneError;
13
14#[derive(Debug, Clone, PartialEq, Eq, ScryptoSbor)]
15pub enum ComposeProofError {
16 NonFungibleOperationNotSupported,
17 InsufficientBaseProofs,
18 InvalidAmount,
19 UnexpectedDecimalComputationError,
20}
21
22pub enum ComposedProof {
23 Fungible(
24 ProofMoveableSubstate,
25 FungibleProofSubstate,
26 Vec<SubstateHandle>,
27 ),
28 NonFungible(
29 ProofMoveableSubstate,
30 NonFungibleProofSubstate,
31 Vec<SubstateHandle>,
32 ),
33}
34
35impl From<ComposedProof> for BTreeMap<SubstateKey, IndexedScryptoValue> {
36 fn from(value: ComposedProof) -> Self {
37 match value {
38 ComposedProof::Fungible(info, proof, ..) => btreemap!(
39 FungibleProofField::Moveable.into() => IndexedScryptoValue::from_typed(&FieldSubstate::new_unlocked_field(info)),
40 FungibleProofField::ProofRefs.into() => IndexedScryptoValue::from_typed(&FieldSubstate::new_unlocked_field(proof)),
41 ),
42 ComposedProof::NonFungible(info, proof, ..) => btreemap!(
43 NonFungibleProofField::Moveable.into() => IndexedScryptoValue::from_typed(&FieldSubstate::new_unlocked_field(info)),
44 NonFungibleProofField::ProofRefs.into() => IndexedScryptoValue::from_typed(&FieldSubstate::new_unlocked_field(proof)),
45 ),
46 }
47 }
48}
49
50pub fn compose_proof_by_amount<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
52 proofs: &[Proof],
53 resource_address: ResourceAddress,
54 amount: Option<Decimal>,
55 api: &mut Y,
56) -> Result<ComposedProof, RuntimeError> {
57 let resource_type = ResourceManager(resource_address).resource_type(api)?;
58
59 if let Some(amount) = amount {
60 if !resource_type.check_amount(amount) {
61 return Err(RuntimeError::ApplicationError(
62 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
63 ComposeProofError::InvalidAmount,
64 )),
65 ));
66 }
67 }
68
69 match resource_type {
70 ResourceType::Fungible { .. } => {
71 compose_fungible_proof(proofs, resource_address, amount, api).map(|(proof, handles)| {
72 ComposedProof::Fungible(ProofMoveableSubstate { restricted: false }, proof, handles)
73 })
74 }
75 ResourceType::NonFungible { .. } => compose_non_fungible_proof(
76 proofs,
77 resource_address,
78 match amount {
79 Some(amount) => {
80 NonFungiblesSpecification::Some(amount.to_string().parse().map_err(|_| {
81 RuntimeError::ApplicationError(ApplicationError::AuthZoneError(
82 AuthZoneError::ComposeProofError(ComposeProofError::InvalidAmount),
83 ))
84 })?)
85 }
86 None => NonFungiblesSpecification::All,
87 },
88 api,
89 )
90 .map(|(proof, handles)| {
91 ComposedProof::NonFungible(ProofMoveableSubstate { restricted: false }, proof, handles)
92 }),
93 }
94}
95
96pub fn compose_proof_by_ids<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
98 proofs: &[Proof],
99 resource_address: ResourceAddress,
100 ids: Option<IndexSet<NonFungibleLocalId>>,
101 api: &mut Y,
102) -> Result<ComposedProof, RuntimeError> {
103 let resource_type = ResourceManager(resource_address).resource_type(api)?;
104
105 match resource_type {
106 ResourceType::Fungible { .. } => {
107 return Err(RuntimeError::ApplicationError(
108 ApplicationError::AuthZoneError(super::AuthZoneError::ComposeProofError(
109 ComposeProofError::NonFungibleOperationNotSupported,
110 )),
111 ));
112 }
113 ResourceType::NonFungible { .. } => compose_non_fungible_proof(
114 proofs,
115 resource_address,
116 match ids {
117 Some(ids) => NonFungiblesSpecification::Exact(ids),
118 None => NonFungiblesSpecification::All,
119 },
120 api,
121 )
122 .map(|(proof, handles)| {
123 ComposedProof::NonFungible(ProofMoveableSubstate { restricted: false }, proof, handles)
124 }),
125 }
126}
127
128fn max_amount_locked<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
133 proofs: &[Proof],
134 resource_address: ResourceAddress,
135 api: &mut Y,
136) -> Result<(Decimal, IndexMap<LocalRef, Decimal>), RuntimeError> {
137 let mut max: IndexMap<LocalRef, Decimal> = index_map_new();
139 for proof in proofs {
140 let blueprint_id = api.get_blueprint_id(proof.0.as_node_id())?;
141
142 if blueprint_id.blueprint_name.eq(FUNGIBLE_PROOF_BLUEPRINT) {
143 let outer_object = api.get_outer_object(proof.0.as_node_id())?;
144 let proof_resource = ResourceAddress::new_or_panic(outer_object.into());
145 if proof_resource == resource_address {
146 let handle = api.kernel_open_substate(
147 proof.0.as_node_id(),
148 MAIN_BASE_PARTITION,
149 &FungibleProofField::ProofRefs.into(),
150 LockFlags::read_only(),
151 SystemLockData::default(),
152 )?;
153 let proof: FieldSubstate<FungibleProofSubstate> =
154 api.kernel_read_substate(handle)?.as_typed().unwrap();
155 for (container, locked_amount) in &proof.into_payload().evidence {
156 if let Some(existing) = max.get_mut(container) {
157 *existing = Decimal::max(*existing, locked_amount.clone());
158 } else {
159 max.insert(container.clone(), locked_amount.clone());
160 }
161 }
162 api.kernel_close_substate(handle)?;
163 }
164 }
165 }
166
167 let mut total = Decimal::ZERO;
168 for v in max.values().cloned() {
169 total = total.checked_add(v).ok_or(RuntimeError::ApplicationError(
170 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
171 ComposeProofError::UnexpectedDecimalComputationError,
172 )),
173 ))?;
174 }
175 let per_container = max.into_iter().collect();
176 Ok((total, per_container))
177}
178
179fn max_ids_locked<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
180 proofs: &[Proof],
181 resource_address: ResourceAddress,
182 api: &mut Y,
183) -> Result<
184 (
185 IndexSet<NonFungibleLocalId>,
186 NonIterMap<LocalRef, IndexSet<NonFungibleLocalId>>,
187 ),
188 RuntimeError,
189> {
190 let mut total: IndexSet<NonFungibleLocalId> = index_set_new();
191 let mut per_container = NonIterMap::<LocalRef, IndexSet<NonFungibleLocalId>>::new();
193 for proof in proofs {
194 let blueprint_id = api.get_blueprint_id(proof.0.as_node_id())?;
195 if blueprint_id.blueprint_name.eq(NON_FUNGIBLE_PROOF_BLUEPRINT) {
196 let outer_object = api.get_outer_object(proof.0.as_node_id())?;
197 let proof_resource = ResourceAddress::new_or_panic(outer_object.into());
198 if proof_resource == resource_address {
199 let handle = api.kernel_open_substate(
200 proof.0.as_node_id(),
201 MAIN_BASE_PARTITION,
202 &NonFungibleProofField::ProofRefs.into(),
203 LockFlags::read_only(),
204 SystemLockData::default(),
205 )?;
206 let proof: FieldSubstate<NonFungibleProofSubstate> =
207 api.kernel_read_substate(handle)?.as_typed().unwrap();
208 for (container, locked_ids) in &proof.into_payload().evidence {
209 total.extend(locked_ids.clone());
210 if let Some(ids) = per_container.get_mut(container) {
211 ids.extend(locked_ids.clone());
212 } else {
213 per_container.insert(container.clone(), locked_ids.clone());
214 }
215 }
216 }
217 }
218 }
219 Ok((total, per_container))
220}
221
222fn compose_fungible_proof<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
223 proofs: &[Proof],
224 resource_address: ResourceAddress,
225 amount: Option<Decimal>,
226 api: &mut Y,
227) -> Result<(FungibleProofSubstate, Vec<SubstateHandle>), RuntimeError> {
228 let (max_locked, mut per_container) = max_amount_locked(proofs, resource_address, api)?;
229 let amount = amount.unwrap_or(max_locked);
230
231 if amount > max_locked {
233 return Err(RuntimeError::ApplicationError(
234 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
235 ComposeProofError::InsufficientBaseProofs,
236 )),
237 ));
238 }
239
240 let mut evidence = index_map_new();
241 let mut remaining = amount.clone();
242 let mut lock_handles = Vec::new();
243 'outer: for proof in proofs {
244 let handle = api.kernel_open_substate(
245 proof.0.as_node_id(),
246 MAIN_BASE_PARTITION,
247 &FungibleProofField::ProofRefs.into(),
248 LockFlags::read_only(),
249 SystemLockData::default(),
250 )?;
251 let substate: FieldSubstate<FungibleProofSubstate> =
252 api.kernel_read_substate(handle)?.as_typed().unwrap();
253 let proof = substate.into_payload();
254 for (container, _) in &proof.evidence {
255 if remaining.is_zero() {
256 break 'outer;
257 }
258
259 if let Some(quota) = per_container.swap_remove(container) {
260 let amount = Decimal::min(remaining, quota);
261 api.call_method(
262 container.as_node_id(),
263 match container {
264 LocalRef::Bucket(_) => FUNGIBLE_BUCKET_LOCK_AMOUNT_IDENT,
265 LocalRef::Vault(_) => FUNGIBLE_VAULT_LOCK_FUNGIBLE_AMOUNT_IDENT,
266 },
267 scrypto_args!(amount),
268 )?;
269 remaining = remaining
270 .checked_sub(amount)
271 .ok_or(RuntimeError::ApplicationError(
272 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
273 ComposeProofError::UnexpectedDecimalComputationError,
274 )),
275 ))?;
276 evidence.insert(container.clone(), amount);
277 }
278 }
279 lock_handles.push(handle);
280 }
281
282 Ok((
283 FungibleProofSubstate::new(amount, evidence)
284 .map_err(|e| RuntimeError::ApplicationError(ApplicationError::ProofError(e)))?,
285 lock_handles,
286 ))
287}
288
289enum NonFungiblesSpecification {
290 All,
291 Some(usize),
292 Exact(IndexSet<NonFungibleLocalId>),
293}
294
295fn compose_non_fungible_proof<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
296 proofs: &[Proof],
297 resource_address: ResourceAddress,
298 ids: NonFungiblesSpecification,
299 api: &mut Y,
300) -> Result<(NonFungibleProofSubstate, Vec<SubstateHandle>), RuntimeError> {
301 let (max_locked, mut per_container) = max_ids_locked(proofs, resource_address, api)?;
302 let ids = match ids {
303 NonFungiblesSpecification::All => max_locked.clone(),
304 NonFungiblesSpecification::Some(n) => {
305 let ids: IndexSet<NonFungibleLocalId> = max_locked.iter().cloned().take(n).collect();
306 if ids.len() != n {
307 return Err(RuntimeError::ApplicationError(
308 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
309 ComposeProofError::InsufficientBaseProofs,
310 )),
311 ));
312 }
313 ids
314 }
315 NonFungiblesSpecification::Exact(ids) => {
316 if !max_locked.is_superset(&ids) {
317 return Err(RuntimeError::ApplicationError(
318 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
319 ComposeProofError::InsufficientBaseProofs,
320 )),
321 ));
322 }
323 ids
324 }
325 };
326
327 if !max_locked.is_superset(&ids) {
328 return Err(RuntimeError::ApplicationError(
329 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
330 ComposeProofError::InsufficientBaseProofs,
331 )),
332 ));
333 }
334
335 let mut evidence = index_map_new();
336 let mut remaining = ids.clone();
337 let mut lock_handles = Vec::new();
338 'outer: for proof in proofs {
339 let handle = api.kernel_open_substate(
340 proof.0.as_node_id(),
341 MAIN_BASE_PARTITION,
342 &NonFungibleProofField::ProofRefs.into(),
343 LockFlags::read_only(),
344 SystemLockData::default(),
345 )?;
346 let substate: FieldSubstate<NonFungibleProofSubstate> =
347 api.kernel_read_substate(handle)?.as_typed().unwrap();
348 let proof = substate.into_payload().clone();
349 for (container, _) in &proof.evidence {
350 if remaining.is_empty() {
351 break 'outer;
352 }
353
354 if let Some(quota) = per_container.remove(container) {
355 let ids = remaining.intersection("a).cloned().collect();
356 api.call_method(
357 container.as_node_id(),
358 match container {
359 LocalRef::Bucket(_) => NON_FUNGIBLE_BUCKET_LOCK_NON_FUNGIBLES_IDENT,
360 LocalRef::Vault(_) => NON_FUNGIBLE_VAULT_LOCK_NON_FUNGIBLES_IDENT,
361 },
362 scrypto_args!(&ids),
363 )?;
364 for id in &ids {
365 remaining.swap_remove(id);
366 }
367 evidence.insert(container.clone(), ids);
368 }
369 }
370 lock_handles.push(handle);
371 }
372
373 Ok((
374 NonFungibleProofSubstate::new(ids.clone(), evidence)
375 .map_err(|e| RuntimeError::ApplicationError(ApplicationError::ProofError(e)))?,
376 lock_handles,
377 ))
378}