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