1use super::{ast as T, env::Env};
5use crate::{
6 execution_mode::ExecutionMode,
7 programmable_transactions::context::EitherError,
8 static_programmable_transactions::{
9 loading::ast::{self as L, Type},
10 spanned::sp,
11 typing::ast::BytesConstraint,
12 },
13};
14use indexmap::{IndexMap, IndexSet};
15use std::rc::Rc;
16use sui_types::{
17 base_types::{ObjectRef, TxContextKind},
18 coin::RESOLVED_COIN_STRUCT,
19 error::{ExecutionError, ExecutionErrorKind, command_argument_error},
20 execution_status::CommandArgumentError,
21};
22
23#[derive(Debug, Clone, Copy)]
24enum SplatLocation {
25 GasCoin,
26 Input(T::InputIndex),
27 Result(u16, u16),
28}
29
30#[derive(Debug, Clone, Copy)]
31enum InputKind {
32 Object,
33 Withdrawal,
34 Pure,
35 Receiving,
36}
37
38struct Context {
39 current_command: u16,
40 input_resolution: Vec<InputKind>,
42 bytes: IndexSet<Vec<u8>>,
43 bytes_idx_remapping: IndexMap<T::InputIndex, T::ByteIndex>,
45 receiving_refs: IndexMap<T::InputIndex, ObjectRef>,
46 objects: IndexMap<T::InputIndex, T::ObjectInput>,
47 withdrawals: IndexMap<T::InputIndex, T::WithdrawalInput>,
48 pure: IndexMap<(T::InputIndex, Type), T::PureInput>,
49 receiving: IndexMap<(T::InputIndex, Type), T::ReceivingInput>,
50 results: Vec<T::ResultType>,
51}
52
53impl Context {
54 fn new(linputs: L::Inputs) -> Result<Self, ExecutionError> {
55 let mut context = Context {
56 current_command: 0,
57 input_resolution: vec![],
58 bytes: IndexSet::new(),
59 bytes_idx_remapping: IndexMap::new(),
60 receiving_refs: IndexMap::new(),
61 objects: IndexMap::new(),
62 withdrawals: IndexMap::new(),
63 pure: IndexMap::new(),
64 receiving: IndexMap::new(),
65 results: vec![],
66 };
67 #[cfg(debug_assertions)]
69 let cloned_inputs = linputs
70 .iter()
71 .map(|(arg, _)| arg.clone())
72 .collect::<Vec<_>>();
73 for (i, (arg, ty)) in linputs.into_iter().enumerate() {
76 let idx = T::InputIndex(i as u16);
77 let kind = match (arg, ty) {
78 (L::InputArg::Pure(bytes), L::InputType::Bytes) => {
79 let (byte_index, _) = context.bytes.insert_full(bytes);
80 context.bytes_idx_remapping.insert(idx, byte_index);
81 InputKind::Pure
82 }
83 (L::InputArg::Receiving(oref), L::InputType::Bytes) => {
84 context.receiving_refs.insert(idx, oref);
85 InputKind::Receiving
86 }
87 (L::InputArg::Object(arg), L::InputType::Fixed(ty)) => {
88 let o = T::ObjectInput {
89 original_input_index: idx,
90 arg,
91 ty,
92 };
93 context.objects.insert(idx, o);
94 InputKind::Object
95 }
96 (L::InputArg::FundsWithdrawal(withdrawal), L::InputType::Fixed(input_ty)) => {
97 let L::FundsWithdrawalArg { ty, owner, amount } = withdrawal;
98 debug_assert!(ty == input_ty);
99 let withdrawal = T::WithdrawalInput {
100 original_input_index: idx,
101 ty,
102 owner,
103 amount,
104 };
105 context.withdrawals.insert(idx, withdrawal);
106 InputKind::Withdrawal
107 }
108 (arg, ty) => invariant_violation!(
109 "Input arg, type mismatch. Unexpected {arg:?} with type {ty:?}"
110 ),
111 };
112 context.input_resolution.push(kind);
113 }
114 #[cfg(debug_assertions)]
115 {
116 for (i, arg) in cloned_inputs.iter().enumerate() {
118 if let L::InputArg::Pure(bytes) = &arg {
119 let idx = T::InputIndex(i as u16);
120 let Some(byte_index) = context.bytes_idx_remapping.get(&idx) else {
121 invariant_violation!("Unbound pure input {}", idx.0);
122 };
123 let Some(interned_bytes) = context.bytes.get_index(*byte_index) else {
124 invariant_violation!("Interned bytes not found for index {}", byte_index);
125 };
126 if interned_bytes != bytes {
127 assert_invariant!(
128 interned_bytes == bytes,
129 "Interned bytes mismatch for input {i}",
130 );
131 }
132 }
133 }
134 }
135 Ok(context)
136 }
137
138 fn finish(self, commands: T::Commands) -> T::Transaction {
139 let Self {
140 bytes,
141 objects,
142 withdrawals,
143 pure,
144 receiving,
145 ..
146 } = self;
147 let objects = objects.into_iter().map(|(_, o)| o).collect();
148 let withdrawals = withdrawals.into_iter().map(|(_, w)| w).collect();
149 let pure = pure.into_iter().map(|(_, p)| p).collect();
150 let receiving = receiving.into_iter().map(|(_, r)| r).collect();
151 T::Transaction {
152 bytes,
153 objects,
154 withdrawals,
155 pure,
156 receiving,
157 commands,
158 }
159 }
160
161 fn fixed_type(
163 &mut self,
164 env: &Env,
165 location: SplatLocation,
166 ) -> Result<Option<(T::Location, Type)>, ExecutionError> {
167 Ok(Some(match location {
168 SplatLocation::GasCoin => (T::Location::GasCoin, env.gas_coin_type()?),
169 SplatLocation::Result(i, j) => (
170 T::Location::Result(i, j),
171 self.results[i as usize][j as usize].clone(),
172 ),
173 SplatLocation::Input(i) => match &self.input_resolution[i.0 as usize] {
174 InputKind::Object => {
175 let Some((object_index, _, object_input)) = self.objects.get_full(&i) else {
176 invariant_violation!("Unbound object input {}", i.0)
177 };
178 (
179 T::Location::ObjectInput(object_index as u16),
180 object_input.ty.clone(),
181 )
182 }
183 InputKind::Withdrawal => {
184 let Some((withdrawal_index, _, withdrawal_input)) =
185 self.withdrawals.get_full(&i)
186 else {
187 invariant_violation!("Unbound withdrawal input {}", i.0)
188 };
189 (
190 T::Location::WithdrawalInput(withdrawal_index as u16),
191 withdrawal_input.ty.clone(),
192 )
193 }
194 InputKind::Pure | InputKind::Receiving => return Ok(None),
195 },
196 }))
197 }
198
199 fn resolve_location(
200 &mut self,
201 env: &Env,
202 location: SplatLocation,
203 expected_ty: &Type,
204 bytes_constraint: BytesConstraint,
205 ) -> Result<(T::Location, Type), ExecutionError> {
206 Ok(match location {
207 SplatLocation::GasCoin | SplatLocation::Result(_, _) => self
208 .fixed_type(env, location)?
209 .ok_or_else(|| make_invariant_violation!("Expected fixed type for {location:?}"))?,
210 SplatLocation::Input(i) => match &self.input_resolution[i.0 as usize] {
211 InputKind::Object | InputKind::Withdrawal => {
212 self.fixed_type(env, location)?.ok_or_else(|| {
213 make_invariant_violation!("Expected fixed type for {location:?}")
214 })?
215 }
216 InputKind::Pure => {
217 let ty = match expected_ty {
218 Type::Reference(_, inner) => (**inner).clone(),
219 ty => ty.clone(),
220 };
221 let k = (i, ty.clone());
222 if !self.pure.contains_key(&k) {
223 let Some(byte_index) = self.bytes_idx_remapping.get(&i).copied() else {
224 invariant_violation!("Unbound pure input {}", i.0);
225 };
226 let pure = T::PureInput {
227 original_input_index: i,
228 byte_index,
229 ty: ty.clone(),
230 constraint: bytes_constraint,
231 };
232 self.pure.insert(k.clone(), pure);
233 }
234 let byte_index = self.pure.get_index_of(&k).unwrap();
235 (T::Location::PureInput(byte_index as u16), ty)
236 }
237 InputKind::Receiving => {
238 let ty = match expected_ty {
239 Type::Reference(_, inner) => (**inner).clone(),
240 ty => ty.clone(),
241 };
242 let k = (i, ty.clone());
243 if !self.receiving.contains_key(&k) {
244 let Some(object_ref) = self.receiving_refs.get(&i).copied() else {
245 invariant_violation!("Unbound receiving input {}", i.0);
246 };
247 let receiving = T::ReceivingInput {
248 original_input_index: i,
249 object_ref,
250 ty: ty.clone(),
251 constraint: bytes_constraint,
252 };
253 self.receiving.insert(k.clone(), receiving);
254 }
255 let byte_index = self.receiving.get_index_of(&k).unwrap();
256 (T::Location::ReceivingInput(byte_index as u16), ty)
257 }
258 },
259 })
260 }
261}
262
263pub fn transaction<Mode: ExecutionMode>(
264 env: &Env,
265 lt: L::Transaction,
266) -> Result<T::Transaction, ExecutionError> {
267 let L::Transaction { inputs, commands } = lt;
268 let mut context = Context::new(inputs)?;
269 let commands = commands
270 .into_iter()
271 .enumerate()
272 .map(|(i, c)| {
273 let idx = i as u16;
274 context.current_command = idx;
275 let (c_, tys) =
276 command::<Mode>(env, &mut context, c).map_err(|e| e.with_command_index(i))?;
277 context.results.push(tys.clone());
278 let c = T::Command_ {
279 command: c_,
280 result_type: tys,
281 drop_values: vec![],
283 consumed_shared_objects: vec![],
285 };
286 Ok(sp(idx, c))
287 })
288 .collect::<Result<Vec<_>, ExecutionError>>()?;
289 let mut ast = context.finish(commands);
290 scope_references::transaction(&mut ast);
292 unused_results::transaction(&mut ast);
294 consumed_shared_objects::transaction(&mut ast)?;
296 Ok(ast)
297}
298
299fn command<Mode: ExecutionMode>(
300 env: &Env,
301 context: &mut Context,
302 command: L::Command,
303) -> Result<(T::Command__, T::ResultType), ExecutionError> {
304 Ok(match command {
305 L::Command::MoveCall(lmc) => {
306 let L::MoveCall {
307 function,
308 arguments: largs,
309 } = *lmc;
310 let arg_locs = locations(context, 0, largs)?;
311 let args = move_call_arguments(env, context, &function, arg_locs)?;
312 let result = function.signature.return_.clone();
313 (
314 T::Command__::MoveCall(Box::new(T::MoveCall {
315 function,
316 arguments: args,
317 })),
318 result,
319 )
320 }
321 L::Command::TransferObjects(lobjects, laddress) => {
322 let object_locs = locations(context, 0, lobjects)?;
323 let address_loc = one_location(context, object_locs.len(), laddress)?;
324 let objects = constrained_arguments(
325 env,
326 context,
327 0,
328 object_locs,
329 |ty| {
330 let abilities = ty.abilities();
331 Ok(abilities.has_store() && abilities.has_key())
332 },
333 CommandArgumentError::InvalidTransferObject,
334 )?;
335 let address = argument(env, context, objects.len(), address_loc, Type::Address)?;
336 (T::Command__::TransferObjects(objects, address), vec![])
337 }
338 L::Command::SplitCoins(lcoin, lamounts) => {
339 let coin_loc = one_location(context, 0, lcoin)?;
340 let amount_locs = locations(context, 1, lamounts)?;
341 let coin = coin_mut_ref_argument(env, context, 0, coin_loc)?;
342 let coin_type = match &coin.value.1 {
343 Type::Reference(true, ty) => (**ty).clone(),
344 ty => invariant_violation!("coin must be a mutable reference. Found: {ty:?}"),
345 };
346 let amounts = arguments(
347 env,
348 context,
349 1,
350 amount_locs,
351 std::iter::repeat_with(|| Type::U64),
352 )?;
353 let result = vec![coin_type.clone(); amounts.len()];
354 (T::Command__::SplitCoins(coin_type, coin, amounts), result)
355 }
356 L::Command::MergeCoins(ltarget, lcoins) => {
357 let target_loc = one_location(context, 0, ltarget)?;
358 let coin_locs = locations(context, 1, lcoins)?;
359 let target = coin_mut_ref_argument(env, context, 0, target_loc)?;
360 let coin_type = match &target.value.1 {
361 Type::Reference(true, ty) => (**ty).clone(),
362 ty => invariant_violation!("target must be a mutable reference. Found: {ty:?}"),
363 };
364 let coins = arguments(
365 env,
366 context,
367 1,
368 coin_locs,
369 std::iter::repeat_with(|| coin_type.clone()),
370 )?;
371 (T::Command__::MergeCoins(coin_type, target, coins), vec![])
372 }
373 L::Command::MakeMoveVec(Some(ty), lelems) => {
374 let elem_locs = locations(context, 0, lelems)?;
375 let elems = arguments(
376 env,
377 context,
378 0,
379 elem_locs,
380 std::iter::repeat_with(|| ty.clone()),
381 )?;
382 (
383 T::Command__::MakeMoveVec(ty.clone(), elems),
384 vec![env.vector_type(ty)?],
385 )
386 }
387 L::Command::MakeMoveVec(None, lelems) => {
388 let mut lelems = lelems.into_iter();
389 let Some(lfirst) = lelems.next() else {
390 invariant_violation!(
392 "input checker ensures if args are empty, there is a type specified"
393 );
394 };
395 let first_loc = one_location(context, 0, lfirst)?;
396 let first_arg = constrained_argument(
397 env,
398 context,
399 0,
400 first_loc,
401 |ty| Ok(ty.abilities().has_key()),
402 CommandArgumentError::InvalidMakeMoveVecNonObjectArgument,
403 )?;
404 let first_ty = first_arg.value.1.clone();
405 let elems_loc = locations(context, 1, lelems)?;
406 let mut elems = arguments(
407 env,
408 context,
409 1,
410 elems_loc,
411 std::iter::repeat_with(|| first_ty.clone()),
412 )?;
413 elems.insert(0, first_arg);
414 (
415 T::Command__::MakeMoveVec(first_ty.clone(), elems),
416 vec![env.vector_type(first_ty)?],
417 )
418 }
419 L::Command::Publish(items, object_ids, linkage) => {
420 let result = if Mode::packages_are_predefined() {
421 vec![]
423 } else {
424 vec![env.upgrade_cap_type()?.clone()]
425 };
426 (T::Command__::Publish(items, object_ids, linkage), result)
427 }
428 L::Command::Upgrade(items, object_ids, object_id, la, linkage) => {
429 let location = one_location(context, 0, la)?;
430 let expected_ty = env.upgrade_ticket_type()?;
431 let a = argument(env, context, 0, location, expected_ty)?;
432 let res = env.upgrade_receipt_type()?;
433 (
434 T::Command__::Upgrade(items, object_ids, object_id, a, linkage),
435 vec![res.clone()],
436 )
437 }
438 })
439}
440
441fn move_call_parameters<'a>(
442 env: &Env,
443 function: &'a L::LoadedFunction,
444) -> Vec<(&'a Type, TxContextKind)> {
445 if env.protocol_config.flexible_tx_context_positions() {
446 function
447 .signature
448 .parameters
449 .iter()
450 .map(|ty| (ty, ty.is_tx_context()))
451 .collect()
452 } else {
453 let mut kinds = function
454 .signature
455 .parameters
456 .iter()
457 .map(|ty| (ty, TxContextKind::None))
458 .collect::<Vec<_>>();
459 if let Some((ty, kind)) = kinds.last_mut() {
460 *kind = ty.is_tx_context();
461 }
462 kinds
463 }
464}
465
466fn move_call_arguments(
467 env: &Env,
468 context: &mut Context,
469 function: &L::LoadedFunction,
470 args: Vec<SplatLocation>,
471) -> Result<Vec<T::Argument>, ExecutionError> {
472 let params = move_call_parameters(env, function);
473 assert_invariant!(
474 params.len() == function.signature.parameters.len(),
475 "Generated parameter types does not match the function signature"
476 );
477 let num_tx_contexts = params
479 .iter()
480 .filter(|(_, k)| matches!(k, TxContextKind::Mutable | TxContextKind::Immutable))
481 .count();
482 let num_user_args = args.len();
483 let Some(num_args) = num_user_args.checked_add(num_tx_contexts) else {
484 invariant_violation!("usize overflow when calculating number of arguments");
485 };
486 let num_parameters = params.len();
487 if num_args != num_parameters {
488 return Err(ExecutionError::new_with_source(
489 ExecutionErrorKind::ArityMismatch,
490 format!(
491 "Expected {} argument{} calling function '{}::{}', but found {}",
492 num_parameters,
493 if num_parameters == 1 { "" } else { "s" },
494 function.storage_id,
495 function.name,
496 num_args,
497 ),
498 ));
499 }
500 let mut args = args.into_iter().enumerate();
502 let res = params
503 .into_iter()
504 .enumerate()
505 .map(|(param_idx, (expected_ty, tx_context_kind))| {
506 Ok(match tx_context_kind {
507 TxContextKind::None => {
508 let Some((arg_idx, location)) = args.next() else {
509 invariant_violation!("arguments are empty but arity was already checked");
510 };
511 argument(env, context, arg_idx, location, expected_ty.clone())?
512 }
513 TxContextKind::Mutable | TxContextKind::Immutable => {
514 let is_mut = match tx_context_kind {
515 TxContextKind::Mutable => true,
516 TxContextKind::Immutable => false,
517 TxContextKind::None => unreachable!(),
518 };
519 let idx = param_idx as u16;
522 let arg__ = T::Argument__::Borrow(is_mut, T::Location::TxContext);
523 let ty = Type::Reference(is_mut, Rc::new(env.tx_context_type()?));
524 sp(idx, (arg__, ty))
525 }
526 })
527 })
528 .collect::<Result<Vec<_>, ExecutionError>>()?;
529
530 assert_invariant!(
531 args.next().is_none(),
532 "some arguments went unused but arity was already checked"
533 );
534 Ok(res)
535}
536
537fn one_location(
538 context: &mut Context,
539 command_arg_idx: usize,
540 arg: L::Argument,
541) -> Result<SplatLocation, ExecutionError> {
542 let locs = locations(context, command_arg_idx, vec![arg])?;
543 let Ok([loc]): Result<[SplatLocation; 1], _> = locs.try_into() else {
544 return Err(command_argument_error(
545 CommandArgumentError::InvalidArgumentArity,
546 command_arg_idx,
547 ));
548 };
549 Ok(loc)
550}
551
552fn locations<Items: IntoIterator<Item = L::Argument>>(
553 context: &mut Context,
554 start_idx: usize,
555 args: Items,
556) -> Result<Vec<SplatLocation>, ExecutionError>
557where
558 Items::IntoIter: ExactSizeIterator,
559{
560 fn splat_arg(
561 context: &mut Context,
562 res: &mut Vec<SplatLocation>,
563 arg: L::Argument,
564 ) -> Result<(), EitherError> {
565 match arg {
566 L::Argument::GasCoin => res.push(SplatLocation::GasCoin),
567 L::Argument::Input(i) => {
568 if i as usize >= context.input_resolution.len() {
569 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
570 }
571 res.push(SplatLocation::Input(T::InputIndex(i)))
572 }
573 L::Argument::NestedResult(i, j) => {
574 let Some(command_result) = context.results.get(i as usize) else {
575 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
576 };
577 if j as usize >= command_result.len() {
578 return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
579 result_idx: i,
580 secondary_idx: j,
581 }
582 .into());
583 };
584 res.push(SplatLocation::Result(i, j))
585 }
586 L::Argument::Result(i) => {
587 let Some(result) = context.results.get(i as usize) else {
588 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
589 };
590 let Ok(len): Result<u16, _> = result.len().try_into() else {
591 invariant_violation!("Result of length greater than u16::MAX");
592 };
593 if len != 1 {
594 return Err(CommandArgumentError::InvalidResultArity { result_idx: i }.into());
596 }
597 res.extend((0..len).map(|j| SplatLocation::Result(i, j)))
598 }
599 }
600 Ok(())
601 }
602
603 let args = args.into_iter();
604 let _args_len = args.len();
605 let mut res = vec![];
606 for (arg_idx, arg) in args.enumerate() {
607 splat_arg(context, &mut res, arg).map_err(|e| {
608 let Some(idx) = start_idx.checked_add(arg_idx) else {
609 return make_invariant_violation!("usize overflow when calculating argument index");
610 };
611 e.into_execution_error(idx)
612 })?
613 }
614 debug_assert_eq!(res.len(), _args_len);
615 Ok(res)
616}
617
618fn arguments(
619 env: &Env,
620 context: &mut Context,
621 start_idx: usize,
622 locations: Vec<SplatLocation>,
623 expected_tys: impl IntoIterator<Item = Type>,
624) -> Result<Vec<T::Argument>, ExecutionError> {
625 locations
626 .into_iter()
627 .zip(expected_tys)
628 .enumerate()
629 .map(|(i, (location, expected_ty))| {
630 let Some(idx) = start_idx.checked_add(i) else {
631 invariant_violation!("usize overflow when calculating argument index");
632 };
633 argument(env, context, idx, location, expected_ty)
634 })
635 .collect()
636}
637
638fn argument(
639 env: &Env,
640 context: &mut Context,
641 command_arg_idx: usize,
642 location: SplatLocation,
643 expected_ty: Type,
644) -> Result<T::Argument, ExecutionError> {
645 let arg__ = argument_(env, context, command_arg_idx, location, &expected_ty)
646 .map_err(|e| e.into_execution_error(command_arg_idx))?;
647 let arg_ = (arg__, expected_ty);
648 Ok(sp(command_arg_idx as u16, arg_))
649}
650
651fn argument_(
652 env: &Env,
653 context: &mut Context,
654 command_arg_idx: usize,
655 location: SplatLocation,
656 expected_ty: &Type,
657) -> Result<T::Argument__, EitherError> {
658 let current_command = context.current_command;
659 let bytes_constraint = BytesConstraint {
660 command: current_command,
661 argument: command_arg_idx as u16,
662 };
663 let (location, actual_ty): (T::Location, Type) =
664 context.resolve_location(env, location, expected_ty, bytes_constraint)?;
665 Ok(match (actual_ty, expected_ty) {
666 (Type::Reference(a_is_mut, a), Type::Reference(b_is_mut, b)) => {
668 let needs_freeze = match (a_is_mut, b_is_mut) {
669 (true, true) | (false, false) => false,
671 (true, false) => true,
673 (false, true) => return Err(CommandArgumentError::TypeMismatch.into()),
675 };
676 debug_assert!(expected_ty.abilities().has_copy());
677 check_type(&a, b)?;
679 if needs_freeze {
680 T::Argument__::Freeze(T::Usage::new_copy(location))
681 } else {
682 T::Argument__::new_copy(location)
683 }
684 }
685 (Type::Reference(_, a), b) => {
686 check_type(&a, b)?;
687 if !b.abilities().has_copy() {
688 return Err(CommandArgumentError::TypeMismatch.into());
690 }
691 T::Argument__::Read(T::Usage::new_copy(location))
692 }
693
694 (actual_ty, Type::Reference(is_mut, inner)) => {
696 check_type(&actual_ty, inner)?;
697 T::Argument__::Borrow(*is_mut, location)
698 }
699 (actual_ty, _) => {
700 check_type(&actual_ty, expected_ty)?;
701 T::Argument__::Use(if expected_ty.abilities().has_copy() {
702 T::Usage::new_copy(location)
703 } else {
704 T::Usage::new_move(location)
705 })
706 }
707 })
708}
709
710fn check_type(actual_ty: &Type, expected_ty: &Type) -> Result<(), CommandArgumentError> {
711 if actual_ty == expected_ty {
712 Ok(())
713 } else {
714 Err(CommandArgumentError::TypeMismatch)
715 }
716}
717
718fn constrained_arguments<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
719 env: &Env,
720 context: &mut Context,
721 start_idx: usize,
722 locations: Vec<SplatLocation>,
723 mut is_valid: P,
724 err_case: CommandArgumentError,
725) -> Result<Vec<T::Argument>, ExecutionError> {
726 let is_valid = &mut is_valid;
727 locations
728 .into_iter()
729 .enumerate()
730 .map(|(i, location)| {
731 let Some(idx) = start_idx.checked_add(i) else {
732 invariant_violation!("usize overflow when calculating argument index");
733 };
734 constrained_argument_(env, context, idx, location, is_valid, err_case)
735 })
736 .collect()
737}
738
739fn constrained_argument<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
740 env: &Env,
741 context: &mut Context,
742 command_arg_idx: usize,
743 location: SplatLocation,
744 mut is_valid: P,
745 err_case: CommandArgumentError,
746) -> Result<T::Argument, ExecutionError> {
747 constrained_argument_(
748 env,
749 context,
750 command_arg_idx,
751 location,
752 &mut is_valid,
753 err_case,
754 )
755}
756
757fn constrained_argument_<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
758 env: &Env,
759 context: &mut Context,
760 command_arg_idx: usize,
761 location: SplatLocation,
762 is_valid: &mut P,
763 err_case: CommandArgumentError,
764) -> Result<T::Argument, ExecutionError> {
765 let arg_ = constrained_argument__(env, context, location, is_valid, err_case)
766 .map_err(|e| e.into_execution_error(command_arg_idx))?;
767 Ok(sp(command_arg_idx as u16, arg_))
768}
769
770fn constrained_argument__<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
771 env: &Env,
772 context: &mut Context,
773 location: SplatLocation,
774 is_valid: &mut P,
775 err_case: CommandArgumentError,
776) -> Result<T::Argument_, EitherError> {
777 if let Some((location, ty)) = constrained_type(env, context, location, is_valid)? {
778 if ty.abilities().has_copy() {
779 Ok((T::Argument__::new_copy(location), ty))
780 } else {
781 Ok((T::Argument__::new_move(location), ty))
782 }
783 } else {
784 Err(err_case.into())
785 }
786}
787
788fn constrained_type<'a, P: FnMut(&Type) -> Result<bool, ExecutionError>>(
789 env: &'a Env,
790 context: &'a mut Context,
791 location: SplatLocation,
792 mut is_valid: P,
793) -> Result<Option<(T::Location, Type)>, ExecutionError> {
794 let Some((location, ty)) = context.fixed_type(env, location)? else {
795 return Ok(None);
796 };
797 Ok(if is_valid(&ty)? {
798 Some((location, ty))
799 } else {
800 None
801 })
802}
803
804fn coin_mut_ref_argument(
805 env: &Env,
806 context: &mut Context,
807 command_arg_idx: usize,
808 location: SplatLocation,
809) -> Result<T::Argument, ExecutionError> {
810 let arg_ = coin_mut_ref_argument_(env, context, location)
811 .map_err(|e| e.into_execution_error(command_arg_idx))?;
812 Ok(sp(command_arg_idx as u16, arg_))
813}
814
815fn coin_mut_ref_argument_(
816 env: &Env,
817 context: &mut Context,
818 location: SplatLocation,
819) -> Result<T::Argument_, EitherError> {
820 let Some((location, actual_ty)) = context.fixed_type(env, location)? else {
821 return Err(CommandArgumentError::TypeMismatch.into());
824 };
825 Ok(match &actual_ty {
826 Type::Reference(is_mut, ty) if *is_mut => {
827 check_coin_type(ty)?;
828 (
829 T::Argument__::new_copy(location),
830 Type::Reference(*is_mut, ty.clone()),
831 )
832 }
833 ty => {
834 check_coin_type(ty)?;
835 (
836 T::Argument__::Borrow(true, location),
837 Type::Reference(true, Rc::new(ty.clone())),
838 )
839 }
840 })
841}
842
843fn check_coin_type(ty: &Type) -> Result<(), EitherError> {
844 let Type::Datatype(dt) = ty else {
845 return Err(CommandArgumentError::TypeMismatch.into());
846 };
847 let resolved = dt.qualified_ident();
848 let is_coin = resolved == RESOLVED_COIN_STRUCT;
849 if is_coin {
850 Ok(())
851 } else {
852 Err(CommandArgumentError::TypeMismatch.into())
853 }
854}
855
856mod scope_references {
861 use crate::{
862 sp,
863 static_programmable_transactions::typing::ast::{self as T, Type},
864 };
865 use std::collections::BTreeSet;
866
867 pub fn transaction(ast: &mut T::Transaction) {
870 let mut used: BTreeSet<(u16, u16)> = BTreeSet::new();
871 for c in ast.commands.iter_mut().rev() {
872 command(&mut used, c);
873 }
874 }
875
876 fn command(used: &mut BTreeSet<(u16, u16)>, sp!(_, c): &mut T::Command) {
877 match &mut c.command {
878 T::Command__::MoveCall(mc) => arguments(used, &mut mc.arguments),
879 T::Command__::TransferObjects(objects, recipient) => {
880 argument(used, recipient);
881 arguments(used, objects);
882 }
883 T::Command__::SplitCoins(_, coin, amounts) => {
884 arguments(used, amounts);
885 argument(used, coin);
886 }
887 T::Command__::MergeCoins(_, target, coins) => {
888 arguments(used, coins);
889 argument(used, target);
890 }
891 T::Command__::MakeMoveVec(_, xs) => arguments(used, xs),
892 T::Command__::Publish(_, _, _) => (),
893 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
894 }
895 }
896
897 fn arguments(used: &mut BTreeSet<(u16, u16)>, args: &mut [T::Argument]) {
898 for arg in args.iter_mut().rev() {
899 argument(used, arg)
900 }
901 }
902
903 fn argument(used: &mut BTreeSet<(u16, u16)>, sp!(_, (arg_, ty)): &mut T::Argument) {
904 let usage = match arg_ {
905 T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
906 T::Argument__::Borrow(_, _) => return,
907 };
908 match (&usage, ty) {
909 (T::Usage::Move(T::Location::Result(i, j)), Type::Reference(_, _)) => {
910 debug_assert!(false, "No reference should be moved at this point");
911 used.insert((*i, *j));
912 }
913 (
914 T::Usage::Copy {
915 location: T::Location::Result(i, j),
916 ..
917 },
918 Type::Reference(_, _),
919 ) => {
920 let last_usage = used.insert((*i, *j));
922 if last_usage {
923 let loc = T::Location::Result(*i, *j);
925 *usage = T::Usage::Move(loc);
926 }
927 }
928 _ => (),
929 }
930 }
931}
932
933mod unused_results {
938 use indexmap::IndexSet;
939
940 use crate::{sp, static_programmable_transactions::typing::ast as T};
941
942 pub fn transaction(ast: &mut T::Transaction) {
946 let mut used: IndexSet<(u16, u16)> = IndexSet::new();
948 for c in &ast.commands {
949 command(&mut used, c);
950 }
951
952 for (i, sp!(_, c)) in ast.commands.iter_mut().enumerate() {
954 debug_assert!(c.drop_values.is_empty());
955 let i = i as u16;
956 c.drop_values = c
957 .result_type
958 .iter()
959 .enumerate()
960 .map(|(j, ty)| (j as u16, ty))
961 .map(|(j, ty)| ty.abilities().has_drop() && !used.contains(&(i, j)))
962 .collect();
963 }
964 }
965
966 fn command(used: &mut IndexSet<(u16, u16)>, sp!(_, c): &T::Command) {
967 match &c.command {
968 T::Command__::MoveCall(mc) => arguments(used, &mc.arguments),
969 T::Command__::TransferObjects(objects, recipient) => {
970 argument(used, recipient);
971 arguments(used, objects);
972 }
973 T::Command__::SplitCoins(_, coin, amounts) => {
974 arguments(used, amounts);
975 argument(used, coin);
976 }
977 T::Command__::MergeCoins(_, target, coins) => {
978 arguments(used, coins);
979 argument(used, target);
980 }
981 T::Command__::MakeMoveVec(_, elements) => arguments(used, elements),
982 T::Command__::Publish(_, _, _) => (),
983 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
984 }
985 }
986
987 fn arguments(used: &mut IndexSet<(u16, u16)>, args: &[T::Argument]) {
988 for arg in args {
989 argument(used, arg)
990 }
991 }
992
993 fn argument(used: &mut IndexSet<(u16, u16)>, sp!(_, (arg_, _)): &T::Argument) {
994 if let T::Location::Result(i, j) = arg_.location() {
995 used.insert((i, j));
996 }
997 }
998}
999
1000mod consumed_shared_objects {
1005
1006 use crate::{
1007 sp, static_programmable_transactions::loading::ast as L,
1008 static_programmable_transactions::typing::ast as T,
1009 };
1010 use sui_types::{base_types::ObjectID, error::ExecutionError};
1011
1012 struct Context {
1014 inputs: Vec<Option<ObjectID>>,
1016 results: Vec<Vec<Option<Vec<ObjectID>>>>,
1017 }
1018
1019 impl Context {
1020 pub fn new(ast: &T::Transaction) -> Self {
1021 let T::Transaction {
1022 bytes: _,
1023 objects,
1024 withdrawals: _,
1025 pure: _,
1026 receiving: _,
1027 commands: _,
1028 } = ast;
1029 let inputs = objects
1030 .iter()
1031 .map(|o| match &o.arg {
1032 L::ObjectArg::SharedObject {
1033 id,
1034 kind: L::SharedObjectKind::Legacy,
1035 ..
1036 } => Some(*id),
1037 L::ObjectArg::ImmObject(_)
1038 | L::ObjectArg::OwnedObject(_)
1039 | L::ObjectArg::SharedObject {
1040 kind: L::SharedObjectKind::Party,
1041 ..
1042 } => None,
1043 })
1044 .collect::<Vec<_>>();
1045 Self {
1046 inputs,
1047 results: vec![],
1048 }
1049 }
1050 }
1051
1052 pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1057 let mut context = Context::new(ast);
1058
1059 for c in &mut ast.commands {
1062 debug_assert!(c.value.consumed_shared_objects.is_empty());
1063 command(&mut context, c)?;
1064 debug_assert!(context.results.last().unwrap().len() == c.value.result_type.len());
1065 }
1066 Ok(())
1067 }
1068
1069 fn command(context: &mut Context, sp!(_, c): &mut T::Command) -> Result<(), ExecutionError> {
1070 let mut acc = vec![];
1071 match &c.command {
1072 T::Command__::MoveCall(mc) => arguments(context, &mut acc, &mc.arguments),
1073 T::Command__::TransferObjects(objects, recipient) => {
1074 argument(context, &mut acc, recipient);
1075 arguments(context, &mut acc, objects);
1076 }
1077 T::Command__::SplitCoins(_, coin, amounts) => {
1078 arguments(context, &mut acc, amounts);
1079 argument(context, &mut acc, coin);
1080 }
1081 T::Command__::MergeCoins(_, target, coins) => {
1082 arguments(context, &mut acc, coins);
1083 argument(context, &mut acc, target);
1084 }
1085 T::Command__::MakeMoveVec(_, elements) => arguments(context, &mut acc, elements),
1086 T::Command__::Publish(_, _, _) => (),
1087 T::Command__::Upgrade(_, _, _, x, _) => argument(context, &mut acc, x),
1088 }
1089 let (consumed, result) = match &c.command {
1090 T::Command__::MakeMoveVec(_, _) => {
1093 assert_invariant!(
1094 c.result_type.len() == 1,
1095 "MakeMoveVec must return a single value"
1096 );
1097 (vec![], vec![Some(acc)])
1098 }
1099 T::Command__::MoveCall(_)
1101 | T::Command__::TransferObjects(_, _)
1102 | T::Command__::SplitCoins(_, _, _)
1103 | T::Command__::MergeCoins(_, _, _)
1104 | T::Command__::Publish(_, _, _)
1105 | T::Command__::Upgrade(_, _, _, _, _) => (acc, vec![None; c.result_type.len()]),
1106 };
1107 c.consumed_shared_objects = consumed;
1108 context.results.push(result);
1109 Ok(())
1110 }
1111
1112 fn arguments(context: &mut Context, acc: &mut Vec<ObjectID>, args: &[T::Argument]) {
1113 for arg in args {
1114 argument(context, acc, arg)
1115 }
1116 }
1117
1118 fn argument(context: &mut Context, acc: &mut Vec<ObjectID>, sp!(_, (arg_, _)): &T::Argument) {
1119 let T::Argument__::Use(T::Usage::Move(loc)) = arg_ else {
1120 return;
1122 };
1123 match loc {
1124 T::Location::TxContext
1126 | T::Location::GasCoin
1127 | T::Location::WithdrawalInput(_)
1128 | T::Location::PureInput(_)
1129 | T::Location::ReceivingInput(_) => (),
1130 T::Location::ObjectInput(i) => {
1131 if let Some(id) = context.inputs[*i as usize] {
1132 acc.push(id);
1133 }
1134 }
1135
1136 T::Location::Result(i, j) => {
1137 if let Some(ids) = &context.results[*i as usize][*j as usize] {
1138 acc.extend(ids.iter().copied());
1139 }
1140 }
1141 }
1142 }
1143}