1use super::*;
2use crate::internal_prelude::*;
3use crate::manifest::*;
4use core::ops::*;
5use radix_engine_interface::prelude::*;
6
7pub struct StaticResourceMovementsVisitor {
10 worktop: TrackedResources,
12 tracked_buckets: IndexMap<ManifestBucket, (ResourceAddress, TrackedResource)>,
14 tracked_named_addresses: IndexMap<ManifestNamedAddress, BlueprintId>,
16 invocation_static_information: IndexMap<usize, InvocationStaticInformation>,
19 current_instruction: Option<CurrentInstruction>,
21 next_invocation_assertion: Option<(OwnedNextCallAssertion, ChangeSource)>,
23}
24
25pub struct CurrentInstruction {
26 index: usize,
27 sent_resources: TrackedResources,
28}
29
30#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
32pub enum ChangeSource {
33 InitialYieldFromParent,
34 Invocation { instruction_index: usize },
35 NewBucket { instruction_index: usize },
36 Assertion { instruction_index: usize },
37}
38
39impl ChangeSource {
40 pub fn invocation_at(instruction_index: usize) -> Self {
41 Self::Invocation { instruction_index }
42 }
43
44 pub fn bucket_at(instruction_index: usize) -> Self {
45 Self::NewBucket { instruction_index }
46 }
47
48 pub fn assertion_at(instruction_index: usize) -> Self {
49 Self::Assertion { instruction_index }
50 }
51}
52
53impl StaticResourceMovementsVisitor {
54 pub fn new(initial_worktop_state_is_unknown: bool) -> Self {
55 let mut worktop = TrackedResources::new_empty();
56
57 if initial_worktop_state_is_unknown {
58 worktop.mut_add_unspecified_resources([ChangeSource::InitialYieldFromParent])
59 }
60
61 Self {
62 worktop,
63 tracked_buckets: Default::default(),
64 tracked_named_addresses: Default::default(),
65 invocation_static_information: Default::default(),
66 current_instruction: None,
67 next_invocation_assertion: None,
68 }
69 }
70
71 pub fn output(self) -> StaticResourceMovementsOutput {
72 StaticResourceMovementsOutput {
73 invocation_static_information: self.invocation_static_information,
74 }
75 }
76
77 fn handle_invocation_end(
78 &mut self,
79 invocation_kind: InvocationKind<'_>,
80 args: &ManifestValue,
81 current_instruction: CurrentInstruction,
82 ) -> Result<InvocationStaticInformation, StaticResourceMovementsError> {
83 let invocation_input = current_instruction.sent_resources;
85
86 let change_source = ChangeSource::Invocation {
87 instruction_index: current_instruction.index,
88 };
89
90 let mut invocation_output = match self.resolve_native_invocation(invocation_kind, args) {
94 Ok(Some((matched_invocation, receiver))) => {
95 matched_invocation.output(InvocationDetails {
96 receiver,
97 sent_resources: &invocation_input,
98 source: change_source,
99 })?
100 }
101 Err(..) | Ok(None) => {
102 TrackedResources::new_with_possible_balance_of_unspecified_resources([
103 change_source,
104 ])
105 }
106 };
107
108 if let Some((assertion, change_source)) = self.next_invocation_assertion.take() {
109 match assertion.as_ref() {
112 NextCallAssertion::ReturnsOnly { constraints } => {
113 invocation_output
114 .handle_resources_only_assertion(constraints, change_source)?;
115 }
116 NextCallAssertion::ReturnsInclude { constraints } => {
117 invocation_output
118 .handle_resources_include_assertion(constraints, change_source)?;
119 }
120 }
121 }
122
123 self.worktop.mut_add(invocation_output.clone())?;
125
126 Ok(InvocationStaticInformation {
127 kind: invocation_kind.into(),
128 input: invocation_input,
129 output: invocation_output,
130 })
131 }
132
133 fn resolve_native_invocation(
134 &self,
135 invocation_kind: InvocationKind,
136 args: &ManifestValue,
137 ) -> Result<
138 Option<(TypedManifestNativeInvocation, InvocationReceiver)>,
139 StaticResourceMovementsError,
140 > {
141 match invocation_kind {
143 InvocationKind::DirectMethod { address, method } => {
144 let resolved_dynamic_address = ResolvedDynamicAddress::StaticAddress(*address);
145 let Some(typed_invocation) =
146 TypedManifestNativeInvocation::from_direct_method_invocation(
147 &resolved_dynamic_address,
148 method,
149 args,
150 )?
151 else {
152 return Ok(None);
153 };
154 let invocation_receiver = InvocationReceiver::DirectAccess(*address);
155 Ok(Some((typed_invocation, invocation_receiver)))
156 }
157 InvocationKind::Method {
158 address,
159 module_id,
160 method,
161 } => {
162 let resolved_dynamic_address = match address {
163 ManifestGlobalAddress::Static(global_address) => {
164 ResolvedDynamicAddress::StaticAddress(*global_address)
165 }
166 ManifestGlobalAddress::Named(named_address) => {
167 let blueprint_id = self.tracked_named_addresses.get(named_address)
168 .expect("Interpreter should have validated the address exists, because we're handling this on instruction end");
169 ResolvedDynamicAddress::BlueprintResolvedFromNamedAddress(
170 blueprint_id.clone(),
171 )
172 }
173 };
174 let Some(typed_invocation) = TypedManifestNativeInvocation::from_method_invocation(
175 &resolved_dynamic_address,
176 module_id,
177 method,
178 args,
179 )?
180 else {
181 return Ok(None);
182 };
183 let invocation_receiver =
184 InvocationReceiver::GlobalMethod(resolved_dynamic_address);
185 Ok(Some((typed_invocation, invocation_receiver)))
186 }
187 InvocationKind::Function {
188 address: ManifestPackageAddress::Static(package_address),
189 blueprint,
190 function,
191 } => {
192 let blueprint_id = BlueprintId::new(package_address, blueprint);
193 let Some(typed_invocation) =
194 TypedManifestNativeInvocation::from_function_invocation(
195 &blueprint_id,
196 function,
197 args,
198 )?
199 else {
200 return Ok(None);
201 };
202 let invocation_receiver = InvocationReceiver::BlueprintFunction(blueprint_id);
203 Ok(Some((typed_invocation, invocation_receiver)))
204 }
205 InvocationKind::YieldToParent
206 | InvocationKind::YieldToChild { .. }
207 | InvocationKind::Function {
208 address: ManifestPackageAddress::Named(_),
209 ..
210 } => Ok(None),
211 }
212 }
213
214 fn current_instruction_index(&mut self) -> usize {
215 self.current_instruction
216 .as_ref()
217 .expect("Should only be called during an instruction")
218 .index
219 }
220
221 fn current_instruction_sent_resources(&mut self) -> &mut TrackedResources {
222 &mut self
223 .current_instruction
224 .as_mut()
225 .expect("Should only be called during an instruction")
226 .sent_resources
227 }
228
229 fn handle_start_instruction(
230 &mut self,
231 OnStartInstruction { index, .. }: OnStartInstruction,
232 ) -> Result<(), StaticResourceMovementsError> {
233 self.current_instruction = Some(CurrentInstruction {
234 index,
235 sent_resources: TrackedResources::new_empty(),
236 });
237 Ok(())
238 }
239
240 fn handle_end_instruction(
241 &mut self,
242 OnEndInstruction { effect, index }: OnEndInstruction,
243 ) -> Result<(), StaticResourceMovementsError> {
244 let instruction_data = self.current_instruction.take().unwrap();
245
246 let ManifestInstructionEffect::Invocation { kind, args, .. } = effect else {
248 return Ok(());
249 };
250
251 let invocation_static_information =
253 self.handle_invocation_end(kind, args, instruction_data)?;
254
255 self.invocation_static_information
257 .insert(index, invocation_static_information);
258
259 Ok(())
260 }
261
262 fn handle_new_bucket(
263 &mut self,
264 OnNewBucket { bucket, state }: OnNewBucket,
265 ) -> Result<(), StaticResourceMovementsError> {
266 let source = ChangeSource::NewBucket {
267 instruction_index: self.current_instruction_index(),
268 };
269 let (resource_address, resource_amount) = match state.source_amount {
270 BucketSourceAmount::AllOnWorktop { resource_address } => {
271 let resource_amount = self.worktop.mut_take_resource(
272 *resource_address,
273 ResourceTakeAmount::All,
274 source,
275 )?;
276 (*resource_address, resource_amount)
277 }
278 BucketSourceAmount::AmountFromWorktop {
279 resource_address,
280 amount,
281 } => {
282 let resource_amount = self.worktop.mut_take_resource(
283 *resource_address,
284 ResourceTakeAmount::exact_amount(amount)?,
285 source,
286 )?;
287 (*resource_address, resource_amount)
288 }
289 BucketSourceAmount::NonFungiblesFromWorktop {
290 resource_address,
291 ids,
292 } => {
293 let resource_amount = self.worktop.mut_take_resource(
294 *resource_address,
295 ResourceTakeAmount::exact_non_fungibles(ids.iter().cloned()),
296 source,
297 )?;
298 (*resource_address, resource_amount)
299 }
300 };
301
302 self.tracked_buckets
303 .insert(bucket, (resource_address, resource_amount));
304
305 Ok(())
306 }
307
308 fn handle_consume_bucket(
309 &mut self,
310 OnConsumeBucket {
311 bucket,
312 destination,
313 ..
314 }: OnConsumeBucket,
315 ) -> Result<(), StaticResourceMovementsError> {
316 let (resource_address, amount) = self
317 .tracked_buckets
318 .swap_remove(&bucket)
319 .expect("Interpreter should ensure the bucket lifetimes are validated");
320
321 match destination {
322 BucketDestination::Worktop => {
323 self.worktop.mut_add_resource(resource_address, amount)?;
324 }
325 BucketDestination::Burned => {}
326 BucketDestination::Invocation(_) => self
327 .current_instruction_sent_resources()
328 .mut_add_resource(resource_address, amount)?,
329 }
330
331 Ok(())
332 }
333
334 fn handle_pass_expression(
335 &mut self,
336 OnPassExpression {
337 expression,
338 destination,
339 ..
340 }: OnPassExpression,
341 ) -> Result<(), StaticResourceMovementsError> {
342 match (expression, destination) {
343 (ManifestExpression::EntireWorktop, ExpressionDestination::Invocation(_)) => {
344 let entire_worktop = self.worktop.take_all();
345 self.current_instruction_sent_resources()
346 .mut_add(entire_worktop)?;
347 }
348 (ManifestExpression::EntireAuthZone, _) => {}
349 }
350
351 Ok(())
352 }
353
354 fn handle_resource_assertion(
355 &mut self,
356 OnResourceAssertion { assertion }: OnResourceAssertion,
357 ) -> Result<(), StaticResourceMovementsError> {
358 let change_source = ChangeSource::assertion_at(self.current_instruction_index());
361 match assertion {
362 ResourceAssertion::Worktop(WorktopAssertion::ResourceNonZeroAmount {
363 resource_address,
364 }) => self.worktop.handle_resource_assertion(
365 *resource_address,
366 ResourceBounds::non_zero(),
367 change_source,
368 ),
369 ResourceAssertion::Worktop(WorktopAssertion::ResourceAtLeastAmount {
370 resource_address,
371 amount,
372 }) => self.worktop.handle_resource_assertion(
373 *resource_address,
374 ResourceBounds::at_least_amount(amount)?,
375 change_source,
376 ),
377 ResourceAssertion::Worktop(WorktopAssertion::ResourceAtLeastNonFungibles {
378 resource_address,
379 ids,
380 }) => self.worktop.handle_resource_assertion(
381 *resource_address,
382 ResourceBounds::at_least_non_fungibles(ids.iter().cloned()),
383 change_source,
384 ),
385 ResourceAssertion::Worktop(WorktopAssertion::ResourcesOnly { constraints }) => self
386 .worktop
387 .handle_resources_only_assertion(constraints, change_source),
388 ResourceAssertion::Worktop(WorktopAssertion::ResourcesInclude { constraints }) => self
389 .worktop
390 .handle_resources_include_assertion(constraints, change_source),
391 ResourceAssertion::NextCall(next_call_assertion) => {
392 if self.next_invocation_assertion.is_some() {
393 panic!("Interpreter should have verified that a next call assertion must be used before another is created");
394 }
395 self.next_invocation_assertion = Some((next_call_assertion.into(), change_source));
396 Ok(())
397 }
398 ResourceAssertion::Bucket(BucketAssertion::Contents { bucket, constraint }) => {
399 let (_, tracked_resource) = self
400 .tracked_buckets
401 .get_mut(&bucket)
402 .expect("Interpreter should have already validated that the bucket exists");
403 tracked_resource.handle_assertion(
404 ResourceBounds::new_for_manifest_constraint(constraint)?,
405 change_source,
406 )
407 }
408 }
409 }
410
411 fn handle_new_named_address(
412 &mut self,
413 OnNewNamedAddress {
414 named_address,
415 package_address,
416 blueprint_name,
417 ..
418 }: OnNewNamedAddress,
419 ) -> Result<(), StaticResourceMovementsError> {
420 self.tracked_named_addresses.insert(
421 named_address,
422 BlueprintId::new(package_address, blueprint_name),
423 );
424 Ok(())
425 }
426
427 fn handle_finish(&mut self, OnFinish: OnFinish) -> Result<(), StaticResourceMovementsError> {
428 for (_resource, resource_bound) in self.worktop.specified_resources() {
430 let (lower_bound, _upper_bound) = resource_bound.bounds().numeric_bounds();
431 if lower_bound.is_positive() {
432 return Err(StaticResourceMovementsError::WorktopEndsWithKnownResourcesPresent);
433 }
434 }
435 Ok(())
436 }
437}
438
439impl ManifestInterpretationVisitor for StaticResourceMovementsVisitor {
440 type Output = StaticResourceMovementsError;
441
442 fn on_start_instruction(&mut self, event: OnStartInstruction) -> ControlFlow<Self::Output> {
443 match self.handle_start_instruction(event) {
444 Ok(()) => ControlFlow::Continue(()),
445 Err(err) => ControlFlow::Break(err),
446 }
447 }
448
449 fn on_end_instruction(&mut self, event: OnEndInstruction) -> ControlFlow<Self::Output> {
450 match self.handle_end_instruction(event) {
451 Ok(()) => ControlFlow::Continue(()),
452 Err(err) => ControlFlow::Break(err),
453 }
454 }
455
456 fn on_new_bucket(&mut self, event: OnNewBucket) -> ControlFlow<Self::Output> {
457 match self.handle_new_bucket(event) {
458 Ok(()) => ControlFlow::Continue(()),
459 Err(err) => ControlFlow::Break(err),
460 }
461 }
462
463 fn on_consume_bucket(&mut self, event: OnConsumeBucket) -> ControlFlow<Self::Output> {
464 match self.handle_consume_bucket(event) {
465 Ok(()) => ControlFlow::Continue(()),
466 Err(err) => ControlFlow::Break(err),
467 }
468 }
469
470 fn on_pass_expression(&mut self, event: OnPassExpression) -> ControlFlow<Self::Output> {
471 match self.handle_pass_expression(event) {
472 Ok(()) => ControlFlow::Continue(()),
473 Err(err) => ControlFlow::Break(err),
474 }
475 }
476
477 fn on_resource_assertion(&mut self, event: OnResourceAssertion) -> ControlFlow<Self::Output> {
478 match self.handle_resource_assertion(event) {
479 Ok(()) => ControlFlow::Continue(()),
480 Err(err) => ControlFlow::Break(err),
481 }
482 }
483
484 fn on_new_named_address(&mut self, event: OnNewNamedAddress) -> ControlFlow<Self::Output> {
485 match self.handle_new_named_address(event) {
486 Ok(()) => ControlFlow::Continue(()),
487 Err(err) => ControlFlow::Break(err),
488 }
489 }
490
491 fn on_finish(&mut self, event: OnFinish) -> ControlFlow<Self::Output> {
492 match self.handle_finish(event) {
493 Ok(()) => ControlFlow::Continue(()),
494 Err(err) => ControlFlow::Break(err),
495 }
496 }
497}