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