1use crate::{
5 execution_mode::ExecutionMode,
6 static_programmable_transactions::{env::Env, typing::ast as T},
7};
8use sui_types::error::ExecutionError;
9
10pub fn refine_and_verify<Mode: ExecutionMode>(
14 env: &Env,
15 ast: &mut T::Transaction,
16) -> Result<(), ExecutionError> {
17 refine::transaction(env, ast)?;
18 verify::transaction::<Mode>(env, ast)?;
19 Ok(())
20}
21
22mod refine {
23 use sui_types::{
24 coin::{COIN_MODULE_NAME, SEND_FUNDS_FUNC_NAME},
25 error::ExecutionError,
26 };
27
28 use crate::{
29 sp,
30 static_programmable_transactions::{
31 env::Env,
32 spanned::sp,
33 typing::{
34 ast::{self as T, Type},
35 translate::coin_inner_type,
36 },
37 },
38 };
39 use std::collections::BTreeSet;
40
41 struct Context {
42 used: BTreeSet<T::Location>,
44 moved: BTreeSet<T::Location>,
48 }
49
50 impl Context {
51 fn new() -> Self {
52 Self {
53 used: BTreeSet::new(),
54 moved: BTreeSet::new(),
55 }
56 }
57 }
58
59 pub fn transaction(env: &Env, ast: &mut T::Transaction) -> Result<(), ExecutionError> {
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(
127 env: &Env,
128 ast: &mut T::Transaction,
129 moved_locations: &BTreeSet<T::Location>,
130 ) -> Result<(), ExecutionError> {
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 )?,
190 arguments: vec![move_result, owner_arg],
191 }));
192 let return_command = sp(
193 cur_command,
194 T::Command_ {
195 command: return_command__,
196 result_type: vec![],
197 drop_values: vec![],
198 consumed_shared_objects: vec![],
199 },
200 );
201 ast.commands.push(return_command);
202 }
203 Ok(())
204 }
205}
206
207mod verify {
208 use crate::{
209 execution_mode::ExecutionMode,
210 sp,
211 static_programmable_transactions::{
212 env::Env,
213 typing::ast::{self as T, Type},
214 },
215 };
216 use sui_types::error::{ExecutionError, ExecutionErrorKind, SafeIndex};
217
218 #[must_use]
219 struct Value;
220
221 struct Context {
222 tx_context: Option<Value>,
223 gas_coin: Option<Value>,
224 objects: Vec<Option<Value>>,
225 withdrawals: Vec<Option<Value>>,
226 pure: Vec<Option<Value>>,
227 receiving: Vec<Option<Value>>,
228 results: Vec<Vec<Option<Value>>>,
229 }
230
231 impl Context {
232 fn new(env: &Env, ast: &T::Transaction) -> Result<Self, ExecutionError> {
233 let objects = ast.objects.iter().map(|_| Some(Value)).collect::<Vec<_>>();
234 let withdrawals = ast
235 .withdrawals
236 .iter()
237 .map(|_| Some(Value))
238 .collect::<Vec<_>>();
239 let pure = ast.pure.iter().map(|_| Some(Value)).collect::<Vec<_>>();
240 let receiving = ast
241 .receiving
242 .iter()
243 .map(|_| Some(Value))
244 .collect::<Vec<_>>();
245 let gas_coin = if ast.gas_coin.is_none()
246 && env.protocol_config.gasless_transaction_drop_safety()
247 {
248 None
249 } else {
250 Some(Value)
251 };
252 Ok(Self {
253 tx_context: Some(Value),
254 gas_coin,
255 objects,
256 withdrawals,
257 pure,
258 receiving,
259 results: Vec::with_capacity(ast.commands.len()),
260 })
261 }
262
263 fn location(&mut self, l: T::Location) -> Result<&mut Option<Value>, ExecutionError> {
264 Ok(match l {
265 T::Location::TxContext => &mut self.tx_context,
266 T::Location::GasCoin => &mut self.gas_coin,
267 T::Location::ObjectInput(i) => self.objects.safe_get_mut(i as usize)?,
268 T::Location::WithdrawalInput(i) => self.withdrawals.safe_get_mut(i as usize)?,
269 T::Location::PureInput(i) => self.pure.safe_get_mut(i as usize)?,
270 T::Location::ReceivingInput(i) => self.receiving.safe_get_mut(i as usize)?,
271 T::Location::Result(i, j) => self
272 .results
273 .safe_get_mut(i as usize)?
274 .safe_get_mut(j as usize)?,
275 })
276 }
277 }
278
279 pub fn transaction<Mode: ExecutionMode>(
282 env: &Env,
283 ast: &T::Transaction,
284 ) -> Result<(), ExecutionError> {
285 let mut context = Context::new(env, ast)?;
286 let commands = &ast.commands;
287 for c in commands {
288 let result =
289 command(&mut context, c).map_err(|e| e.with_command_index(c.idx as usize))?;
290 assert_invariant!(
291 result.len() == c.value.result_type.len(),
292 "result length mismatch"
293 );
294 assert_invariant!(
296 result.len() == c.value.drop_values.len(),
297 "drop values length mismatch"
298 );
299 let result_values = result
300 .into_iter()
301 .zip(c.value.drop_values.iter().copied())
302 .map(|(v, drop)| {
303 if !drop {
304 Some(v)
305 } else {
306 consume_value(v);
307 None
308 }
309 })
310 .collect();
311 context.results.push(result_values);
312 }
313
314 let Context {
315 tx_context,
316 gas_coin,
317 objects,
318 withdrawals,
319 pure,
320 receiving,
321 results,
322 } = context;
323 consume_value_opt(gas_coin);
324 consume_value_opts(objects);
326 consume_value_opts(withdrawals);
327 consume_value_opts(pure);
328 consume_value_opts(receiving);
329 assert_invariant!(results.len() == commands.len(), "result length mismatch");
330 for (i, (result, c)) in results.into_iter().zip(&ast.commands).enumerate() {
331 let tys = &c.value.result_type;
332 assert_invariant!(result.len() == tys.len(), "result length mismatch");
333 for (j, (vopt, ty)) in result.into_iter().zip(tys).enumerate() {
334 drop_value_opt::<Mode>((i, j), vopt, ty)?;
335 }
336 }
337 assert_invariant!(tx_context.is_some(), "tx_context should never be moved");
338 Ok(())
339 }
340
341 fn command(
342 context: &mut Context,
343 sp!(_, c): &T::Command,
344 ) -> Result<Vec<Value>, ExecutionError> {
345 let result_tys = &c.result_type;
346 Ok(match &c.command {
347 T::Command__::MoveCall(mc) => {
348 let T::MoveCall {
349 function,
350 arguments: args,
351 } = &**mc;
352 let return_ = &function.signature.return_;
353 let arg_values = arguments(context, args)?;
354 consume_values(arg_values);
355 (0..return_.len()).map(|_| Value).collect()
356 }
357 T::Command__::TransferObjects(objects, recipient) => {
358 let object_values = arguments(context, objects)?;
359 let recipient_value = argument(context, recipient)?;
360 consume_values(object_values);
361 consume_value(recipient_value);
362 vec![]
363 }
364 T::Command__::SplitCoins(_, coin, amounts) => {
365 let coin_value = argument(context, coin)?;
366 let amount_values = arguments(context, amounts)?;
367 consume_values(amount_values);
368 consume_value(coin_value);
369 (0..amounts.len()).map(|_| Value).collect()
370 }
371 T::Command__::MergeCoins(_, target, coins) => {
372 let target_value = argument(context, target)?;
373 let coin_values = arguments(context, coins)?;
374 consume_values(coin_values);
375 consume_value(target_value);
376 vec![]
377 }
378 T::Command__::MakeMoveVec(_, xs) => {
379 let vs = arguments(context, xs)?;
380 consume_values(vs);
381 vec![Value]
382 }
383 T::Command__::Publish(_, _, _) => result_tys.iter().map(|_| Value).collect(),
384 T::Command__::Upgrade(_, _, _, x, _) => {
385 let v = argument(context, x)?;
386 consume_value(v);
387 vec![Value]
388 }
389 })
390 }
391
392 fn consume_values(_: Vec<Value>) {}
393
394 fn consume_value(_: Value) {}
395
396 fn consume_value_opts(_: Vec<Option<Value>>) {}
397
398 fn consume_value_opt(_: Option<Value>) {}
399
400 fn drop_value_opt<Mode: ExecutionMode>(
401 idx: (usize, usize),
402 value: Option<Value>,
403 ty: &Type,
404 ) -> Result<(), ExecutionError> {
405 match value {
406 Some(v) => drop_value::<Mode>(idx, v, ty),
407 None => Ok(()),
408 }
409 }
410
411 fn drop_value<Mode: ExecutionMode>(
412 (i, j): (usize, usize),
413 value: Value,
414 ty: &Type,
415 ) -> Result<(), ExecutionError> {
416 let abilities = ty.abilities();
417 if !abilities.has_drop() && !Mode::allow_arbitrary_values() {
418 let msg = if abilities.has_copy() {
419 "The value has copy, but not drop. \
420 Its last usage must be by-value so it can be taken."
421 } else {
422 "Unused value without drop"
423 };
424 return Err(ExecutionError::new_with_source(
425 ExecutionErrorKind::UnusedValueWithoutDrop {
426 result_idx: checked_as!(i, u16)?,
427 secondary_idx: checked_as!(j, u16)?,
428 },
429 msg,
430 ));
431 }
432 consume_value(value);
433 Ok(())
434 }
435
436 fn arguments(context: &mut Context, xs: &[T::Argument]) -> Result<Vec<Value>, ExecutionError> {
437 xs.iter().map(|x| argument(context, x)).collect()
438 }
439
440 fn argument(context: &mut Context, sp!(_, x): &T::Argument) -> Result<Value, ExecutionError> {
441 match &x.0 {
442 T::Argument__::Use(T::Usage::Move(location)) => move_value(context, *location),
443 T::Argument__::Use(T::Usage::Copy { location, .. }) => copy_value(context, *location),
444 T::Argument__::Borrow(_, location) => borrow_location(context, *location),
445 T::Argument__::Read(usage) => read_ref(context, usage),
446 T::Argument__::Freeze(usage) => freeze_ref(context, usage),
447 }
448 }
449
450 fn move_value(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
451 let Some(value) = context.location(l)?.take() else {
452 invariant_violation!("memory safety should have failed")
453 };
454 Ok(value)
455 }
456
457 fn copy_value(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
458 assert_invariant!(
459 context.location(l)?.is_some(),
460 "memory safety should have failed"
461 );
462 Ok(Value)
463 }
464
465 fn borrow_location(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
466 assert_invariant!(
467 context.location(l)?.is_some(),
468 "memory safety should have failed"
469 );
470 Ok(Value)
471 }
472
473 fn read_ref(context: &mut Context, u: &T::Usage) -> Result<Value, ExecutionError> {
474 let value = match u {
475 T::Usage::Move(l) => move_value(context, *l)?,
476 T::Usage::Copy { location, .. } => copy_value(context, *location)?,
477 };
478 consume_value(value);
479 Ok(Value)
480 }
481
482 fn freeze_ref(context: &mut Context, u: &T::Usage) -> Result<Value, ExecutionError> {
483 let value = match u {
484 T::Usage::Move(l) => move_value(context, *l)?,
485 T::Usage::Copy { location, .. } => copy_value(context, *location)?,
486 };
487 consume_value(value);
488 Ok(Value)
489 }
490}