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 args = move_call_arguments(env, context, &function, arg_locs)?;
312            let result = function.signature.return_.clone();
313            (
314                T::Command__::MoveCall(Box::new(T::MoveCall {
315                    function,
316                    arguments: args,
317                })),
318                result,
319            )
320        }
321        L::Command::TransferObjects(lobjects, laddress) => {
322            let object_locs = locations(context, 0, lobjects)?;
323            let address_loc = one_location(context, object_locs.len(), laddress)?;
324            let objects = constrained_arguments(
325                env,
326                context,
327                0,
328                object_locs,
329                |ty| {
330                    let abilities = ty.abilities();
331                    Ok(abilities.has_store() && abilities.has_key())
332                },
333                CommandArgumentError::InvalidTransferObject,
334            )?;
335            let address = argument(env, context, objects.len(), address_loc, Type::Address)?;
336            (T::Command__::TransferObjects(objects, address), vec![])
337        }
338        L::Command::SplitCoins(lcoin, lamounts) => {
339            let coin_loc = one_location(context, 0, lcoin)?;
340            let amount_locs = locations(context, 1, lamounts)?;
341            let coin = coin_mut_ref_argument(env, context, 0, coin_loc)?;
342            let coin_type = match &coin.value.1 {
343                Type::Reference(true, ty) => (**ty).clone(),
344                ty => invariant_violation!("coin must be a mutable reference. Found: {ty:?}"),
345            };
346            let amounts = arguments(
347                env,
348                context,
349                1,
350                amount_locs,
351                std::iter::repeat_with(|| Type::U64),
352            )?;
353            let result = vec![coin_type.clone(); amounts.len()];
354            (T::Command__::SplitCoins(coin_type, coin, amounts), result)
355        }
356        L::Command::MergeCoins(ltarget, lcoins) => {
357            let target_loc = one_location(context, 0, ltarget)?;
358            let coin_locs = locations(context, 1, lcoins)?;
359            let target = coin_mut_ref_argument(env, context, 0, target_loc)?;
360            let coin_type = match &target.value.1 {
361                Type::Reference(true, ty) => (**ty).clone(),
362                ty => invariant_violation!("target must be a mutable reference. Found: {ty:?}"),
363            };
364            let coins = arguments(
365                env,
366                context,
367                1,
368                coin_locs,
369                std::iter::repeat_with(|| coin_type.clone()),
370            )?;
371            (T::Command__::MergeCoins(coin_type, target, coins), vec![])
372        }
373        L::Command::MakeMoveVec(Some(ty), lelems) => {
374            let elem_locs = locations(context, 0, lelems)?;
375            let elems = arguments(
376                env,
377                context,
378                0,
379                elem_locs,
380                std::iter::repeat_with(|| ty.clone()),
381            )?;
382            (
383                T::Command__::MakeMoveVec(ty.clone(), elems),
384                vec![env.vector_type(ty)?],
385            )
386        }
387        L::Command::MakeMoveVec(None, lelems) => {
388            let mut lelems = lelems.into_iter();
389            let Some(lfirst) = lelems.next() else {
390                // TODO maybe this should be a different errors for CLI usage
391                invariant_violation!(
392                    "input checker ensures if args are empty, there is a type specified"
393                );
394            };
395            let first_loc = one_location(context, 0, lfirst)?;
396            let first_arg = constrained_argument(
397                env,
398                context,
399                0,
400                first_loc,
401                |ty| Ok(ty.abilities().has_key()),
402                CommandArgumentError::InvalidMakeMoveVecNonObjectArgument,
403            )?;
404            let first_ty = first_arg.value.1.clone();
405            let elems_loc = locations(context, 1, lelems)?;
406            let mut elems = arguments(
407                env,
408                context,
409                1,
410                elems_loc,
411                std::iter::repeat_with(|| first_ty.clone()),
412            )?;
413            elems.insert(0, first_arg);
414            (
415                T::Command__::MakeMoveVec(first_ty.clone(), elems),
416                vec![env.vector_type(first_ty)?],
417            )
418        }
419        L::Command::Publish(items, object_ids, linkage) => {
420            let result = if Mode::packages_are_predefined() {
421                // If packages are predefined, no upgrade cap is made
422                vec![]
423            } else {
424                vec![env.upgrade_cap_type()?.clone()]
425            };
426            (T::Command__::Publish(items, object_ids, linkage), result)
427        }
428        L::Command::Upgrade(items, object_ids, object_id, la, linkage) => {
429            let location = one_location(context, 0, la)?;
430            let expected_ty = env.upgrade_ticket_type()?;
431            let a = argument(env, context, 0, location, expected_ty)?;
432            let res = env.upgrade_receipt_type()?;
433            (
434                T::Command__::Upgrade(items, object_ids, object_id, a, linkage),
435                vec![res.clone()],
436            )
437        }
438    })
439}
440
441fn move_call_parameters<'a>(
442    env: &Env,
443    function: &'a L::LoadedFunction,
444) -> Vec<(&'a Type, TxContextKind)> {
445    if env.protocol_config.flexible_tx_context_positions() {
446        function
447            .signature
448            .parameters
449            .iter()
450            .map(|ty| (ty, ty.is_tx_context()))
451            .collect()
452    } else {
453        let mut kinds = function
454            .signature
455            .parameters
456            .iter()
457            .map(|ty| (ty, TxContextKind::None))
458            .collect::<Vec<_>>();
459        if let Some((ty, kind)) = kinds.last_mut() {
460            *kind = ty.is_tx_context();
461        }
462        kinds
463    }
464}
465
466fn move_call_arguments(
467    env: &Env,
468    context: &mut Context,
469    function: &L::LoadedFunction,
470    args: Vec<SplatLocation>,
471) -> Result<Vec<T::Argument>, ExecutionError> {
472    let params = move_call_parameters(env, function);
473    assert_invariant!(
474        params.len() == function.signature.parameters.len(),
475        "Generated parameter types does not match the function signature"
476    );
477    // check arity
478    let num_tx_contexts = params
479        .iter()
480        .filter(|(_, k)| matches!(k, TxContextKind::Mutable | TxContextKind::Immutable))
481        .count();
482    let num_user_args = args.len();
483    let Some(num_args) = num_user_args.checked_add(num_tx_contexts) else {
484        invariant_violation!("usize overflow when calculating number of arguments");
485    };
486    let num_parameters = params.len();
487    if num_args != num_parameters {
488        return Err(ExecutionError::new_with_source(
489            ExecutionErrorKind::ArityMismatch,
490            format!(
491                "Expected {} argument{} calling function '{}::{}', but found {}",
492                num_parameters,
493                if num_parameters == 1 { "" } else { "s" },
494                function.storage_id,
495                function.name,
496                num_args,
497            ),
498        ));
499    }
500    // construct arguments, injecting tx context args as needed
501    let mut args = args.into_iter().enumerate();
502    let res = params
503        .into_iter()
504        .enumerate()
505        .map(|(param_idx, (expected_ty, tx_context_kind))| {
506            Ok(match tx_context_kind {
507                TxContextKind::None => {
508                    let Some((arg_idx, location)) = args.next() else {
509                        invariant_violation!("arguments are empty but arity was already checked");
510                    };
511                    argument(env, context, arg_idx, location, expected_ty.clone())?
512                }
513                TxContextKind::Mutable | TxContextKind::Immutable => {
514                    let is_mut = match tx_context_kind {
515                        TxContextKind::Mutable => true,
516                        TxContextKind::Immutable => false,
517                        TxContextKind::None => unreachable!(),
518                    };
519                    // TODO this might overlap or be  out of bounds of the original PTB arguments...
520                    // what do we do here?
521                    let idx = param_idx as u16;
522                    let arg__ = T::Argument__::Borrow(is_mut, T::Location::TxContext);
523                    let ty = Type::Reference(is_mut, Rc::new(env.tx_context_type()?));
524                    sp(idx, (arg__, ty))
525                }
526            })
527        })
528        .collect::<Result<Vec<_>, ExecutionError>>()?;
529
530    assert_invariant!(
531        args.next().is_none(),
532        "some arguments went unused but arity was already checked"
533    );
534    Ok(res)
535}
536
537fn one_location(
538    context: &mut Context,
539    command_arg_idx: usize,
540    arg: L::Argument,
541) -> Result<SplatLocation, ExecutionError> {
542    let locs = locations(context, command_arg_idx, vec![arg])?;
543    let Ok([loc]): Result<[SplatLocation; 1], _> = locs.try_into() else {
544        return Err(command_argument_error(
545            CommandArgumentError::InvalidArgumentArity,
546            command_arg_idx,
547        ));
548    };
549    Ok(loc)
550}
551
552fn locations<Items: IntoIterator<Item = L::Argument>>(
553    context: &mut Context,
554    start_idx: usize,
555    args: Items,
556) -> Result<Vec<SplatLocation>, ExecutionError>
557where
558    Items::IntoIter: ExactSizeIterator,
559{
560    fn splat_arg(
561        context: &mut Context,
562        res: &mut Vec<SplatLocation>,
563        arg: L::Argument,
564    ) -> Result<(), EitherError> {
565        match arg {
566            L::Argument::GasCoin => res.push(SplatLocation::GasCoin),
567            L::Argument::Input(i) => {
568                if i as usize >= context.input_resolution.len() {
569                    return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
570                }
571                res.push(SplatLocation::Input(T::InputIndex(i)))
572            }
573            L::Argument::NestedResult(i, j) => {
574                let Some(command_result) = context.results.get(i as usize) else {
575                    return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
576                };
577                if j as usize >= command_result.len() {
578                    return Err(CommandArgumentError::SecondaryIndexOutOfBounds {
579                        result_idx: i,
580                        secondary_idx: j,
581                    }
582                    .into());
583                };
584                res.push(SplatLocation::Result(i, j))
585            }
586            L::Argument::Result(i) => {
587                let Some(result) = context.results.get(i as usize) else {
588                    return Err(CommandArgumentError::IndexOutOfBounds { idx: i }.into());
589                };
590                let Ok(len): Result<u16, _> = result.len().try_into() else {
591                    invariant_violation!("Result of length greater than u16::MAX");
592                };
593                if len != 1 {
594                    // TODO protocol config to allow splatting of args
595                    return Err(CommandArgumentError::InvalidResultArity { result_idx: i }.into());
596                }
597                res.extend((0..len).map(|j| SplatLocation::Result(i, j)))
598            }
599        }
600        Ok(())
601    }
602
603    let args = args.into_iter();
604    let _args_len = args.len();
605    let mut res = vec![];
606    for (arg_idx, arg) in args.enumerate() {
607        splat_arg(context, &mut res, arg).map_err(|e| {
608            let Some(idx) = start_idx.checked_add(arg_idx) else {
609                return make_invariant_violation!("usize overflow when calculating argument index");
610            };
611            e.into_execution_error(idx)
612        })?
613    }
614    debug_assert_eq!(res.len(), _args_len);
615    Ok(res)
616}
617
618fn arguments(
619    env: &Env,
620    context: &mut Context,
621    start_idx: usize,
622    locations: Vec<SplatLocation>,
623    expected_tys: impl IntoIterator<Item = Type>,
624) -> Result<Vec<T::Argument>, ExecutionError> {
625    locations
626        .into_iter()
627        .zip(expected_tys)
628        .enumerate()
629        .map(|(i, (location, expected_ty))| {
630            let Some(idx) = start_idx.checked_add(i) else {
631                invariant_violation!("usize overflow when calculating argument index");
632            };
633            argument(env, context, idx, location, expected_ty)
634        })
635        .collect()
636}
637
638fn argument(
639    env: &Env,
640    context: &mut Context,
641    command_arg_idx: usize,
642    location: SplatLocation,
643    expected_ty: Type,
644) -> Result<T::Argument, ExecutionError> {
645    let arg__ = argument_(env, context, command_arg_idx, location, &expected_ty)
646        .map_err(|e| e.into_execution_error(command_arg_idx))?;
647    let arg_ = (arg__, expected_ty);
648    Ok(sp(command_arg_idx as u16, arg_))
649}
650
651fn argument_(
652    env: &Env,
653    context: &mut Context,
654    command_arg_idx: usize,
655    location: SplatLocation,
656    expected_ty: &Type,
657) -> Result<T::Argument__, EitherError> {
658    let current_command = context.current_command;
659    let bytes_constraint = BytesConstraint {
660        command: current_command,
661        argument: command_arg_idx as u16,
662    };
663    let (location, actual_ty): (T::Location, Type) =
664        context.resolve_location(env, location, expected_ty, bytes_constraint)?;
665    Ok(match (actual_ty, expected_ty) {
666        // Reference location types
667        (Type::Reference(a_is_mut, a), Type::Reference(b_is_mut, b)) => {
668            let needs_freeze = match (a_is_mut, b_is_mut) {
669                // same mutability
670                (true, true) | (false, false) => false,
671                // mut *can* be used as imm
672                (true, false) => true,
673                // imm cannot be used as mut
674                (false, true) => return Err(CommandArgumentError::TypeMismatch.into()),
675            };
676            debug_assert!(expected_ty.abilities().has_copy());
677            // unused since the type is fixed
678            check_type(&a, b)?;
679            if needs_freeze {
680                T::Argument__::Freeze(T::Usage::new_copy(location))
681            } else {
682                T::Argument__::new_copy(location)
683            }
684        }
685        (Type::Reference(_, a), b) => {
686            check_type(&a, b)?;
687            if !b.abilities().has_copy() {
688                // TODO this should be a different error for missing copy
689                return Err(CommandArgumentError::TypeMismatch.into());
690            }
691            T::Argument__::Read(T::Usage::new_copy(location))
692        }
693
694        // Non reference location types
695        (actual_ty, Type::Reference(is_mut, inner)) => {
696            check_type(&actual_ty, inner)?;
697            T::Argument__::Borrow(/* mut */ *is_mut, location)
698        }
699        (actual_ty, _) => {
700            check_type(&actual_ty, expected_ty)?;
701            T::Argument__::Use(if expected_ty.abilities().has_copy() {
702                T::Usage::new_copy(location)
703            } else {
704                T::Usage::new_move(location)
705            })
706        }
707    })
708}
709
710fn check_type(actual_ty: &Type, expected_ty: &Type) -> Result<(), CommandArgumentError> {
711    if actual_ty == expected_ty {
712        Ok(())
713    } else {
714        Err(CommandArgumentError::TypeMismatch)
715    }
716}
717
718fn constrained_arguments<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
719    env: &Env,
720    context: &mut Context,
721    start_idx: usize,
722    locations: Vec<SplatLocation>,
723    mut is_valid: P,
724    err_case: CommandArgumentError,
725) -> Result<Vec<T::Argument>, ExecutionError> {
726    let is_valid = &mut is_valid;
727    locations
728        .into_iter()
729        .enumerate()
730        .map(|(i, location)| {
731            let Some(idx) = start_idx.checked_add(i) else {
732                invariant_violation!("usize overflow when calculating argument index");
733            };
734            constrained_argument_(env, context, idx, location, is_valid, err_case)
735        })
736        .collect()
737}
738
739fn constrained_argument<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
740    env: &Env,
741    context: &mut Context,
742    command_arg_idx: usize,
743    location: SplatLocation,
744    mut is_valid: P,
745    err_case: CommandArgumentError,
746) -> Result<T::Argument, ExecutionError> {
747    constrained_argument_(
748        env,
749        context,
750        command_arg_idx,
751        location,
752        &mut is_valid,
753        err_case,
754    )
755}
756
757fn constrained_argument_<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
758    env: &Env,
759    context: &mut Context,
760    command_arg_idx: usize,
761    location: SplatLocation,
762    is_valid: &mut P,
763    err_case: CommandArgumentError,
764) -> Result<T::Argument, ExecutionError> {
765    let arg_ = constrained_argument__(env, context, location, is_valid, err_case)
766        .map_err(|e| e.into_execution_error(command_arg_idx))?;
767    Ok(sp(command_arg_idx as u16, arg_))
768}
769
770fn constrained_argument__<P: FnMut(&Type) -> Result<bool, ExecutionError>>(
771    env: &Env,
772    context: &mut Context,
773    location: SplatLocation,
774    is_valid: &mut P,
775    err_case: CommandArgumentError,
776) -> Result<T::Argument_, EitherError> {
777    if let Some((location, ty)) = constrained_type(env, context, location, is_valid)? {
778        if ty.abilities().has_copy() {
779            Ok((T::Argument__::new_copy(location), ty))
780        } else {
781            Ok((T::Argument__::new_move(location), ty))
782        }
783    } else {
784        Err(err_case.into())
785    }
786}
787
788fn constrained_type<'a, P: FnMut(&Type) -> Result<bool, ExecutionError>>(
789    env: &'a Env,
790    context: &'a mut Context,
791    location: SplatLocation,
792    mut is_valid: P,
793) -> Result<Option<(T::Location, Type)>, ExecutionError> {
794    let Some((location, ty)) = context.fixed_type(env, location)? else {
795        return Ok(None);
796    };
797    Ok(if is_valid(&ty)? {
798        Some((location, ty))
799    } else {
800        None
801    })
802}
803
804fn coin_mut_ref_argument(
805    env: &Env,
806    context: &mut Context,
807    command_arg_idx: usize,
808    location: SplatLocation,
809) -> Result<T::Argument, ExecutionError> {
810    let arg_ = coin_mut_ref_argument_(env, context, location)
811        .map_err(|e| e.into_execution_error(command_arg_idx))?;
812    Ok(sp(command_arg_idx as u16, arg_))
813}
814
815fn coin_mut_ref_argument_(
816    env: &Env,
817    context: &mut Context,
818    location: SplatLocation,
819) -> Result<T::Argument_, EitherError> {
820    let Some((location, actual_ty)) = context.fixed_type(env, location)? else {
821        // TODO we do not currently bytes in any mode as that would require additional type
822        // inference not currently supported
823        return Err(CommandArgumentError::TypeMismatch.into());
824    };
825    Ok(match &actual_ty {
826        Type::Reference(is_mut, ty) if *is_mut => {
827            check_coin_type(ty)?;
828            (
829                T::Argument__::new_copy(location),
830                Type::Reference(*is_mut, ty.clone()),
831            )
832        }
833        ty => {
834            check_coin_type(ty)?;
835            (
836                T::Argument__::Borrow(/* mut */ true, location),
837                Type::Reference(true, Rc::new(ty.clone())),
838            )
839        }
840    })
841}
842
843fn check_coin_type(ty: &Type) -> Result<(), EitherError> {
844    let Type::Datatype(dt) = ty else {
845        return Err(CommandArgumentError::TypeMismatch.into());
846    };
847    let resolved = dt.qualified_ident();
848    let is_coin = resolved == RESOLVED_COIN_STRUCT;
849    if is_coin {
850        Ok(())
851    } else {
852        Err(CommandArgumentError::TypeMismatch.into())
853    }
854}
855
856//**************************************************************************************************
857// Reference scoping
858//**************************************************************************************************
859
860mod scope_references {
861    use crate::{
862        sp,
863        static_programmable_transactions::typing::ast::{self as T, Type},
864    };
865    use std::collections::BTreeSet;
866
867    /// To mimic proper scoping of references, the last usage of a reference is made a Move instead
868    /// of a Copy.
869    pub fn transaction(ast: &mut T::Transaction) {
870        let mut used: BTreeSet<(u16, u16)> = BTreeSet::new();
871        for c in ast.commands.iter_mut().rev() {
872            command(&mut used, c);
873        }
874    }
875
876    fn command(used: &mut BTreeSet<(u16, u16)>, sp!(_, c): &mut T::Command) {
877        match &mut c.command {
878            T::Command__::MoveCall(mc) => arguments(used, &mut mc.arguments),
879            T::Command__::TransferObjects(objects, recipient) => {
880                argument(used, recipient);
881                arguments(used, objects);
882            }
883            T::Command__::SplitCoins(_, coin, amounts) => {
884                arguments(used, amounts);
885                argument(used, coin);
886            }
887            T::Command__::MergeCoins(_, target, coins) => {
888                arguments(used, coins);
889                argument(used, target);
890            }
891            T::Command__::MakeMoveVec(_, xs) => arguments(used, xs),
892            T::Command__::Publish(_, _, _) => (),
893            T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
894        }
895    }
896
897    fn arguments(used: &mut BTreeSet<(u16, u16)>, args: &mut [T::Argument]) {
898        for arg in args.iter_mut().rev() {
899            argument(used, arg)
900        }
901    }
902
903    fn argument(used: &mut BTreeSet<(u16, u16)>, sp!(_, (arg_, ty)): &mut T::Argument) {
904        let usage = match arg_ {
905            T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
906            T::Argument__::Borrow(_, _) => return,
907        };
908        match (&usage, ty) {
909            (T::Usage::Move(T::Location::Result(i, j)), Type::Reference(_, _)) => {
910                debug_assert!(false, "No reference should be moved at this point");
911                used.insert((*i, *j));
912            }
913            (
914                T::Usage::Copy {
915                    location: T::Location::Result(i, j),
916                    ..
917                },
918                Type::Reference(_, _),
919            ) => {
920                // we are at the last usage of a reference result if it was not yet added to the set
921                let last_usage = used.insert((*i, *j));
922                if last_usage {
923                    // if it was the last usage, we need to change the Copy to a Move
924                    let loc = T::Location::Result(*i, *j);
925                    *usage = T::Usage::Move(loc);
926                }
927            }
928            _ => (),
929        }
930    }
931}
932
933//**************************************************************************************************
934// Unused results
935//**************************************************************************************************
936
937mod unused_results {
938    use indexmap::IndexSet;
939
940    use crate::{sp, static_programmable_transactions::typing::ast as T};
941
942    /// Finds what `Result` indexes are never used in the transaction.
943    /// For each command, marks the indexes of result values with `drop` that are never referred to
944    /// via `Result`.
945    pub fn transaction(ast: &mut T::Transaction) {
946        // Collect all used result locations (i, j) across all commands
947        let mut used: IndexSet<(u16, u16)> = IndexSet::new();
948        for c in &ast.commands {
949            command(&mut used, c);
950        }
951
952        // For each command, mark unused result indexes with `drop`
953        for (i, sp!(_, c)) in ast.commands.iter_mut().enumerate() {
954            debug_assert!(c.drop_values.is_empty());
955            let i = i as u16;
956            c.drop_values = c
957                .result_type
958                .iter()
959                .enumerate()
960                .map(|(j, ty)| (j as u16, ty))
961                .map(|(j, ty)| ty.abilities().has_drop() && !used.contains(&(i, j)))
962                .collect();
963        }
964    }
965
966    fn command(used: &mut IndexSet<(u16, u16)>, sp!(_, c): &T::Command) {
967        match &c.command {
968            T::Command__::MoveCall(mc) => arguments(used, &mc.arguments),
969            T::Command__::TransferObjects(objects, recipient) => {
970                argument(used, recipient);
971                arguments(used, objects);
972            }
973            T::Command__::SplitCoins(_, coin, amounts) => {
974                arguments(used, amounts);
975                argument(used, coin);
976            }
977            T::Command__::MergeCoins(_, target, coins) => {
978                arguments(used, coins);
979                argument(used, target);
980            }
981            T::Command__::MakeMoveVec(_, elements) => arguments(used, elements),
982            T::Command__::Publish(_, _, _) => (),
983            T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
984        }
985    }
986
987    fn arguments(used: &mut IndexSet<(u16, u16)>, args: &[T::Argument]) {
988        for arg in args {
989            argument(used, arg)
990        }
991    }
992
993    fn argument(used: &mut IndexSet<(u16, u16)>, sp!(_, (arg_, _)): &T::Argument) {
994        if let T::Location::Result(i, j) = arg_.location() {
995            used.insert((i, j));
996        }
997    }
998}
999
1000//**************************************************************************************************
1001// consumed shared object IDs
1002//**************************************************************************************************
1003
1004mod consumed_shared_objects {
1005
1006    use crate::{
1007        sp, static_programmable_transactions::loading::ast as L,
1008        static_programmable_transactions::typing::ast as T,
1009    };
1010    use sui_types::{base_types::ObjectID, error::ExecutionError};
1011
1012    // Shared object (non-party) IDs contained in each location
1013    struct Context {
1014        // (legacy) shared object IDs that are used as inputs
1015        inputs: Vec<Option<ObjectID>>,
1016        results: Vec<Vec<Option<Vec<ObjectID>>>>,
1017    }
1018
1019    impl Context {
1020        pub fn new(ast: &T::Transaction) -> Self {
1021            let T::Transaction {
1022                bytes: _,
1023                objects,
1024                withdrawals: _,
1025                pure: _,
1026                receiving: _,
1027                commands: _,
1028            } = ast;
1029            let inputs = objects
1030                .iter()
1031                .map(|o| match &o.arg {
1032                    L::ObjectArg::SharedObject {
1033                        id,
1034                        kind: L::SharedObjectKind::Legacy,
1035                        ..
1036                    } => Some(*id),
1037                    L::ObjectArg::ImmObject(_)
1038                    | L::ObjectArg::OwnedObject(_)
1039                    | L::ObjectArg::SharedObject {
1040                        kind: L::SharedObjectKind::Party,
1041                        ..
1042                    } => None,
1043                })
1044                .collect::<Vec<_>>();
1045            Self {
1046                inputs,
1047                results: vec![],
1048            }
1049        }
1050    }
1051
1052    /// Finds what shared objects are taken by-value by each command and must be either
1053    /// deleted or re-shared.
1054    /// MakeMoveVec is the only command that can take shared objects by-value and propagate them
1055    /// for another command.
1056    pub fn transaction(ast: &mut T::Transaction) -> Result<(), ExecutionError> {
1057        let mut context = Context::new(ast);
1058
1059        // For each command, find what shared objects are taken by-value and mark them as being
1060        // consumed
1061        for c in &mut ast.commands {
1062            debug_assert!(c.value.consumed_shared_objects.is_empty());
1063            command(&mut context, c)?;
1064            debug_assert!(context.results.last().unwrap().len() == c.value.result_type.len());
1065        }
1066        Ok(())
1067    }
1068
1069    fn command(context: &mut Context, sp!(_, c): &mut T::Command) -> Result<(), ExecutionError> {
1070        let mut acc = vec![];
1071        match &c.command {
1072            T::Command__::MoveCall(mc) => arguments(context, &mut acc, &mc.arguments),
1073            T::Command__::TransferObjects(objects, recipient) => {
1074                argument(context, &mut acc, recipient);
1075                arguments(context, &mut acc, objects);
1076            }
1077            T::Command__::SplitCoins(_, coin, amounts) => {
1078                arguments(context, &mut acc, amounts);
1079                argument(context, &mut acc, coin);
1080            }
1081            T::Command__::MergeCoins(_, target, coins) => {
1082                arguments(context, &mut acc, coins);
1083                argument(context, &mut acc, target);
1084            }
1085            T::Command__::MakeMoveVec(_, elements) => arguments(context, &mut acc, elements),
1086            T::Command__::Publish(_, _, _) => (),
1087            T::Command__::Upgrade(_, _, _, x, _) => argument(context, &mut acc, x),
1088        }
1089        let (consumed, result) = match &c.command {
1090            // make move vec does not "consume" any by-value shared objects, and can propagate
1091            // them to a later command
1092            T::Command__::MakeMoveVec(_, _) => {
1093                assert_invariant!(
1094                    c.result_type.len() == 1,
1095                    "MakeMoveVec must return a single value"
1096                );
1097                (vec![], vec![Some(acc)])
1098            }
1099            // these commands do not propagate shared objects, and consume any in the acc
1100            T::Command__::MoveCall(_)
1101            | T::Command__::TransferObjects(_, _)
1102            | T::Command__::SplitCoins(_, _, _)
1103            | T::Command__::MergeCoins(_, _, _)
1104            | T::Command__::Publish(_, _, _)
1105            | T::Command__::Upgrade(_, _, _, _, _) => (acc, vec![None; c.result_type.len()]),
1106        };
1107        c.consumed_shared_objects = consumed;
1108        context.results.push(result);
1109        Ok(())
1110    }
1111
1112    fn arguments(context: &mut Context, acc: &mut Vec<ObjectID>, args: &[T::Argument]) {
1113        for arg in args {
1114            argument(context, acc, arg)
1115        }
1116    }
1117
1118    fn argument(context: &mut Context, acc: &mut Vec<ObjectID>, sp!(_, (arg_, _)): &T::Argument) {
1119        let T::Argument__::Use(T::Usage::Move(loc)) = arg_ else {
1120            // only Move usage can take shared objects by-value since they cannot be copied
1121            return;
1122        };
1123        match loc {
1124            // no shared objects in these locations
1125            T::Location::TxContext
1126            | T::Location::GasCoin
1127            | T::Location::WithdrawalInput(_)
1128            | T::Location::PureInput(_)
1129            | T::Location::ReceivingInput(_) => (),
1130            T::Location::ObjectInput(i) => {
1131                if let Some(id) = context.inputs[*i as usize] {
1132                    acc.push(id);
1133                }
1134            }
1135
1136            T::Location::Result(i, j) => {
1137                if let Some(ids) = &context.results[*i as usize][*j as usize] {
1138                    acc.extend(ids.iter().copied());
1139                }
1140            }
1141        }
1142    }
1143}