sui_adapter_latest/static_programmable_transactions/typing/
translate.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use 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    /// What kind of input is at each original index
47    input_resolution: Vec<InputKind>,
48    bytes: IndexSet<Vec<u8>>,
49    // Mapping from original index to `bytes`
50    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        // clone inputs for debug assertions
84        #[cfg(debug_assertions)]
85        let cloned_inputs = linputs
86            .iter()
87            .map(|(arg, _)| arg.clone())
88            .collect::<Vec<_>>();
89        // - intern the bytes
90        // - build maps for object, pure, and receiving inputs
91        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            // iterate to check the correctness of bytes interning
138            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    // Get the fixed type of a location. Returns `None` for Pure and Receiving inputs,
229    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            // computed later
364            drop_values: vec![],
365            // computed later
366            consumed_shared_objects: vec![],
367        };
368        context.push_result(c)?
369    }
370    let mut ast = context.finish();
371    // mark the last usage of references as Move instead of Copy
372    scope_references::transaction(&mut ast);
373    // mark unused results to be dropped
374    unused_results::transaction(&mut ast)?;
375    // track shared object IDs
376    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                // TODO maybe this should be a different errors for CLI usage
472                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                // If packages are predefined, no upgrade cap is made
503                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    // check arity
546    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    // construct arguments, injecting tx context args as needed
569    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                    // TODO this might overlap or be  out of bounds of the original PTB arguments...
588                    // what do we do here?
589                    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                    // TODO protocol config to allow splatting of args
663                    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        // Intentional zip: expected_tys may be an infinite repeat iterator
697        // TODO: Consider fixing callers to not use infinite repeat iterator.
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        // Reference location types
738        (Type::Reference(a_is_mut, a), Type::Reference(b_is_mut, b)) => {
739            let needs_freeze = match (a_is_mut, b_is_mut) {
740                // same mutability
741                (true, true) | (false, false) => false,
742                // mut *can* be used as imm
743                (true, false) => true,
744                // imm cannot be used as mut
745                (false, true) => return Err(CommandArgumentError::TypeMismatch.into()),
746            };
747            debug_assert!(expected_ty.abilities().has_copy());
748            // unused since the type is fixed
749            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                // TODO this should be a different error for missing copy
760                return Err(CommandArgumentError::TypeMismatch.into());
761            }
762            T::Argument__::Read(T::Usage::new_copy(location))
763        }
764
765        // Non reference location types
766        (actual_ty, Type::Reference(is_mut, inner)) => {
767            check_type(&actual_ty, inner)?;
768            T::Argument__::Borrow(/* mut */ *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        // TODO we do not currently bytes in any mode as that would require additional type
886        // inference not currently supported
887        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(/* mut */ 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
915//**************************************************************************************************
916// Withdrawal compatibility conversion
917//**************************************************************************************************
918
919/// Determines which withdrawal inputs need to be converted for compatibility, and appends the
920/// owner address of each such withdrawal as a new pure input.
921fn determine_withdrawal_compatibility_inputs(
922    _env: &Env,
923    inputs: &mut L::Inputs,
924) -> Result<IndexMap</* input withdrawal */ u16, /* owner address input */ 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    // mapping from original withdrawal input index to new coin result index
956    remap: IndexMap<u16, u16>,
957    // increment for all subsequent result indices
958    lift: u16,
959}
960
961/// For each withdrawal input that needs conversion, insert a conversion command to a
962/// `sui::coin::Coin<T>` and swaps references to that input to the conversion result.
963/// Adjusts result indices in subsequent commands accordingly.
964fn withdrawal_compatibility_conversion(
965    env: &Env,
966    context: &mut Context,
967    withdrawal_compatability_inputs: IndexMap<
968        /* input withdrawal */ u16,
969        /* owner address input */ 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</* Result index */ 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    // Grab the owner `address`
997    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    // insert a conversion command
1022    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    // manage metadata
1044    context.withdrawal_compatibility_conversions.insert(
1045        location,
1046        T::WithdrawalCompatibilityConversion {
1047            owner: owner_location,
1048            conversion_result: conversion_idx,
1049        },
1050    );
1051    // the result of the conversion is at (conversion_idx, 0)
1052    Ok(conversion_idx)
1053}
1054
1055/// Increments all result major indices by the lift amount.
1056/// Remaps any converted withdrawal inputs to the new coin result
1057fn 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
1083/// Returns the inner type `T` if the type is `sui::coin::Coin<T>`, else `None`
1084pub(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
1095/// Returns the inner type `T` if the type is `sui::balance::Balance<T>`, else `None`
1096pub(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
1107/// Returns the inner type `T` if the type is `sui::funds_accumulator::Withdrawal<T>`, else `None`
1108pub(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
1119//**************************************************************************************************
1120// Reference scoping
1121//**************************************************************************************************
1122
1123mod 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    /// To mimic proper scoping of references, the last usage of a reference is made a Move instead
1131    /// of a Copy.
1132    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                // we are at the last usage of a reference result if it was not yet added to the set
1184                let last_usage = used.insert((*i, *j));
1185                if last_usage {
1186                    // if it was the last usage, we need to change the Copy to a Move
1187                    let loc = T::Location::Result(*i, *j);
1188                    *usage = T::Usage::Move(loc);
1189                }
1190            }
1191            _ => (),
1192        }
1193    }
1194}
1195
1196//**************************************************************************************************
1197// Unused results
1198//**************************************************************************************************
1199
1200mod 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    /// Finds what `Result` indexes are never used in the transaction.
1207    /// For each command, marks the indexes of result values with `drop` that are never referred to
1208    /// via `Result`.
1209    pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1210        // Collect all used result locations (i, j) across all commands
1211        let mut used: IndexSet<(u16, u16)> = IndexSet::new();
1212        for c in &ast.commands {
1213            command(&mut used, c);
1214        }
1215
1216        // For each command, mark unused result indexes with `drop`
1217        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
1266//**************************************************************************************************
1267// consumed shared object IDs
1268//**************************************************************************************************
1269
1270mod 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    // Shared object (non-party) IDs contained in each location
1282    struct Context {
1283        // (legacy) shared object IDs that are used as inputs
1284        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    /// Finds what shared objects are taken by-value by each command and must be either
1325    /// deleted or re-shared.
1326    /// MakeMoveVec is the only command that can take shared objects by-value and propagate them
1327    /// for another command.
1328    pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1329        let mut context = Context::new(ast);
1330
1331        // For each command, find what shared objects are taken by-value and mark them as being
1332        // consumed
1333        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            // make move vec does not "consume" any by-value shared objects, and can propagate
1363            // them to a later command
1364            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            // these commands do not propagate shared objects, and consume any in the acc
1372            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            // only Move usage can take shared objects by-value since they cannot be copied
1402            return Ok(());
1403        };
1404        match loc {
1405            // no shared objects in these locations
1406            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}