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 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
94fn 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 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
218pub 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
228fn 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 }
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
268fn 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 T::Argument__::Borrow(false, _)
288 | T::Argument__::Use(T::Usage::Copy { .. })
289 | T::Argument__::Read(_)
290 | T::Argument__::Freeze(_) => Ok(()),
291 }
292}
293
294fn 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
320fn 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
346fn 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 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}