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, 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(
199 &mut self,
200 env: &Env,
201 location: T::Location,
202 ) -> Result<Option<Type>, ExecutionError> {
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(
230 &mut self,
231 env: &Env,
232 splat_location: SplatLocation,
233 ) -> Result<Option<(T::Location, Type)>, ExecutionError> {
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(
260 &mut self,
261 env: &Env,
262 splat_location: SplatLocation,
263 expected_ty: &Type,
264 bytes_constraint: BytesConstraint,
265 ) -> Result<(T::Location, Type), ExecutionError> {
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,
338 lt: L::Transaction,
339) -> Result<T::Transaction, ExecutionError> {
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,
382 context: &mut Context,
383 command: L::Command,
384) -> Result<(T::Command__, T::ResultType), ExecutionError> {
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>(
523 _env: &Env,
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(
535 env: &Env,
536 context: &mut Context,
537 function: &L::LoadedFunction,
538 args: Vec<SplatLocation>,
539) -> Result<Vec<T::Argument>, ExecutionError> {
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(ExecutionError::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<_>, ExecutionError>>()?;
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(
606 context: &mut Context,
607 command_arg_idx: usize,
608 arg: L::Argument,
609) -> Result<SplatLocation, ExecutionError> {
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 };
617 Ok(loc)
618}
619
620fn locations<Items: IntoIterator<Item = L::Argument>>(
621 context: &mut Context,
622 start_idx: usize,
623 args: Items,
624) -> Result<Vec<SplatLocation>, ExecutionError>
625where
626 Items::IntoIter: ExactSizeIterator,
627{
628 fn splat_arg(
629 context: &mut Context,
630 res: &mut Vec<SplatLocation>,
631 arg: L::Argument,
632 ) -> Result<(), EitherError> {
633 match arg {
634 L::Argument::GasCoin => res.push(SplatLocation::GasCoin),
635 L::Argument::Input(i) => {
636 if i as usize >= context.input_resolution.len() {
637 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
638 }
639 res.push(SplatLocation::Input(T::InputIndex(i)))
640 }
641 L::Argument::NestedResult(i, j) => {
642 let Some(command_result) = context.result_type(i) else {
643 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
644 };
645 if j as usize >= command_result.len() {
646 return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
647 result_idx: i,
648 secondary_idx: j,
649 }
650 .into());
651 };
652 res.push(SplatLocation::Result(i, j))
653 }
654 L::Argument::Result(i) => {
655 let Some(result) = context.result_type(i) else {
656 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
657 };
658 let Ok(len): Result<u16, _> = result.len().try_into() else {
659 invariant_violation!("Result of length greater than u16::MAX");
660 };
661 if len != 1 {
662 return Err(CommandArgumentError::InvalidResultArity { result_idx: i }.into());
664 }
665 res.extend((0..len).map(|j| SplatLocation::Result(i, j)))
666 }
667 }
668 Ok(())
669 }
670
671 let args = args.into_iter();
672 let _args_len = args.len();
673 let mut res = vec![];
674 for (arg_idx, arg) in args.enumerate() {
675 splat_arg(context, &mut res, arg).map_err(|e| {
676 let Some(idx) = start_idx.checked_add(arg_idx) else {
677 return make_invariant_violation!("usize overflow when calculating argument index");
678 };
679 e.into_execution_error(idx)
680 })?
681 }
682 debug_assert_eq!(res.len(), _args_len);
683 Ok(res)
684}
685
686fn arguments(
687 env: &Env,
688 context: &mut Context,
689 start_idx: usize,
690 locations: Vec<SplatLocation>,
691 expected_tys: impl IntoIterator<Item = Type>,
692) -> Result<Vec<T::Argument>, ExecutionError> {
693 #[allow(clippy::disallowed_methods)]
694 locations
695 .into_iter()
696 .zip(expected_tys)
699 .enumerate()
700 .map(|(i, (location, expected_ty))| {
701 let Some(idx) = start_idx.checked_add(i) else {
702 invariant_violation!("usize overflow when calculating argument index");
703 };
704 argument(env, context, idx, location, expected_ty)
705 })
706 .collect()
707}
708
709fn argument(
710 env: &Env,
711 context: &mut Context,
712 command_arg_idx: usize,
713 location: SplatLocation,
714 expected_ty: Type,
715) -> Result<T::Argument, ExecutionError> {
716 let arg__ = argument_(env, context, command_arg_idx, location, &expected_ty)
717 .map_err(|e| e.into_execution_error(command_arg_idx))?;
718 let arg_ = (arg__, expected_ty);
719 Ok(sp(checked_as!(command_arg_idx, u16)?, arg_))
720}
721
722fn argument_(
723 env: &Env,
724 context: &mut Context,
725 command_arg_idx: usize,
726 location: SplatLocation,
727 expected_ty: &Type,
728) -> Result<T::Argument__, EitherError> {
729 let current_command = context.current_command;
730 let bytes_constraint = BytesConstraint {
731 command: current_command,
732 argument: checked_as!(command_arg_idx, u16)?,
733 };
734 let (location, actual_ty) =
735 context.resolve_location(env, location, expected_ty, bytes_constraint)?;
736 Ok(match (actual_ty, expected_ty) {
737 (Type::Reference(a_is_mut, a), Type::Reference(b_is_mut, b)) => {
739 let needs_freeze = match (a_is_mut, b_is_mut) {
740 (true, true) | (false, false) => false,
742 (true, false) => true,
744 (false, true) => return Err(CommandArgumentError::TypeMismatch.into()),
746 };
747 debug_assert!(expected_ty.abilities().has_copy());
748 check_type(&a, b)?;
750 if needs_freeze {
751 T::Argument__::Freeze(T::Usage::new_copy(location))
752 } else {
753 T::Argument__::new_copy(location)
754 }
755 }
756 (Type::Reference(_, a), b) => {
757 check_type(&a, b)?;
758 if !b.abilities().has_copy() {
759 return Err(CommandArgumentError::TypeMismatch.into());
761 }
762 T::Argument__::Read(T::Usage::new_copy(location))
763 }
764
765 (actual_ty, Type::Reference(is_mut, inner)) => {
767 check_type(&actual_ty, inner)?;
768 T::Argument__::Borrow(*is_mut, location)
769 }
770 (actual_ty, _) => {
771 check_type(&actual_ty, expected_ty)?;
772 T::Argument__::Use(if expected_ty.abilities().has_copy() {
773 T::Usage::new_copy(location)
774 } else {
775 T::Usage::new_move(location)
776 })
777 }
778 })
779}
780
781fn check_type(actual_ty: &Type, expected_ty: &Type) -> Result<(), CommandArgumentError> {
782 if actual_ty == expected_ty {
783 Ok(())
784 } else {
785 Err(CommandArgumentError::TypeMismatch)
786 }
787}
788
789fn constrained_arguments(
790 env: &Env,
791 context: &mut Context,
792 start_idx: usize,
793 locations: Vec<SplatLocation>,
794 constraint: AbilitySet,
795 err_case: CommandArgumentError,
796) -> Result<Vec<T::Argument>, ExecutionError> {
797 locations
798 .into_iter()
799 .enumerate()
800 .map(|(i, location)| {
801 let Some(idx) = start_idx.checked_add(i) else {
802 invariant_violation!("usize overflow when calculating argument index");
803 };
804 constrained_argument(env, context, idx, location, constraint, err_case)
805 })
806 .collect()
807}
808
809fn constrained_argument(
810 env: &Env,
811 context: &mut Context,
812 command_arg_idx: usize,
813 location: SplatLocation,
814 constraint: AbilitySet,
815 err_case: CommandArgumentError,
816) -> Result<T::Argument, ExecutionError> {
817 let arg_ = constrained_argument_(
818 env,
819 context,
820 command_arg_idx,
821 location,
822 constraint,
823 err_case,
824 )
825 .map_err(|e| e.into_execution_error(command_arg_idx))?;
826 Ok(sp(checked_as!(command_arg_idx, u16)?, arg_))
827}
828
829fn constrained_argument_(
830 env: &Env,
831 context: &mut Context,
832 command_arg_idx: usize,
833 location: SplatLocation,
834 constraint: AbilitySet,
835 err_case: CommandArgumentError,
836) -> Result<T::Argument_, EitherError> {
837 if let Some((location, ty)) =
838 constrained_type(env, context, command_arg_idx, location, constraint)?
839 {
840 if ty.abilities().has_copy() {
841 Ok((T::Argument__::new_copy(location), ty))
842 } else {
843 Ok((T::Argument__::new_move(location), ty))
844 }
845 } else {
846 Err(err_case.into())
847 }
848}
849
850fn constrained_type<'a>(
851 env: &'a Env,
852 context: &'a mut Context,
853 _command_arg_idx: usize,
854 location: SplatLocation,
855 constraint: AbilitySet,
856) -> Result<Option<(T::Location, Type)>, ExecutionError> {
857 let Some((location, ty)) = context.fixed_type(env, location)? else {
858 return Ok(None);
859 };
860 Ok(if constraint.is_subset(ty.abilities()) {
861 Some((location, ty))
862 } else {
863 None
864 })
865}
866
867fn coin_mut_ref_argument(
868 env: &Env,
869 context: &mut Context,
870 command_arg_idx: usize,
871 location: SplatLocation,
872) -> Result<T::Argument, ExecutionError> {
873 let arg_ = coin_mut_ref_argument_(env, context, command_arg_idx, location)
874 .map_err(|e| e.into_execution_error(command_arg_idx))?;
875 Ok(sp(checked_as!(command_arg_idx, u16)?, arg_))
876}
877
878fn coin_mut_ref_argument_(
879 env: &Env,
880 context: &mut Context,
881 _command_arg_idx: usize,
882 location: SplatLocation,
883) -> Result<T::Argument_, EitherError> {
884 let Some((location, actual_ty)) = context.fixed_type(env, location)? else {
885 return Err(CommandArgumentError::TypeMismatch.into());
888 };
889 Ok(match &actual_ty {
890 Type::Reference(is_mut, ty) if *is_mut => {
891 check_coin_type(ty)?;
892 (
893 T::Argument__::new_copy(location),
894 Type::Reference(*is_mut, ty.clone()),
895 )
896 }
897 ty => {
898 check_coin_type(ty)?;
899 (
900 T::Argument__::Borrow(true, location),
901 Type::Reference(true, Rc::new(ty.clone())),
902 )
903 }
904 })
905}
906
907fn check_coin_type(ty: &Type) -> Result<(), EitherError> {
908 if coin_inner_type(ty).is_some() {
909 Ok(())
910 } else {
911 Err(CommandArgumentError::TypeMismatch.into())
912 }
913}
914
915fn determine_withdrawal_compatibility_inputs(
922 _env: &Env,
923 inputs: &mut L::Inputs,
924) -> Result<IndexMap<u16, u16>, ExecutionError> {
925 let withdrawal_compatibility_owners: IndexMap<u16, AccountAddress> = inputs
926 .iter()
927 .enumerate()
928 .filter_map(|(i, (input_arg, _))| {
929 if let L::InputArg::FundsWithdrawal(withdrawal) = input_arg
930 && withdrawal.from_compatibility_object
931 {
932 Some((i, withdrawal.owner))
933 } else {
934 None
935 }
936 })
937 .map(|(i, owner)| Ok((checked_as!(i, u16)?, owner)))
938 .collect::<Result<_, ExecutionError>>()?;
939 withdrawal_compatibility_owners
940 .into_iter()
941 .map(|(i, owner)| {
942 let owner_idx = checked_as!(inputs.len(), u16)?;
943 let bytes: Vec<u8> = bcs::to_bytes(&owner).map_err(|_| {
944 make_invariant_violation!(
945 "Failed to serialize owner address for withdrawal compatibility input",
946 )
947 })?;
948 inputs.push((L::InputArg::Pure(bytes), L::InputType::Bytes));
949 Ok((i, owner_idx))
950 })
951 .collect()
952}
953
954struct WithdrawalCompatibilityRemap {
955 remap: IndexMap<u16, u16>,
957 lift: u16,
959}
960
961fn withdrawal_compatibility_conversion(
965 env: &Env,
966 context: &mut Context,
967 withdrawal_compatability_inputs: IndexMap<
968 u16,
969 u16,
970 >,
971 commands: &mut [L::Command],
972) -> Result<(), ExecutionError> {
973 let mut compatibility_remap = WithdrawalCompatibilityRemap {
974 remap: IndexMap::new(),
975 lift: 0,
976 };
977 for (input, owner_idx) in withdrawal_compatability_inputs {
978 let result_idx = convert_withdrawal_to_coin(env, context, input, owner_idx)?;
979 compatibility_remap.remap.insert(input, result_idx);
980 }
981 compatibility_remap.lift = checked_as!(context.commands.len(), u16)?;
982 lift_result_indices(&compatibility_remap, commands)
983}
984
985fn convert_withdrawal_to_coin(
986 env: &Env,
987 context: &mut Context,
988 withdrawal_input: u16,
989 owner_input: u16,
990) -> Result<u16, ExecutionError> {
991 assert_invariant!(
992 env.protocol_config
993 .convert_withdrawal_compatibility_ptb_arguments(),
994 "convert_withdrawal_to_coin called when conversion is disabled"
995 );
996 let (owner_location, _owner_ty) = context.resolve_location(
998 env,
999 SplatLocation::Input(T::InputIndex(owner_input)),
1000 &Type::Address,
1001 BytesConstraint {
1002 command: 0,
1003 argument: 0,
1004 },
1005 )?;
1006 let Some((location, withdrawal_ty)) =
1007 context.fixed_type(env, SplatLocation::Input(T::InputIndex(withdrawal_input)))?
1008 else {
1009 invariant_violation!(
1010 "Expected fixed type for withdrawal compatibility input {}",
1011 withdrawal_input
1012 )
1013 };
1014 let Some(inner_ty) = withdrawal_inner_type(&withdrawal_ty)
1015 .and_then(balance_inner_type)
1016 .cloned()
1017 else {
1018 invariant_violation!("convert_withdrawal_to_coin called with non-withdrawal type");
1019 };
1020 let idx = 0u16;
1021 let withdrawal_arg_ = T::Argument__::new_move(location);
1023 let withdrawal_arg = sp(idx, (withdrawal_arg_, withdrawal_ty));
1024 let ctx_arg_ = T::Argument__::Borrow(true, T::Location::TxContext);
1025 let ctx_ty = Type::Reference(true, Rc::new(env.tx_context_type()?));
1026 let ctx_arg = sp(idx, (ctx_arg_, ctx_ty));
1027 let conversion_command__ = T::Command__::MoveCall(Box::new(T::MoveCall {
1028 function: env.load_framework_function(
1029 COIN_MODULE_NAME,
1030 REDEEM_FUNDS_FUNC_NAME,
1031 vec![inner_ty.clone()],
1032 )?,
1033 arguments: vec![withdrawal_arg, ctx_arg],
1034 }));
1035 let conversion_command_ = T::Command_ {
1036 command: conversion_command__,
1037 result_type: vec![env.coin_type(inner_ty.clone())?],
1038 drop_values: vec![],
1039 consumed_shared_objects: vec![],
1040 };
1041 let conversion_idx = checked_as!(context.commands.len(), u16)?;
1042 context.push_result(conversion_command_)?;
1043 context.withdrawal_compatibility_conversions.insert(
1045 location,
1046 T::WithdrawalCompatibilityConversion {
1047 owner: owner_location,
1048 conversion_result: conversion_idx,
1049 },
1050 );
1051 Ok(conversion_idx)
1053}
1054
1055fn lift_result_indices(
1058 remap: &WithdrawalCompatibilityRemap,
1059 commands: &mut [L::Command],
1060) -> Result<(), ExecutionError> {
1061 for command in commands {
1062 for arg in command.arguments_mut() {
1063 match arg {
1064 L::Argument::NestedResult(result, _) | L::Argument::Result(result) => {
1065 *result = remap.lift.checked_add(*result).ok_or_else(|| {
1066 make_invariant_violation!(
1067 "u16 overflow when lifting result index during withdrawal compatibility",
1068 )
1069 })?;
1070 }
1071 L::Argument::Input(i) => {
1072 if let Some(converted_withdrawal) = remap.remap.get(i).copied() {
1073 *arg = L::Argument::NestedResult(converted_withdrawal, 0);
1074 }
1075 }
1076 L::Argument::GasCoin => (),
1077 }
1078 }
1079 }
1080 Ok(())
1081}
1082
1083pub(crate) fn coin_inner_type(ty: &Type) -> Option<&Type> {
1085 if let Type::Datatype(dt) = ty
1086 && dt.type_arguments.len() == 1
1087 && dt.qualified_ident() == RESOLVED_COIN_STRUCT
1088 {
1089 Some(dt.type_arguments.first().unwrap())
1090 } else {
1091 None
1092 }
1093}
1094
1095pub(crate) fn balance_inner_type(ty: &Type) -> Option<&Type> {
1097 if let Type::Datatype(dt) = ty
1098 && dt.type_arguments.len() == 1
1099 && dt.qualified_ident() == RESOLVED_BALANCE_STRUCT
1100 {
1101 Some(dt.type_arguments.first().unwrap())
1102 } else {
1103 None
1104 }
1105}
1106
1107pub(crate) fn withdrawal_inner_type(ty: &Type) -> Option<&Type> {
1109 if let Type::Datatype(dt) = ty
1110 && dt.type_arguments.len() == 1
1111 && dt.qualified_ident() == RESOLVED_WITHDRAWAL_STRUCT
1112 {
1113 Some(dt.type_arguments.first().unwrap())
1114 } else {
1115 None
1116 }
1117}
1118
1119mod scope_references {
1124 use crate::{
1125 sp,
1126 static_programmable_transactions::typing::ast::{self as T, Type},
1127 };
1128 use std::collections::BTreeSet;
1129
1130 pub fn transaction(ast: &mut T::Transaction) {
1133 let mut used: BTreeSet<(u16, u16)> = BTreeSet::new();
1134 for c in ast.commands.iter_mut().rev() {
1135 command(&mut used, c);
1136 }
1137 }
1138
1139 fn command(used: &mut BTreeSet<(u16, u16)>, sp!(_, c): &mut T::Command) {
1140 match &mut c.command {
1141 T::Command__::MoveCall(mc) => arguments(used, &mut mc.arguments),
1142 T::Command__::TransferObjects(objects, recipient) => {
1143 argument(used, recipient);
1144 arguments(used, objects);
1145 }
1146 T::Command__::SplitCoins(_, coin, amounts) => {
1147 arguments(used, amounts);
1148 argument(used, coin);
1149 }
1150 T::Command__::MergeCoins(_, target, coins) => {
1151 arguments(used, coins);
1152 argument(used, target);
1153 }
1154 T::Command__::MakeMoveVec(_, xs) => arguments(used, xs),
1155 T::Command__::Publish(_, _, _) => (),
1156 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
1157 }
1158 }
1159
1160 fn arguments(used: &mut BTreeSet<(u16, u16)>, args: &mut [T::Argument]) {
1161 for arg in args.iter_mut().rev() {
1162 argument(used, arg)
1163 }
1164 }
1165
1166 fn argument(used: &mut BTreeSet<(u16, u16)>, sp!(_, (arg_, ty)): &mut T::Argument) {
1167 let usage = match arg_ {
1168 T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
1169 T::Argument__::Borrow(_, _) => return,
1170 };
1171 match (&usage, ty) {
1172 (T::Usage::Move(T::Location::Result(i, j)), Type::Reference(_, _)) => {
1173 debug_assert!(false, "No reference should be moved at this point");
1174 used.insert((*i, *j));
1175 }
1176 (
1177 T::Usage::Copy {
1178 location: T::Location::Result(i, j),
1179 ..
1180 },
1181 Type::Reference(_, _),
1182 ) => {
1183 let last_usage = used.insert((*i, *j));
1185 if last_usage {
1186 let loc = T::Location::Result(*i, *j);
1188 *usage = T::Usage::Move(loc);
1189 }
1190 }
1191 _ => (),
1192 }
1193 }
1194}
1195
1196mod unused_results {
1201 use indexmap::IndexSet;
1202 use sui_types::error::ExecutionError;
1203
1204 use crate::{sp, static_programmable_transactions::typing::ast as T};
1205
1206 pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1210 let mut used: IndexSet<(u16, u16)> = IndexSet::new();
1212 for c in &ast.commands {
1213 command(&mut used, c);
1214 }
1215
1216 for (i, sp!(_, c)) in ast.commands.iter_mut().enumerate() {
1218 debug_assert!(c.drop_values.is_empty());
1219 let i = checked_as!(i, u16)?;
1220 c.drop_values = c
1221 .result_type
1222 .iter()
1223 .enumerate()
1224 .map(|(j, ty)| {
1225 Ok(ty.abilities().has_drop() && !used.contains(&(i, checked_as!(j, u16)?)))
1226 })
1227 .collect::<Result<_, ExecutionError>>()?;
1228 }
1229 Ok(())
1230 }
1231
1232 fn command(used: &mut IndexSet<(u16, u16)>, sp!(_, c): &T::Command) {
1233 match &c.command {
1234 T::Command__::MoveCall(mc) => arguments(used, &mc.arguments),
1235 T::Command__::TransferObjects(objects, recipient) => {
1236 argument(used, recipient);
1237 arguments(used, objects);
1238 }
1239 T::Command__::SplitCoins(_, coin, amounts) => {
1240 arguments(used, amounts);
1241 argument(used, coin);
1242 }
1243 T::Command__::MergeCoins(_, target, coins) => {
1244 arguments(used, coins);
1245 argument(used, target);
1246 }
1247 T::Command__::MakeMoveVec(_, elements) => arguments(used, elements),
1248 T::Command__::Publish(_, _, _) => (),
1249 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
1250 }
1251 }
1252
1253 fn arguments(used: &mut IndexSet<(u16, u16)>, args: &[T::Argument]) {
1254 for arg in args {
1255 argument(used, arg)
1256 }
1257 }
1258
1259 fn argument(used: &mut IndexSet<(u16, u16)>, sp!(_, (arg_, _)): &T::Argument) {
1260 if let T::Location::Result(i, j) = arg_.location() {
1261 used.insert((i, j));
1262 }
1263 }
1264}
1265
1266mod consumed_shared_objects {
1271
1272 use crate::{
1273 sp, static_programmable_transactions::loading::ast as L,
1274 static_programmable_transactions::typing::ast as T,
1275 };
1276 use sui_types::{
1277 base_types::ObjectID,
1278 error::{ExecutionError, SafeIndex},
1279 };
1280
1281 struct Context {
1283 inputs: Vec<Option<ObjectID>>,
1285 results: Vec<Vec<Option<Vec<ObjectID>>>>,
1286 }
1287
1288 impl Context {
1289 pub fn new(ast: &T::Transaction) -> Self {
1290 let T::Transaction {
1291 gas_payment: _,
1292 bytes: _,
1293 objects,
1294 withdrawals: _,
1295 pure: _,
1296 receiving: _,
1297 withdrawal_compatibility_conversions: _,
1298 original_command_len: _,
1299 commands: _,
1300 } = ast;
1301 let inputs = objects
1302 .iter()
1303 .map(|o| match &o.arg {
1304 L::ObjectArg::SharedObject {
1305 id,
1306 kind: L::SharedObjectKind::Legacy,
1307 ..
1308 } => Some(*id),
1309 L::ObjectArg::ImmObject(_)
1310 | L::ObjectArg::OwnedObject(_)
1311 | L::ObjectArg::SharedObject {
1312 kind: L::SharedObjectKind::Party,
1313 ..
1314 } => None,
1315 })
1316 .collect::<Vec<_>>();
1317 Self {
1318 inputs,
1319 results: vec![],
1320 }
1321 }
1322 }
1323
1324 pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1329 let mut context = Context::new(ast);
1330
1331 for c in &mut ast.commands {
1334 debug_assert!(c.value.consumed_shared_objects.is_empty());
1335 command(&mut context, c)?;
1336 debug_assert!(context.results.last().unwrap().len() == c.value.result_type.len());
1337 }
1338 Ok(())
1339 }
1340
1341 fn command(context: &mut Context, sp!(_, c): &mut T::Command) -> Result<(), ExecutionError> {
1342 let mut acc = vec![];
1343 match &c.command {
1344 T::Command__::MoveCall(mc) => arguments(context, &mut acc, &mc.arguments)?,
1345 T::Command__::TransferObjects(objects, recipient) => {
1346 argument(context, &mut acc, recipient)?;
1347 arguments(context, &mut acc, objects)?;
1348 }
1349 T::Command__::SplitCoins(_, coin, amounts) => {
1350 arguments(context, &mut acc, amounts)?;
1351 argument(context, &mut acc, coin)?;
1352 }
1353 T::Command__::MergeCoins(_, target, coins) => {
1354 arguments(context, &mut acc, coins)?;
1355 argument(context, &mut acc, target)?;
1356 }
1357 T::Command__::MakeMoveVec(_, elements) => arguments(context, &mut acc, elements)?,
1358 T::Command__::Publish(_, _, _) => (),
1359 T::Command__::Upgrade(_, _, _, x, _) => argument(context, &mut acc, x)?,
1360 }
1361 let (consumed, result) = match &c.command {
1362 T::Command__::MakeMoveVec(_, _) => {
1365 assert_invariant!(
1366 c.result_type.len() == 1,
1367 "MakeMoveVec must return a single value"
1368 );
1369 (vec![], vec![Some(acc)])
1370 }
1371 T::Command__::MoveCall(_)
1373 | T::Command__::TransferObjects(_, _)
1374 | T::Command__::SplitCoins(_, _, _)
1375 | T::Command__::MergeCoins(_, _, _)
1376 | T::Command__::Publish(_, _, _)
1377 | T::Command__::Upgrade(_, _, _, _, _) => (acc, vec![None; c.result_type.len()]),
1378 };
1379 c.consumed_shared_objects = consumed;
1380 context.results.push(result);
1381 Ok(())
1382 }
1383
1384 fn arguments(
1385 context: &mut Context,
1386 acc: &mut Vec<ObjectID>,
1387 args: &[T::Argument],
1388 ) -> Result<(), ExecutionError> {
1389 for arg in args {
1390 argument(context, acc, arg)?
1391 }
1392 Ok(())
1393 }
1394
1395 fn argument(
1396 context: &mut Context,
1397 acc: &mut Vec<ObjectID>,
1398 sp!(_, (arg_, _)): &T::Argument,
1399 ) -> Result<(), ExecutionError> {
1400 let T::Argument__::Use(T::Usage::Move(loc)) = arg_ else {
1401 return Ok(());
1403 };
1404 match loc {
1405 T::Location::TxContext
1407 | T::Location::GasCoin
1408 | T::Location::WithdrawalInput(_)
1409 | T::Location::PureInput(_)
1410 | T::Location::ReceivingInput(_) => (),
1411 T::Location::ObjectInput(i) => {
1412 if let Some(id) = *context.inputs.safe_get(*i as usize)? {
1413 acc.push(id);
1414 }
1415 }
1416
1417 T::Location::Result(i, j) => {
1418 if let Some(ids) = context
1419 .results
1420 .safe_get(*i as usize)?
1421 .safe_get(*j as usize)?
1422 {
1423 acc.extend(ids.iter().copied());
1424 }
1425 }
1426 };
1427 Ok(())
1428 }
1429}