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