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