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