1use super::{ast as T, env::Env};
5use crate::{
6 execution_mode::ExecutionMode,
7 gas_charger::GasPayment,
8 static_programmable_transactions::execution::context::EitherError,
9 static_programmable_transactions::{
10 loading::ast::{self as L, Type},
11 spanned::sp,
12 typing::ast::BytesConstraint,
13 },
14};
15use indexmap::{IndexMap, IndexSet};
16use move_binary_format::file_format::{Ability, AbilitySet};
17use move_core_types::account_address::AccountAddress;
18use std::rc::Rc;
19use sui_types::{
20 balance::RESOLVED_BALANCE_STRUCT,
21 base_types::{ObjectRef, TxContextKind},
22 coin::{COIN_MODULE_NAME, REDEEM_FUNDS_FUNC_NAME, RESOLVED_COIN_STRUCT},
23 error::{ExecutionError, ExecutionErrorTrait, SafeIndex, command_argument_error},
24 execution_status::{CommandArgumentError, ExecutionErrorKind},
25 funds_accumulator::RESOLVED_WITHDRAWAL_STRUCT,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
29enum SplatLocation {
30 GasCoin,
31 Input(T::InputIndex),
32 Result(u16, u16),
33}
34
35#[derive(Debug, Clone, Copy)]
36enum InputKind {
37 Object,
38 Withdrawal,
39 Pure,
40 Receiving,
41}
42
43struct Context {
44 current_command: u16,
45 gas_payment: Option<GasPayment>,
46 input_resolution: Vec<InputKind>,
48 bytes: IndexSet<Vec<u8>>,
49 bytes_idx_remapping: IndexMap<T::InputIndex, T::ByteIndex>,
51 receiving_refs: IndexMap<T::InputIndex, ObjectRef>,
52 objects: IndexMap<T::InputIndex, T::ObjectInput>,
53 withdrawals: IndexMap<T::InputIndex, T::WithdrawalInput>,
54 pure: IndexMap<(T::InputIndex, Type), T::PureInput>,
55 receiving: IndexMap<(T::InputIndex, Type), T::ReceivingInput>,
56 withdrawal_compatibility_conversions:
57 IndexMap<T::Location, T::WithdrawalCompatibilityConversion>,
58 original_command_len: usize,
59 commands: Vec<T::Command>,
60}
61
62impl Context {
63 fn new(
64 gas_payment: Option<GasPayment>,
65 original_command_len: usize,
66 linputs: L::Inputs,
67 ) -> Result<Self, ExecutionError> {
68 let mut context = Context {
69 current_command: 0,
70 gas_payment,
71 input_resolution: vec![],
72 original_command_len,
73 bytes: IndexSet::new(),
74 bytes_idx_remapping: IndexMap::new(),
75 receiving_refs: IndexMap::new(),
76 objects: IndexMap::new(),
77 withdrawals: IndexMap::new(),
78 pure: IndexMap::new(),
79 withdrawal_compatibility_conversions: IndexMap::new(),
80 receiving: IndexMap::new(),
81 commands: vec![],
82 };
83 #[cfg(debug_assertions)]
85 let cloned_inputs = linputs
86 .iter()
87 .map(|(arg, _)| arg.clone())
88 .collect::<Vec<_>>();
89 for (i, (arg, ty)) in linputs.into_iter().enumerate() {
92 let idx = T::InputIndex(checked_as!(i, u16)?);
93 let kind = match (arg, ty) {
94 (L::InputArg::Pure(bytes), L::InputType::Bytes) => {
95 let (byte_index, _) = context.bytes.insert_full(bytes);
96 context.bytes_idx_remapping.insert(idx, byte_index);
97 InputKind::Pure
98 }
99 (L::InputArg::Receiving(oref), L::InputType::Bytes) => {
100 context.receiving_refs.insert(idx, oref);
101 InputKind::Receiving
102 }
103 (L::InputArg::Object(arg), L::InputType::Fixed(ty)) => {
104 let o = T::ObjectInput {
105 original_input_index: idx,
106 arg,
107 ty,
108 };
109 context.objects.insert(idx, o);
110 InputKind::Object
111 }
112 (L::InputArg::FundsWithdrawal(withdrawal), L::InputType::Fixed(input_ty)) => {
113 let L::FundsWithdrawalArg {
114 from_compatibility_object: _,
115 ty,
116 owner,
117 amount,
118 } = withdrawal;
119 debug_assert!(ty == input_ty);
120 let withdrawal = T::WithdrawalInput {
121 original_input_index: idx,
122 ty,
123 owner,
124 amount,
125 };
126 context.withdrawals.insert(idx, withdrawal);
127 InputKind::Withdrawal
128 }
129 (arg, ty) => invariant_violation!(
130 "Input arg, type mismatch. Unexpected {arg:?} with type {ty:?}"
131 ),
132 };
133 context.input_resolution.push(kind);
134 }
135 #[cfg(debug_assertions)]
136 {
137 for (i, arg) in cloned_inputs.iter().enumerate() {
139 if let L::InputArg::Pure(bytes) = &arg {
140 let idx = T::InputIndex(checked_as!(i, u16)?);
141 let Some(byte_index) = context.bytes_idx_remapping.get(&idx) else {
142 invariant_violation!("Unbound pure input {}", idx.0);
143 };
144 let Some(interned_bytes) = context.bytes.get_index(*byte_index) else {
145 invariant_violation!("Interned bytes not found for index {}", byte_index);
146 };
147 if interned_bytes != bytes {
148 assert_invariant!(
149 interned_bytes == bytes,
150 "Interned bytes mismatch for input {i}",
151 );
152 }
153 }
154 }
155 }
156 Ok(context)
157 }
158
159 fn finish(self) -> T::Transaction {
160 let Self {
161 gas_payment,
162 bytes,
163 objects,
164 withdrawals,
165 pure,
166 receiving,
167 commands,
168 withdrawal_compatibility_conversions,
169 original_command_len,
170 ..
171 } = self;
172 let objects = objects.into_iter().map(|(_, o)| o).collect();
173 let withdrawals = withdrawals.into_iter().map(|(_, w)| w).collect();
174 let pure = pure.into_iter().map(|(_, p)| p).collect();
175 let receiving = receiving.into_iter().map(|(_, r)| r).collect();
176 T::Transaction {
177 gas_payment,
178 bytes,
179 objects,
180 withdrawals,
181 pure,
182 receiving,
183 withdrawal_compatibility_conversions,
184 original_command_len,
185 commands,
186 }
187 }
188
189 fn push_result(&mut self, command: T::Command_) -> Result<(), ExecutionError> {
190 self.commands.push(sp(self.current_command, command));
191 Ok(())
192 }
193
194 fn result_type(&self, i: u16) -> Option<&T::ResultType> {
195 self.commands.get(i as usize).map(|c| &c.value.result_type)
196 }
197
198 fn fixed_location_type<Mode: ExecutionMode>(
199 &mut self,
200 env: &Env<Mode>,
201 location: T::Location,
202 ) -> Result<Option<Type>, Mode::Error> {
203 Ok(Some(match location {
204 T::Location::TxContext => env.tx_context_type()?,
205 T::Location::GasCoin => env.gas_coin_type()?,
206 T::Location::Result(i, j) => {
207 let Some(tys) = self.result_type(i) else {
208 invariant_violation!("Result index {i} is out of bounds")
209 };
210 tys.safe_get(j as usize)?.clone()
211 }
212 T::Location::ObjectInput(i) => {
213 let Some((_, object_input)) = self.objects.get_index(i as usize) else {
214 invariant_violation!("Unbound object input {}", i)
215 };
216 object_input.ty.clone()
217 }
218 T::Location::WithdrawalInput(i) => {
219 let Some((_, withdrawal_input)) = self.withdrawals.get_index(i as usize) else {
220 invariant_violation!("Unbound withdrawal input {}", i)
221 };
222 withdrawal_input.ty.clone()
223 }
224 T::Location::PureInput(_) | T::Location::ReceivingInput(_) => return Ok(None),
225 }))
226 }
227
228 fn fixed_type<Mode: ExecutionMode>(
230 &mut self,
231 env: &Env<Mode>,
232 splat_location: SplatLocation,
233 ) -> Result<Option<(T::Location, Type)>, Mode::Error> {
234 let location = match splat_location {
235 SplatLocation::GasCoin => T::Location::GasCoin,
236 SplatLocation::Result(i, j) => T::Location::Result(i, j),
237 SplatLocation::Input(i) => match self.input_resolution.safe_get(i.0 as usize)? {
238 InputKind::Object => {
239 let Some(index) = self.objects.get_index_of(&i) else {
240 invariant_violation!("Unbound object input {}", i.0)
241 };
242 T::Location::ObjectInput(checked_as!(index, u16)?)
243 }
244 InputKind::Withdrawal => {
245 let Some(withdrawal_index) = self.withdrawals.get_index_of(&i) else {
246 invariant_violation!("Unbound withdrawal input {}", i.0)
247 };
248 T::Location::WithdrawalInput(checked_as!(withdrawal_index, u16)?)
249 }
250 InputKind::Pure | InputKind::Receiving => return Ok(None),
251 },
252 };
253 let Some(ty) = self.fixed_location_type(env, location)? else {
254 invariant_violation!("Location {location:?} does not have a fixed type")
255 };
256 Ok(Some((location, ty)))
257 }
258
259 fn resolve_location<Mode: ExecutionMode>(
260 &mut self,
261 env: &Env<Mode>,
262 splat_location: SplatLocation,
263 expected_ty: &Type,
264 bytes_constraint: BytesConstraint,
265 ) -> Result<(T::Location, Type), Mode::Error> {
266 let location = match splat_location {
267 SplatLocation::GasCoin => T::Location::GasCoin,
268 SplatLocation::Result(i, j) => T::Location::Result(i, j),
269 SplatLocation::Input(i) => match self.input_resolution.safe_get(i.0 as usize)? {
270 InputKind::Object => {
271 let Some(index) = self.objects.get_index_of(&i) else {
272 invariant_violation!("Unbound object input {}", i.0)
273 };
274 T::Location::ObjectInput(checked_as!(index, u16)?)
275 }
276 InputKind::Withdrawal => {
277 let Some(index) = self.withdrawals.get_index_of(&i) else {
278 invariant_violation!("Unbound withdrawal input {}", i.0)
279 };
280 T::Location::WithdrawalInput(checked_as!(index, u16)?)
281 }
282 InputKind::Pure => {
283 let ty = match expected_ty {
284 Type::Reference(_, inner) => (**inner).clone(),
285 ty => ty.clone(),
286 };
287 let k = (i, ty.clone());
288 if !self.pure.contains_key(&k) {
289 let Some(byte_index) = self.bytes_idx_remapping.get(&i).copied() else {
290 invariant_violation!("Unbound pure input {}", i.0);
291 };
292 let pure = T::PureInput {
293 original_input_index: i,
294 byte_index,
295 ty: ty.clone(),
296 constraint: bytes_constraint,
297 };
298 self.pure.insert(k.clone(), pure);
299 }
300 let byte_index = self.pure.get_index_of(&k).unwrap();
301 return Ok((T::Location::PureInput(checked_as!(byte_index, u16)?), ty));
302 }
303 InputKind::Receiving => {
304 let ty = match expected_ty {
305 Type::Reference(_, inner) => (**inner).clone(),
306 ty => ty.clone(),
307 };
308 let k = (i, ty.clone());
309 if !self.receiving.contains_key(&k) {
310 let Some(object_ref) = self.receiving_refs.get(&i).copied() else {
311 invariant_violation!("Unbound receiving input {}", i.0);
312 };
313 let receiving = T::ReceivingInput {
314 original_input_index: i,
315 object_ref,
316 ty: ty.clone(),
317 constraint: bytes_constraint,
318 };
319 self.receiving.insert(k.clone(), receiving);
320 }
321 let byte_index = self.receiving.get_index_of(&k).unwrap();
322 return Ok((
323 T::Location::ReceivingInput(checked_as!(byte_index, u16)?),
324 ty,
325 ));
326 }
327 },
328 };
329 let Some(ty) = self.fixed_location_type(env, location)? else {
330 invariant_violation!("Location {location:?} does not have a fixed type")
331 };
332 Ok((location, ty))
333 }
334}
335
336pub fn transaction<Mode: ExecutionMode>(
337 env: &Env<Mode>,
338 lt: L::Transaction,
339) -> Result<T::Transaction, Mode::Error> {
340 let L::Transaction {
341 gas_payment,
342 mut inputs,
343 original_command_len,
344 mut commands,
345 } = lt;
346 let withdrawal_compatability_inputs =
347 determine_withdrawal_compatibility_inputs(env, &mut inputs)?;
348 let mut context = Context::new(gas_payment, original_command_len, inputs)?;
349 withdrawal_compatibility_conversion(
350 env,
351 &mut context,
352 withdrawal_compatability_inputs,
353 &mut commands,
354 )?;
355 for (i, c) in commands.into_iter().enumerate() {
356 let idx = checked_as!(i, u16)?;
357 context.current_command = idx;
358 let (c_, tys) =
359 command::<Mode>(env, &mut context, c).map_err(|e| e.with_command_index(i))?;
360 let c = T::Command_ {
361 command: c_,
362 result_type: tys,
363 drop_values: vec![],
365 consumed_shared_objects: vec![],
367 };
368 context.push_result(c)?
369 }
370 let mut ast = context.finish();
371 scope_references::transaction(&mut ast);
373 unused_results::transaction(&mut ast)?;
375 consumed_shared_objects::transaction(&mut ast)?;
377 Ok(ast)
378}
379
380fn command<Mode: ExecutionMode>(
381 env: &Env<Mode>,
382 context: &mut Context,
383 command: L::Command,
384) -> Result<(T::Command__, T::ResultType), Mode::Error> {
385 Ok(match command {
386 L::Command::MoveCall(lmc) => {
387 let L::MoveCall {
388 function,
389 arguments: largs,
390 } = *lmc;
391 let arg_locs = locations(context, 0, largs)?;
392 let args = move_call_arguments(env, context, &function, arg_locs)?;
393 let result = function.signature.return_.clone();
394 (
395 T::Command__::MoveCall(Box::new(T::MoveCall {
396 function,
397 arguments: args,
398 })),
399 result,
400 )
401 }
402 L::Command::TransferObjects(lobjects, laddress) => {
403 const TRANSFER_OBJECTS_CONSTRAINT: AbilitySet =
404 AbilitySet::singleton(Ability::Store).union(AbilitySet::singleton(Ability::Key));
405 let object_locs = locations(context, 0, lobjects)?;
406 let address_loc = one_location(context, object_locs.len(), laddress)?;
407 let objects = constrained_arguments(
408 env,
409 context,
410 0,
411 object_locs,
412 TRANSFER_OBJECTS_CONSTRAINT,
413 CommandArgumentError::InvalidTransferObject,
414 )?;
415 let address = argument(env, context, objects.len(), address_loc, Type::Address)?;
416 (T::Command__::TransferObjects(objects, address), vec![])
417 }
418 L::Command::SplitCoins(lcoin, lamounts) => {
419 let coin_loc = one_location(context, 0, lcoin)?;
420 let amount_locs = locations(context, 1, lamounts)?;
421 let coin = coin_mut_ref_argument(env, context, 0, coin_loc)?;
422 let coin_type = match &coin.value.1 {
423 Type::Reference(true, ty) => (**ty).clone(),
424 ty => invariant_violation!("coin must be a mutable reference. Found: {ty:?}"),
425 };
426 let amounts = arguments(
427 env,
428 context,
429 1,
430 amount_locs,
431 std::iter::repeat_with(|| Type::U64),
432 )?;
433 let result = vec![coin_type.clone(); amounts.len()];
434 (T::Command__::SplitCoins(coin_type, coin, amounts), result)
435 }
436 L::Command::MergeCoins(ltarget, lcoins) => {
437 let target_loc = one_location(context, 0, ltarget)?;
438 let coin_locs = locations(context, 1, lcoins)?;
439 let target = coin_mut_ref_argument(env, context, 0, target_loc)?;
440 let coin_type = match &target.value.1 {
441 Type::Reference(true, ty) => (**ty).clone(),
442 ty => invariant_violation!("target must be a mutable reference. Found: {ty:?}"),
443 };
444 let coins = arguments(
445 env,
446 context,
447 1,
448 coin_locs,
449 std::iter::repeat_with(|| coin_type.clone()),
450 )?;
451 (T::Command__::MergeCoins(coin_type, target, coins), vec![])
452 }
453 L::Command::MakeMoveVec(Some(ty), lelems) => {
454 let elem_locs = locations(context, 0, lelems)?;
455 let elems = arguments(
456 env,
457 context,
458 0,
459 elem_locs,
460 std::iter::repeat_with(|| ty.clone()),
461 )?;
462 (
463 T::Command__::MakeMoveVec(ty.clone(), elems),
464 vec![env.vector_type(ty)?],
465 )
466 }
467 L::Command::MakeMoveVec(None, lelems) => {
468 const MAKE_MOVE_VEC_OBJECT_CONSTRAINT: AbilitySet = AbilitySet::singleton(Ability::Key);
469 let mut lelems = lelems.into_iter();
470 let Some(lfirst) = lelems.next() else {
471 invariant_violation!(
473 "input checker ensures if args are empty, there is a type specified"
474 );
475 };
476 let first_loc = one_location(context, 0, lfirst)?;
477 let first_arg = constrained_argument(
478 env,
479 context,
480 0,
481 first_loc,
482 MAKE_MOVE_VEC_OBJECT_CONSTRAINT,
483 CommandArgumentError::InvalidMakeMoveVecNonObjectArgument,
484 )?;
485 let first_ty = first_arg.value.1.clone();
486 let elems_loc = locations(context, 1, lelems)?;
487 let mut elems = arguments(
488 env,
489 context,
490 1,
491 elems_loc,
492 std::iter::repeat_with(|| first_ty.clone()),
493 )?;
494 elems.insert(0, first_arg);
495 (
496 T::Command__::MakeMoveVec(first_ty.clone(), elems),
497 vec![env.vector_type(first_ty)?],
498 )
499 }
500 L::Command::Publish(items, object_ids, linkage) => {
501 let result = if Mode::packages_are_predefined() {
502 vec![]
504 } else {
505 vec![env.upgrade_cap_type()?.clone()]
506 };
507 (T::Command__::Publish(items, object_ids, linkage), result)
508 }
509 L::Command::Upgrade(items, object_ids, object_id, la, linkage) => {
510 let location = one_location(context, 0, la)?;
511 let expected_ty = env.upgrade_ticket_type()?;
512 let a = argument(env, context, 0, location, expected_ty)?;
513 let res = env.upgrade_receipt_type()?;
514 (
515 T::Command__::Upgrade(items, object_ids, object_id, a, linkage),
516 vec![res.clone()],
517 )
518 }
519 })
520}
521
522fn move_call_parameters<'a, Mode: ExecutionMode>(
523 _env: &Env<Mode>,
524 function: &'a L::LoadedFunction,
525) -> Vec<(&'a Type, TxContextKind)> {
526 function
527 .signature
528 .parameters
529 .iter()
530 .map(|ty| (ty, ty.is_tx_context()))
531 .collect()
532}
533
534fn move_call_arguments<Mode: ExecutionMode>(
535 env: &Env<Mode>,
536 context: &mut Context,
537 function: &L::LoadedFunction,
538 args: Vec<SplatLocation>,
539) -> Result<Vec<T::Argument>, Mode::Error> {
540 let params = move_call_parameters(env, function);
541 assert_invariant!(
542 params.len() == function.signature.parameters.len(),
543 "Generated parameter types does not match the function signature"
544 );
545 let num_tx_contexts = params
547 .iter()
548 .filter(|(_, k)| matches!(k, TxContextKind::Mutable | TxContextKind::Immutable))
549 .count();
550 let num_user_args = args.len();
551 let Some(num_args) = num_user_args.checked_add(num_tx_contexts) else {
552 invariant_violation!("usize overflow when calculating number of arguments");
553 };
554 let num_parameters = params.len();
555 if num_args != num_parameters {
556 return Err(Mode::Error::new_with_source(
557 ExecutionErrorKind::ArityMismatch,
558 format!(
559 "Expected {} argument{} calling function '{}::{}', but found {}",
560 num_parameters,
561 if num_parameters == 1 { "" } else { "s" },
562 function.version_mid,
563 function.name,
564 num_args,
565 ),
566 ));
567 }
568 let mut args = args.into_iter().enumerate();
570 let res = params
571 .into_iter()
572 .enumerate()
573 .map(|(param_idx, (expected_ty, tx_context_kind))| {
574 Ok(match tx_context_kind {
575 TxContextKind::None => {
576 let Some((arg_idx, location)) = args.next() else {
577 invariant_violation!("arguments are empty but arity was already checked");
578 };
579 argument(env, context, arg_idx, location, expected_ty.clone())?
580 }
581 TxContextKind::Mutable | TxContextKind::Immutable => {
582 let is_mut = match tx_context_kind {
583 TxContextKind::Mutable => true,
584 TxContextKind::Immutable => false,
585 TxContextKind::None => unreachable!(),
586 };
587 let idx = checked_as!(param_idx, u16)?;
590 let arg__ = T::Argument__::Borrow(is_mut, T::Location::TxContext);
591 let ty = Type::Reference(is_mut, Rc::new(env.tx_context_type()?));
592 sp(idx, (arg__, ty))
593 }
594 })
595 })
596 .collect::<Result<Vec<_>, Mode::Error>>()?;
597
598 assert_invariant!(
599 args.next().is_none(),
600 "some arguments went unused but arity was already checked"
601 );
602 Ok(res)
603}
604
605fn one_location<E: ExecutionErrorTrait>(
606 context: &mut Context,
607 command_arg_idx: usize,
608 arg: L::Argument,
609) -> Result<SplatLocation, E> {
610 let locs = locations(context, command_arg_idx, vec![arg])?;
611 let Ok([loc]): Result<[SplatLocation; 1], _> = locs.try_into() else {
612 return Err(command_argument_error(
613 CommandArgumentError::InvalidArgumentArity,
614 command_arg_idx,
615 )
616 .into());
617 };
618 Ok(loc)
619}
620
621fn locations<E: ExecutionErrorTrait, Items: IntoIterator<Item = L::Argument>>(
622 context: &mut Context,
623 start_idx: usize,
624 args: Items,
625) -> Result<Vec<SplatLocation>, E>
626where
627 Items::IntoIter: ExactSizeIterator,
628{
629 fn splat_arg<E: ExecutionErrorTrait>(
630 context: &mut Context,
631 res: &mut Vec<SplatLocation>,
632 arg: L::Argument,
633 ) -> Result<(), EitherError<E>> {
634 match arg {
635 L::Argument::GasCoin => res.push(SplatLocation::GasCoin),
636 L::Argument::Input(i) => {
637 if i as usize >= context.input_resolution.len() {
638 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
639 }
640 res.push(SplatLocation::Input(T::InputIndex(i)))
641 }
642 L::Argument::NestedResult(i, j) => {
643 let Some(command_result) = context.result_type(i) else {
644 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
645 };
646 if j as usize >= command_result.len() {
647 return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
648 result_idx: i,
649 secondary_idx: j,
650 }
651 .into());
652 };
653 res.push(SplatLocation::Result(i, j))
654 }
655 L::Argument::Result(i) => {
656 let Some(result) = context.result_type(i) else {
657 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
658 };
659 let Ok(len): Result<u16, _> = result.len().try_into() else {
660 invariant_violation!("Result of length greater than u16::MAX");
661 };
662 if len != 1 {
663 return Err(CommandArgumentError::InvalidResultArity { result_idx: i }.into());
665 }
666 res.extend((0..len).map(|j| SplatLocation::Result(i, j)))
667 }
668 }
669 Ok(())
670 }
671
672 let args = args.into_iter();
673 let _args_len = args.len();
674 let mut res = vec![];
675 for (arg_idx, arg) in args.enumerate() {
676 splat_arg::<E>(context, &mut res, arg).map_err(|e| {
677 let Some(idx) = start_idx.checked_add(arg_idx) else {
678 return make_invariant_violation!("usize overflow when calculating argument index")
679 .into();
680 };
681 e.into_execution_error(idx)
682 })?
683 }
684 debug_assert_eq!(res.len(), _args_len);
685 Ok(res)
686}
687
688fn arguments<Mode: ExecutionMode>(
689 env: &Env<Mode>,
690 context: &mut Context,
691 start_idx: usize,
692 locations: Vec<SplatLocation>,
693 expected_tys: impl IntoIterator<Item = Type>,
694) -> Result<Vec<T::Argument>, Mode::Error> {
695 #[allow(clippy::disallowed_methods)]
696 locations
697 .into_iter()
698 .zip(expected_tys)
701 .enumerate()
702 .map(|(i, (location, expected_ty))| {
703 let Some(idx) = start_idx.checked_add(i) else {
704 invariant_violation!("usize overflow when calculating argument index");
705 };
706 argument(env, context, idx, location, expected_ty)
707 })
708 .collect()
709}
710
711fn argument<Mode: ExecutionMode>(
712 env: &Env<Mode>,
713 context: &mut Context,
714 command_arg_idx: usize,
715 location: SplatLocation,
716 expected_ty: Type,
717) -> Result<T::Argument, Mode::Error> {
718 let arg__ = argument_(env, context, command_arg_idx, location, &expected_ty)
719 .map_err(|e| e.into_execution_error(command_arg_idx))?;
720 let arg_ = (arg__, expected_ty);
721 Ok(sp(checked_as!(command_arg_idx, u16)?, arg_))
722}
723
724fn argument_<Mode: ExecutionMode>(
725 env: &Env<Mode>,
726 context: &mut Context,
727 command_arg_idx: usize,
728 location: SplatLocation,
729 expected_ty: &Type,
730) -> Result<T::Argument__, EitherError<Mode::Error>> {
731 let current_command = context.current_command;
732 let bytes_constraint = BytesConstraint {
733 command: current_command,
734 argument: checked_as!(command_arg_idx, u16)?,
735 };
736 let (location, actual_ty) = context
737 .resolve_location(env, location, expected_ty, bytes_constraint)
738 .map_err(EitherError::Execution)?;
739 Ok(match (actual_ty, expected_ty) {
740 (Type::Reference(a_is_mut, a), Type::Reference(b_is_mut, b)) => {
742 let needs_freeze = match (a_is_mut, b_is_mut) {
743 (true, true) | (false, false) => false,
745 (true, false) => true,
747 (false, true) => return Err(CommandArgumentError::TypeMismatch.into()),
749 };
750 debug_assert!(expected_ty.abilities().has_copy());
751 check_type(&a, b)?;
753 if needs_freeze {
754 T::Argument__::Freeze(T::Usage::new_copy(location))
755 } else {
756 T::Argument__::new_copy(location)
757 }
758 }
759 (Type::Reference(_, a), b) => {
760 check_type(&a, b)?;
761 if !b.abilities().has_copy() {
762 return Err(CommandArgumentError::TypeMismatch.into());
764 }
765 T::Argument__::Read(T::Usage::new_copy(location))
766 }
767
768 (actual_ty, Type::Reference(is_mut, inner)) => {
770 check_type(&actual_ty, inner)?;
771 T::Argument__::Borrow(*is_mut, location)
772 }
773 (actual_ty, _) => {
774 check_type(&actual_ty, expected_ty)?;
775 T::Argument__::Use(if expected_ty.abilities().has_copy() {
776 T::Usage::new_copy(location)
777 } else {
778 T::Usage::new_move(location)
779 })
780 }
781 })
782}
783
784fn check_type(actual_ty: &Type, expected_ty: &Type) -> Result<(), CommandArgumentError> {
785 if actual_ty == expected_ty {
786 Ok(())
787 } else {
788 Err(CommandArgumentError::TypeMismatch)
789 }
790}
791
792fn constrained_arguments<Mode: ExecutionMode>(
793 env: &Env<Mode>,
794 context: &mut Context,
795 start_idx: usize,
796 locations: Vec<SplatLocation>,
797 constraint: AbilitySet,
798 err_case: CommandArgumentError,
799) -> Result<Vec<T::Argument>, Mode::Error> {
800 locations
801 .into_iter()
802 .enumerate()
803 .map(|(i, location)| {
804 let Some(idx) = start_idx.checked_add(i) else {
805 invariant_violation!("usize overflow when calculating argument index");
806 };
807 constrained_argument(env, context, idx, location, constraint, err_case)
808 })
809 .collect()
810}
811
812fn constrained_argument<Mode: ExecutionMode>(
813 env: &Env<Mode>,
814 context: &mut Context,
815 command_arg_idx: usize,
816 location: SplatLocation,
817 constraint: AbilitySet,
818 err_case: CommandArgumentError,
819) -> Result<T::Argument, Mode::Error> {
820 let arg_ = constrained_argument_(
821 env,
822 context,
823 command_arg_idx,
824 location,
825 constraint,
826 err_case,
827 )
828 .map_err(|e| e.into_execution_error(command_arg_idx))?;
829 Ok(sp(checked_as!(command_arg_idx, u16)?, arg_))
830}
831
832fn constrained_argument_<Mode: ExecutionMode>(
833 env: &Env<Mode>,
834 context: &mut Context,
835 command_arg_idx: usize,
836 location: SplatLocation,
837 constraint: AbilitySet,
838 err_case: CommandArgumentError,
839) -> Result<T::Argument_, EitherError<Mode::Error>> {
840 if let Some((location, ty)) =
841 constrained_type(env, context, command_arg_idx, location, constraint)
842 .map_err(EitherError::Execution)?
843 {
844 if ty.abilities().has_copy() {
845 Ok((T::Argument__::new_copy(location), ty))
846 } else {
847 Ok((T::Argument__::new_move(location), ty))
848 }
849 } else {
850 Err(err_case.into())
851 }
852}
853
854fn constrained_type<'a, Mode: ExecutionMode>(
855 env: &'a Env<Mode>,
856 context: &'a mut Context,
857 _command_arg_idx: usize,
858 location: SplatLocation,
859 constraint: AbilitySet,
860) -> Result<Option<(T::Location, Type)>, Mode::Error> {
861 let Some((location, ty)) = context.fixed_type(env, location)? else {
862 return Ok(None);
863 };
864 Ok(if constraint.is_subset(ty.abilities()) {
865 Some((location, ty))
866 } else {
867 None
868 })
869}
870
871fn coin_mut_ref_argument<Mode: ExecutionMode>(
872 env: &Env<Mode>,
873 context: &mut Context,
874 command_arg_idx: usize,
875 location: SplatLocation,
876) -> Result<T::Argument, Mode::Error> {
877 let arg_ = coin_mut_ref_argument_(env, context, command_arg_idx, location)
878 .map_err(|e| e.into_execution_error(command_arg_idx))?;
879 Ok(sp(checked_as!(command_arg_idx, u16)?, arg_))
880}
881
882fn coin_mut_ref_argument_<Mode: ExecutionMode>(
883 env: &Env<Mode>,
884 context: &mut Context,
885 _command_arg_idx: usize,
886 location: SplatLocation,
887) -> Result<T::Argument_, EitherError<Mode::Error>> {
888 let Some((location, actual_ty)) = context
889 .fixed_type(env, location)
890 .map_err(EitherError::Execution)?
891 else {
892 return Err(CommandArgumentError::TypeMismatch.into());
895 };
896 Ok(match &actual_ty {
897 Type::Reference(is_mut, ty) if *is_mut => {
898 check_coin_type(ty)?;
899 (
900 T::Argument__::new_copy(location),
901 Type::Reference(*is_mut, ty.clone()),
902 )
903 }
904 ty => {
905 check_coin_type(ty)?;
906 (
907 T::Argument__::Borrow(true, location),
908 Type::Reference(true, Rc::new(ty.clone())),
909 )
910 }
911 })
912}
913
914fn check_coin_type<E: ExecutionErrorTrait>(ty: &Type) -> Result<(), EitherError<E>> {
915 if coin_inner_type(ty).is_some() {
916 Ok(())
917 } else {
918 Err(CommandArgumentError::TypeMismatch.into())
919 }
920}
921
922fn determine_withdrawal_compatibility_inputs<Mode: ExecutionMode>(
929 _env: &Env<Mode>,
930 inputs: &mut L::Inputs,
931) -> Result<IndexMap<u16, u16>, Mode::Error> {
932 let withdrawal_compatibility_owners: IndexMap<u16, AccountAddress> = inputs
933 .iter()
934 .enumerate()
935 .filter_map(|(i, (input_arg, _))| {
936 if let L::InputArg::FundsWithdrawal(withdrawal) = input_arg
937 && withdrawal.from_compatibility_object
938 {
939 Some((i, withdrawal.owner))
940 } else {
941 None
942 }
943 })
944 .map(|(i, owner)| Ok((checked_as!(i, u16)?, owner)))
945 .collect::<Result<_, Mode::Error>>()?;
946 withdrawal_compatibility_owners
947 .into_iter()
948 .map(|(i, owner)| {
949 let owner_idx = checked_as!(inputs.len(), u16)?;
950 let bytes: Vec<u8> = bcs::to_bytes(&owner).map_err(|_| {
951 make_invariant_violation!(
952 "Failed to serialize owner address for withdrawal compatibility input",
953 )
954 })?;
955 inputs.push((L::InputArg::Pure(bytes), L::InputType::Bytes));
956 Ok((i, owner_idx))
957 })
958 .collect()
959}
960
961struct WithdrawalCompatibilityRemap {
962 remap: IndexMap<u16, u16>,
964 lift: u16,
966}
967
968fn withdrawal_compatibility_conversion<Mode: ExecutionMode>(
972 env: &Env<Mode>,
973 context: &mut Context,
974 withdrawal_compatability_inputs: IndexMap<
975 u16,
976 u16,
977 >,
978 commands: &mut [L::Command],
979) -> Result<(), Mode::Error> {
980 let mut compatibility_remap = WithdrawalCompatibilityRemap {
981 remap: IndexMap::new(),
982 lift: 0,
983 };
984 for (input, owner_idx) in withdrawal_compatability_inputs {
985 let result_idx = convert_withdrawal_to_coin(env, context, input, owner_idx)?;
986 compatibility_remap.remap.insert(input, result_idx);
987 }
988 compatibility_remap.lift = checked_as!(context.commands.len(), u16)?;
989 lift_result_indices(&compatibility_remap, commands)?;
990 Ok(())
991}
992
993fn convert_withdrawal_to_coin<Mode: ExecutionMode>(
994 env: &Env<Mode>,
995 context: &mut Context,
996 withdrawal_input: u16,
997 owner_input: u16,
998) -> Result<u16, Mode::Error> {
999 assert_invariant!(
1000 env.protocol_config
1001 .convert_withdrawal_compatibility_ptb_arguments(),
1002 "convert_withdrawal_to_coin called when conversion is disabled"
1003 );
1004 let (owner_location, _owner_ty) = context.resolve_location(
1006 env,
1007 SplatLocation::Input(T::InputIndex(owner_input)),
1008 &Type::Address,
1009 BytesConstraint {
1010 command: 0,
1011 argument: 0,
1012 },
1013 )?;
1014 let Some((location, withdrawal_ty)) =
1015 context.fixed_type(env, SplatLocation::Input(T::InputIndex(withdrawal_input)))?
1016 else {
1017 invariant_violation!(
1018 "Expected fixed type for withdrawal compatibility input {}",
1019 withdrawal_input
1020 )
1021 };
1022 let Some(inner_ty) = withdrawal_inner_type(&withdrawal_ty)
1023 .and_then(balance_inner_type)
1024 .cloned()
1025 else {
1026 invariant_violation!("convert_withdrawal_to_coin called with non-withdrawal type");
1027 };
1028 let idx = 0u16;
1029 let withdrawal_arg_ = T::Argument__::new_move(location);
1031 let withdrawal_arg = sp(idx, (withdrawal_arg_, withdrawal_ty));
1032 let ctx_arg_ = T::Argument__::Borrow(true, T::Location::TxContext);
1033 let ctx_ty = Type::Reference(true, Rc::new(env.tx_context_type()?));
1034 let ctx_arg = sp(idx, (ctx_arg_, ctx_ty));
1035 let conversion_command__ = T::Command__::MoveCall(Box::new(T::MoveCall {
1036 function: env.load_framework_function(
1037 COIN_MODULE_NAME,
1038 REDEEM_FUNDS_FUNC_NAME,
1039 vec![inner_ty.clone()],
1040 )?,
1041 arguments: vec![withdrawal_arg, ctx_arg],
1042 }));
1043 let conversion_command_ = T::Command_ {
1044 command: conversion_command__,
1045 result_type: vec![env.coin_type(inner_ty.clone())?],
1046 drop_values: vec![],
1047 consumed_shared_objects: vec![],
1048 };
1049 let conversion_idx = checked_as!(context.commands.len(), u16)?;
1050 context.push_result(conversion_command_)?;
1051 context.withdrawal_compatibility_conversions.insert(
1053 location,
1054 T::WithdrawalCompatibilityConversion {
1055 owner: owner_location,
1056 conversion_result: conversion_idx,
1057 },
1058 );
1059 Ok(conversion_idx)
1061}
1062
1063fn lift_result_indices(
1066 remap: &WithdrawalCompatibilityRemap,
1067 commands: &mut [L::Command],
1068) -> Result<(), ExecutionError> {
1069 for command in commands {
1070 for arg in command.arguments_mut() {
1071 match arg {
1072 L::Argument::NestedResult(result, _) | L::Argument::Result(result) => {
1073 *result = remap.lift.checked_add(*result).ok_or_else(|| {
1074 make_invariant_violation!(
1075 "u16 overflow when lifting result index during withdrawal compatibility",
1076 )
1077 })?;
1078 }
1079 L::Argument::Input(i) => {
1080 if let Some(converted_withdrawal) = remap.remap.get(i).copied() {
1081 *arg = L::Argument::NestedResult(converted_withdrawal, 0);
1082 }
1083 }
1084 L::Argument::GasCoin => (),
1085 }
1086 }
1087 }
1088 Ok(())
1089}
1090
1091pub(crate) fn coin_inner_type(ty: &Type) -> Option<&Type> {
1093 if let Type::Datatype(dt) = ty
1094 && dt.type_arguments.len() == 1
1095 && dt.qualified_ident() == RESOLVED_COIN_STRUCT
1096 {
1097 Some(dt.type_arguments.first().unwrap())
1098 } else {
1099 None
1100 }
1101}
1102
1103pub(crate) fn balance_inner_type(ty: &Type) -> Option<&Type> {
1105 if let Type::Datatype(dt) = ty
1106 && dt.type_arguments.len() == 1
1107 && dt.qualified_ident() == RESOLVED_BALANCE_STRUCT
1108 {
1109 Some(dt.type_arguments.first().unwrap())
1110 } else {
1111 None
1112 }
1113}
1114
1115pub(crate) fn withdrawal_inner_type(ty: &Type) -> Option<&Type> {
1117 if let Type::Datatype(dt) = ty
1118 && dt.type_arguments.len() == 1
1119 && dt.qualified_ident() == RESOLVED_WITHDRAWAL_STRUCT
1120 {
1121 Some(dt.type_arguments.first().unwrap())
1122 } else {
1123 None
1124 }
1125}
1126
1127mod scope_references {
1132 use crate::{
1133 sp,
1134 static_programmable_transactions::typing::ast::{self as T, Type},
1135 };
1136 use std::collections::BTreeSet;
1137
1138 pub fn transaction(ast: &mut T::Transaction) {
1141 let mut used: BTreeSet<(u16, u16)> = BTreeSet::new();
1142 for c in ast.commands.iter_mut().rev() {
1143 command(&mut used, c);
1144 }
1145 }
1146
1147 fn command(used: &mut BTreeSet<(u16, u16)>, sp!(_, c): &mut T::Command) {
1148 match &mut c.command {
1149 T::Command__::MoveCall(mc) => arguments(used, &mut mc.arguments),
1150 T::Command__::TransferObjects(objects, recipient) => {
1151 argument(used, recipient);
1152 arguments(used, objects);
1153 }
1154 T::Command__::SplitCoins(_, coin, amounts) => {
1155 arguments(used, amounts);
1156 argument(used, coin);
1157 }
1158 T::Command__::MergeCoins(_, target, coins) => {
1159 arguments(used, coins);
1160 argument(used, target);
1161 }
1162 T::Command__::MakeMoveVec(_, xs) => arguments(used, xs),
1163 T::Command__::Publish(_, _, _) => (),
1164 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
1165 }
1166 }
1167
1168 fn arguments(used: &mut BTreeSet<(u16, u16)>, args: &mut [T::Argument]) {
1169 for arg in args.iter_mut().rev() {
1170 argument(used, arg)
1171 }
1172 }
1173
1174 fn argument(used: &mut BTreeSet<(u16, u16)>, sp!(_, (arg_, ty)): &mut T::Argument) {
1175 let usage = match arg_ {
1176 T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
1177 T::Argument__::Borrow(_, _) => return,
1178 };
1179 match (&usage, ty) {
1180 (T::Usage::Move(T::Location::Result(i, j)), Type::Reference(_, _)) => {
1181 debug_assert!(false, "No reference should be moved at this point");
1182 used.insert((*i, *j));
1183 }
1184 (
1185 T::Usage::Copy {
1186 location: T::Location::Result(i, j),
1187 ..
1188 },
1189 Type::Reference(_, _),
1190 ) => {
1191 let last_usage = used.insert((*i, *j));
1193 if last_usage {
1194 let loc = T::Location::Result(*i, *j);
1196 *usage = T::Usage::Move(loc);
1197 }
1198 }
1199 _ => (),
1200 }
1201 }
1202}
1203
1204mod unused_results {
1209 use indexmap::IndexSet;
1210 use sui_types::error::ExecutionError;
1211
1212 use crate::{sp, static_programmable_transactions::typing::ast as T};
1213
1214 pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1218 let mut used: IndexSet<(u16, u16)> = IndexSet::new();
1220 for c in &ast.commands {
1221 command(&mut used, c);
1222 }
1223
1224 for (i, sp!(_, c)) in ast.commands.iter_mut().enumerate() {
1226 debug_assert!(c.drop_values.is_empty());
1227 let i = checked_as!(i, u16)?;
1228 c.drop_values = c
1229 .result_type
1230 .iter()
1231 .enumerate()
1232 .map(|(j, ty)| {
1233 Ok(ty.abilities().has_drop() && !used.contains(&(i, checked_as!(j, u16)?)))
1234 })
1235 .collect::<Result<_, ExecutionError>>()?;
1236 }
1237 Ok(())
1238 }
1239
1240 fn command(used: &mut IndexSet<(u16, u16)>, sp!(_, c): &T::Command) {
1241 match &c.command {
1242 T::Command__::MoveCall(mc) => arguments(used, &mc.arguments),
1243 T::Command__::TransferObjects(objects, recipient) => {
1244 argument(used, recipient);
1245 arguments(used, objects);
1246 }
1247 T::Command__::SplitCoins(_, coin, amounts) => {
1248 arguments(used, amounts);
1249 argument(used, coin);
1250 }
1251 T::Command__::MergeCoins(_, target, coins) => {
1252 arguments(used, coins);
1253 argument(used, target);
1254 }
1255 T::Command__::MakeMoveVec(_, elements) => arguments(used, elements),
1256 T::Command__::Publish(_, _, _) => (),
1257 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
1258 }
1259 }
1260
1261 fn arguments(used: &mut IndexSet<(u16, u16)>, args: &[T::Argument]) {
1262 for arg in args {
1263 argument(used, arg)
1264 }
1265 }
1266
1267 fn argument(used: &mut IndexSet<(u16, u16)>, sp!(_, (arg_, _)): &T::Argument) {
1268 if let T::Location::Result(i, j) = arg_.location() {
1269 used.insert((i, j));
1270 }
1271 }
1272}
1273
1274mod consumed_shared_objects {
1279
1280 use crate::{
1281 sp, static_programmable_transactions::loading::ast as L,
1282 static_programmable_transactions::typing::ast as T,
1283 };
1284 use sui_types::{
1285 base_types::ObjectID,
1286 error::{ExecutionError, SafeIndex},
1287 };
1288
1289 struct Context {
1291 inputs: Vec<Option<ObjectID>>,
1293 results: Vec<Vec<Option<Vec<ObjectID>>>>,
1294 }
1295
1296 impl Context {
1297 pub fn new(ast: &T::Transaction) -> Self {
1298 let T::Transaction {
1299 gas_payment: _,
1300 bytes: _,
1301 objects,
1302 withdrawals: _,
1303 pure: _,
1304 receiving: _,
1305 withdrawal_compatibility_conversions: _,
1306 original_command_len: _,
1307 commands: _,
1308 } = ast;
1309 let inputs = objects
1310 .iter()
1311 .map(|o| match &o.arg {
1312 L::ObjectArg::SharedObject {
1313 id,
1314 kind: L::SharedObjectKind::Legacy,
1315 ..
1316 } => Some(*id),
1317 L::ObjectArg::ImmObject(_)
1318 | L::ObjectArg::OwnedObject(_)
1319 | L::ObjectArg::SharedObject {
1320 kind: L::SharedObjectKind::Party,
1321 ..
1322 } => None,
1323 })
1324 .collect::<Vec<_>>();
1325 Self {
1326 inputs,
1327 results: vec![],
1328 }
1329 }
1330 }
1331
1332 pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1337 let mut context = Context::new(ast);
1338
1339 for c in &mut ast.commands {
1342 debug_assert!(c.value.consumed_shared_objects.is_empty());
1343 command(&mut context, c)?;
1344 debug_assert!(context.results.last().unwrap().len() == c.value.result_type.len());
1345 }
1346 Ok(())
1347 }
1348
1349 fn command(context: &mut Context, sp!(_, c): &mut T::Command) -> Result<(), ExecutionError> {
1350 let mut acc = vec![];
1351 match &c.command {
1352 T::Command__::MoveCall(mc) => arguments(context, &mut acc, &mc.arguments)?,
1353 T::Command__::TransferObjects(objects, recipient) => {
1354 argument(context, &mut acc, recipient)?;
1355 arguments(context, &mut acc, objects)?;
1356 }
1357 T::Command__::SplitCoins(_, coin, amounts) => {
1358 arguments(context, &mut acc, amounts)?;
1359 argument(context, &mut acc, coin)?;
1360 }
1361 T::Command__::MergeCoins(_, target, coins) => {
1362 arguments(context, &mut acc, coins)?;
1363 argument(context, &mut acc, target)?;
1364 }
1365 T::Command__::MakeMoveVec(_, elements) => arguments(context, &mut acc, elements)?,
1366 T::Command__::Publish(_, _, _) => (),
1367 T::Command__::Upgrade(_, _, _, x, _) => argument(context, &mut acc, x)?,
1368 }
1369 let (consumed, result) = match &c.command {
1370 T::Command__::MakeMoveVec(_, _) => {
1373 assert_invariant!(
1374 c.result_type.len() == 1,
1375 "MakeMoveVec must return a single value"
1376 );
1377 (vec![], vec![Some(acc)])
1378 }
1379 T::Command__::MoveCall(_)
1381 | T::Command__::TransferObjects(_, _)
1382 | T::Command__::SplitCoins(_, _, _)
1383 | T::Command__::MergeCoins(_, _, _)
1384 | T::Command__::Publish(_, _, _)
1385 | T::Command__::Upgrade(_, _, _, _, _) => (acc, vec![None; c.result_type.len()]),
1386 };
1387 c.consumed_shared_objects = consumed;
1388 context.results.push(result);
1389 Ok(())
1390 }
1391
1392 fn arguments(
1393 context: &mut Context,
1394 acc: &mut Vec<ObjectID>,
1395 args: &[T::Argument],
1396 ) -> Result<(), ExecutionError> {
1397 for arg in args {
1398 argument(context, acc, arg)?
1399 }
1400 Ok(())
1401 }
1402
1403 fn argument(
1404 context: &mut Context,
1405 acc: &mut Vec<ObjectID>,
1406 sp!(_, (arg_, _)): &T::Argument,
1407 ) -> Result<(), ExecutionError> {
1408 let T::Argument__::Use(T::Usage::Move(loc)) = arg_ else {
1409 return Ok(());
1411 };
1412 match loc {
1413 T::Location::TxContext
1415 | T::Location::GasCoin
1416 | T::Location::WithdrawalInput(_)
1417 | T::Location::PureInput(_)
1418 | T::Location::ReceivingInput(_) => (),
1419 T::Location::ObjectInput(i) => {
1420 if let Some(id) = *context.inputs.safe_get(*i as usize)? {
1421 acc.push(id);
1422 }
1423 }
1424
1425 T::Location::Result(i, j) => {
1426 if let Some(ids) = context
1427 .results
1428 .safe_get(*i as usize)?
1429 .safe_get(*j as usize)?
1430 {
1431 acc.extend(ids.iter().copied());
1432 }
1433 }
1434 };
1435 Ok(())
1436 }
1437}