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