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 { .. } => Err(RuntimeError::ApplicationError(
107 ApplicationError::AuthZoneError(super::AuthZoneError::ComposeProofError(
108 ComposeProofError::NonFungibleOperationNotSupported,
109 )),
110 )),
111 ResourceType::NonFungible { .. } => compose_non_fungible_proof(
112 proofs,
113 resource_address,
114 match ids {
115 Some(ids) => NonFungiblesSpecification::Exact(ids),
116 None => NonFungiblesSpecification::All,
117 },
118 api,
119 )
120 .map(|(proof, handles)| {
121 ComposedProof::NonFungible(ProofMoveableSubstate { restricted: false }, proof, handles)
122 }),
123 }
124}
125
126fn max_amount_locked<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
131 proofs: &[Proof],
132 resource_address: ResourceAddress,
133 api: &mut Y,
134) -> Result<(Decimal, IndexMap<LocalRef, Decimal>), RuntimeError> {
135 let mut max: IndexMap<LocalRef, Decimal> = index_map_new();
137 for proof in proofs {
138 let blueprint_id = api.get_blueprint_id(proof.0.as_node_id())?;
139
140 if blueprint_id.blueprint_name.eq(FUNGIBLE_PROOF_BLUEPRINT) {
141 let outer_object = api.get_outer_object(proof.0.as_node_id())?;
142 let proof_resource = ResourceAddress::new_or_panic(outer_object.into());
143 if proof_resource == resource_address {
144 let handle = api.kernel_open_substate(
145 proof.0.as_node_id(),
146 MAIN_BASE_PARTITION,
147 &FungibleProofField::ProofRefs.into(),
148 LockFlags::read_only(),
149 SystemLockData::default(),
150 )?;
151 let proof: FieldSubstate<FungibleProofSubstate> =
152 api.kernel_read_substate(handle)?.as_typed().unwrap();
153 for (container, locked_amount) in &proof.into_payload().evidence {
154 if let Some(existing) = max.get_mut(container) {
155 *existing = Decimal::max(*existing, *locked_amount);
156 } else {
157 max.insert(*container, *locked_amount);
158 }
159 }
160 api.kernel_close_substate(handle)?;
161 }
162 }
163 }
164
165 let mut total = Decimal::ZERO;
166 for v in max.values().cloned() {
167 total = total.checked_add(v).ok_or(RuntimeError::ApplicationError(
168 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
169 ComposeProofError::UnexpectedDecimalComputationError,
170 )),
171 ))?;
172 }
173 let per_container = max.into_iter().collect();
174 Ok((total, per_container))
175}
176
177#[allow(clippy::type_complexity)]
178fn max_ids_locked<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
179 proofs: &[Proof],
180 resource_address: ResourceAddress,
181 api: &mut Y,
182) -> Result<
183 (
184 IndexSet<NonFungibleLocalId>,
185 NonIterMap<LocalRef, IndexSet<NonFungibleLocalId>>,
186 ),
187 RuntimeError,
188> {
189 let mut total: IndexSet<NonFungibleLocalId> = index_set_new();
190 let mut per_container = NonIterMap::<LocalRef, IndexSet<NonFungibleLocalId>>::new();
192 for proof in proofs {
193 let blueprint_id = api.get_blueprint_id(proof.0.as_node_id())?;
194 if blueprint_id.blueprint_name.eq(NON_FUNGIBLE_PROOF_BLUEPRINT) {
195 let outer_object = api.get_outer_object(proof.0.as_node_id())?;
196 let proof_resource = ResourceAddress::new_or_panic(outer_object.into());
197 if proof_resource == resource_address {
198 let handle = api.kernel_open_substate(
199 proof.0.as_node_id(),
200 MAIN_BASE_PARTITION,
201 &NonFungibleProofField::ProofRefs.into(),
202 LockFlags::read_only(),
203 SystemLockData::default(),
204 )?;
205 let proof: FieldSubstate<NonFungibleProofSubstate> =
206 api.kernel_read_substate(handle)?.as_typed().unwrap();
207 for (container, locked_ids) in &proof.into_payload().evidence {
208 total.extend(locked_ids.clone());
209 if let Some(ids) = per_container.get_mut(container) {
210 ids.extend(locked_ids.clone());
211 } else {
212 per_container.insert(*container, locked_ids.clone());
213 }
214 }
215 }
216 }
217 }
218 Ok((total, per_container))
219}
220
221fn compose_fungible_proof<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
222 proofs: &[Proof],
223 resource_address: ResourceAddress,
224 amount: Option<Decimal>,
225 api: &mut Y,
226) -> Result<(FungibleProofSubstate, Vec<SubstateHandle>), RuntimeError> {
227 let (max_locked, mut per_container) = max_amount_locked(proofs, resource_address, api)?;
228 let amount = amount.unwrap_or(max_locked);
229
230 if amount > max_locked {
232 return Err(RuntimeError::ApplicationError(
233 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
234 ComposeProofError::InsufficientBaseProofs,
235 )),
236 ));
237 }
238
239 let mut evidence = index_map_new();
240 let mut remaining = amount;
241 let mut lock_handles = Vec::new();
242 'outer: for proof in proofs {
243 let handle = api.kernel_open_substate(
244 proof.0.as_node_id(),
245 MAIN_BASE_PARTITION,
246 &FungibleProofField::ProofRefs.into(),
247 LockFlags::read_only(),
248 SystemLockData::default(),
249 )?;
250 let substate: FieldSubstate<FungibleProofSubstate> =
251 api.kernel_read_substate(handle)?.as_typed().unwrap();
252 let proof = substate.into_payload();
253 for (container, _) in &proof.evidence {
254 if remaining.is_zero() {
255 break 'outer;
256 }
257
258 if let Some(quota) = per_container.swap_remove(container) {
259 let amount = Decimal::min(remaining, quota);
260 api.call_method(
261 container.as_node_id(),
262 match container {
263 LocalRef::Bucket(_) => FUNGIBLE_BUCKET_LOCK_AMOUNT_IDENT,
264 LocalRef::Vault(_) => FUNGIBLE_VAULT_LOCK_FUNGIBLE_AMOUNT_IDENT,
265 },
266 scrypto_args!(amount),
267 )?;
268 remaining = remaining
269 .checked_sub(amount)
270 .ok_or(RuntimeError::ApplicationError(
271 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
272 ComposeProofError::UnexpectedDecimalComputationError,
273 )),
274 ))?;
275 evidence.insert(*container, amount);
276 }
277 }
278 lock_handles.push(handle);
279 }
280
281 Ok((
282 FungibleProofSubstate::new(amount, evidence)
283 .map_err(|e| RuntimeError::ApplicationError(ApplicationError::ProofError(e)))?,
284 lock_handles,
285 ))
286}
287
288enum NonFungiblesSpecification {
289 All,
290 Some(usize),
291 Exact(IndexSet<NonFungibleLocalId>),
292}
293
294fn compose_non_fungible_proof<Y: KernelSubstateApi<SystemLockData> + SystemApi<RuntimeError>>(
295 proofs: &[Proof],
296 resource_address: ResourceAddress,
297 ids: NonFungiblesSpecification,
298 api: &mut Y,
299) -> Result<(NonFungibleProofSubstate, Vec<SubstateHandle>), RuntimeError> {
300 let (max_locked, mut per_container) = max_ids_locked(proofs, resource_address, api)?;
301 let ids = match ids {
302 NonFungiblesSpecification::All => max_locked.clone(),
303 NonFungiblesSpecification::Some(n) => {
304 let ids: IndexSet<NonFungibleLocalId> = max_locked.iter().take(n).cloned().collect();
305 if ids.len() != n {
306 return Err(RuntimeError::ApplicationError(
307 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
308 ComposeProofError::InsufficientBaseProofs,
309 )),
310 ));
311 }
312 ids
313 }
314 NonFungiblesSpecification::Exact(ids) => {
315 if !max_locked.is_superset(&ids) {
316 return Err(RuntimeError::ApplicationError(
317 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
318 ComposeProofError::InsufficientBaseProofs,
319 )),
320 ));
321 }
322 ids
323 }
324 };
325
326 if !max_locked.is_superset(&ids) {
327 return Err(RuntimeError::ApplicationError(
328 ApplicationError::AuthZoneError(AuthZoneError::ComposeProofError(
329 ComposeProofError::InsufficientBaseProofs,
330 )),
331 ));
332 }
333
334 let mut evidence = index_map_new();
335 let mut remaining = ids.clone();
336 let mut lock_handles = Vec::new();
337 'outer: for proof in proofs {
338 let handle = api.kernel_open_substate(
339 proof.0.as_node_id(),
340 MAIN_BASE_PARTITION,
341 &NonFungibleProofField::ProofRefs.into(),
342 LockFlags::read_only(),
343 SystemLockData::default(),
344 )?;
345 let substate: FieldSubstate<NonFungibleProofSubstate> =
346 api.kernel_read_substate(handle)?.as_typed().unwrap();
347 let proof = substate.into_payload().clone();
348 for (container, _) in &proof.evidence {
349 if remaining.is_empty() {
350 break 'outer;
351 }
352
353 if let Some(quota) = per_container.remove(container) {
354 let ids = remaining.intersection("a).cloned().collect();
355 api.call_method(
356 container.as_node_id(),
357 match container {
358 LocalRef::Bucket(_) => NON_FUNGIBLE_BUCKET_LOCK_NON_FUNGIBLES_IDENT,
359 LocalRef::Vault(_) => NON_FUNGIBLE_VAULT_LOCK_NON_FUNGIBLES_IDENT,
360 },
361 scrypto_args!(&ids),
362 )?;
363 for id in &ids {
364 remaining.swap_remove(id);
365 }
366 evidence.insert(*container, ids);
367 }
368 }
369 lock_handles.push(handle);
370 }
371
372 Ok((
373 NonFungibleProofSubstate::new(ids.clone(), evidence)
374 .map_err(|e| RuntimeError::ApplicationError(ApplicationError::ProofError(e)))?,
375 lock_handles,
376 ))
377}