1use crate::{
5 execution_mode::ExecutionMode,
6 static_programmable_transactions::{env::Env, typing::ast as T},
7};
8
9pub fn refine_and_verify<Mode: ExecutionMode>(
13 env: &Env<Mode>,
14 ast: &mut T::Transaction,
15) -> Result<(), Mode::Error> {
16 refine::transaction(env, ast)?;
17 verify::transaction::<Mode>(env, ast)?;
18 Ok(())
19}
20
21mod refine {
22 use crate::execution_mode::ExecutionMode;
23 use sui_types::coin::{COIN_MODULE_NAME, SEND_FUNDS_FUNC_NAME};
24
25 use crate::{
26 sp,
27 static_programmable_transactions::{
28 env::Env,
29 spanned::sp,
30 typing::{
31 ast::{self as T, Type},
32 translate::coin_inner_type,
33 },
34 },
35 };
36 use std::collections::BTreeSet;
37
38 struct Context {
39 used: BTreeSet<T::Location>,
41 moved: BTreeSet<T::Location>,
45 }
46
47 impl Context {
48 fn new() -> Self {
49 Self {
50 used: BTreeSet::new(),
51 moved: BTreeSet::new(),
52 }
53 }
54 }
55
56 pub fn transaction<Mode: ExecutionMode>(
59 env: &Env<Mode>,
60 ast: &mut T::Transaction,
61 ) -> Result<(), Mode::Error> {
62 let mut context = Context::new();
63 for c in ast.commands.iter_mut().rev() {
64 command(&mut context, c);
65 }
66 return_unused_withdrawal_conversions(env, ast, &context.moved)
67 }
68
69 fn command(context: &mut Context, sp!(_, c): &mut T::Command) {
70 match &mut c.command {
71 T::Command__::MoveCall(mc) => arguments(context, &mut mc.arguments),
72 T::Command__::TransferObjects(objects, recipient) => {
73 argument(context, recipient);
74 arguments(context, objects);
75 }
76 T::Command__::SplitCoins(_, coin, amounts) => {
77 arguments(context, amounts);
78 argument(context, coin);
79 }
80 T::Command__::MergeCoins(_, target, coins) => {
81 arguments(context, coins);
82 argument(context, target);
83 }
84 T::Command__::MakeMoveVec(_, xs) => arguments(context, xs),
85 T::Command__::Publish(_, _, _) => (),
86 T::Command__::Upgrade(_, _, _, x, _) => argument(context, x),
87 }
88 }
89
90 fn arguments(context: &mut Context, args: &mut [T::Argument]) {
91 for arg in args.iter_mut().rev() {
92 argument(context, arg)
93 }
94 }
95
96 fn argument(context: &mut Context, arg: &mut T::Argument) {
97 let usage = match &mut arg.value.0 {
98 T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
99 T::Argument__::Borrow(_, loc) => {
100 context.used.insert(*loc);
102 return;
103 }
104 };
105 match &usage {
106 T::Usage::Move(loc) => {
107 context.used.insert(*loc);
109 context.moved.insert(*loc);
110 }
111 T::Usage::Copy { location, borrowed } => {
112 let location = *location;
114 let last_usage = context.used.insert(location);
115 if last_usage && !borrowed.get().unwrap() {
116 *usage = T::Usage::Move(location);
118 context.moved.insert(location);
119 }
120 }
121 }
122 }
123
124 fn return_unused_withdrawal_conversions<Mode: ExecutionMode>(
127 env: &Env<Mode>,
128 ast: &mut T::Transaction,
129 moved_locations: &BTreeSet<T::Location>,
130 ) -> Result<(), Mode::Error> {
131 assert_invariant!(
133 ast.withdrawal_compatibility_conversions.is_empty()
134 || env.protocol_config.enable_accumulators(),
135 "Withdrawal conversions should be empty if accumulators are not enabled"
136 );
137 for conversion_info in
138 ast.withdrawal_compatibility_conversions
139 .values()
140 .filter(|conversion| {
141 let conversion_location = T::Location::Result(conversion.conversion_result, 0);
144 !moved_locations.contains(&conversion_location)
145 })
146 {
147 let Some(cur_command) = ast.commands.len().checked_sub(1) else {
148 invariant_violation!("cannot be zero commands with a conversion")
149 };
150 let cur_command = checked_as!(cur_command, u16)?;
151 let T::WithdrawalCompatibilityConversion {
152 owner,
153 conversion_result,
154 } = *conversion_info;
155 let Some(conversion_command) = ast.commands.get(conversion_result as usize) else {
156 invariant_violation!("conversion result should be a valid command index")
157 };
158 assert_invariant!(
159 conversion_command.value.result_type.len() == 1,
160 "conversion should have one result"
161 );
162 let T::Location::PureInput(owner_pure_idx) = owner else {
163 invariant_violation!("owner should be a pure input")
164 };
165 assert_invariant!(
166 ast.pure.len() > owner_pure_idx as usize,
167 "owner pure input index out of bounds"
168 );
169 assert_invariant!(
170 ast.pure.get(owner_pure_idx as usize).unwrap().ty == T::Type::Address,
171 "owner pure input should be an address"
172 );
173 let Some(conversion_ty) = conversion_command.value.result_type.first() else {
174 invariant_violation!("conversion should have a result type")
175 };
176 let Some(inner_ty) = coin_inner_type(conversion_ty) else {
177 invariant_violation!("conversion result should be a coin type")
178 };
179 let move_result_ = T::Argument__::new_move(T::Location::Result(conversion_result, 0));
180 let move_result = sp(cur_command, (move_result_, conversion_ty.clone()));
181 let owner_ty = Type::Address;
182 let owner_arg_ = T::Argument__::new_move(owner);
183 let owner_arg = sp(cur_command, (owner_arg_, owner_ty));
184 let return_command__ = T::Command__::MoveCall(Box::new(T::MoveCall {
185 function: env.load_framework_function(
186 COIN_MODULE_NAME,
187 SEND_FUNDS_FUNC_NAME,
188 vec![inner_ty.clone()],
189 ast.unified_linkage.as_ref(),
190 )?,
191 arguments: vec![move_result, owner_arg],
192 }));
193 let return_command = sp(
194 cur_command,
195 T::Command_ {
196 command: return_command__,
197 result_type: vec![],
198 drop_values: vec![],
199 incurs_post_execution_checks: false,
200 },
201 );
202 ast.commands.push(return_command);
203 }
204 Ok(())
205 }
206}
207
208mod verify {
209 use crate::{
210 execution_mode::ExecutionMode,
211 sp,
212 static_programmable_transactions::{
213 env::Env,
214 typing::ast::{self as T, Type},
215 },
216 };
217 use mysten_common::ZipDebugEqIteratorExt;
218 use sui_types::error::{ExecutionErrorTrait, SafeIndex};
219 use sui_types::execution_status::ExecutionErrorKind;
220
221 #[must_use]
222 struct Value;
223
224 struct Context {
225 tx_context: Option<Value>,
226 gas_coin: Option<Value>,
227 objects: Vec<Option<Value>>,
228 withdrawals: Vec<Option<Value>>,
229 pure: Vec<Option<Value>>,
230 receiving: Vec<Option<Value>>,
231 results: Vec<Vec<Option<Value>>>,
232 }
233
234 impl Context {
235 fn new<Mode: ExecutionMode>(_env: &Env<Mode>, ast: &T::Transaction) -> Self {
236 let objects = ast.objects.iter().map(|_| Some(Value)).collect::<Vec<_>>();
237 let withdrawals = ast
238 .withdrawals
239 .iter()
240 .map(|_| Some(Value))
241 .collect::<Vec<_>>();
242 let pure = ast.pure.iter().map(|_| Some(Value)).collect::<Vec<_>>();
243 let receiving = ast
244 .receiving
245 .iter()
246 .map(|_| Some(Value))
247 .collect::<Vec<_>>();
248 let gas_coin = if ast.gas_payment.is_none() {
249 None
250 } else {
251 Some(Value)
252 };
253 Self {
254 tx_context: Some(Value),
255 gas_coin,
256 objects,
257 withdrawals,
258 pure,
259 receiving,
260 results: Vec::with_capacity(ast.commands.len()),
261 }
262 }
263
264 fn location<E: ExecutionErrorTrait>(
265 &mut self,
266 l: T::Location,
267 ) -> Result<&mut Option<Value>, E> {
268 Ok(match l {
269 T::Location::TxContext => &mut self.tx_context,
270 T::Location::GasCoin => &mut self.gas_coin,
271 T::Location::ObjectInput(i) => self.objects.safe_get_mut(i as usize)?,
272 T::Location::WithdrawalInput(i) => self.withdrawals.safe_get_mut(i as usize)?,
273 T::Location::PureInput(i) => self.pure.safe_get_mut(i as usize)?,
274 T::Location::ReceivingInput(i) => self.receiving.safe_get_mut(i as usize)?,
275 T::Location::Result(i, j) => self
276 .results
277 .safe_get_mut(i as usize)?
278 .safe_get_mut(j as usize)?,
279 })
280 }
281 }
282
283 pub fn transaction<Mode: ExecutionMode>(
286 env: &Env<Mode>,
287 ast: &T::Transaction,
288 ) -> Result<(), Mode::Error> {
289 let mut context = Context::new(env, ast);
290 let commands = &ast.commands;
291 for c in commands {
292 let result = command::<Mode::Error>(&mut context, c)
293 .map_err(|e| e.with_command_index(c.idx as usize))?;
294 assert_invariant!(
295 result.len() == c.value.result_type.len(),
296 "result length mismatch"
297 );
298 assert_invariant!(
300 result.len() == c.value.drop_values.len(),
301 "drop values length mismatch"
302 );
303 let result_values = result
304 .into_iter()
305 .zip_debug_eq(c.value.drop_values.iter().copied())
306 .map(|(v, drop)| {
307 if !drop {
308 Some(v)
309 } else {
310 consume_value(v);
311 None
312 }
313 })
314 .collect();
315 context.results.push(result_values);
316 }
317
318 let Context {
319 tx_context,
320 gas_coin,
321 objects,
322 withdrawals,
323 pure,
324 receiving,
325 results,
326 } = context;
327 consume_value_opt(gas_coin);
328 consume_value_opts(objects);
330 consume_value_opts(withdrawals);
331 consume_value_opts(pure);
332 consume_value_opts(receiving);
333 assert_invariant!(results.len() == commands.len(), "result length mismatch");
334 for (i, (result, c)) in results.into_iter().zip_debug_eq(&ast.commands).enumerate() {
335 let tys = &c.value.result_type;
336 assert_invariant!(result.len() == tys.len(), "result length mismatch");
337 for (j, (vopt, ty)) in result.into_iter().zip_debug_eq(tys).enumerate() {
338 drop_value_opt::<Mode>((i, j), vopt, ty)?;
339 }
340 }
341 assert_invariant!(tx_context.is_some(), "tx_context should never be moved");
342 Ok(())
343 }
344
345 fn command<E: ExecutionErrorTrait>(
346 context: &mut Context,
347 sp!(_, c): &T::Command,
348 ) -> Result<Vec<Value>, E> {
349 let result_tys = &c.result_type;
350 Ok(match &c.command {
351 T::Command__::MoveCall(mc) => {
352 let T::MoveCall {
353 function,
354 arguments: args,
355 } = &**mc;
356 let return_ = &function.signature.return_;
357 let arg_values = arguments(context, args)?;
358 consume_values(arg_values);
359 (0..return_.len()).map(|_| Value).collect()
360 }
361 T::Command__::TransferObjects(objects, recipient) => {
362 let object_values = arguments(context, objects)?;
363 let recipient_value = argument(context, recipient)?;
364 consume_values(object_values);
365 consume_value(recipient_value);
366 vec![]
367 }
368 T::Command__::SplitCoins(_, coin, amounts) => {
369 let coin_value = argument(context, coin)?;
370 let amount_values = arguments(context, amounts)?;
371 consume_values(amount_values);
372 consume_value(coin_value);
373 (0..amounts.len()).map(|_| Value).collect()
374 }
375 T::Command__::MergeCoins(_, target, coins) => {
376 let target_value = argument(context, target)?;
377 let coin_values = arguments(context, coins)?;
378 consume_values(coin_values);
379 consume_value(target_value);
380 vec![]
381 }
382 T::Command__::MakeMoveVec(_, xs) => {
383 let vs = arguments(context, xs)?;
384 consume_values(vs);
385 vec![Value]
386 }
387 T::Command__::Publish(_, _, _) => result_tys.iter().map(|_| Value).collect(),
388 T::Command__::Upgrade(_, _, _, x, _) => {
389 let v = argument(context, x)?;
390 consume_value(v);
391 vec![Value]
392 }
393 })
394 }
395
396 fn consume_values(_: Vec<Value>) {}
397
398 fn consume_value(_: Value) {}
399
400 fn consume_value_opts(_: Vec<Option<Value>>) {}
401
402 fn consume_value_opt(_: Option<Value>) {}
403
404 fn drop_value_opt<Mode: ExecutionMode>(
405 idx: (usize, usize),
406 value: Option<Value>,
407 ty: &Type,
408 ) -> Result<(), Mode::Error> {
409 match value {
410 Some(v) => drop_value::<Mode>(idx, v, ty),
411 None => Ok(()),
412 }
413 }
414
415 fn drop_value<Mode: ExecutionMode>(
416 (i, j): (usize, usize),
417 value: Value,
418 ty: &Type,
419 ) -> Result<(), Mode::Error> {
420 let abilities = ty.abilities();
421 if !abilities.has_drop() && !Mode::allow_arbitrary_values() {
422 let msg = if abilities.has_copy() {
423 "The value has copy, but not drop. \
424 Its last usage must be by-value so it can be taken."
425 } else {
426 "Unused value without drop"
427 };
428 return Err(Mode::Error::new_with_source(
429 ExecutionErrorKind::UnusedValueWithoutDrop {
430 result_idx: checked_as!(i, u16)?,
431 secondary_idx: checked_as!(j, u16)?,
432 },
433 msg,
434 ));
435 }
436 consume_value(value);
437 Ok(())
438 }
439
440 fn arguments<E: ExecutionErrorTrait>(
441 context: &mut Context,
442 xs: &[T::Argument],
443 ) -> Result<Vec<Value>, E> {
444 xs.iter().map(|x| argument(context, x)).collect()
445 }
446
447 fn argument<E: ExecutionErrorTrait>(
448 context: &mut Context,
449 sp!(_, x): &T::Argument,
450 ) -> Result<Value, E> {
451 match &x.0 {
452 T::Argument__::Use(T::Usage::Move(location)) => move_value(context, *location),
453 T::Argument__::Use(T::Usage::Copy { location, .. }) => copy_value(context, *location),
454 T::Argument__::Borrow(_, location) => borrow_location(context, *location),
455 T::Argument__::Read(usage) => read_ref(context, usage),
456 T::Argument__::Freeze(usage) => freeze_ref(context, usage),
457 }
458 }
459
460 fn move_value<E: ExecutionErrorTrait>(
461 context: &mut Context,
462 l: T::Location,
463 ) -> Result<Value, E> {
464 let Some(value) = context.location::<E>(l)?.take() else {
465 invariant_violation!("memory safety should have failed")
466 };
467 Ok(value)
468 }
469
470 fn copy_value<E: ExecutionErrorTrait>(
471 context: &mut Context,
472 l: T::Location,
473 ) -> Result<Value, E> {
474 assert_invariant!(
475 context.location::<E>(l)?.is_some(),
476 "memory safety should have failed"
477 );
478 Ok(Value)
479 }
480
481 fn borrow_location<E: ExecutionErrorTrait>(
482 context: &mut Context,
483 l: T::Location,
484 ) -> Result<Value, E> {
485 assert_invariant!(
486 context.location::<E>(l)?.is_some(),
487 "memory safety should have failed"
488 );
489 Ok(Value)
490 }
491
492 fn read_ref<E: ExecutionErrorTrait>(context: &mut Context, u: &T::Usage) -> Result<Value, E> {
493 let value = match u {
494 T::Usage::Move(l) => move_value::<E>(context, *l)?,
495 T::Usage::Copy { location, .. } => copy_value::<E>(context, *location)?,
496 };
497 consume_value(value);
498 Ok(Value)
499 }
500
501 fn freeze_ref<E: ExecutionErrorTrait>(context: &mut Context, u: &T::Usage) -> Result<Value, E> {
502 let value = match u {
503 T::Usage::Move(l) => move_value::<E>(context, *l)?,
504 T::Usage::Copy { location, .. } => copy_value::<E>(context, *location)?,
505 };
506 consume_value(value);
507 Ok(Value)
508 }
509}