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 tx_context_kind = tx_context_kind(&function);
312 let parameter_tys = match tx_context_kind {
313 TxContextKind::None => &function.signature.parameters,
314 TxContextKind::Mutable | TxContextKind::Immutable => {
315 let Some(n_) = function.signature.parameters.len().checked_sub(1) else {
316 invariant_violation!(
317 "A function with a TxContext should have at least one parameter"
318 )
319 };
320 &function.signature.parameters[0..n_]
321 }
322 };
323 let num_args = arg_locs.len();
324 let num_parameters = parameter_tys.len();
325 if num_args != num_parameters {
326 return Err(ExecutionError::new_with_source(
327 ExecutionErrorKind::ArityMismatch,
328 format!(
329 "Expected {} argument{} calling function '{}::{}', but found {}",
330 num_parameters,
331 if num_parameters == 1 { "" } else { "s" },
332 function.storage_id,
333 function.name,
334 num_args,
335 ),
336 ));
337 }
338 let mut args = arguments(env, context, 0, arg_locs, parameter_tys.iter().cloned())?;
339 match tx_context_kind {
340 TxContextKind::None => (),
341 TxContextKind::Mutable | TxContextKind::Immutable => {
342 let is_mut = match tx_context_kind {
343 TxContextKind::Mutable => true,
344 TxContextKind::Immutable => false,
345 TxContextKind::None => unreachable!(),
346 };
347 let idx = args.len() as u16;
350 let arg__ = T::Argument__::Borrow(is_mut, T::Location::TxContext);
351 let ty = Type::Reference(is_mut, Rc::new(env.tx_context_type()?));
352 args.push(sp(idx, (arg__, ty)));
353 }
354 }
355 let result = function.signature.return_.clone();
356 (
357 T::Command__::MoveCall(Box::new(T::MoveCall {
358 function,
359 arguments: args,
360 })),
361 result,
362 )
363 }
364 L::Command::TransferObjects(lobjects, laddress) => {
365 let object_locs = locations(context, 0, lobjects)?;
366 let address_loc = one_location(context, object_locs.len(), laddress)?;
367 let objects = constrained_arguments(
368 env,
369 context,
370 0,
371 object_locs,
372 |ty| {
373 let abilities = ty.abilities();
374 Ok(abilities.has_store() && abilities.has_key())
375 },
376 CommandArgumentError::InvalidTransferObject,
377 )?;
378 let address = argument(env, context, objects.len(), address_loc, Type::Address)?;
379 (T::Command__::TransferObjects(objects, address), vec![])
380 }
381 L::Command::SplitCoins(lcoin, lamounts) => {
382 let coin_loc = one_location(context, 0, lcoin)?;
383 let amount_locs = locations(context, 1, lamounts)?;
384 let coin = coin_mut_ref_argument(env, context, 0, coin_loc)?;
385 let coin_type = match &coin.value.1 {
386 Type::Reference(true, ty) => (**ty).clone(),
387 ty => invariant_violation!("coin must be a mutable reference. Found: {ty:?}"),
388 };
389 let amounts = arguments(
390 env,
391 context,
392 1,
393 amount_locs,
394 std::iter::repeat_with(|| Type::U64),
395 )?;
396 let result = vec![coin_type.clone(); amounts.len()];
397 (T::Command__::SplitCoins(coin_type, coin, amounts), result)
398 }
399 L::Command::MergeCoins(ltarget, lcoins) => {
400 let target_loc = one_location(context, 0, ltarget)?;
401 let coin_locs = locations(context, 1, lcoins)?;
402 let target = coin_mut_ref_argument(env, context, 0, target_loc)?;
403 let coin_type = match &target.value.1 {
404 Type::Reference(true, ty) => (**ty).clone(),
405 ty => invariant_violation!("target must be a mutable reference. Found: {ty:?}"),
406 };
407 let coins = arguments(
408 env,
409 context,
410 1,
411 coin_locs,
412 std::iter::repeat_with(|| coin_type.clone()),
413 )?;
414 (T::Command__::MergeCoins(coin_type, target, coins), vec![])
415 }
416 L::Command::MakeMoveVec(Some(ty), lelems) => {
417 let elem_locs = locations(context, 0, lelems)?;
418 let elems = arguments(
419 env,
420 context,
421 0,
422 elem_locs,
423 std::iter::repeat_with(|| ty.clone()),
424 )?;
425 (
426 T::Command__::MakeMoveVec(ty.clone(), elems),
427 vec![env.vector_type(ty)?],
428 )
429 }
430 L::Command::MakeMoveVec(None, lelems) => {
431 let mut lelems = lelems.into_iter();
432 let Some(lfirst) = lelems.next() else {
433 invariant_violation!(
435 "input checker ensures if args are empty, there is a type specified"
436 );
437 };
438 let first_loc = one_location(context, 0, lfirst)?;
439 let first_arg = constrained_argument(
440 env,
441 context,
442 0,
443 first_loc,
444 |ty| Ok(ty.abilities().has_key()),
445 CommandArgumentError::InvalidMakeMoveVecNonObjectArgument,
446 )?;
447 let first_ty = first_arg.value.1.clone();
448 let elems_loc = locations(context, 1, lelems)?;
449 let mut elems = arguments(
450 env,
451 context,
452 1,
453 elems_loc,
454 std::iter::repeat_with(|| first_ty.clone()),
455 )?;
456 elems.insert(0, first_arg);
457 (
458 T::Command__::MakeMoveVec(first_ty.clone(), elems),
459 vec![env.vector_type(first_ty)?],
460 )
461 }
462 L::Command::Publish(items, object_ids, linkage) => {
463 let result = if Mode::packages_are_predefined() {
464 vec![]
466 } else {
467 vec![env.upgrade_cap_type()?.clone()]
468 };
469 (T::Command__::Publish(items, object_ids, linkage), result)
470 }
471 L::Command::Upgrade(items, object_ids, object_id, la, linkage) => {
472 let location = one_location(context, 0, la)?;
473 let expected_ty = env.upgrade_ticket_type()?;
474 let a = argument(env, context, 0, location, expected_ty)?;
475 let res = env.upgrade_receipt_type()?;
476 (
477 T::Command__::Upgrade(items, object_ids, object_id, a, linkage),
478 vec![res.clone()],
479 )
480 }
481 })
482}
483
484fn tx_context_kind(function: &L::LoadedFunction) -> TxContextKind {
485 match function.signature.parameters.last() {
486 Some(ty) => ty.is_tx_context(),
487 None => TxContextKind::None,
488 }
489}
490
491fn one_location(
492 context: &mut Context,
493 command_arg_idx: usize,
494 arg: L::Argument,
495) -> Result<SplatLocation, ExecutionError> {
496 let locs = locations(context, command_arg_idx, vec![arg])?;
497 let Ok([loc]): Result<[SplatLocation; 1], _> = locs.try_into() else {
498 return Err(command_argument_error(
499 CommandArgumentError::InvalidArgumentArity,
500 command_arg_idx,
501 ));
502 };
503 Ok(loc)
504}
505
506fn locations<Items: IntoIterator<Item = L::Argument>>(
507 context: &mut Context,
508 start_idx: usize,
509 args: Items,
510) -> Result<Vec<SplatLocation>, ExecutionError>
511where
512 Items::IntoIter: ExactSizeIterator,
513{
514 fn splat_arg(
515 context: &mut Context,
516 res: &mut Vec<SplatLocation>,
517 arg: L::Argument,
518 ) -> Result<(), EitherError> {
519 match arg {
520 L::Argument::GasCoin => res.push(SplatLocation::GasCoin),
521 L::Argument::Input(i) => {
522 if i as usize >= context.input_resolution.len() {
523 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
524 }
525 res.push(SplatLocation::Input(T::InputIndex(i)))
526 }
527 L::Argument::NestedResult(i, j) => {
528 let Some(command_result) = context.results.get(i as usize) else {
529 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
530 };
531 if j as usize >= command_result.len() {
532 return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
533 result_idx: i,
534 secondary_idx: j,
535 }
536 .into());
537 };
538 res.push(SplatLocation::Result(i, j))
539 }
540 L::Argument::Result(i) => {
541 let Some(result) = context.results.get(i as usize) else {
542 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
543 };
544 let Ok(len): Result<u16, _> = result.len().try_into() else {
545 invariant_violation!("Result of length greater than u16::MAX");
546 };
547 if len != 1 {
548 return Err(CommandArgumentError::InvalidResultArity { result_idx: i }.into());
550 }
551 res.extend((0..len).map(|j| SplatLocation::Result(i, j)))
552 }
553 }
554 Ok(())
555 }
556
557 let args = args.into_iter();
558 let _args_len = args.len();
559 let mut res = vec![];
560 for (arg_idx, arg) in args.enumerate() {
561 splat_arg(context, &mut res, arg).map_err(|e| {
562 let Some(idx) = start_idx.checked_add(arg_idx) else {
563 return make_invariant_violation!("usize overflow when calculating argument index");
564 };
565 e.into_execution_error(idx)
566 })?
567 }
568 debug_assert_eq!(res.len(), _args_len);
569 Ok(res)
570}
571
572fn arguments(
573 env: &Env,
574 context: &mut Context,
575 start_idx: usize,
576 locations: Vec<SplatLocation>,
577 expected_tys: impl IntoIterator<Item = Type>,
578) -> Result<Vec<T::Argument>, ExecutionError> {
579 locations
580 .into_iter()
581 .zip(expected_tys)
582 .enumerate()
583 .map(|(i, (location, expected_ty))| {
584 let Some(idx) = start_idx.checked_add(i) else {
585 invariant_violation!("usize overflow when calculating argument index");
586 };
587 argument(env, context, idx, location, expected_ty)
588 })
589 .collect()
590}
591
592fn argument(
593 env: &Env,
594 context: &mut Context,
595 command_arg_idx: usize,
596 location: SplatLocation,
597 expected_ty: Type,
598) -> Result<T::Argument, ExecutionError> {
599 let arg__ = argument_(env, context, command_arg_idx, location, &expected_ty)
600 .map_err(|e| e.into_execution_error(command_arg_idx))?;
601 let arg_ = (arg__, expected_ty);
602 Ok(sp(command_arg_idx as u16, arg_))
603}
604
605fn argument_(
606 env: &Env,
607 context: &mut Context,
608 command_arg_idx: usize,
609 location: SplatLocation,
610 expected_ty: &Type,
611) -> Result<T::Argument__, EitherError> {
612 let current_command = context.current_command;
613 let bytes_constraint = BytesConstraint {
614 command: current_command,
615 argument: command_arg_idx as u16,
616 };
617 let (location, actual_ty): (T::Location, Type) =
618 context.resolve_location(env, location, expected_ty, bytes_constraint)?;
619 Ok(match (actual_ty, expected_ty) {
620 (Type::Reference(a_is_mut, a), Type::Reference(b_is_mut, b)) => {
622 let needs_freeze = match (a_is_mut, b_is_mut) {
623 (true, true) | (false, false) => false,
625 (true, false) => true,
627 (false, true) => return Err(CommandArgumentError::TypeMismatch.into()),
629 };
630 debug_assert!(expected_ty.abilities().has_copy());
631 check_type(&a, b)?;
633 if needs_freeze {
634 T::Argument__::Freeze(T::Usage::new_copy(location))
635 } else {
636 T::Argument__::new_copy(location)
637 }
638 }
639 (Type::Reference(_, a), b) => {
640 check_type(&a, b)?;
641 if !b.abilities().has_copy() {
642 return Err(CommandArgumentError::TypeMismatch.into());
644 }
645 T::Argument__::Read(T::Usage::new_copy(location))
646 }
647
648 (actual_ty, Type::Reference(is_mut, inner)) => {
650 check_type(&actual_ty, inner)?;
651 T::Argument__::Borrow(*is_mut, location)
652 }
653 (actual_ty, _) => {
654 check_type(&actual_ty, expected_ty)?;
655 T::Argument__::Use(if expected_ty.abilities().has_copy() {
656 T::Usage::new_copy(location)
657 } else {
658 T::Usage::new_move(location)
659 })
660 }
661 })
662}
663
664fn check_type(actual_ty: &Type, expected_ty: &Type) -> Result<(), CommandArgumentError> {
665 if actual_ty == expected_ty {
666 Ok(())
667 } else {
668 Err(CommandArgumentError::TypeMismatch)
669 }
670}
671
672fn constrained_arguments<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
673 env: &Env,
674 context: &mut Context,
675 start_idx: usize,
676 locations: Vec<SplatLocation>,
677 mut is_valid: P,
678 err_case: CommandArgumentError,
679) -> Result<Vec<T::Argument>, ExecutionError> {
680 let is_valid = &mut is_valid;
681 locations
682 .into_iter()
683 .enumerate()
684 .map(|(i, location)| {
685 let Some(idx) = start_idx.checked_add(i) else {
686 invariant_violation!("usize overflow when calculating argument index");
687 };
688 constrained_argument_(env, context, idx, location, is_valid, err_case)
689 })
690 .collect()
691}
692
693fn constrained_argument<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
694 env: &Env,
695 context: &mut Context,
696 command_arg_idx: usize,
697 location: SplatLocation,
698 mut is_valid: P,
699 err_case: CommandArgumentError,
700) -> Result<T::Argument, ExecutionError> {
701 constrained_argument_(
702 env,
703 context,
704 command_arg_idx,
705 location,
706 &mut is_valid,
707 err_case,
708 )
709}
710
711fn constrained_argument_<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
712 env: &Env,
713 context: &mut Context,
714 command_arg_idx: usize,
715 location: SplatLocation,
716 is_valid: &mut P,
717 err_case: CommandArgumentError,
718) -> Result<T::Argument, ExecutionError> {
719 let arg_ = constrained_argument__(env, context, location, is_valid, err_case)
720 .map_err(|e| e.into_execution_error(command_arg_idx))?;
721 Ok(sp(command_arg_idx as u16, arg_))
722}
723
724fn constrained_argument__<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
725 env: &Env,
726 context: &mut Context,
727 location: SplatLocation,
728 is_valid: &mut P,
729 err_case: CommandArgumentError,
730) -> Result<T::Argument_, EitherError> {
731 if let Some((location, ty)) = constrained_type(env, context, location, is_valid)? {
732 if ty.abilities().has_copy() {
733 Ok((T::Argument__::new_copy(location), ty))
734 } else {
735 Ok((T::Argument__::new_move(location), ty))
736 }
737 } else {
738 Err(err_case.into())
739 }
740}
741
742fn constrained_type<'a, P: FnMut(&Type) -> Result<bool, ExecutionError>>(
743 env: &'a Env,
744 context: &'a mut Context,
745 location: SplatLocation,
746 mut is_valid: P,
747) -> Result<Option<(T::Location, Type)>, ExecutionError> {
748 let Some((location, ty)) = context.fixed_type(env, location)? else {
749 return Ok(None);
750 };
751 Ok(if is_valid(&ty)? {
752 Some((location, ty))
753 } else {
754 None
755 })
756}
757
758fn coin_mut_ref_argument(
759 env: &Env,
760 context: &mut Context,
761 command_arg_idx: usize,
762 location: SplatLocation,
763) -> Result<T::Argument, ExecutionError> {
764 let arg_ = coin_mut_ref_argument_(env, context, location)
765 .map_err(|e| e.into_execution_error(command_arg_idx))?;
766 Ok(sp(command_arg_idx as u16, arg_))
767}
768
769fn coin_mut_ref_argument_(
770 env: &Env,
771 context: &mut Context,
772 location: SplatLocation,
773) -> Result<T::Argument_, EitherError> {
774 let Some((location, actual_ty)) = context.fixed_type(env, location)? else {
775 return Err(CommandArgumentError::TypeMismatch.into());
778 };
779 Ok(match &actual_ty {
780 Type::Reference(is_mut, ty) if *is_mut => {
781 check_coin_type(ty)?;
782 (
783 T::Argument__::new_copy(location),
784 Type::Reference(*is_mut, ty.clone()),
785 )
786 }
787 ty => {
788 check_coin_type(ty)?;
789 (
790 T::Argument__::Borrow(true, location),
791 Type::Reference(true, Rc::new(ty.clone())),
792 )
793 }
794 })
795}
796
797fn check_coin_type(ty: &Type) -> Result<(), EitherError> {
798 let Type::Datatype(dt) = ty else {
799 return Err(CommandArgumentError::TypeMismatch.into());
800 };
801 let resolved = dt.qualified_ident();
802 let is_coin = resolved == RESOLVED_COIN_STRUCT;
803 if is_coin {
804 Ok(())
805 } else {
806 Err(CommandArgumentError::TypeMismatch.into())
807 }
808}
809
810mod scope_references {
815 use crate::{
816 sp,
817 static_programmable_transactions::typing::ast::{self as T, Type},
818 };
819 use std::collections::BTreeSet;
820
821 pub fn transaction(ast: &mut T::Transaction) {
824 let mut used: BTreeSet<(u16, u16)> = BTreeSet::new();
825 for c in ast.commands.iter_mut().rev() {
826 command(&mut used, c);
827 }
828 }
829
830 fn command(used: &mut BTreeSet<(u16, u16)>, sp!(_, c): &mut T::Command) {
831 match &mut c.command {
832 T::Command__::MoveCall(mc) => arguments(used, &mut mc.arguments),
833 T::Command__::TransferObjects(objects, recipient) => {
834 argument(used, recipient);
835 arguments(used, objects);
836 }
837 T::Command__::SplitCoins(_, coin, amounts) => {
838 arguments(used, amounts);
839 argument(used, coin);
840 }
841 T::Command__::MergeCoins(_, target, coins) => {
842 arguments(used, coins);
843 argument(used, target);
844 }
845 T::Command__::MakeMoveVec(_, xs) => arguments(used, xs),
846 T::Command__::Publish(_, _, _) => (),
847 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
848 }
849 }
850
851 fn arguments(used: &mut BTreeSet<(u16, u16)>, args: &mut [T::Argument]) {
852 for arg in args.iter_mut().rev() {
853 argument(used, arg)
854 }
855 }
856
857 fn argument(used: &mut BTreeSet<(u16, u16)>, sp!(_, (arg_, ty)): &mut T::Argument) {
858 let usage = match arg_ {
859 T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
860 T::Argument__::Borrow(_, _) => return,
861 };
862 match (&usage, ty) {
863 (T::Usage::Move(T::Location::Result(i, j)), Type::Reference(_, _)) => {
864 debug_assert!(false, "No reference should be moved at this point");
865 used.insert((*i, *j));
866 }
867 (
868 T::Usage::Copy {
869 location: T::Location::Result(i, j),
870 ..
871 },
872 Type::Reference(_, _),
873 ) => {
874 let last_usage = used.insert((*i, *j));
876 if last_usage {
877 let loc = T::Location::Result(*i, *j);
879 *usage = T::Usage::Move(loc);
880 }
881 }
882 _ => (),
883 }
884 }
885}
886
887mod unused_results {
892 use indexmap::IndexSet;
893
894 use crate::{sp, static_programmable_transactions::typing::ast as T};
895
896 pub fn transaction(ast: &mut T::Transaction) {
900 let mut used: IndexSet<(u16, u16)> = IndexSet::new();
902 for c in &ast.commands {
903 command(&mut used, c);
904 }
905
906 for (i, sp!(_, c)) in ast.commands.iter_mut().enumerate() {
908 debug_assert!(c.drop_values.is_empty());
909 let i = i as u16;
910 c.drop_values = c
911 .result_type
912 .iter()
913 .enumerate()
914 .map(|(j, ty)| (j as u16, ty))
915 .map(|(j, ty)| ty.abilities().has_drop() && !used.contains(&(i, j)))
916 .collect();
917 }
918 }
919
920 fn command(used: &mut IndexSet<(u16, u16)>, sp!(_, c): &T::Command) {
921 match &c.command {
922 T::Command__::MoveCall(mc) => arguments(used, &mc.arguments),
923 T::Command__::TransferObjects(objects, recipient) => {
924 argument(used, recipient);
925 arguments(used, objects);
926 }
927 T::Command__::SplitCoins(_, coin, amounts) => {
928 arguments(used, amounts);
929 argument(used, coin);
930 }
931 T::Command__::MergeCoins(_, target, coins) => {
932 arguments(used, coins);
933 argument(used, target);
934 }
935 T::Command__::MakeMoveVec(_, elements) => arguments(used, elements),
936 T::Command__::Publish(_, _, _) => (),
937 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
938 }
939 }
940
941 fn arguments(used: &mut IndexSet<(u16, u16)>, args: &[T::Argument]) {
942 for arg in args {
943 argument(used, arg)
944 }
945 }
946
947 fn argument(used: &mut IndexSet<(u16, u16)>, sp!(_, (arg_, _)): &T::Argument) {
948 if let T::Location::Result(i, j) = arg_.location() {
949 used.insert((i, j));
950 }
951 }
952}
953
954mod consumed_shared_objects {
959
960 use crate::{
961 sp, static_programmable_transactions::loading::ast as L,
962 static_programmable_transactions::typing::ast as T,
963 };
964 use sui_types::{base_types::ObjectID, error::ExecutionError};
965
966 struct Context {
968 inputs: Vec<Option<ObjectID>>,
970 results: Vec<Vec<Option<Vec<ObjectID>>>>,
971 }
972
973 impl Context {
974 pub fn new(ast: &T::Transaction) -> Self {
975 let T::Transaction {
976 bytes: _,
977 objects,
978 withdrawals: _,
979 pure: _,
980 receiving: _,
981 commands: _,
982 } = ast;
983 let inputs = objects
984 .iter()
985 .map(|o| match &o.arg {
986 L::ObjectArg::SharedObject {
987 id,
988 kind: L::SharedObjectKind::Legacy,
989 ..
990 } => Some(*id),
991 L::ObjectArg::ImmObject(_)
992 | L::ObjectArg::OwnedObject(_)
993 | L::ObjectArg::SharedObject {
994 kind: L::SharedObjectKind::Party,
995 ..
996 } => None,
997 })
998 .collect::<Vec<_>>();
999 Self {
1000 inputs,
1001 results: vec![],
1002 }
1003 }
1004 }
1005
1006 pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1011 let mut context = Context::new(ast);
1012
1013 for c in &mut ast.commands {
1016 debug_assert!(c.value.consumed_shared_objects.is_empty());
1017 command(&mut context, c)?;
1018 debug_assert!(context.results.last().unwrap().len() == c.value.result_type.len());
1019 }
1020 Ok(())
1021 }
1022
1023 fn command(context: &mut Context, sp!(_, c): &mut T::Command) -> Result<(), ExecutionError> {
1024 let mut acc = vec![];
1025 match &c.command {
1026 T::Command__::MoveCall(mc) => arguments(context, &mut acc, &mc.arguments),
1027 T::Command__::TransferObjects(objects, recipient) => {
1028 argument(context, &mut acc, recipient);
1029 arguments(context, &mut acc, objects);
1030 }
1031 T::Command__::SplitCoins(_, coin, amounts) => {
1032 arguments(context, &mut acc, amounts);
1033 argument(context, &mut acc, coin);
1034 }
1035 T::Command__::MergeCoins(_, target, coins) => {
1036 arguments(context, &mut acc, coins);
1037 argument(context, &mut acc, target);
1038 }
1039 T::Command__::MakeMoveVec(_, elements) => arguments(context, &mut acc, elements),
1040 T::Command__::Publish(_, _, _) => (),
1041 T::Command__::Upgrade(_, _, _, x, _) => argument(context, &mut acc, x),
1042 }
1043 let (consumed, result) = match &c.command {
1044 T::Command__::MakeMoveVec(_, _) => {
1047 assert_invariant!(
1048 c.result_type.len() == 1,
1049 "MakeMoveVec must return a single value"
1050 );
1051 (vec![], vec![Some(acc)])
1052 }
1053 T::Command__::MoveCall(_)
1055 | T::Command__::TransferObjects(_, _)
1056 | T::Command__::SplitCoins(_, _, _)
1057 | T::Command__::MergeCoins(_, _, _)
1058 | T::Command__::Publish(_, _, _)
1059 | T::Command__::Upgrade(_, _, _, _, _) => (acc, vec![None; c.result_type.len()]),
1060 };
1061 c.consumed_shared_objects = consumed;
1062 context.results.push(result);
1063 Ok(())
1064 }
1065
1066 fn arguments(context: &mut Context, acc: &mut Vec<ObjectID>, args: &[T::Argument]) {
1067 for arg in args {
1068 argument(context, acc, arg)
1069 }
1070 }
1071
1072 fn argument(context: &mut Context, acc: &mut Vec<ObjectID>, sp!(_, (arg_, _)): &T::Argument) {
1073 let T::Argument__::Use(T::Usage::Move(loc)) = arg_ else {
1074 return;
1076 };
1077 match loc {
1078 T::Location::TxContext
1080 | T::Location::GasCoin
1081 | T::Location::WithdrawalInput(_)
1082 | T::Location::PureInput(_)
1083 | T::Location::ReceivingInput(_) => (),
1084 T::Location::ObjectInput(i) => {
1085 if let Some(id) = context.inputs[*i as usize] {
1086 acc.push(id);
1087 }
1088 }
1089
1090 T::Location::Result(i, j) => {
1091 if let Some(ids) = &context.results[*i as usize][*j as usize] {
1092 acc.extend(ids.iter().copied());
1093 }
1094 }
1095 }
1096 }
1097}