1use super::{ast as T, env::Env};
5use crate::{
6 execution_mode::ExecutionMode,
7 programmable_transactions::context::EitherError,
8 static_programmable_transactions::{
9 loading::ast::{self as L, Type},
10 spanned::sp,
11 typing::ast::BytesConstraint,
12 },
13};
14use indexmap::{IndexMap, IndexSet};
15use move_binary_format::file_format::{Ability, AbilitySet};
16use move_core_types::account_address::AccountAddress;
17use std::rc::Rc;
18use sui_types::{
19 balance::RESOLVED_BALANCE_STRUCT,
20 base_types::{ObjectID, ObjectRef, TxContextKind},
21 coin::{COIN_MODULE_NAME, REDEEM_FUNDS_FUNC_NAME, RESOLVED_COIN_STRUCT},
22 error::{ExecutionError, ExecutionErrorKind, SafeIndex, command_argument_error},
23 execution_status::CommandArgumentError,
24 funds_accumulator::RESOLVED_WITHDRAWAL_STRUCT,
25};
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
28enum SplatLocation {
29 GasCoin,
30 Input(T::InputIndex),
31 Result(u16, u16),
32}
33
34#[derive(Debug, Clone, Copy)]
35enum InputKind {
36 Object,
37 Withdrawal,
38 Pure,
39 Receiving,
40}
41
42struct Context {
43 current_command: u16,
44 gas_coin: Option<ObjectID>,
45 input_resolution: Vec<InputKind>,
47 bytes: IndexSet<Vec<u8>>,
48 bytes_idx_remapping: IndexMap<T::InputIndex, T::ByteIndex>,
50 receiving_refs: IndexMap<T::InputIndex, ObjectRef>,
51 objects: IndexMap<T::InputIndex, T::ObjectInput>,
52 withdrawals: IndexMap<T::InputIndex, T::WithdrawalInput>,
53 pure: IndexMap<(T::InputIndex, Type), T::PureInput>,
54 receiving: IndexMap<(T::InputIndex, Type), T::ReceivingInput>,
55 withdrawal_compatibility_conversions:
56 IndexMap<T::Location, T::WithdrawalCompatibilityConversion>,
57 commands: Vec<T::Command>,
58}
59
60impl Context {
61 fn new(gas_coin: Option<ObjectID>, linputs: L::Inputs) -> Result<Self, ExecutionError> {
62 let mut context = Context {
63 current_command: 0,
64 gas_coin,
65 input_resolution: vec![],
66 bytes: IndexSet::new(),
67 bytes_idx_remapping: IndexMap::new(),
68 receiving_refs: IndexMap::new(),
69 objects: IndexMap::new(),
70 withdrawals: IndexMap::new(),
71 pure: IndexMap::new(),
72 withdrawal_compatibility_conversions: IndexMap::new(),
73 receiving: IndexMap::new(),
74 commands: vec![],
75 };
76 #[cfg(debug_assertions)]
78 let cloned_inputs = linputs
79 .iter()
80 .map(|(arg, _)| arg.clone())
81 .collect::<Vec<_>>();
82 for (i, (arg, ty)) in linputs.into_iter().enumerate() {
85 let idx = T::InputIndex(checked_as!(i, u16)?);
86 let kind = match (arg, ty) {
87 (L::InputArg::Pure(bytes), L::InputType::Bytes) => {
88 let (byte_index, _) = context.bytes.insert_full(bytes);
89 context.bytes_idx_remapping.insert(idx, byte_index);
90 InputKind::Pure
91 }
92 (L::InputArg::Receiving(oref), L::InputType::Bytes) => {
93 context.receiving_refs.insert(idx, oref);
94 InputKind::Receiving
95 }
96 (L::InputArg::Object(arg), L::InputType::Fixed(ty)) => {
97 let o = T::ObjectInput {
98 original_input_index: idx,
99 arg,
100 ty,
101 };
102 context.objects.insert(idx, o);
103 InputKind::Object
104 }
105 (L::InputArg::FundsWithdrawal(withdrawal), L::InputType::Fixed(input_ty)) => {
106 let L::FundsWithdrawalArg {
107 from_compatibility_object: _,
108 ty,
109 owner,
110 amount,
111 } = withdrawal;
112 debug_assert!(ty == input_ty);
113 let withdrawal = T::WithdrawalInput {
114 original_input_index: idx,
115 ty,
116 owner,
117 amount,
118 };
119 context.withdrawals.insert(idx, withdrawal);
120 InputKind::Withdrawal
121 }
122 (arg, ty) => invariant_violation!(
123 "Input arg, type mismatch. Unexpected {arg:?} with type {ty:?}"
124 ),
125 };
126 context.input_resolution.push(kind);
127 }
128 #[cfg(debug_assertions)]
129 {
130 for (i, arg) in cloned_inputs.iter().enumerate() {
132 if let L::InputArg::Pure(bytes) = &arg {
133 let idx = T::InputIndex(checked_as!(i, u16)?);
134 let Some(byte_index) = context.bytes_idx_remapping.get(&idx) else {
135 invariant_violation!("Unbound pure input {}", idx.0);
136 };
137 let Some(interned_bytes) = context.bytes.get_index(*byte_index) else {
138 invariant_violation!("Interned bytes not found for index {}", byte_index);
139 };
140 if interned_bytes != bytes {
141 assert_invariant!(
142 interned_bytes == bytes,
143 "Interned bytes mismatch for input {i}",
144 );
145 }
146 }
147 }
148 }
149 Ok(context)
150 }
151
152 fn finish(self) -> T::Transaction {
153 let Self {
154 gas_coin,
155 bytes,
156 objects,
157 withdrawals,
158 pure,
159 receiving,
160 commands,
161 withdrawal_compatibility_conversions,
162 ..
163 } = self;
164 let objects = objects.into_iter().map(|(_, o)| o).collect();
165 let withdrawals = withdrawals.into_iter().map(|(_, w)| w).collect();
166 let pure = pure.into_iter().map(|(_, p)| p).collect();
167 let receiving = receiving.into_iter().map(|(_, r)| r).collect();
168 T::Transaction {
169 gas_coin,
170 bytes,
171 objects,
172 withdrawals,
173 pure,
174 receiving,
175 withdrawal_compatibility_conversions,
176 commands,
177 }
178 }
179
180 fn push_result(&mut self, command: T::Command_) -> Result<(), ExecutionError> {
181 self.commands.push(sp(self.current_command, command));
182 Ok(())
183 }
184
185 fn result_type(&self, i: u16) -> Option<&T::ResultType> {
186 self.commands.get(i as usize).map(|c| &c.value.result_type)
187 }
188
189 fn fixed_location_type(
190 &mut self,
191 env: &Env,
192 location: T::Location,
193 ) -> Result<Option<Type>, ExecutionError> {
194 Ok(Some(match location {
195 T::Location::TxContext => env.tx_context_type()?,
196 T::Location::GasCoin => env.gas_coin_type()?,
197 T::Location::Result(i, j) => {
198 let Some(tys) = self.result_type(i) else {
199 invariant_violation!("Result index {i} is out of bounds")
200 };
201 tys.safe_get(j as usize)?.clone()
202 }
203 T::Location::ObjectInput(i) => {
204 let Some((_, object_input)) = self.objects.get_index(i as usize) else {
205 invariant_violation!("Unbound object input {}", i)
206 };
207 object_input.ty.clone()
208 }
209 T::Location::WithdrawalInput(i) => {
210 let Some((_, withdrawal_input)) = self.withdrawals.get_index(i as usize) else {
211 invariant_violation!("Unbound withdrawal input {}", i)
212 };
213 withdrawal_input.ty.clone()
214 }
215 T::Location::PureInput(_) | T::Location::ReceivingInput(_) => return Ok(None),
216 }))
217 }
218
219 fn fixed_type(
221 &mut self,
222 env: &Env,
223 splat_location: SplatLocation,
224 ) -> Result<Option<(T::Location, Type)>, ExecutionError> {
225 let location = match splat_location {
226 SplatLocation::GasCoin => T::Location::GasCoin,
227 SplatLocation::Result(i, j) => T::Location::Result(i, j),
228 SplatLocation::Input(i) => match self.input_resolution.safe_get(i.0 as usize)? {
229 InputKind::Object => {
230 let Some(index) = self.objects.get_index_of(&i) else {
231 invariant_violation!("Unbound object input {}", i.0)
232 };
233 T::Location::ObjectInput(checked_as!(index, u16)?)
234 }
235 InputKind::Withdrawal => {
236 let Some(withdrawal_index) = self.withdrawals.get_index_of(&i) else {
237 invariant_violation!("Unbound withdrawal input {}", i.0)
238 };
239 T::Location::WithdrawalInput(checked_as!(withdrawal_index, u16)?)
240 }
241 InputKind::Pure | InputKind::Receiving => return Ok(None),
242 },
243 };
244 let Some(ty) = self.fixed_location_type(env, location)? else {
245 invariant_violation!("Location {location:?} does not have a fixed type")
246 };
247 Ok(Some((location, ty)))
248 }
249
250 fn resolve_location(
251 &mut self,
252 env: &Env,
253 splat_location: SplatLocation,
254 expected_ty: &Type,
255 bytes_constraint: BytesConstraint,
256 ) -> Result<(T::Location, Type), ExecutionError> {
257 let location = match splat_location {
258 SplatLocation::GasCoin => T::Location::GasCoin,
259 SplatLocation::Result(i, j) => T::Location::Result(i, j),
260 SplatLocation::Input(i) => match self.input_resolution.safe_get(i.0 as usize)? {
261 InputKind::Object => {
262 let Some(index) = self.objects.get_index_of(&i) else {
263 invariant_violation!("Unbound object input {}", i.0)
264 };
265 T::Location::ObjectInput(checked_as!(index, u16)?)
266 }
267 InputKind::Withdrawal => {
268 let Some(index) = self.withdrawals.get_index_of(&i) else {
269 invariant_violation!("Unbound withdrawal input {}", i.0)
270 };
271 T::Location::WithdrawalInput(checked_as!(index, u16)?)
272 }
273 InputKind::Pure => {
274 let ty = match expected_ty {
275 Type::Reference(_, inner) => (**inner).clone(),
276 ty => ty.clone(),
277 };
278 let k = (i, ty.clone());
279 if !self.pure.contains_key(&k) {
280 let Some(byte_index) = self.bytes_idx_remapping.get(&i).copied() else {
281 invariant_violation!("Unbound pure input {}", i.0);
282 };
283 let pure = T::PureInput {
284 original_input_index: i,
285 byte_index,
286 ty: ty.clone(),
287 constraint: bytes_constraint,
288 };
289 self.pure.insert(k.clone(), pure);
290 }
291 let byte_index = self.pure.get_index_of(&k).unwrap();
292 return Ok((T::Location::PureInput(checked_as!(byte_index, u16)?), ty));
293 }
294 InputKind::Receiving => {
295 let ty = match expected_ty {
296 Type::Reference(_, inner) => (**inner).clone(),
297 ty => ty.clone(),
298 };
299 let k = (i, ty.clone());
300 if !self.receiving.contains_key(&k) {
301 let Some(object_ref) = self.receiving_refs.get(&i).copied() else {
302 invariant_violation!("Unbound receiving input {}", i.0);
303 };
304 let receiving = T::ReceivingInput {
305 original_input_index: i,
306 object_ref,
307 ty: ty.clone(),
308 constraint: bytes_constraint,
309 };
310 self.receiving.insert(k.clone(), receiving);
311 }
312 let byte_index = self.receiving.get_index_of(&k).unwrap();
313 return Ok((
314 T::Location::ReceivingInput(checked_as!(byte_index, u16)?),
315 ty,
316 ));
317 }
318 },
319 };
320 let Some(ty) = self.fixed_location_type(env, location)? else {
321 invariant_violation!("Location {location:?} does not have a fixed type")
322 };
323 Ok((location, ty))
324 }
325}
326
327pub fn transaction<Mode: ExecutionMode>(
328 env: &Env,
329 lt: L::Transaction,
330) -> Result<T::Transaction, ExecutionError> {
331 let L::Transaction {
332 gas_coin,
333 mut inputs,
334 mut commands,
335 } = lt;
336 let withdrawal_compatability_inputs =
337 determine_withdrawal_compatibility_inputs(env, &mut inputs)?;
338 let mut context = Context::new(gas_coin, inputs)?;
339 withdrawal_compatibility_conversion(
340 env,
341 &mut context,
342 withdrawal_compatability_inputs,
343 &mut commands,
344 )?;
345 for (i, c) in commands.into_iter().enumerate() {
346 let idx = checked_as!(i, u16)?;
347 context.current_command = idx;
348 let (c_, tys) =
349 command::<Mode>(env, &mut context, c).map_err(|e| e.with_command_index(i))?;
350 let c = T::Command_ {
351 command: c_,
352 result_type: tys,
353 drop_values: vec![],
355 consumed_shared_objects: vec![],
357 };
358 context.push_result(c)?
359 }
360 let mut ast = context.finish();
361 scope_references::transaction(&mut ast);
363 unused_results::transaction(&mut ast)?;
365 consumed_shared_objects::transaction(&mut ast)?;
367 Ok(ast)
368}
369
370fn command<Mode: ExecutionMode>(
371 env: &Env,
372 context: &mut Context,
373 command: L::Command,
374) -> Result<(T::Command__, T::ResultType), ExecutionError> {
375 Ok(match command {
376 L::Command::MoveCall(lmc) => {
377 let L::MoveCall {
378 function,
379 arguments: largs,
380 } = *lmc;
381 let arg_locs = locations(context, 0, largs)?;
382 let args = move_call_arguments(env, context, &function, arg_locs)?;
383 let result = function.signature.return_.clone();
384 (
385 T::Command__::MoveCall(Box::new(T::MoveCall {
386 function,
387 arguments: args,
388 })),
389 result,
390 )
391 }
392 L::Command::TransferObjects(lobjects, laddress) => {
393 const TRANSFER_OBJECTS_CONSTRAINT: AbilitySet =
394 AbilitySet::singleton(Ability::Store).union(AbilitySet::singleton(Ability::Key));
395 let object_locs = locations(context, 0, lobjects)?;
396 let address_loc = one_location(context, object_locs.len(), laddress)?;
397 let objects = constrained_arguments(
398 env,
399 context,
400 0,
401 object_locs,
402 TRANSFER_OBJECTS_CONSTRAINT,
403 CommandArgumentError::InvalidTransferObject,
404 )?;
405 let address = argument(env, context, objects.len(), address_loc, Type::Address)?;
406 (T::Command__::TransferObjects(objects, address), vec![])
407 }
408 L::Command::SplitCoins(lcoin, lamounts) => {
409 let coin_loc = one_location(context, 0, lcoin)?;
410 let amount_locs = locations(context, 1, lamounts)?;
411 let coin = coin_mut_ref_argument(env, context, 0, coin_loc)?;
412 let coin_type = match &coin.value.1 {
413 Type::Reference(true, ty) => (**ty).clone(),
414 ty => invariant_violation!("coin must be a mutable reference. Found: {ty:?}"),
415 };
416 let amounts = arguments(
417 env,
418 context,
419 1,
420 amount_locs,
421 std::iter::repeat_with(|| Type::U64),
422 )?;
423 let result = vec![coin_type.clone(); amounts.len()];
424 (T::Command__::SplitCoins(coin_type, coin, amounts), result)
425 }
426 L::Command::MergeCoins(ltarget, lcoins) => {
427 let target_loc = one_location(context, 0, ltarget)?;
428 let coin_locs = locations(context, 1, lcoins)?;
429 let target = coin_mut_ref_argument(env, context, 0, target_loc)?;
430 let coin_type = match &target.value.1 {
431 Type::Reference(true, ty) => (**ty).clone(),
432 ty => invariant_violation!("target must be a mutable reference. Found: {ty:?}"),
433 };
434 let coins = arguments(
435 env,
436 context,
437 1,
438 coin_locs,
439 std::iter::repeat_with(|| coin_type.clone()),
440 )?;
441 (T::Command__::MergeCoins(coin_type, target, coins), vec![])
442 }
443 L::Command::MakeMoveVec(Some(ty), lelems) => {
444 let elem_locs = locations(context, 0, lelems)?;
445 let elems = arguments(
446 env,
447 context,
448 0,
449 elem_locs,
450 std::iter::repeat_with(|| ty.clone()),
451 )?;
452 (
453 T::Command__::MakeMoveVec(ty.clone(), elems),
454 vec![env.vector_type(ty)?],
455 )
456 }
457 L::Command::MakeMoveVec(None, lelems) => {
458 const MAKE_MOVE_VEC_OBJECT_CONSTRAINT: AbilitySet = AbilitySet::singleton(Ability::Key);
459 let mut lelems = lelems.into_iter();
460 let Some(lfirst) = lelems.next() else {
461 invariant_violation!(
463 "input checker ensures if args are empty, there is a type specified"
464 );
465 };
466 let first_loc = one_location(context, 0, lfirst)?;
467 let first_arg = constrained_argument(
468 env,
469 context,
470 0,
471 first_loc,
472 MAKE_MOVE_VEC_OBJECT_CONSTRAINT,
473 CommandArgumentError::InvalidMakeMoveVecNonObjectArgument,
474 )?;
475 let first_ty = first_arg.value.1.clone();
476 let elems_loc = locations(context, 1, lelems)?;
477 let mut elems = arguments(
478 env,
479 context,
480 1,
481 elems_loc,
482 std::iter::repeat_with(|| first_ty.clone()),
483 )?;
484 elems.insert(0, first_arg);
485 (
486 T::Command__::MakeMoveVec(first_ty.clone(), elems),
487 vec![env.vector_type(first_ty)?],
488 )
489 }
490 L::Command::Publish(items, object_ids, linkage) => {
491 let result = if Mode::packages_are_predefined() {
492 vec![]
494 } else {
495 vec![env.upgrade_cap_type()?.clone()]
496 };
497 (T::Command__::Publish(items, object_ids, linkage), result)
498 }
499 L::Command::Upgrade(items, object_ids, object_id, la, linkage) => {
500 let location = one_location(context, 0, la)?;
501 let expected_ty = env.upgrade_ticket_type()?;
502 let a = argument(env, context, 0, location, expected_ty)?;
503 let res = env.upgrade_receipt_type()?;
504 (
505 T::Command__::Upgrade(items, object_ids, object_id, a, linkage),
506 vec![res.clone()],
507 )
508 }
509 })
510}
511
512fn move_call_parameters<'a>(
513 env: &Env,
514 function: &'a L::LoadedFunction,
515) -> Vec<(&'a Type, TxContextKind)> {
516 if env.protocol_config.flexible_tx_context_positions() {
517 function
518 .signature
519 .parameters
520 .iter()
521 .map(|ty| (ty, ty.is_tx_context()))
522 .collect()
523 } else {
524 let mut kinds = function
525 .signature
526 .parameters
527 .iter()
528 .map(|ty| (ty, TxContextKind::None))
529 .collect::<Vec<_>>();
530 if let Some((ty, kind)) = kinds.last_mut() {
531 *kind = ty.is_tx_context();
532 }
533 kinds
534 }
535}
536
537fn move_call_arguments(
538 env: &Env,
539 context: &mut Context,
540 function: &L::LoadedFunction,
541 args: Vec<SplatLocation>,
542) -> Result<Vec<T::Argument>, ExecutionError> {
543 let params = move_call_parameters(env, function);
544 assert_invariant!(
545 params.len() == function.signature.parameters.len(),
546 "Generated parameter types does not match the function signature"
547 );
548 let num_tx_contexts = params
550 .iter()
551 .filter(|(_, k)| matches!(k, TxContextKind::Mutable | TxContextKind::Immutable))
552 .count();
553 let num_user_args = args.len();
554 let Some(num_args) = num_user_args.checked_add(num_tx_contexts) else {
555 invariant_violation!("usize overflow when calculating number of arguments");
556 };
557 let num_parameters = params.len();
558 if num_args != num_parameters {
559 return Err(ExecutionError::new_with_source(
560 ExecutionErrorKind::ArityMismatch,
561 format!(
562 "Expected {} argument{} calling function '{}::{}', but found {}",
563 num_parameters,
564 if num_parameters == 1 { "" } else { "s" },
565 function.storage_id,
566 function.name,
567 num_args,
568 ),
569 ));
570 }
571 let mut args = args.into_iter().enumerate();
573 let res = params
574 .into_iter()
575 .enumerate()
576 .map(|(param_idx, (expected_ty, tx_context_kind))| {
577 Ok(match tx_context_kind {
578 TxContextKind::None => {
579 let Some((arg_idx, location)) = args.next() else {
580 invariant_violation!("arguments are empty but arity was already checked");
581 };
582 argument(env, context, arg_idx, location, expected_ty.clone())?
583 }
584 TxContextKind::Mutable | TxContextKind::Immutable => {
585 let is_mut = match tx_context_kind {
586 TxContextKind::Mutable => true,
587 TxContextKind::Immutable => false,
588 TxContextKind::None => unreachable!(),
589 };
590 let idx = checked_as!(param_idx, u16)?;
593 let arg__ = T::Argument__::Borrow(is_mut, T::Location::TxContext);
594 let ty = Type::Reference(is_mut, Rc::new(env.tx_context_type()?));
595 sp(idx, (arg__, ty))
596 }
597 })
598 })
599 .collect::<Result<Vec<_>, ExecutionError>>()?;
600
601 assert_invariant!(
602 args.next().is_none(),
603 "some arguments went unused but arity was already checked"
604 );
605 Ok(res)
606}
607
608fn one_location(
609 context: &mut Context,
610 command_arg_idx: usize,
611 arg: L::Argument,
612) -> Result<SplatLocation, ExecutionError> {
613 let locs = locations(context, command_arg_idx, vec![arg])?;
614 let Ok([loc]): Result<[SplatLocation; 1], _> = locs.try_into() else {
615 return Err(command_argument_error(
616 CommandArgumentError::InvalidArgumentArity,
617 command_arg_idx,
618 ));
619 };
620 Ok(loc)
621}
622
623fn locations<Items: IntoIterator<Item = L::Argument>>(
624 context: &mut Context,
625 start_idx: usize,
626 args: Items,
627) -> Result<Vec<SplatLocation>, ExecutionError>
628where
629 Items::IntoIter: ExactSizeIterator,
630{
631 fn splat_arg(
632 context: &mut Context,
633 res: &mut Vec<SplatLocation>,
634 arg: L::Argument,
635 ) -> Result<(), EitherError> {
636 match arg {
637 L::Argument::GasCoin => res.push(SplatLocation::GasCoin),
638 L::Argument::Input(i) => {
639 if i as usize >= context.input_resolution.len() {
640 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
641 }
642 res.push(SplatLocation::Input(T::InputIndex(i)))
643 }
644 L::Argument::NestedResult(i, j) => {
645 let Some(command_result) = context.result_type(i) else {
646 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
647 };
648 if j as usize >= command_result.len() {
649 return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
650 result_idx: i,
651 secondary_idx: j,
652 }
653 .into());
654 };
655 res.push(SplatLocation::Result(i, j))
656 }
657 L::Argument::Result(i) => {
658 let Some(result) = context.result_type(i) else {
659 return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
660 };
661 let Ok(len): Result<u16, _> = result.len().try_into() else {
662 invariant_violation!("Result of length greater than u16::MAX");
663 };
664 if len != 1 {
665 return Err(CommandArgumentError::InvalidResultArity { result_idx: i }.into());
667 }
668 res.extend((0..len).map(|j| SplatLocation::Result(i, j)))
669 }
670 }
671 Ok(())
672 }
673
674 let args = args.into_iter();
675 let _args_len = args.len();
676 let mut res = vec![];
677 for (arg_idx, arg) in args.enumerate() {
678 splat_arg(context, &mut res, arg).map_err(|e| {
679 let Some(idx) = start_idx.checked_add(arg_idx) else {
680 return make_invariant_violation!("usize overflow when calculating argument index");
681 };
682 e.into_execution_error(idx)
683 })?
684 }
685 debug_assert_eq!(res.len(), _args_len);
686 Ok(res)
687}
688
689fn arguments(
690 env: &Env,
691 context: &mut Context,
692 start_idx: usize,
693 locations: Vec<SplatLocation>,
694 expected_tys: impl IntoIterator<Item = Type>,
695) -> Result<Vec<T::Argument>, ExecutionError> {
696 locations
697 .into_iter()
698 .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_coin: _,
1292 bytes: _,
1293 objects,
1294 withdrawals: _,
1295 pure: _,
1296 receiving: _,
1297 withdrawal_compatibility_conversions: _,
1298 commands: _,
1299 } = ast;
1300 let inputs = objects
1301 .iter()
1302 .map(|o| match &o.arg {
1303 L::ObjectArg::SharedObject {
1304 id,
1305 kind: L::SharedObjectKind::Legacy,
1306 ..
1307 } => Some(*id),
1308 L::ObjectArg::ImmObject(_)
1309 | L::ObjectArg::OwnedObject(_)
1310 | L::ObjectArg::SharedObject {
1311 kind: L::SharedObjectKind::Party,
1312 ..
1313 } => None,
1314 })
1315 .collect::<Vec<_>>();
1316 Self {
1317 inputs,
1318 results: vec![],
1319 }
1320 }
1321 }
1322
1323 pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1328 let mut context = Context::new(ast);
1329
1330 for c in &mut ast.commands {
1333 debug_assert!(c.value.consumed_shared_objects.is_empty());
1334 command(&mut context, c)?;
1335 debug_assert!(context.results.last().unwrap().len() == c.value.result_type.len());
1336 }
1337 Ok(())
1338 }
1339
1340 fn command(context: &mut Context, sp!(_, c): &mut T::Command) -> Result<(), ExecutionError> {
1341 let mut acc = vec![];
1342 match &c.command {
1343 T::Command__::MoveCall(mc) => arguments(context, &mut acc, &mc.arguments)?,
1344 T::Command__::TransferObjects(objects, recipient) => {
1345 argument(context, &mut acc, recipient)?;
1346 arguments(context, &mut acc, objects)?;
1347 }
1348 T::Command__::SplitCoins(_, coin, amounts) => {
1349 arguments(context, &mut acc, amounts)?;
1350 argument(context, &mut acc, coin)?;
1351 }
1352 T::Command__::MergeCoins(_, target, coins) => {
1353 arguments(context, &mut acc, coins)?;
1354 argument(context, &mut acc, target)?;
1355 }
1356 T::Command__::MakeMoveVec(_, elements) => arguments(context, &mut acc, elements)?,
1357 T::Command__::Publish(_, _, _) => (),
1358 T::Command__::Upgrade(_, _, _, x, _) => argument(context, &mut acc, x)?,
1359 }
1360 let (consumed, result) = match &c.command {
1361 T::Command__::MakeMoveVec(_, _) => {
1364 assert_invariant!(
1365 c.result_type.len() == 1,
1366 "MakeMoveVec must return a single value"
1367 );
1368 (vec![], vec![Some(acc)])
1369 }
1370 T::Command__::MoveCall(_)
1372 | T::Command__::TransferObjects(_, _)
1373 | T::Command__::SplitCoins(_, _, _)
1374 | T::Command__::MergeCoins(_, _, _)
1375 | T::Command__::Publish(_, _, _)
1376 | T::Command__::Upgrade(_, _, _, _, _) => (acc, vec![None; c.result_type.len()]),
1377 };
1378 c.consumed_shared_objects = consumed;
1379 context.results.push(result);
1380 Ok(())
1381 }
1382
1383 fn arguments(
1384 context: &mut Context,
1385 acc: &mut Vec<ObjectID>,
1386 args: &[T::Argument],
1387 ) -> Result<(), ExecutionError> {
1388 for arg in args {
1389 argument(context, acc, arg)?
1390 }
1391 Ok(())
1392 }
1393
1394 fn argument(
1395 context: &mut Context,
1396 acc: &mut Vec<ObjectID>,
1397 sp!(_, (arg_, _)): &T::Argument,
1398 ) -> Result<(), ExecutionError> {
1399 let T::Argument__::Use(T::Usage::Move(loc)) = arg_ else {
1400 return Ok(());
1402 };
1403 match loc {
1404 T::Location::TxContext
1406 | T::Location::GasCoin
1407 | T::Location::WithdrawalInput(_)
1408 | T::Location::PureInput(_)
1409 | T::Location::ReceivingInput(_) => (),
1410 T::Location::ObjectInput(i) => {
1411 if let Some(id) = *context.inputs.safe_get(*i as usize)? {
1412 acc.push(id);
1413 }
1414 }
1415
1416 T::Location::Result(i, j) => {
1417 if let Some(ids) = context
1418 .results
1419 .safe_get(*i as usize)?
1420 .safe_get(*j as usize)?
1421 {
1422 acc.extend(ids.iter().copied());
1423 }
1424 }
1425 };
1426 Ok(())
1427 }
1428}