1use crate::{
5 execution_mode::ExecutionMode,
6 sp,
7 static_programmable_transactions::execution::context::{
8 PrimitiveArgumentLayout, bcs_argument_validate,
9 },
10 static_programmable_transactions::{
11 env::Env,
12 loading::ast::{ObjectMutability, Type},
13 typing::ast::{self as T, BytesConstraint, ObjectArg},
14 },
15};
16use indexmap::IndexSet;
17use sui_types::{
18 SUI_FRAMEWORK_ADDRESS,
19 base_types::{RESOLVED_ASCII_STR, RESOLVED_STD_OPTION, RESOLVED_UTF8_STR},
20 coin::{COIN_MODULE_NAME, SEND_FUNDS_FUNC_NAME},
21 error::{ExecutionError, SafeIndex, command_argument_error},
22 execution_status::{CommandArgumentError, ExecutionErrorKind},
23 id::RESOLVED_SUI_ID,
24 transfer::RESOLVED_RECEIVING_STRUCT,
25};
26
27struct ObjectUsage {
28 allow_by_value: bool,
29 allow_by_mut_ref: bool,
30}
31
32struct Context {
33 objects: Vec<ObjectUsage>,
34}
35
36impl Context {
37 fn new(txn: &T::Transaction) -> Self {
38 let objects = txn
39 .objects
40 .iter()
41 .map(|object_input| match &object_input.arg {
42 ObjectArg::ImmObject(_) => ObjectUsage {
43 allow_by_value: false,
44 allow_by_mut_ref: false,
45 },
46 ObjectArg::OwnedObject(_) => ObjectUsage {
47 allow_by_value: true,
48 allow_by_mut_ref: true,
49 },
50 ObjectArg::SharedObject { mutability, .. } => ObjectUsage {
51 allow_by_value: match mutability {
52 ObjectMutability::Mutable => true,
53 ObjectMutability::Immutable => false,
54 ObjectMutability::NonExclusiveWrite => true,
57 },
58 allow_by_mut_ref: match mutability {
59 ObjectMutability::Mutable => true,
60 ObjectMutability::Immutable => false,
61 ObjectMutability::NonExclusiveWrite => true,
62 },
63 },
64 })
65 .collect();
66 Self { objects }
67 }
68}
69
70pub fn verify<Mode: ExecutionMode>(env: &Env, txn: &T::Transaction) -> Result<(), ExecutionError> {
78 let T::Transaction {
79 gas_payment: _,
80 bytes,
81 objects: _,
82 withdrawals: _,
83 pure,
84 receiving,
85 withdrawal_compatibility_conversions: _,
86 commands,
87 } = txn;
88 for pure in pure {
89 check_pure_input::<Mode>(bytes, pure)?;
90 }
91 for receiving in receiving {
92 check_receiving_input(receiving)?;
93 }
94 let context = &mut Context::new(txn);
95 for c in commands {
96 command(env, context, c).map_err(|e| e.with_command_index(c.idx as usize))?;
97 }
98 Ok(())
99}
100
101fn check_pure_input<Mode: ExecutionMode>(
106 bytes: &IndexSet<Vec<u8>>,
107 pure: &T::PureInput,
108) -> Result<(), ExecutionError> {
109 let T::PureInput {
110 original_input_index,
111 byte_index,
112 ty,
113 constraint,
114 } = pure;
115 let Some(bcs_bytes) = bytes.get_index(*byte_index) else {
116 invariant_violation!(
117 "Unbound byte index {} for pure input at index {}",
118 byte_index,
119 original_input_index.0
120 );
121 };
122 let BytesConstraint { command, argument } = constraint;
123 check_pure_bytes::<Mode>(*argument, bcs_bytes, ty)
124 .map_err(|e| e.with_command_index(*command as usize))
125}
126
127fn check_pure_bytes<Mode: ExecutionMode>(
128 command_arg_idx: u16,
129 bytes: &[u8],
130 constraint: &Type,
131) -> Result<(), ExecutionError> {
132 assert_invariant!(
133 !matches!(constraint, Type::Reference(_, _)),
134 "references should not be added as a constraint"
135 );
136 if Mode::allow_arbitrary_values() {
137 return Ok(());
138 }
139 let Some(layout) = primitive_serialization_layout(constraint)? else {
140 let msg = format!(
141 "Invalid usage of `Pure` argument for a non-primitive argument type at index {command_arg_idx}.",
142 );
143 return Err(ExecutionError::new_with_source(
144 ExecutionErrorKind::command_argument_error(
145 CommandArgumentError::InvalidUsageOfPureArg,
146 command_arg_idx,
147 ),
148 msg,
149 ));
150 };
151 bcs_argument_validate(bytes, command_arg_idx, layout)?;
152 Ok(())
153}
154
155fn primitive_serialization_layout(
156 param_ty: &Type,
157) -> Result<Option<PrimitiveArgumentLayout>, ExecutionError> {
158 Ok(match param_ty {
159 Type::Signer => return Ok(None),
160 Type::Reference(_, _) => {
161 invariant_violation!("references should not be added as a constraint")
162 }
163 Type::Bool => Some(PrimitiveArgumentLayout::Bool),
164 Type::U8 => Some(PrimitiveArgumentLayout::U8),
165 Type::U16 => Some(PrimitiveArgumentLayout::U16),
166 Type::U32 => Some(PrimitiveArgumentLayout::U32),
167 Type::U64 => Some(PrimitiveArgumentLayout::U64),
168 Type::U128 => Some(PrimitiveArgumentLayout::U128),
169 Type::U256 => Some(PrimitiveArgumentLayout::U256),
170 Type::Address => Some(PrimitiveArgumentLayout::Address),
171
172 Type::Vector(v) => {
173 let info_opt = primitive_serialization_layout(&v.element_type)?;
174 info_opt.map(|layout| PrimitiveArgumentLayout::Vector(Box::new(layout)))
175 }
176 Type::Datatype(dt) => {
177 let resolved = dt.qualified_ident();
178 if resolved == RESOLVED_STD_OPTION && dt.type_arguments.len() == 1 {
180 let info_opt = primitive_serialization_layout(dt.type_arguments.first().unwrap())?;
181 info_opt.map(|layout| PrimitiveArgumentLayout::Option(Box::new(layout)))
182 } else if dt.type_arguments.is_empty() {
183 if resolved == RESOLVED_SUI_ID {
184 Some(PrimitiveArgumentLayout::Address)
185 } else if resolved == RESOLVED_ASCII_STR {
186 Some(PrimitiveArgumentLayout::Ascii)
187 } else if resolved == RESOLVED_UTF8_STR {
188 Some(PrimitiveArgumentLayout::UTF8)
189 } else {
190 None
191 }
192 } else {
193 None
194 }
195 }
196 })
197}
198
199fn check_receiving_input(receiving: &T::ReceivingInput) -> Result<(), ExecutionError> {
200 let T::ReceivingInput {
201 original_input_index: _,
202 object_ref: _,
203 ty,
204 constraint,
205 } = receiving;
206 let BytesConstraint { command, argument } = constraint;
207 check_receiving(*argument, ty).map_err(|e| e.with_command_index(*command as usize))
208}
209
210fn check_receiving(command_arg_idx: u16, constraint: &Type) -> Result<(), ExecutionError> {
211 if is_valid_receiving(constraint) {
212 Ok(())
213 } else {
214 Err(command_argument_error(
215 CommandArgumentError::TypeMismatch,
216 command_arg_idx as usize,
217 ))
218 }
219}
220
221pub fn is_valid_pure_type(constraint: &Type) -> Result<bool, ExecutionError> {
222 Ok(primitive_serialization_layout(constraint)?.is_some())
223}
224
225pub fn is_valid_receiving(constraint: &Type) -> bool {
227 let Type::Datatype(dt) = constraint else {
228 return false;
229 };
230 dt.qualified_ident() == RESOLVED_RECEIVING_STRUCT
231 && dt.type_arguments.len() == 1
232 && dt.type_arguments.first().unwrap().abilities().has_key()
233}
234
235fn command(env: &Env, context: &mut Context, sp!(_, c): &T::Command) -> Result<(), ExecutionError> {
240 match &c.command {
241 T::Command__::MoveCall(mc) => {
242 check_obj_usages(context, &mc.arguments)?;
243 if !(env.protocol_config.enable_accumulators() && is_coin_send_funds(&mc.function)) {
244 check_gas_by_values(&mc.arguments)?;
246 }
247 }
248 T::Command__::TransferObjects(objects, recipient) => {
249 check_obj_usages(context, objects)?;
250 check_obj_usage(context, recipient)?;
251 }
253 T::Command__::SplitCoins(_, coin, amounts) => {
254 check_obj_usage(context, coin)?;
255 check_obj_usages(context, amounts)?;
256 check_gas_by_value(coin)?;
257 check_gas_by_values(amounts)?;
258 }
259 T::Command__::MergeCoins(_, target, coins) => {
260 check_obj_usage(context, target)?;
261 check_obj_usages(context, coins)?;
262 check_gas_by_value(target)?;
263 check_gas_by_values(coins)?;
264 }
265 T::Command__::MakeMoveVec(_, xs) => {
266 check_obj_usages(context, xs)?;
267 check_gas_by_values(xs)?;
268 }
269 T::Command__::Publish(_, _, _) => (),
270 T::Command__::Upgrade(_, _, _, x, _) => {
271 check_obj_usage(context, x)?;
272 check_gas_by_value(x)?;
273 }
274 }
275 Ok(())
276}
277
278fn check_obj_usages(
280 context: &mut Context,
281 arguments: &[T::Argument],
282) -> Result<(), ExecutionError> {
283 for arg in arguments {
284 check_obj_usage(context, arg)?;
285 }
286 Ok(())
287}
288
289fn check_obj_usage(context: &mut Context, arg: &T::Argument) -> Result<(), ExecutionError> {
290 match &arg.value.0 {
291 T::Argument__::Borrow(true, l) => check_obj_by_mut_ref(context, arg.idx, l),
292 T::Argument__::Use(T::Usage::Move(l)) => check_by_value(context, arg.idx, l),
293 T::Argument__::Borrow(false, _)
298 | T::Argument__::Use(T::Usage::Copy { .. })
299 | T::Argument__::Read(_)
300 | T::Argument__::Freeze(_) => Ok(()),
301 }
302}
303
304fn check_obj_by_mut_ref(
306 context: &mut Context,
307 arg_idx: u16,
308 location: &T::Location,
309) -> Result<(), ExecutionError> {
310 match location {
311 T::Location::WithdrawalInput(_)
312 | T::Location::PureInput(_)
313 | T::Location::ReceivingInput(_)
314 | T::Location::TxContext
315 | T::Location::GasCoin
316 | T::Location::Result(_, _) => Ok(()),
317 T::Location::ObjectInput(idx) => {
318 if !context.objects.safe_get(*idx as usize)?.allow_by_mut_ref {
319 Err(command_argument_error(
320 CommandArgumentError::InvalidObjectByMutRef,
321 arg_idx as usize,
322 ))
323 } else {
324 Ok(())
325 }
326 }
327 }
328}
329
330fn check_by_value(
332 context: &mut Context,
333 arg_idx: u16,
334 location: &T::Location,
335) -> Result<(), ExecutionError> {
336 match location {
337 T::Location::GasCoin
338 | T::Location::Result(_, _)
339 | T::Location::TxContext
340 | T::Location::WithdrawalInput(_)
341 | T::Location::PureInput(_)
342 | T::Location::ReceivingInput(_) => Ok(()),
343 T::Location::ObjectInput(idx) => {
344 if !context.objects.safe_get(*idx as usize)?.allow_by_value {
345 Err(command_argument_error(
346 CommandArgumentError::InvalidObjectByValue,
347 arg_idx as usize,
348 ))
349 } else {
350 Ok(())
351 }
352 }
353 }
354}
355
356fn check_gas_by_values(arguments: &[T::Argument]) -> Result<(), ExecutionError> {
358 for arg in arguments {
359 check_gas_by_value(arg)?;
360 }
361 Ok(())
362}
363
364fn check_gas_by_value(arg: &T::Argument) -> Result<(), ExecutionError> {
365 match &arg.value.0 {
366 T::Argument__::Use(T::Usage::Move(l)) => check_gas_by_value_loc(arg.idx, l),
367 T::Argument__::Borrow(_, _)
369 | T::Argument__::Use(T::Usage::Copy { .. })
370 | T::Argument__::Read(_)
371 | T::Argument__::Freeze(_) => Ok(()),
372 }
373}
374
375fn check_gas_by_value_loc(idx: u16, location: &T::Location) -> Result<(), ExecutionError> {
376 match location {
377 T::Location::GasCoin => Err(command_argument_error(
378 CommandArgumentError::InvalidGasCoinUsage,
379 idx as usize,
380 )),
381 T::Location::TxContext
382 | T::Location::ObjectInput(_)
383 | T::Location::WithdrawalInput(_)
384 | T::Location::PureInput(_)
385 | T::Location::ReceivingInput(_)
386 | T::Location::Result(_, _) => Ok(()),
387 }
388}
389
390pub fn is_coin_send_funds(function: &T::LoadedFunction) -> bool {
391 function.original_mid.address() == &SUI_FRAMEWORK_ADDRESS
392 && function.original_mid.name() == COIN_MODULE_NAME
393 && function.name.as_ident_str() == SEND_FUNDS_FUNC_NAME
394}