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