Skip to main content

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