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