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