sui_adapter_latest/static_programmable_transactions/typing/verify/
input_arguments.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::{
5    execution_mode::ExecutionMode,
6    programmable_transactions::execution::{PrimitiveArgumentLayout, bcs_argument_validate},
7    sp,
8    static_programmable_transactions::{
9        env::Env,
10        loading::ast::{ObjectMutability, Type},
11        typing::ast::{self as T, BytesConstraint, ObjectArg},
12    },
13};
14use indexmap::IndexSet;
15use sui_types::{
16    base_types::{RESOLVED_ASCII_STR, RESOLVED_STD_OPTION, RESOLVED_UTF8_STR},
17    error::{ExecutionError, ExecutionErrorKind, SafeIndex, command_argument_error},
18    execution_status::CommandArgumentError,
19    id::RESOLVED_SUI_ID,
20    transfer::RESOLVED_RECEIVING_STRUCT,
21};
22
23struct ObjectUsage {
24    allow_by_value: bool,
25    allow_by_mut_ref: bool,
26}
27
28struct Context {
29    objects: Vec<ObjectUsage>,
30}
31
32impl Context {
33    fn new(txn: &T::Transaction) -> Self {
34        let objects = txn
35            .objects
36            .iter()
37            .map(|object_input| match &object_input.arg {
38                ObjectArg::ImmObject(_) => ObjectUsage {
39                    allow_by_value: false,
40                    allow_by_mut_ref: false,
41                },
42                ObjectArg::OwnedObject(_) => ObjectUsage {
43                    allow_by_value: true,
44                    allow_by_mut_ref: true,
45                },
46                ObjectArg::SharedObject { mutability, .. } => ObjectUsage {
47                    allow_by_value: match mutability {
48                        ObjectMutability::Mutable => true,
49                        ObjectMutability::Immutable => false,
50                        // NonExclusiveWrite can be taken by value, but unless it is re-shared
51                        // with no mutations, the transaction will abort.
52                        ObjectMutability::NonExclusiveWrite => true,
53                    },
54                    allow_by_mut_ref: match mutability {
55                        ObjectMutability::Mutable => true,
56                        ObjectMutability::Immutable => false,
57                        ObjectMutability::NonExclusiveWrite => true,
58                    },
59                },
60            })
61            .collect();
62        Self { objects }
63    }
64}
65
66/// Verifies two properties for input objects:
67/// 1. That the `Pure` inputs can be serialized to the type inferred and that the type is
68///    permissible
69///    - Can be relaxed under certain execution modes
70/// 2. That any `Object` arguments are used validly. This means mutable references are taken only
71///    on mutable objects. And that the gas coin is only taken by value in transfer objects
72pub fn verify<Mode: ExecutionMode>(_env: &Env, txn: &T::Transaction) -> Result<(), ExecutionError> {
73    let T::Transaction {
74        gas_coin: _,
75        bytes,
76        objects: _,
77        withdrawals: _,
78        pure,
79        receiving,
80        withdrawal_compatibility_conversions: _,
81        commands,
82    } = txn;
83    for pure in pure {
84        check_pure_input::<Mode>(bytes, pure)?;
85    }
86    for receiving in receiving {
87        check_receving_input(receiving)?;
88    }
89    let context = &mut Context::new(txn);
90    for c in commands {
91        command(context, c).map_err(|e| e.with_command_index(c.idx as usize))?;
92    }
93    Ok(())
94}
95
96//**************************************************************************************************
97// Pure bytes
98//**************************************************************************************************
99
100fn check_pure_input<Mode: ExecutionMode>(
101    bytes: &IndexSet<Vec<u8>>,
102    pure: &T::PureInput,
103) -> Result<(), ExecutionError> {
104    let T::PureInput {
105        original_input_index,
106        byte_index,
107        ty,
108        constraint,
109    } = pure;
110    let Some(bcs_bytes) = bytes.get_index(*byte_index) else {
111        invariant_violation!(
112            "Unbound byte index {} for pure input at index {}",
113            byte_index,
114            original_input_index.0
115        );
116    };
117    let BytesConstraint { command, argument } = constraint;
118    check_pure_bytes::<Mode>(*argument, bcs_bytes, ty)
119        .map_err(|e| e.with_command_index(*command as usize))
120}
121
122fn check_pure_bytes<Mode: ExecutionMode>(
123    command_arg_idx: u16,
124    bytes: &[u8],
125    constraint: &Type,
126) -> Result<(), ExecutionError> {
127    assert_invariant!(
128        !matches!(constraint, Type::Reference(_, _)),
129        "references should not be added as a constraint"
130    );
131    if Mode::allow_arbitrary_values() {
132        return Ok(());
133    }
134    let Some(layout) = primitive_serialization_layout(constraint)? else {
135        let msg = format!(
136            "Invalid usage of `Pure` argument for a non-primitive argument type at index {command_arg_idx}.",
137        );
138        return Err(ExecutionError::new_with_source(
139            ExecutionErrorKind::command_argument_error(
140                CommandArgumentError::InvalidUsageOfPureArg,
141                command_arg_idx,
142            ),
143            msg,
144        ));
145    };
146    bcs_argument_validate(bytes, command_arg_idx, layout)?;
147    Ok(())
148}
149
150fn primitive_serialization_layout(
151    param_ty: &Type,
152) -> Result<Option<PrimitiveArgumentLayout>, ExecutionError> {
153    Ok(match param_ty {
154        Type::Signer => return Ok(None),
155        Type::Reference(_, _) => {
156            invariant_violation!("references should not be added as a constraint")
157        }
158        Type::Bool => Some(PrimitiveArgumentLayout::Bool),
159        Type::U8 => Some(PrimitiveArgumentLayout::U8),
160        Type::U16 => Some(PrimitiveArgumentLayout::U16),
161        Type::U32 => Some(PrimitiveArgumentLayout::U32),
162        Type::U64 => Some(PrimitiveArgumentLayout::U64),
163        Type::U128 => Some(PrimitiveArgumentLayout::U128),
164        Type::U256 => Some(PrimitiveArgumentLayout::U256),
165        Type::Address => Some(PrimitiveArgumentLayout::Address),
166
167        Type::Vector(v) => {
168            let info_opt = primitive_serialization_layout(&v.element_type)?;
169            info_opt.map(|layout| PrimitiveArgumentLayout::Vector(Box::new(layout)))
170        }
171        Type::Datatype(dt) => {
172            let resolved = dt.qualified_ident();
173            // is option of a string
174            if resolved == RESOLVED_STD_OPTION && dt.type_arguments.len() == 1 {
175                let info_opt = primitive_serialization_layout(dt.type_arguments.first().unwrap())?;
176                info_opt.map(|layout| PrimitiveArgumentLayout::Option(Box::new(layout)))
177            } else if dt.type_arguments.is_empty() {
178                if resolved == RESOLVED_SUI_ID {
179                    Some(PrimitiveArgumentLayout::Address)
180                } else if resolved == RESOLVED_ASCII_STR {
181                    Some(PrimitiveArgumentLayout::Ascii)
182                } else if resolved == RESOLVED_UTF8_STR {
183                    Some(PrimitiveArgumentLayout::UTF8)
184                } else {
185                    None
186                }
187            } else {
188                None
189            }
190        }
191    })
192}
193
194fn check_receving_input(receiving: &T::ReceivingInput) -> Result<(), ExecutionError> {
195    let T::ReceivingInput {
196        original_input_index: _,
197        object_ref: _,
198        ty,
199        constraint,
200    } = receiving;
201    let BytesConstraint { command, argument } = constraint;
202    check_receiving(*argument, ty).map_err(|e| e.with_command_index(*command as usize))
203}
204
205fn check_receiving(command_arg_idx: u16, constraint: &Type) -> Result<(), ExecutionError> {
206    if is_valid_receiving(constraint) {
207        Ok(())
208    } else {
209        Err(command_argument_error(
210            CommandArgumentError::TypeMismatch,
211            command_arg_idx as usize,
212        ))
213    }
214}
215
216pub fn is_valid_pure_type(constraint: &Type) -> Result<bool, ExecutionError> {
217    Ok(primitive_serialization_layout(constraint)?.is_some())
218}
219
220/// Returns true if a type is a `Receiving<t>` where `t` has `key`
221pub fn is_valid_receiving(constraint: &Type) -> bool {
222    let Type::Datatype(dt) = constraint else {
223        return false;
224    };
225    dt.qualified_ident() == RESOLVED_RECEIVING_STRUCT
226        && dt.type_arguments.len() == 1
227        && dt.type_arguments.first().unwrap().abilities().has_key()
228}
229
230//**************************************************************************************************
231// Object usage
232//**************************************************************************************************
233
234fn command(context: &mut Context, sp!(_, c): &T::Command) -> Result<(), ExecutionError> {
235    match &c.command {
236        T::Command__::MoveCall(mc) => {
237            check_obj_usages(context, &mc.arguments)?;
238            check_gas_by_values(&mc.arguments)?;
239        }
240        T::Command__::TransferObjects(objects, recipient) => {
241            check_obj_usages(context, objects)?;
242            check_obj_usage(context, recipient)?;
243            // gas can be used by value in TransferObjects
244        }
245        T::Command__::SplitCoins(_, coin, amounts) => {
246            check_obj_usage(context, coin)?;
247            check_obj_usages(context, amounts)?;
248            check_gas_by_value(coin)?;
249            check_gas_by_values(amounts)?;
250        }
251        T::Command__::MergeCoins(_, target, coins) => {
252            check_obj_usage(context, target)?;
253            check_obj_usages(context, coins)?;
254            check_gas_by_value(target)?;
255            check_gas_by_values(coins)?;
256        }
257        T::Command__::MakeMoveVec(_, xs) => {
258            check_obj_usages(context, xs)?;
259            check_gas_by_values(xs)?;
260        }
261        T::Command__::Publish(_, _, _) => (),
262        T::Command__::Upgrade(_, _, _, x, _) => {
263            check_obj_usage(context, x)?;
264            check_gas_by_value(x)?;
265        }
266    }
267    Ok(())
268}
269
270// Checks for valid by-mut-ref and by-value usage of input objects
271fn check_obj_usages(
272    context: &mut Context,
273    arguments: &[T::Argument],
274) -> Result<(), ExecutionError> {
275    for arg in arguments {
276        check_obj_usage(context, arg)?;
277    }
278    Ok(())
279}
280
281fn check_obj_usage(context: &mut Context, arg: &T::Argument) -> Result<(), ExecutionError> {
282    match &arg.value.0 {
283        T::Argument__::Borrow(true, l) => check_obj_by_mut_ref(context, arg.idx, l),
284        T::Argument__::Use(T::Usage::Move(l)) => check_by_value(context, arg.idx, l),
285        // We do not care about
286        // - immutable object borrowing
287        // - copying/read ref (since you cannot copy objects)
288        // - freeze (since an input object cannot be a reference without a borrow)
289        T::Argument__::Borrow(false, _)
290        | T::Argument__::Use(T::Usage::Copy { .. })
291        | T::Argument__::Read(_)
292        | T::Argument__::Freeze(_) => Ok(()),
293    }
294}
295
296// Checks for valid by-mut-ref usage of input objects
297fn check_obj_by_mut_ref(
298    context: &mut Context,
299    arg_idx: u16,
300    location: &T::Location,
301) -> Result<(), ExecutionError> {
302    match location {
303        T::Location::WithdrawalInput(_)
304        | T::Location::PureInput(_)
305        | T::Location::ReceivingInput(_)
306        | T::Location::TxContext
307        | T::Location::GasCoin
308        | T::Location::Result(_, _) => Ok(()),
309        T::Location::ObjectInput(idx) => {
310            if !context.objects.safe_get(*idx as usize)?.allow_by_mut_ref {
311                Err(command_argument_error(
312                    CommandArgumentError::InvalidObjectByMutRef,
313                    arg_idx as usize,
314                ))
315            } else {
316                Ok(())
317            }
318        }
319    }
320}
321
322// Checks for valid by-value usage of input objects
323fn check_by_value(
324    context: &mut Context,
325    arg_idx: u16,
326    location: &T::Location,
327) -> Result<(), ExecutionError> {
328    match location {
329        T::Location::GasCoin
330        | T::Location::Result(_, _)
331        | T::Location::TxContext
332        | T::Location::WithdrawalInput(_)
333        | T::Location::PureInput(_)
334        | T::Location::ReceivingInput(_) => Ok(()),
335        T::Location::ObjectInput(idx) => {
336            if !context.objects.safe_get(*idx as usize)?.allow_by_value {
337                Err(command_argument_error(
338                    CommandArgumentError::InvalidObjectByValue,
339                    arg_idx as usize,
340                ))
341            } else {
342                Ok(())
343            }
344        }
345    }
346}
347
348// Checks for no by value usage of gas
349fn check_gas_by_values(arguments: &[T::Argument]) -> Result<(), ExecutionError> {
350    for arg in arguments {
351        check_gas_by_value(arg)?;
352    }
353    Ok(())
354}
355
356fn check_gas_by_value(arg: &T::Argument) -> Result<(), ExecutionError> {
357    match &arg.value.0 {
358        T::Argument__::Use(T::Usage::Move(l)) => check_gas_by_value_loc(arg.idx, l),
359        // We do not care about the read/freeze case since they cannot move an object input
360        T::Argument__::Borrow(_, _)
361        | T::Argument__::Use(T::Usage::Copy { .. })
362        | T::Argument__::Read(_)
363        | T::Argument__::Freeze(_) => Ok(()),
364    }
365}
366
367fn check_gas_by_value_loc(idx: u16, location: &T::Location) -> Result<(), ExecutionError> {
368    match location {
369        T::Location::GasCoin => Err(command_argument_error(
370            CommandArgumentError::InvalidGasCoinUsage,
371            idx as usize,
372        )),
373        T::Location::TxContext
374        | T::Location::ObjectInput(_)
375        | T::Location::WithdrawalInput(_)
376        | T::Location::PureInput(_)
377        | T::Location::ReceivingInput(_)
378        | T::Location::Result(_, _) => Ok(()),
379    }
380}