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, 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 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 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
96fn 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 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
220pub 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
230fn 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 }
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
270fn 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 T::Argument__::Borrow(false, _)
290 | T::Argument__::Use(T::Usage::Copy { .. })
291 | T::Argument__::Read(_)
292 | T::Argument__::Freeze(_) => Ok(()),
293 }
294}
295
296fn 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
322fn 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
348fn 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 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}