sui_adapter_latest/static_programmable_transactions/typing/
translate.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use super::{ast as T, env::Env};
5use crate::{
6    execution_mode::ExecutionMode,
7    programmable_transactions::context::EitherError,
8    static_programmable_transactions::{
9        loading::ast::{self as L, Type},
10        spanned::sp,
11        typing::ast::BytesConstraint,
12    },
13};
14use indexmap::{IndexMap, IndexSet};
15use std::rc::Rc;
16use sui_types::{
17    base_types::{ObjectRef, TxContextKind},
18    coin::RESOLVED_COIN_STRUCT,
19    error::{ExecutionError, ExecutionErrorKind, command_argument_error},
20    execution_status::CommandArgumentError,
21};
22
23#[derive(Debug, Clone, Copy)]
24enum SplatLocation {
25    GasCoin,
26    Input(T::InputIndex),
27    Result(u16, u16),
28}
29
30#[derive(Debug, Clone, Copy)]
31enum InputKind {
32    Object,
33    Withdrawal,
34    Pure,
35    Receiving,
36}
37
38struct Context {
39    current_command: u16,
40    /// What kind of input is at each original index
41    input_resolution: Vec<InputKind>,
42    bytes: IndexSet<Vec<u8>>,
43    // Mapping from original index to `bytes`
44    bytes_idx_remapping: IndexMap<T::InputIndex, T::ByteIndex>,
45    receiving_refs: IndexMap<T::InputIndex, ObjectRef>,
46    objects: IndexMap<T::InputIndex, T::ObjectInput>,
47    withdrawals: IndexMap<T::InputIndex, T::WithdrawalInput>,
48    pure: IndexMap<(T::InputIndex, Type), T::PureInput>,
49    receiving: IndexMap<(T::InputIndex, Type), T::ReceivingInput>,
50    results: Vec<T::ResultType>,
51}
52
53impl Context {
54    fn new(linputs: L::Inputs) -> Result<Self, ExecutionError> {
55        let mut context = Context {
56            current_command: 0,
57            input_resolution: vec![],
58            bytes: IndexSet::new(),
59            bytes_idx_remapping: IndexMap::new(),
60            receiving_refs: IndexMap::new(),
61            objects: IndexMap::new(),
62            withdrawals: IndexMap::new(),
63            pure: IndexMap::new(),
64            receiving: IndexMap::new(),
65            results: vec![],
66        };
67        // clone inputs for debug assertions
68        #[cfg(debug_assertions)]
69        let cloned_inputs = linputs
70            .iter()
71            .map(|(arg, _)| arg.clone())
72            .collect::<Vec<_>>();
73        // - intern the bytes
74        // - build maps for object, pure, and receiving inputs
75        for (i, (arg, ty)) in linputs.into_iter().enumerate() {
76            let idx = T::InputIndex(i as u16);
77            let kind = match (arg, ty) {
78                (L::InputArg::Pure(bytes), L::InputType::Bytes) => {
79                    let (byte_index, _) = context.bytes.insert_full(bytes);
80                    context.bytes_idx_remapping.insert(idx, byte_index);
81                    InputKind::Pure
82                }
83                (L::InputArg::Receiving(oref), L::InputType::Bytes) => {
84                    context.receiving_refs.insert(idx, oref);
85                    InputKind::Receiving
86                }
87                (L::InputArg::Object(arg), L::InputType::Fixed(ty)) => {
88                    let o = T::ObjectInput {
89                        original_input_index: idx,
90                        arg,
91                        ty,
92                    };
93                    context.objects.insert(idx, o);
94                    InputKind::Object
95                }
96                (L::InputArg::FundsWithdrawal(withdrawal), L::InputType::Fixed(input_ty)) => {
97                    let L::FundsWithdrawalArg { ty, owner, amount } = withdrawal;
98                    debug_assert!(ty == input_ty);
99                    let withdrawal = T::WithdrawalInput {
100                        original_input_index: idx,
101                        ty,
102                        owner,
103                        amount,
104                    };
105                    context.withdrawals.insert(idx, withdrawal);
106                    InputKind::Withdrawal
107                }
108                (arg, ty) => invariant_violation!(
109                    "Input arg, type mismatch. Unexpected {arg:?} with type {ty:?}"
110                ),
111            };
112            context.input_resolution.push(kind);
113        }
114        #[cfg(debug_assertions)]
115        {
116            // iterate to check the correctness of bytes interning
117            for (i, arg) in cloned_inputs.iter().enumerate() {
118                if let L::InputArg::Pure(bytes) = &arg {
119                    let idx = T::InputIndex(i as u16);
120                    let Some(byte_index) = context.bytes_idx_remapping.get(&idx) else {
121                        invariant_violation!("Unbound pure input {}", idx.0);
122                    };
123                    let Some(interned_bytes) = context.bytes.get_index(*byte_index) else {
124                        invariant_violation!("Interned bytes not found for index {}", byte_index);
125                    };
126                    if interned_bytes != bytes {
127                        assert_invariant!(
128                            interned_bytes == bytes,
129                            "Interned bytes mismatch for input {i}",
130                        );
131                    }
132                }
133            }
134        }
135        Ok(context)
136    }
137
138    fn finish(self, commands: T::Commands) -> T::Transaction {
139        let Self {
140            bytes,
141            objects,
142            withdrawals,
143            pure,
144            receiving,
145            ..
146        } = self;
147        let objects = objects.into_iter().map(|(_, o)| o).collect();
148        let withdrawals = withdrawals.into_iter().map(|(_, w)| w).collect();
149        let pure = pure.into_iter().map(|(_, p)| p).collect();
150        let receiving = receiving.into_iter().map(|(_, r)| r).collect();
151        T::Transaction {
152            bytes,
153            objects,
154            withdrawals,
155            pure,
156            receiving,
157            commands,
158        }
159    }
160
161    // Get the fixed type of a location. Returns `None` for Pure and Receiving inputs,
162    fn fixed_type(
163        &mut self,
164        env: &Env,
165        location: SplatLocation,
166    ) -> Result<Option<(T::Location, Type)>, ExecutionError> {
167        Ok(Some(match location {
168            SplatLocation::GasCoin => (T::Location::GasCoin, env.gas_coin_type()?),
169            SplatLocation::Result(i, j) => (
170                T::Location::Result(i, j),
171                self.results[i as usize][j as usize].clone(),
172            ),
173            SplatLocation::Input(i) => match &self.input_resolution[i.0 as usize] {
174                InputKind::Object => {
175                    let Some((object_index, _, object_input)) = self.objects.get_full(&i) else {
176                        invariant_violation!("Unbound object input {}", i.0)
177                    };
178                    (
179                        T::Location::ObjectInput(object_index as u16),
180                        object_input.ty.clone(),
181                    )
182                }
183                InputKind::Withdrawal => {
184                    let Some((withdrawal_index, _, withdrawal_input)) =
185                        self.withdrawals.get_full(&i)
186                    else {
187                        invariant_violation!("Unbound withdrawal input {}", i.0)
188                    };
189                    (
190                        T::Location::WithdrawalInput(withdrawal_index as u16),
191                        withdrawal_input.ty.clone(),
192                    )
193                }
194                InputKind::Pure | InputKind::Receiving => return Ok(None),
195            },
196        }))
197    }
198
199    fn resolve_location(
200        &mut self,
201        env: &Env,
202        location: SplatLocation,
203        expected_ty: &Type,
204        bytes_constraint: BytesConstraint,
205    ) -> Result<(T::Location, Type), ExecutionError> {
206        Ok(match location {
207            SplatLocation::GasCoin | SplatLocation::Result(_, _) => self
208                .fixed_type(env, location)?
209                .ok_or_else(|| make_invariant_violation!("Expected fixed type for {location:?}"))?,
210            SplatLocation::Input(i) => match &self.input_resolution[i.0 as usize] {
211                InputKind::Object | InputKind::Withdrawal => {
212                    self.fixed_type(env, location)?.ok_or_else(|| {
213                        make_invariant_violation!("Expected fixed type for {location:?}")
214                    })?
215                }
216                InputKind::Pure => {
217                    let ty = match expected_ty {
218                        Type::Reference(_, inner) => (**inner).clone(),
219                        ty => ty.clone(),
220                    };
221                    let k = (i, ty.clone());
222                    if !self.pure.contains_key(&k) {
223                        let Some(byte_index) = self.bytes_idx_remapping.get(&i).copied() else {
224                            invariant_violation!("Unbound pure input {}", i.0);
225                        };
226                        let pure = T::PureInput {
227                            original_input_index: i,
228                            byte_index,
229                            ty: ty.clone(),
230                            constraint: bytes_constraint,
231                        };
232                        self.pure.insert(k.clone(), pure);
233                    }
234                    let byte_index = self.pure.get_index_of(&k).unwrap();
235                    (T::Location::PureInput(byte_index as u16), ty)
236                }
237                InputKind::Receiving => {
238                    let ty = match expected_ty {
239                        Type::Reference(_, inner) => (**inner).clone(),
240                        ty => ty.clone(),
241                    };
242                    let k = (i, ty.clone());
243                    if !self.receiving.contains_key(&k) {
244                        let Some(object_ref) = self.receiving_refs.get(&i).copied() else {
245                            invariant_violation!("Unbound receiving input {}", i.0);
246                        };
247                        let receiving = T::ReceivingInput {
248                            original_input_index: i,
249                            object_ref,
250                            ty: ty.clone(),
251                            constraint: bytes_constraint,
252                        };
253                        self.receiving.insert(k.clone(), receiving);
254                    }
255                    let byte_index = self.receiving.get_index_of(&k).unwrap();
256                    (T::Location::ReceivingInput(byte_index as u16), ty)
257                }
258            },
259        })
260    }
261}
262
263pub fn transaction<Mode: ExecutionMode>(
264    env: &Env,
265    lt: L::Transaction,
266) -> Result<T::Transaction, ExecutionError> {
267    let L::Transaction { inputs, commands } = lt;
268    let mut context = Context::new(inputs)?;
269    let commands = commands
270        .into_iter()
271        .enumerate()
272        .map(|(i, c)| {
273            let idx = i as u16;
274            context.current_command = idx;
275            let (c_, tys) =
276                command::<Mode>(env, &mut context, c).map_err(|e| e.with_command_index(i))?;
277            context.results.push(tys.clone());
278            let c = T::Command_ {
279                command: c_,
280                result_type: tys,
281                // computed later
282                drop_values: vec![],
283                // computed later
284                consumed_shared_objects: vec![],
285            };
286            Ok(sp(idx, c))
287        })
288        .collect::<Result<Vec<_>, ExecutionError>>()?;
289    let mut ast = context.finish(commands);
290    // mark the last usage of references as Move instead of Copy
291    scope_references::transaction(&mut ast);
292    // mark unused results to be dropped
293    unused_results::transaction(&mut ast);
294    // track shared object IDs
295    consumed_shared_objects::transaction(&mut ast)?;
296    Ok(ast)
297}
298
299fn command<Mode: ExecutionMode>(
300    env: &Env,
301    context: &mut Context,
302    command: L::Command,
303) -> Result<(T::Command__, T::ResultType), ExecutionError> {
304    Ok(match command {
305        L::Command::MoveCall(lmc) => {
306            let L::MoveCall {
307                function,
308                arguments: largs,
309            } = *lmc;
310            let arg_locs = locations(context, 0, largs)?;
311            let tx_context_kind = tx_context_kind(&function);
312            let parameter_tys = match tx_context_kind {
313                TxContextKind::None => &function.signature.parameters,
314                TxContextKind::Mutable | TxContextKind::Immutable => {
315                    let Some(n_) = function.signature.parameters.len().checked_sub(1) else {
316                        invariant_violation!(
317                            "A function with a TxContext should have at least one parameter"
318                        )
319                    };
320                    &function.signature.parameters[0..n_]
321                }
322            };
323            let num_args = arg_locs.len();
324            let num_parameters = parameter_tys.len();
325            if num_args != num_parameters {
326                return Err(ExecutionError::new_with_source(
327                    ExecutionErrorKind::ArityMismatch,
328                    format!(
329                        "Expected {} argument{} calling function '{}::{}', but found {}",
330                        num_parameters,
331                        if num_parameters == 1 { "" } else { "s" },
332                        function.storage_id,
333                        function.name,
334                        num_args,
335                    ),
336                ));
337            }
338            let mut args = arguments(env, context, 0, arg_locs, parameter_tys.iter().cloned())?;
339            match tx_context_kind {
340                TxContextKind::None => (),
341                TxContextKind::Mutable | TxContextKind::Immutable => {
342                    let is_mut = match tx_context_kind {
343                        TxContextKind::Mutable => true,
344                        TxContextKind::Immutable => false,
345                        TxContextKind::None => unreachable!(),
346                    };
347                    // TODO this is out of bounds of the original PTB arguments... what do we
348                    // do here?
349                    let idx = args.len() as u16;
350                    let arg__ = T::Argument__::Borrow(is_mut, T::Location::TxContext);
351                    let ty = Type::Reference(is_mut, Rc::new(env.tx_context_type()?));
352                    args.push(sp(idx, (arg__, ty)));
353                }
354            }
355            let result = function.signature.return_.clone();
356            (
357                T::Command__::MoveCall(Box::new(T::MoveCall {
358                    function,
359                    arguments: args,
360                })),
361                result,
362            )
363        }
364        L::Command::TransferObjects(lobjects, laddress) => {
365            let object_locs = locations(context, 0, lobjects)?;
366            let address_loc = one_location(context, object_locs.len(), laddress)?;
367            let objects = constrained_arguments(
368                env,
369                context,
370                0,
371                object_locs,
372                |ty| {
373                    let abilities = ty.abilities();
374                    Ok(abilities.has_store() && abilities.has_key())
375                },
376                CommandArgumentError::InvalidTransferObject,
377            )?;
378            let address = argument(env, context, objects.len(), address_loc, Type::Address)?;
379            (T::Command__::TransferObjects(objects, address), vec![])
380        }
381        L::Command::SplitCoins(lcoin, lamounts) => {
382            let coin_loc = one_location(context, 0, lcoin)?;
383            let amount_locs = locations(context, 1, lamounts)?;
384            let coin = coin_mut_ref_argument(env, context, 0, coin_loc)?;
385            let coin_type = match &coin.value.1 {
386                Type::Reference(true, ty) => (**ty).clone(),
387                ty => invariant_violation!("coin must be a mutable reference. Found: {ty:?}"),
388            };
389            let amounts = arguments(
390                env,
391                context,
392                1,
393                amount_locs,
394                std::iter::repeat_with(|| Type::U64),
395            )?;
396            let result = vec![coin_type.clone(); amounts.len()];
397            (T::Command__::SplitCoins(coin_type, coin, amounts), result)
398        }
399        L::Command::MergeCoins(ltarget, lcoins) => {
400            let target_loc = one_location(context, 0, ltarget)?;
401            let coin_locs = locations(context, 1, lcoins)?;
402            let target = coin_mut_ref_argument(env, context, 0, target_loc)?;
403            let coin_type = match &target.value.1 {
404                Type::Reference(true, ty) => (**ty).clone(),
405                ty => invariant_violation!("target must be a mutable reference. Found: {ty:?}"),
406            };
407            let coins = arguments(
408                env,
409                context,
410                1,
411                coin_locs,
412                std::iter::repeat_with(|| coin_type.clone()),
413            )?;
414            (T::Command__::MergeCoins(coin_type, target, coins), vec![])
415        }
416        L::Command::MakeMoveVec(Some(ty), lelems) => {
417            let elem_locs = locations(context, 0, lelems)?;
418            let elems = arguments(
419                env,
420                context,
421                0,
422                elem_locs,
423                std::iter::repeat_with(|| ty.clone()),
424            )?;
425            (
426                T::Command__::MakeMoveVec(ty.clone(), elems),
427                vec![env.vector_type(ty)?],
428            )
429        }
430        L::Command::MakeMoveVec(None, lelems) => {
431            let mut lelems = lelems.into_iter();
432            let Some(lfirst) = lelems.next() else {
433                // TODO maybe this should be a different errors for CLI usage
434                invariant_violation!(
435                    "input checker ensures if args are empty, there is a type specified"
436                );
437            };
438            let first_loc = one_location(context, 0, lfirst)?;
439            let first_arg = constrained_argument(
440                env,
441                context,
442                0,
443                first_loc,
444                |ty| Ok(ty.abilities().has_key()),
445                CommandArgumentError::InvalidMakeMoveVecNonObjectArgument,
446            )?;
447            let first_ty = first_arg.value.1.clone();
448            let elems_loc = locations(context, 1, lelems)?;
449            let mut elems = arguments(
450                env,
451                context,
452                1,
453                elems_loc,
454                std::iter::repeat_with(|| first_ty.clone()),
455            )?;
456            elems.insert(0, first_arg);
457            (
458                T::Command__::MakeMoveVec(first_ty.clone(), elems),
459                vec![env.vector_type(first_ty)?],
460            )
461        }
462        L::Command::Publish(items, object_ids, linkage) => {
463            let result = if Mode::packages_are_predefined() {
464                // If packages are predefined, no upgrade cap is made
465                vec![]
466            } else {
467                vec![env.upgrade_cap_type()?.clone()]
468            };
469            (T::Command__::Publish(items, object_ids, linkage), result)
470        }
471        L::Command::Upgrade(items, object_ids, object_id, la, linkage) => {
472            let location = one_location(context, 0, la)?;
473            let expected_ty = env.upgrade_ticket_type()?;
474            let a = argument(env, context, 0, location, expected_ty)?;
475            let res = env.upgrade_receipt_type()?;
476            (
477                T::Command__::Upgrade(items, object_ids, object_id, a, linkage),
478                vec![res.clone()],
479            )
480        }
481    })
482}
483
484fn tx_context_kind(function: &L::LoadedFunction) -> TxContextKind {
485    match function.signature.parameters.last() {
486        Some(ty) => ty.is_tx_context(),
487        None => TxContextKind::None,
488    }
489}
490
491fn one_location(
492    context: &mut Context,
493    command_arg_idx: usize,
494    arg: L::Argument,
495) -> Result<SplatLocation, ExecutionError> {
496    let locs = locations(context, command_arg_idx, vec![arg])?;
497    let Ok([loc]): Result<[SplatLocation; 1], _> = locs.try_into() else {
498        return Err(command_argument_error(
499            CommandArgumentError::InvalidArgumentArity,
500            command_arg_idx,
501        ));
502    };
503    Ok(loc)
504}
505
506fn locations<Items: IntoIterator<Item = L::Argument>>(
507    context: &mut Context,
508    start_idx: usize,
509    args: Items,
510) -> Result<Vec<SplatLocation>, ExecutionError>
511where
512    Items::IntoIter: ExactSizeIterator,
513{
514    fn splat_arg(
515        context: &mut Context,
516        res: &mut Vec<SplatLocation>,
517        arg: L::Argument,
518    ) -> Result<(), EitherError> {
519        match arg {
520            L::Argument::GasCoin => res.push(SplatLocation::GasCoin),
521            L::Argument::Input(i) => {
522                if i as usize >= context.input_resolution.len() {
523                    return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
524                }
525                res.push(SplatLocation::Input(T::InputIndex(i)))
526            }
527            L::Argument::NestedResult(i, j) => {
528                let Some(command_result) = context.results.get(i as usize) else {
529                    return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
530                };
531                if j as usize >= command_result.len() {
532                    return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
533                        result_idx: i,
534                        secondary_idx: j,
535                    }
536                    .into());
537                };
538                res.push(SplatLocation::Result(i, j))
539            }
540            L::Argument::Result(i) => {
541                let Some(result) = context.results.get(i as usize) else {
542                    return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
543                };
544                let Ok(len): Result<u16, _> = result.len().try_into() else {
545                    invariant_violation!("Result of length greater than u16::MAX");
546                };
547                if len != 1 {
548                    // TODO protocol config to allow splatting of args
549                    return Err(CommandArgumentError::InvalidResultArity { result_idx: i }.into());
550                }
551                res.extend((0..len).map(|j| SplatLocation::Result(i, j)))
552            }
553        }
554        Ok(())
555    }
556
557    let args = args.into_iter();
558    let _args_len = args.len();
559    let mut res = vec![];
560    for (arg_idx, arg) in args.enumerate() {
561        splat_arg(context, &mut res, arg).map_err(|e| {
562            let Some(idx) = start_idx.checked_add(arg_idx) else {
563                return make_invariant_violation!("usize overflow when calculating argument index");
564            };
565            e.into_execution_error(idx)
566        })?
567    }
568    debug_assert_eq!(res.len(), _args_len);
569    Ok(res)
570}
571
572fn arguments(
573    env: &Env,
574    context: &mut Context,
575    start_idx: usize,
576    locations: Vec<SplatLocation>,
577    expected_tys: impl IntoIterator<Item = Type>,
578) -> Result<Vec<T::Argument>, ExecutionError> {
579    locations
580        .into_iter()
581        .zip(expected_tys)
582        .enumerate()
583        .map(|(i, (location, expected_ty))| {
584            let Some(idx) = start_idx.checked_add(i) else {
585                invariant_violation!("usize overflow when calculating argument index");
586            };
587            argument(env, context, idx, location, expected_ty)
588        })
589        .collect()
590}
591
592fn argument(
593    env: &Env,
594    context: &mut Context,
595    command_arg_idx: usize,
596    location: SplatLocation,
597    expected_ty: Type,
598) -> Result<T::Argument, ExecutionError> {
599    let arg__ = argument_(env, context, command_arg_idx, location, &expected_ty)
600        .map_err(|e| e.into_execution_error(command_arg_idx))?;
601    let arg_ = (arg__, expected_ty);
602    Ok(sp(command_arg_idx as u16, arg_))
603}
604
605fn argument_(
606    env: &Env,
607    context: &mut Context,
608    command_arg_idx: usize,
609    location: SplatLocation,
610    expected_ty: &Type,
611) -> Result<T::Argument__, EitherError> {
612    let current_command = context.current_command;
613    let bytes_constraint = BytesConstraint {
614        command: current_command,
615        argument: command_arg_idx as u16,
616    };
617    let (location, actual_ty): (T::Location, Type) =
618        context.resolve_location(env, location, expected_ty, bytes_constraint)?;
619    Ok(match (actual_ty, expected_ty) {
620        // Reference location types
621        (Type::Reference(a_is_mut, a), Type::Reference(b_is_mut, b)) => {
622            let needs_freeze = match (a_is_mut, b_is_mut) {
623                // same mutability
624                (true, true) | (false, false) => false,
625                // mut *can* be used as imm
626                (true, false) => true,
627                // imm cannot be used as mut
628                (false, true) => return Err(CommandArgumentError::TypeMismatch.into()),
629            };
630            debug_assert!(expected_ty.abilities().has_copy());
631            // unused since the type is fixed
632            check_type(&a, b)?;
633            if needs_freeze {
634                T::Argument__::Freeze(T::Usage::new_copy(location))
635            } else {
636                T::Argument__::new_copy(location)
637            }
638        }
639        (Type::Reference(_, a), b) => {
640            check_type(&a, b)?;
641            if !b.abilities().has_copy() {
642                // TODO this should be a different error for missing copy
643                return Err(CommandArgumentError::TypeMismatch.into());
644            }
645            T::Argument__::Read(T::Usage::new_copy(location))
646        }
647
648        // Non reference location types
649        (actual_ty, Type::Reference(is_mut, inner)) => {
650            check_type(&actual_ty, inner)?;
651            T::Argument__::Borrow(/* mut */ *is_mut, location)
652        }
653        (actual_ty, _) => {
654            check_type(&actual_ty, expected_ty)?;
655            T::Argument__::Use(if expected_ty.abilities().has_copy() {
656                T::Usage::new_copy(location)
657            } else {
658                T::Usage::new_move(location)
659            })
660        }
661    })
662}
663
664fn check_type(actual_ty: &Type, expected_ty: &Type) -> Result<(), CommandArgumentError> {
665    if actual_ty == expected_ty {
666        Ok(())
667    } else {
668        Err(CommandArgumentError::TypeMismatch)
669    }
670}
671
672fn constrained_arguments<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
673    env: &Env,
674    context: &mut Context,
675    start_idx: usize,
676    locations: Vec<SplatLocation>,
677    mut is_valid: P,
678    err_case: CommandArgumentError,
679) -> Result<Vec<T::Argument>, ExecutionError> {
680    let is_valid = &mut is_valid;
681    locations
682        .into_iter()
683        .enumerate()
684        .map(|(i, location)| {
685            let Some(idx) = start_idx.checked_add(i) else {
686                invariant_violation!("usize overflow when calculating argument index");
687            };
688            constrained_argument_(env, context, idx, location, is_valid, err_case)
689        })
690        .collect()
691}
692
693fn constrained_argument<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
694    env: &Env,
695    context: &mut Context,
696    command_arg_idx: usize,
697    location: SplatLocation,
698    mut is_valid: P,
699    err_case: CommandArgumentError,
700) -> Result<T::Argument, ExecutionError> {
701    constrained_argument_(
702        env,
703        context,
704        command_arg_idx,
705        location,
706        &mut is_valid,
707        err_case,
708    )
709}
710
711fn constrained_argument_<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
712    env: &Env,
713    context: &mut Context,
714    command_arg_idx: usize,
715    location: SplatLocation,
716    is_valid: &mut P,
717    err_case: CommandArgumentError,
718) -> Result<T::Argument, ExecutionError> {
719    let arg_ = constrained_argument__(env, context, location, is_valid, err_case)
720        .map_err(|e| e.into_execution_error(command_arg_idx))?;
721    Ok(sp(command_arg_idx as u16, arg_))
722}
723
724fn constrained_argument__<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
725    env: &Env,
726    context: &mut Context,
727    location: SplatLocation,
728    is_valid: &mut P,
729    err_case: CommandArgumentError,
730) -> Result<T::Argument_, EitherError> {
731    if let Some((location, ty)) = constrained_type(env, context, location, is_valid)? {
732        if ty.abilities().has_copy() {
733            Ok((T::Argument__::new_copy(location), ty))
734        } else {
735            Ok((T::Argument__::new_move(location), ty))
736        }
737    } else {
738        Err(err_case.into())
739    }
740}
741
742fn constrained_type<'a, P: FnMut(&Type) -> Result<bool, ExecutionError>>(
743    env: &'a Env,
744    context: &'a mut Context,
745    location: SplatLocation,
746    mut is_valid: P,
747) -> Result<Option<(T::Location, Type)>, ExecutionError> {
748    let Some((location, ty)) = context.fixed_type(env, location)? else {
749        return Ok(None);
750    };
751    Ok(if is_valid(&ty)? {
752        Some((location, ty))
753    } else {
754        None
755    })
756}
757
758fn coin_mut_ref_argument(
759    env: &Env,
760    context: &mut Context,
761    command_arg_idx: usize,
762    location: SplatLocation,
763) -> Result<T::Argument, ExecutionError> {
764    let arg_ = coin_mut_ref_argument_(env, context, location)
765        .map_err(|e| e.into_execution_error(command_arg_idx))?;
766    Ok(sp(command_arg_idx as u16, arg_))
767}
768
769fn coin_mut_ref_argument_(
770    env: &Env,
771    context: &mut Context,
772    location: SplatLocation,
773) -> Result<T::Argument_, EitherError> {
774    let Some((location, actual_ty)) = context.fixed_type(env, location)? else {
775        // TODO we do not currently bytes in any mode as that would require additional type
776        // inference not currently supported
777        return Err(CommandArgumentError::TypeMismatch.into());
778    };
779    Ok(match &actual_ty {
780        Type::Reference(is_mut, ty) if *is_mut => {
781            check_coin_type(ty)?;
782            (
783                T::Argument__::new_copy(location),
784                Type::Reference(*is_mut, ty.clone()),
785            )
786        }
787        ty => {
788            check_coin_type(ty)?;
789            (
790                T::Argument__::Borrow(/* mut */ true, location),
791                Type::Reference(true, Rc::new(ty.clone())),
792            )
793        }
794    })
795}
796
797fn check_coin_type(ty: &Type) -> Result<(), EitherError> {
798    let Type::Datatype(dt) = ty else {
799        return Err(CommandArgumentError::TypeMismatch.into());
800    };
801    let resolved = dt.qualified_ident();
802    let is_coin = resolved == RESOLVED_COIN_STRUCT;
803    if is_coin {
804        Ok(())
805    } else {
806        Err(CommandArgumentError::TypeMismatch.into())
807    }
808}
809
810//**************************************************************************************************
811// Reference scoping
812//**************************************************************************************************
813
814mod scope_references {
815    use crate::{
816        sp,
817        static_programmable_transactions::typing::ast::{self as T, Type},
818    };
819    use std::collections::BTreeSet;
820
821    /// To mimic proper scoping of references, the last usage of a reference is made a Move instead
822    /// of a Copy.
823    pub fn transaction(ast: &mut T::Transaction) {
824        let mut used: BTreeSet<(u16, u16)> = BTreeSet::new();
825        for c in ast.commands.iter_mut().rev() {
826            command(&mut used, c);
827        }
828    }
829
830    fn command(used: &mut BTreeSet<(u16, u16)>, sp!(_, c): &mut T::Command) {
831        match &mut c.command {
832            T::Command__::MoveCall(mc) => arguments(used, &mut mc.arguments),
833            T::Command__::TransferObjects(objects, recipient) => {
834                argument(used, recipient);
835                arguments(used, objects);
836            }
837            T::Command__::SplitCoins(_, coin, amounts) => {
838                arguments(used, amounts);
839                argument(used, coin);
840            }
841            T::Command__::MergeCoins(_, target, coins) => {
842                arguments(used, coins);
843                argument(used, target);
844            }
845            T::Command__::MakeMoveVec(_, xs) => arguments(used, xs),
846            T::Command__::Publish(_, _, _) => (),
847            T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
848        }
849    }
850
851    fn arguments(used: &mut BTreeSet<(u16, u16)>, args: &mut [T::Argument]) {
852        for arg in args.iter_mut().rev() {
853            argument(used, arg)
854        }
855    }
856
857    fn argument(used: &mut BTreeSet<(u16, u16)>, sp!(_, (arg_, ty)): &mut T::Argument) {
858        let usage = match arg_ {
859            T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
860            T::Argument__::Borrow(_, _) => return,
861        };
862        match (&usage, ty) {
863            (T::Usage::Move(T::Location::Result(i, j)), Type::Reference(_, _)) => {
864                debug_assert!(false, "No reference should be moved at this point");
865                used.insert((*i, *j));
866            }
867            (
868                T::Usage::Copy {
869                    location: T::Location::Result(i, j),
870                    ..
871                },
872                Type::Reference(_, _),
873            ) => {
874                // we are at the last usage of a reference result if it was not yet added to the set
875                let last_usage = used.insert((*i, *j));
876                if last_usage {
877                    // if it was the last usage, we need to change the Copy to a Move
878                    let loc = T::Location::Result(*i, *j);
879                    *usage = T::Usage::Move(loc);
880                }
881            }
882            _ => (),
883        }
884    }
885}
886
887//**************************************************************************************************
888// Unused results
889//**************************************************************************************************
890
891mod unused_results {
892    use indexmap::IndexSet;
893
894    use crate::{sp, static_programmable_transactions::typing::ast as T};
895
896    /// Finds what `Result` indexes are never used in the transaction.
897    /// For each command, marks the indexes of result values with `drop` that are never referred to
898    /// via `Result`.
899    pub fn transaction(ast: &mut T::Transaction) {
900        // Collect all used result locations (i, j) across all commands
901        let mut used: IndexSet<(u16, u16)> = IndexSet::new();
902        for c in &ast.commands {
903            command(&mut used, c);
904        }
905
906        // For each command, mark unused result indexes with `drop`
907        for (i, sp!(_, c)) in ast.commands.iter_mut().enumerate() {
908            debug_assert!(c.drop_values.is_empty());
909            let i = i as u16;
910            c.drop_values = c
911                .result_type
912                .iter()
913                .enumerate()
914                .map(|(j, ty)| (j as u16, ty))
915                .map(|(j, ty)| ty.abilities().has_drop() && !used.contains(&(i, j)))
916                .collect();
917        }
918    }
919
920    fn command(used: &mut IndexSet<(u16, u16)>, sp!(_, c): &T::Command) {
921        match &c.command {
922            T::Command__::MoveCall(mc) => arguments(used, &mc.arguments),
923            T::Command__::TransferObjects(objects, recipient) => {
924                argument(used, recipient);
925                arguments(used, objects);
926            }
927            T::Command__::SplitCoins(_, coin, amounts) => {
928                arguments(used, amounts);
929                argument(used, coin);
930            }
931            T::Command__::MergeCoins(_, target, coins) => {
932                arguments(used, coins);
933                argument(used, target);
934            }
935            T::Command__::MakeMoveVec(_, elements) => arguments(used, elements),
936            T::Command__::Publish(_, _, _) => (),
937            T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
938        }
939    }
940
941    fn arguments(used: &mut IndexSet<(u16, u16)>, args: &[T::Argument]) {
942        for arg in args {
943            argument(used, arg)
944        }
945    }
946
947    fn argument(used: &mut IndexSet<(u16, u16)>, sp!(_, (arg_, _)): &T::Argument) {
948        if let T::Location::Result(i, j) = arg_.location() {
949            used.insert((i, j));
950        }
951    }
952}
953
954//**************************************************************************************************
955// consumed shared object IDs
956//**************************************************************************************************
957
958mod consumed_shared_objects {
959
960    use crate::{
961        sp, static_programmable_transactions::loading::ast as L,
962        static_programmable_transactions::typing::ast as T,
963    };
964    use sui_types::{base_types::ObjectID, error::ExecutionError};
965
966    // Shared object (non-party) IDs contained in each location
967    struct Context {
968        // (legacy) shared object IDs that are used as inputs
969        inputs: Vec<Option<ObjectID>>,
970        results: Vec<Vec<Option<Vec<ObjectID>>>>,
971    }
972
973    impl Context {
974        pub fn new(ast: &T::Transaction) -> Self {
975            let T::Transaction {
976                bytes: _,
977                objects,
978                withdrawals: _,
979                pure: _,
980                receiving: _,
981                commands: _,
982            } = ast;
983            let inputs = objects
984                .iter()
985                .map(|o| match &o.arg {
986                    L::ObjectArg::SharedObject {
987                        id,
988                        kind: L::SharedObjectKind::Legacy,
989                        ..
990                    } => Some(*id),
991                    L::ObjectArg::ImmObject(_)
992                    | L::ObjectArg::OwnedObject(_)
993                    | L::ObjectArg::SharedObject {
994                        kind: L::SharedObjectKind::Party,
995                        ..
996                    } => None,
997                })
998                .collect::<Vec<_>>();
999            Self {
1000                inputs,
1001                results: vec![],
1002            }
1003        }
1004    }
1005
1006    /// Finds what shared objects are taken by-value by each command and must be either
1007    /// deleted or re-shared.
1008    /// MakeMoveVec is the only command that can take shared objects by-value and propagate them
1009    /// for another command.
1010    pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1011        let mut context = Context::new(ast);
1012
1013        // For each command, find what shared objects are taken by-value and mark them as being
1014        // consumed
1015        for c in &mut ast.commands {
1016            debug_assert!(c.value.consumed_shared_objects.is_empty());
1017            command(&mut context, c)?;
1018            debug_assert!(context.results.last().unwrap().len() == c.value.result_type.len());
1019        }
1020        Ok(())
1021    }
1022
1023    fn command(context: &mut Context, sp!(_, c): &mut T::Command) -> Result<(), ExecutionError> {
1024        let mut acc = vec![];
1025        match &c.command {
1026            T::Command__::MoveCall(mc) => arguments(context, &mut acc, &mc.arguments),
1027            T::Command__::TransferObjects(objects, recipient) => {
1028                argument(context, &mut acc, recipient);
1029                arguments(context, &mut acc, objects);
1030            }
1031            T::Command__::SplitCoins(_, coin, amounts) => {
1032                arguments(context, &mut acc, amounts);
1033                argument(context, &mut acc, coin);
1034            }
1035            T::Command__::MergeCoins(_, target, coins) => {
1036                arguments(context, &mut acc, coins);
1037                argument(context, &mut acc, target);
1038            }
1039            T::Command__::MakeMoveVec(_, elements) => arguments(context, &mut acc, elements),
1040            T::Command__::Publish(_, _, _) => (),
1041            T::Command__::Upgrade(_, _, _, x, _) => argument(context, &mut acc, x),
1042        }
1043        let (consumed, result) = match &c.command {
1044            // make move vec does not "consume" any by-value shared objects, and can propagate
1045            // them to a later command
1046            T::Command__::MakeMoveVec(_, _) => {
1047                assert_invariant!(
1048                    c.result_type.len() == 1,
1049                    "MakeMoveVec must return a single value"
1050                );
1051                (vec![], vec![Some(acc)])
1052            }
1053            // these commands do not propagate shared objects, and consume any in the acc
1054            T::Command__::MoveCall(_)
1055            | T::Command__::TransferObjects(_, _)
1056            | T::Command__::SplitCoins(_, _, _)
1057            | T::Command__::MergeCoins(_, _, _)
1058            | T::Command__::Publish(_, _, _)
1059            | T::Command__::Upgrade(_, _, _, _, _) => (acc, vec![None; c.result_type.len()]),
1060        };
1061        c.consumed_shared_objects = consumed;
1062        context.results.push(result);
1063        Ok(())
1064    }
1065
1066    fn arguments(context: &mut Context, acc: &mut Vec<ObjectID>, args: &[T::Argument]) {
1067        for arg in args {
1068            argument(context, acc, arg)
1069        }
1070    }
1071
1072    fn argument(context: &mut Context, acc: &mut Vec<ObjectID>, sp!(_, (arg_, _)): &T::Argument) {
1073        let T::Argument__::Use(T::Usage::Move(loc)) = arg_ else {
1074            // only Move usage can take shared objects by-value since they cannot be copied
1075            return;
1076        };
1077        match loc {
1078            // no shared objects in these locations
1079            T::Location::TxContext
1080            | T::Location::GasCoin
1081            | T::Location::WithdrawalInput(_)
1082            | T::Location::PureInput(_)
1083            | T::Location::ReceivingInput(_) => (),
1084            T::Location::ObjectInput(i) => {
1085                if let Some(id) = context.inputs[*i as usize] {
1086                    acc.push(id);
1087                }
1088            }
1089
1090            T::Location::Result(i, j) => {
1091                if let Some(ids) = &context.results[*i as usize][*j as usize] {
1092                    acc.extend(ids.iter().copied());
1093                }
1094            }
1095        }
1096    }
1097}