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::Type,
13 typing::ast::{self as T, BytesConstraint},
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::{ExecutionErrorTrait, 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| {
42 let allow_by_value = object_input.arg.refined_permissions.can_use_mutably();
43 let allow_by_mut_ref = object_input.arg.refined_permissions.can_use_mutably();
44 ObjectUsage {
45 allow_by_value,
46 allow_by_mut_ref,
47 }
48 })
49 .collect();
50 Self { objects }
51 }
52}
53
54pub fn verify<Mode: ExecutionMode>(
62 env: &Env<Mode>,
63 txn: &T::Transaction,
64) -> Result<(), Mode::Error> {
65 let T::Transaction {
66 gas_payment: _,
67 bytes,
68 objects: _,
69 withdrawals: _,
70 pure,
71 receiving,
72 withdrawal_compatibility_conversions: _,
73 original_command_len: _,
74 commands,
75 unified_linkage: _,
76 } = txn;
77 for pure in pure {
78 check_pure_input::<Mode>(bytes, pure)?;
79 }
80 for receiving in receiving {
81 check_receiving_input(receiving)?;
82 }
83 let context = &mut Context::new(txn);
84 for c in commands {
85 command(env, context, c).map_err(|e| e.with_command_index(c.idx as usize))?;
86 }
87 Ok(())
88}
89
90fn check_pure_input<Mode: ExecutionMode>(
95 bytes: &IndexSet<Vec<u8>>,
96 pure: &T::PureInput,
97) -> Result<(), Mode::Error> {
98 let T::PureInput {
99 original_input_index,
100 byte_index,
101 ty,
102 constraint,
103 } = pure;
104 let Some(bcs_bytes) = bytes.get_index(*byte_index) else {
105 invariant_violation!(
106 "Unbound byte index {} for pure input at index {}",
107 byte_index,
108 original_input_index.0
109 );
110 };
111 let BytesConstraint { command, argument } = constraint;
112 check_pure_bytes::<Mode>(*argument, bcs_bytes, ty)
113 .map_err(|e| e.with_command_index(*command as usize))
114}
115
116fn check_pure_bytes<Mode: ExecutionMode>(
117 command_arg_idx: u16,
118 bytes: &[u8],
119 constraint: &Type,
120) -> Result<(), Mode::Error> {
121 assert_invariant!(
122 !matches!(constraint, Type::Reference(_, _)),
123 "references should not be added as a constraint"
124 );
125 if Mode::allow_arbitrary_values() {
126 return Ok(());
127 }
128 let Some(layout) = primitive_serialization_layout::<Mode::Error>(constraint)? else {
129 let msg = format!(
130 "Invalid usage of `Pure` argument for a non-primitive argument type at index {command_arg_idx}.",
131 );
132 return Err(Mode::Error::new_with_source(
133 ExecutionErrorKind::command_argument_error(
134 CommandArgumentError::InvalidUsageOfPureArg,
135 command_arg_idx,
136 ),
137 msg,
138 ));
139 };
140 bcs_argument_validate(bytes, command_arg_idx, layout)?;
141 Ok(())
142}
143
144fn primitive_serialization_layout<E: ExecutionErrorTrait>(
145 param_ty: &Type,
146) -> Result<Option<PrimitiveArgumentLayout>, E> {
147 Ok(match param_ty {
148 Type::Signer => return Ok(None),
149 Type::Reference(_, _) => {
150 invariant_violation!("references should not be added as a constraint")
151 }
152 Type::Bool => Some(PrimitiveArgumentLayout::Bool),
153 Type::U8 => Some(PrimitiveArgumentLayout::U8),
154 Type::U16 => Some(PrimitiveArgumentLayout::U16),
155 Type::U32 => Some(PrimitiveArgumentLayout::U32),
156 Type::U64 => Some(PrimitiveArgumentLayout::U64),
157 Type::U128 => Some(PrimitiveArgumentLayout::U128),
158 Type::U256 => Some(PrimitiveArgumentLayout::U256),
159 Type::Address => Some(PrimitiveArgumentLayout::Address),
160
161 Type::Vector(v) => {
162 let info_opt = primitive_serialization_layout::<E>(&v.element_type)?;
163 info_opt.map(|layout| PrimitiveArgumentLayout::Vector(Box::new(layout)))
164 }
165 Type::Datatype(dt) => {
166 let resolved = dt.qualified_ident();
167 if resolved == RESOLVED_STD_OPTION && dt.type_arguments.len() == 1 {
169 let info_opt =
170 primitive_serialization_layout::<E>(dt.type_arguments.first().unwrap())?;
171 info_opt.map(|layout| PrimitiveArgumentLayout::Option(Box::new(layout)))
172 } else if dt.type_arguments.is_empty() {
173 if resolved == RESOLVED_SUI_ID {
174 Some(PrimitiveArgumentLayout::Address)
175 } else if resolved == RESOLVED_ASCII_STR {
176 Some(PrimitiveArgumentLayout::Ascii)
177 } else if resolved == RESOLVED_UTF8_STR {
178 Some(PrimitiveArgumentLayout::UTF8)
179 } else {
180 None
181 }
182 } else {
183 None
184 }
185 }
186 })
187}
188
189fn check_receiving_input<E: ExecutionErrorTrait>(receiving: &T::ReceivingInput) -> Result<(), E> {
190 let T::ReceivingInput {
191 original_input_index: _,
192 object_ref: _,
193 ty,
194 constraint,
195 } = receiving;
196 let BytesConstraint { command, argument } = constraint;
197 check_receiving::<E>(*argument, ty).map_err(|e| e.with_command_index(*command as usize))
198}
199
200fn check_receiving<E: ExecutionErrorTrait>(
201 command_arg_idx: u16,
202 constraint: &Type,
203) -> Result<(), E> {
204 if is_valid_receiving(constraint) {
205 Ok(())
206 } else {
207 Err(
208 command_argument_error(CommandArgumentError::TypeMismatch, command_arg_idx as usize)
209 .into(),
210 )
211 }
212}
213
214pub fn is_valid_pure_type<E: ExecutionErrorTrait>(constraint: &Type) -> Result<bool, E> {
215 Ok(primitive_serialization_layout::<E>(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.first().unwrap().abilities().has_key()
226}
227
228fn command<Mode: ExecutionMode>(
233 env: &Env<Mode>,
234 context: &mut Context,
235 sp!(_, c): &T::Command,
236) -> Result<(), Mode::Error> {
237 match &c.command {
238 T::Command__::MoveCall(mc) => {
239 check_obj_usages(context, &mc.arguments)?;
240 if !(env.protocol_config.enable_accumulators() && is_coin_send_funds(&mc.function)) {
241 check_gas_by_values(&mc.arguments)?;
243 }
244 }
245 T::Command__::TransferObjects(objects, recipient) => {
246 check_obj_usages(context, objects)?;
247 check_obj_usage(context, recipient)?;
248 }
250 T::Command__::SplitCoins(_, coin, amounts) => {
251 check_obj_usage(context, coin)?;
252 check_obj_usages(context, amounts)?;
253 check_gas_by_value(coin)?;
254 check_gas_by_values(amounts)?;
255 }
256 T::Command__::MergeCoins(_, target, coins) => {
257 check_obj_usage(context, target)?;
258 check_obj_usages(context, coins)?;
259 check_gas_by_value(target)?;
260 check_gas_by_values(coins)?;
261 }
262 T::Command__::MakeMoveVec(_, xs) => {
263 check_obj_usages(context, xs)?;
264 check_gas_by_values(xs)?;
265 }
266 T::Command__::Publish(_, _, _) => (),
267 T::Command__::Upgrade(_, _, _, x, _) => {
268 check_obj_usage(context, x)?;
269 check_gas_by_value(x)?;
270 }
271 }
272 Ok(())
273}
274
275fn check_obj_usages<E: ExecutionErrorTrait>(
277 context: &mut Context,
278 arguments: &[T::Argument],
279) -> Result<(), E> {
280 for arg in arguments {
281 check_obj_usage(context, arg)?;
282 }
283 Ok(())
284}
285
286fn check_obj_usage<E: ExecutionErrorTrait>(
287 context: &mut Context,
288 arg: &T::Argument,
289) -> Result<(), E> {
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<E: ExecutionErrorTrait>(
306 context: &mut Context,
307 arg_idx: u16,
308 location: &T::Location,
309) -> Result<(), E> {
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 .into())
324 } else {
325 Ok(())
326 }
327 }
328 }
329}
330
331fn check_by_value<E: ExecutionErrorTrait>(
333 context: &mut Context,
334 arg_idx: u16,
335 location: &T::Location,
336) -> Result<(), E> {
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 .into())
351 } else {
352 Ok(())
353 }
354 }
355 }
356}
357
358fn check_gas_by_values<E: ExecutionErrorTrait>(arguments: &[T::Argument]) -> Result<(), E> {
360 for arg in arguments {
361 check_gas_by_value(arg)?;
362 }
363 Ok(())
364}
365
366fn check_gas_by_value<E: ExecutionErrorTrait>(arg: &T::Argument) -> Result<(), E> {
367 match &arg.value.0 {
368 T::Argument__::Use(T::Usage::Move(l)) => check_gas_by_value_loc(arg.idx, l),
369 T::Argument__::Borrow(_, _)
371 | T::Argument__::Use(T::Usage::Copy { .. })
372 | T::Argument__::Read(_)
373 | T::Argument__::Freeze(_) => Ok(()),
374 }
375}
376
377fn check_gas_by_value_loc<E: ExecutionErrorTrait>(
378 idx: u16,
379 location: &T::Location,
380) -> Result<(), E> {
381 match location {
382 T::Location::GasCoin => Err(command_argument_error(
383 CommandArgumentError::InvalidGasCoinUsage,
384 idx as usize,
385 )
386 .into()),
387 T::Location::TxContext
388 | T::Location::ObjectInput(_)
389 | T::Location::WithdrawalInput(_)
390 | T::Location::PureInput(_)
391 | T::Location::ReceivingInput(_)
392 | T::Location::Result(_, _) => Ok(()),
393 }
394}
395
396pub fn is_coin_send_funds(function: &T::LoadedFunction) -> bool {
397 function.original_mid.address() == &SUI_FRAMEWORK_ADDRESS
398 && function.original_mid.name() == COIN_MODULE_NAME
399 && function.name.as_ident_str() == SEND_FUNDS_FUNC_NAME
400}