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, 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        bytes,
75        objects: _,
76        withdrawals: _,
77        pure,
78        receiving,
79        commands,
80    } = txn;
81    for pure in pure {
82        check_pure_input::<Mode>(bytes, pure)?;
83    }
84    for receiving in receiving {
85        check_receving_input(receiving)?;
86    }
87    let context = &mut Context::new(txn);
88    for c in commands {
89        command(context, c).map_err(|e| e.with_command_index(c.idx as usize))?;
90    }
91    Ok(())
92}
93
94//**************************************************************************************************
95// Pure bytes
96//**************************************************************************************************
97
98fn check_pure_input<Mode: ExecutionMode>(
99    bytes: &IndexSet<Vec<u8>>,
100    pure: &T::PureInput,
101) -> Result<(), ExecutionError> {
102    let T::PureInput {
103        original_input_index,
104        byte_index,
105        ty,
106        constraint,
107    } = pure;
108    let Some(bcs_bytes) = bytes.get_index(*byte_index) else {
109        invariant_violation!(
110            "Unbound byte index {} for pure input at index {}",
111            byte_index,
112            original_input_index.0
113        );
114    };
115    let BytesConstraint { command, argument } = constraint;
116    check_pure_bytes::<Mode>(*argument, bcs_bytes, ty)
117        .map_err(|e| e.with_command_index(*command as usize))
118}
119
120fn check_pure_bytes<Mode: ExecutionMode>(
121    command_arg_idx: u16,
122    bytes: &[u8],
123    constraint: &Type,
124) -> Result<(), ExecutionError> {
125    assert_invariant!(
126        !matches!(constraint, Type::Reference(_, _)),
127        "references should not be added as a constraint"
128    );
129    if Mode::allow_arbitrary_values() {
130        return Ok(());
131    }
132    let Some(layout) = primitive_serialization_layout(constraint)? else {
133        let msg = format!(
134            "Invalid usage of `Pure` argument for a non-primitive argument type at index {command_arg_idx}.",
135        );
136        return Err(ExecutionError::new_with_source(
137            ExecutionErrorKind::command_argument_error(
138                CommandArgumentError::InvalidUsageOfPureArg,
139                command_arg_idx,
140            ),
141            msg,
142        ));
143    };
144    bcs_argument_validate(bytes, command_arg_idx, layout)?;
145    Ok(())
146}
147
148fn primitive_serialization_layout(
149    param_ty: &Type,
150) -> Result<Option<PrimitiveArgumentLayout>, ExecutionError> {
151    Ok(match param_ty {
152        Type::Signer => return Ok(None),
153        Type::Reference(_, _) => {
154            invariant_violation!("references should not be added as a constraint")
155        }
156        Type::Bool => Some(PrimitiveArgumentLayout::Bool),
157        Type::U8 => Some(PrimitiveArgumentLayout::U8),
158        Type::U16 => Some(PrimitiveArgumentLayout::U16),
159        Type::U32 => Some(PrimitiveArgumentLayout::U32),
160        Type::U64 => Some(PrimitiveArgumentLayout::U64),
161        Type::U128 => Some(PrimitiveArgumentLayout::U128),
162        Type::U256 => Some(PrimitiveArgumentLayout::U256),
163        Type::Address => Some(PrimitiveArgumentLayout::Address),
164
165        Type::Vector(v) => {
166            let info_opt = primitive_serialization_layout(&v.element_type)?;
167            info_opt.map(|layout| PrimitiveArgumentLayout::Vector(Box::new(layout)))
168        }
169        Type::Datatype(dt) => {
170            let resolved = dt.qualified_ident();
171            // is option of a string
172            if resolved == RESOLVED_STD_OPTION && dt.type_arguments.len() == 1 {
173                let info_opt = primitive_serialization_layout(&dt.type_arguments[0])?;
174                info_opt.map(|layout| PrimitiveArgumentLayout::Option(Box::new(layout)))
175            } else if dt.type_arguments.is_empty() {
176                if resolved == RESOLVED_SUI_ID {
177                    Some(PrimitiveArgumentLayout::Address)
178                } else if resolved == RESOLVED_ASCII_STR {
179                    Some(PrimitiveArgumentLayout::Ascii)
180                } else if resolved == RESOLVED_UTF8_STR {
181                    Some(PrimitiveArgumentLayout::UTF8)
182                } else {
183                    None
184                }
185            } else {
186                None
187            }
188        }
189    })
190}
191
192fn check_receving_input(receiving: &T::ReceivingInput) -> Result<(), ExecutionError> {
193    let T::ReceivingInput {
194        original_input_index: _,
195        object_ref: _,
196        ty,
197        constraint,
198    } = receiving;
199    let BytesConstraint { command, argument } = constraint;
200    check_receiving(*argument, ty).map_err(|e| e.with_command_index(*command as usize))
201}
202
203fn check_receiving(command_arg_idx: u16, constraint: &Type) -> Result<(), ExecutionError> {
204    if is_valid_receiving(constraint) {
205        Ok(())
206    } else {
207        Err(command_argument_error(
208            CommandArgumentError::TypeMismatch,
209            command_arg_idx as usize,
210        ))
211    }
212}
213
214pub fn is_valid_pure_type(constraint: &Type) -> Result<bool, ExecutionError> {
215    Ok(primitive_serialization_layout(constraint)?.is_some())
216}
217
218/// Returns true if a type is a `Receiving<t>` where `t` has `key`
219pub fn is_valid_receiving(constraint: &Type) -> bool {
220    let Type::Datatype(dt) = constraint else {
221        return false;
222    };
223    dt.qualified_ident() == RESOLVED_RECEIVING_STRUCT
224        && dt.type_arguments.len() == 1
225        && dt.type_arguments[0].abilities().has_key()
226}
227
228//**************************************************************************************************
229// Object usage
230//**************************************************************************************************
231
232fn command(context: &mut Context, sp!(_, c): &T::Command) -> Result<(), ExecutionError> {
233    match &c.command {
234        T::Command__::MoveCall(mc) => {
235            check_obj_usages(context, &mc.arguments)?;
236            check_gas_by_values(&mc.arguments)?;
237        }
238        T::Command__::TransferObjects(objects, recipient) => {
239            check_obj_usages(context, objects)?;
240            check_obj_usage(context, recipient)?;
241            // gas can be used by value in TransferObjects
242        }
243        T::Command__::SplitCoins(_, coin, amounts) => {
244            check_obj_usage(context, coin)?;
245            check_obj_usages(context, amounts)?;
246            check_gas_by_value(coin)?;
247            check_gas_by_values(amounts)?;
248        }
249        T::Command__::MergeCoins(_, target, coins) => {
250            check_obj_usage(context, target)?;
251            check_obj_usages(context, coins)?;
252            check_gas_by_value(target)?;
253            check_gas_by_values(coins)?;
254        }
255        T::Command__::MakeMoveVec(_, xs) => {
256            check_obj_usages(context, xs)?;
257            check_gas_by_values(xs)?;
258        }
259        T::Command__::Publish(_, _, _) => (),
260        T::Command__::Upgrade(_, _, _, x, _) => {
261            check_obj_usage(context, x)?;
262            check_gas_by_value(x)?;
263        }
264    }
265    Ok(())
266}
267
268// Checks for valid by-mut-ref and by-value usage of input objects
269fn check_obj_usages(
270    context: &mut Context,
271    arguments: &[T::Argument],
272) -> Result<(), ExecutionError> {
273    for arg in arguments {
274        check_obj_usage(context, arg)?;
275    }
276    Ok(())
277}
278
279fn check_obj_usage(context: &mut Context, arg: &T::Argument) -> Result<(), ExecutionError> {
280    match &arg.value.0 {
281        T::Argument__::Borrow(true, l) => check_obj_by_mut_ref(context, arg.idx, l),
282        T::Argument__::Use(T::Usage::Move(l)) => check_by_value(context, arg.idx, l),
283        // We do not care about
284        // - immutable object borrowing
285        // - copying/read ref (since you cannot copy objects)
286        // - freeze (since an input object cannot be a reference without a borrow)
287        T::Argument__::Borrow(false, _)
288        | T::Argument__::Use(T::Usage::Copy { .. })
289        | T::Argument__::Read(_)
290        | T::Argument__::Freeze(_) => Ok(()),
291    }
292}
293
294// Checks for valid by-mut-ref usage of input objects
295fn check_obj_by_mut_ref(
296    context: &mut Context,
297    arg_idx: u16,
298    location: &T::Location,
299) -> Result<(), ExecutionError> {
300    match location {
301        T::Location::WithdrawalInput(_)
302        | T::Location::PureInput(_)
303        | T::Location::ReceivingInput(_)
304        | T::Location::TxContext
305        | T::Location::GasCoin
306        | T::Location::Result(_, _) => Ok(()),
307        T::Location::ObjectInput(idx) => {
308            if !context.objects[*idx as usize].allow_by_mut_ref {
309                Err(command_argument_error(
310                    CommandArgumentError::InvalidObjectByMutRef,
311                    arg_idx as usize,
312                ))
313            } else {
314                Ok(())
315            }
316        }
317    }
318}
319
320// Checks for valid by-value usage of input objects
321fn check_by_value(
322    context: &mut Context,
323    arg_idx: u16,
324    location: &T::Location,
325) -> Result<(), ExecutionError> {
326    match location {
327        T::Location::GasCoin
328        | T::Location::Result(_, _)
329        | T::Location::TxContext
330        | T::Location::WithdrawalInput(_)
331        | T::Location::PureInput(_)
332        | T::Location::ReceivingInput(_) => Ok(()),
333        T::Location::ObjectInput(idx) => {
334            if !context.objects[*idx as usize].allow_by_value {
335                Err(command_argument_error(
336                    CommandArgumentError::InvalidObjectByValue,
337                    arg_idx as usize,
338                ))
339            } else {
340                Ok(())
341            }
342        }
343    }
344}
345
346// Checks for no by value usage of gas
347fn check_gas_by_values(arguments: &[T::Argument]) -> Result<(), ExecutionError> {
348    for arg in arguments {
349        check_gas_by_value(arg)?;
350    }
351    Ok(())
352}
353
354fn check_gas_by_value(arg: &T::Argument) -> Result<(), ExecutionError> {
355    match &arg.value.0 {
356        T::Argument__::Use(T::Usage::Move(l)) => check_gas_by_value_loc(arg.idx, l),
357        // We do not care about the read/freeze case since they cannot move an object input
358        T::Argument__::Borrow(_, _)
359        | T::Argument__::Use(T::Usage::Copy { .. })
360        | T::Argument__::Read(_)
361        | T::Argument__::Freeze(_) => Ok(()),
362    }
363}
364
365fn check_gas_by_value_loc(idx: u16, location: &T::Location) -> Result<(), ExecutionError> {
366    match location {
367        T::Location::GasCoin => Err(command_argument_error(
368            CommandArgumentError::InvalidGasCoinUsage,
369            idx as usize,
370        )),
371        T::Location::TxContext
372        | T::Location::ObjectInput(_)
373        | T::Location::WithdrawalInput(_)
374        | T::Location::PureInput(_)
375        | T::Location::ReceivingInput(_)
376        | T::Location::Result(_, _) => Ok(()),
377    }
378}