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