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(ast);
18 verify::transaction::<Mode>(env, ast)?;
19 Ok(())
20}
21
22mod refine {
23 use crate::{
24 sp,
25 static_programmable_transactions::typing::ast::{self as T},
26 };
27 use std::collections::BTreeSet;
28
29 pub fn transaction(ast: &mut T::Transaction) {
32 let mut used: BTreeSet<T::Location> = BTreeSet::new();
33 for c in ast.commands.iter_mut().rev() {
34 command(&mut used, c);
35 }
36 }
37
38 fn command(used: &mut BTreeSet<T::Location>, sp!(_, c): &mut T::Command) {
39 match &mut c.command {
40 T::Command__::MoveCall(mc) => arguments(used, &mut mc.arguments),
41 T::Command__::TransferObjects(objects, recipient) => {
42 argument(used, recipient);
43 arguments(used, objects);
44 }
45 T::Command__::SplitCoins(_, coin, amounts) => {
46 arguments(used, amounts);
47 argument(used, coin);
48 }
49 T::Command__::MergeCoins(_, target, coins) => {
50 arguments(used, coins);
51 argument(used, target);
52 }
53 T::Command__::MakeMoveVec(_, xs) => arguments(used, xs),
54 T::Command__::Publish(_, _, _) => (),
55 T::Command__::Upgrade(_, _, _, x, _) => argument(used, x),
56 }
57 }
58
59 fn arguments(used: &mut BTreeSet<T::Location>, args: &mut [T::Argument]) {
60 for arg in args.iter_mut().rev() {
61 argument(used, arg)
62 }
63 }
64
65 fn argument(used: &mut BTreeSet<T::Location>, arg: &mut T::Argument) {
66 let usage = match &mut arg.value.0 {
67 T::Argument__::Use(u) | T::Argument__::Read(u) | T::Argument__::Freeze(u) => u,
68 T::Argument__::Borrow(_, loc) => {
69 used.insert(*loc);
71 return;
72 }
73 };
74 match &usage {
75 T::Usage::Move(loc) => {
76 used.insert(*loc);
78 }
79 T::Usage::Copy { location, borrowed } => {
80 let location = *location;
82 let last_usage = used.insert(location);
83 if last_usage && !borrowed.get().unwrap() {
84 *usage = T::Usage::Move(location);
86 }
87 }
88 }
89 }
90}
91
92mod verify {
93 use crate::{
94 execution_mode::ExecutionMode,
95 sp,
96 static_programmable_transactions::{
97 env::Env,
98 typing::ast::{self as T, Type},
99 },
100 };
101 use sui_types::error::{ExecutionError, ExecutionErrorKind};
102
103 #[must_use]
104 struct Value;
105
106 struct Context {
107 tx_context: Option<Value>,
108 gas_coin: Option<Value>,
109 objects: Vec<Option<Value>>,
110 withdrawals: Vec<Option<Value>>,
111 pure: Vec<Option<Value>>,
112 receiving: Vec<Option<Value>>,
113 results: Vec<Vec<Option<Value>>>,
114 }
115
116 impl Context {
117 fn new(ast: &T::Transaction) -> Result<Self, ExecutionError> {
118 let objects = ast.objects.iter().map(|_| Some(Value)).collect::<Vec<_>>();
119 let withdrawals = ast
120 .withdrawals
121 .iter()
122 .map(|_| Some(Value))
123 .collect::<Vec<_>>();
124 let pure = ast.pure.iter().map(|_| Some(Value)).collect::<Vec<_>>();
125 let receiving = ast
126 .receiving
127 .iter()
128 .map(|_| Some(Value))
129 .collect::<Vec<_>>();
130 Ok(Self {
131 tx_context: Some(Value),
132 gas_coin: Some(Value),
133 objects,
134 withdrawals,
135 pure,
136 receiving,
137 results: Vec::with_capacity(ast.commands.len()),
138 })
139 }
140
141 fn location(&mut self, l: T::Location) -> &mut Option<Value> {
142 match l {
143 T::Location::TxContext => &mut self.tx_context,
144 T::Location::GasCoin => &mut self.gas_coin,
145 T::Location::ObjectInput(i) => &mut self.objects[i as usize],
146 T::Location::WithdrawalInput(i) => &mut self.withdrawals[i as usize],
147 T::Location::PureInput(i) => &mut self.pure[i as usize],
148 T::Location::ReceivingInput(i) => &mut self.receiving[i as usize],
149 T::Location::Result(i, j) => &mut self.results[i as usize][j as usize],
150 }
151 }
152 }
153
154 pub fn transaction<Mode: ExecutionMode>(
157 _env: &Env,
158 ast: &T::Transaction,
159 ) -> Result<(), ExecutionError> {
160 let mut context = Context::new(ast)?;
161 let commands = &ast.commands;
162 for c in commands {
163 let result =
164 command(&mut context, c).map_err(|e| e.with_command_index(c.idx as usize))?;
165 assert_invariant!(
166 result.len() == c.value.result_type.len(),
167 "result length mismatch"
168 );
169 assert_invariant!(
171 result.len() == c.value.drop_values.len(),
172 "drop values length mismatch"
173 );
174 let result_values = result
175 .into_iter()
176 .zip(c.value.drop_values.iter().copied())
177 .map(|(v, drop)| {
178 if !drop {
179 Some(v)
180 } else {
181 consume_value(v);
182 None
183 }
184 })
185 .collect();
186 context.results.push(result_values);
187 }
188
189 let Context {
190 tx_context,
191 gas_coin,
192 objects,
193 withdrawals,
194 pure,
195 receiving,
196 results,
197 } = context;
198 consume_value_opt(gas_coin);
199 consume_value_opts(objects);
201 consume_value_opts(withdrawals);
202 consume_value_opts(pure);
203 consume_value_opts(receiving);
204 assert_invariant!(results.len() == commands.len(), "result length mismatch");
205 for (i, (result, c)) in results.into_iter().zip(&ast.commands).enumerate() {
206 let tys = &c.value.result_type;
207 assert_invariant!(result.len() == tys.len(), "result length mismatch");
208 for (j, (vopt, ty)) in result.into_iter().zip(tys).enumerate() {
209 drop_value_opt::<Mode>((i, j), vopt, ty)?;
210 }
211 }
212 assert_invariant!(tx_context.is_some(), "tx_context should never be moved");
213 Ok(())
214 }
215
216 fn command(
217 context: &mut Context,
218 sp!(_, c): &T::Command,
219 ) -> Result<Vec<Value>, ExecutionError> {
220 let result_tys = &c.result_type;
221 Ok(match &c.command {
222 T::Command__::MoveCall(mc) => {
223 let T::MoveCall {
224 function,
225 arguments: args,
226 } = &**mc;
227 let return_ = &function.signature.return_;
228 let arg_values = arguments(context, args)?;
229 consume_values(arg_values);
230 (0..return_.len()).map(|_| Value).collect()
231 }
232 T::Command__::TransferObjects(objects, recipient) => {
233 let object_values = arguments(context, objects)?;
234 let recipient_value = argument(context, recipient)?;
235 consume_values(object_values);
236 consume_value(recipient_value);
237 vec![]
238 }
239 T::Command__::SplitCoins(_, coin, amounts) => {
240 let coin_value = argument(context, coin)?;
241 let amount_values = arguments(context, amounts)?;
242 consume_values(amount_values);
243 consume_value(coin_value);
244 (0..amounts.len()).map(|_| Value).collect()
245 }
246 T::Command__::MergeCoins(_, target, coins) => {
247 let target_value = argument(context, target)?;
248 let coin_values = arguments(context, coins)?;
249 consume_values(coin_values);
250 consume_value(target_value);
251 vec![]
252 }
253 T::Command__::MakeMoveVec(_, xs) => {
254 let vs = arguments(context, xs)?;
255 consume_values(vs);
256 vec![Value]
257 }
258 T::Command__::Publish(_, _, _) => result_tys.iter().map(|_| Value).collect(),
259 T::Command__::Upgrade(_, _, _, x, _) => {
260 let v = argument(context, x)?;
261 consume_value(v);
262 vec![Value]
263 }
264 })
265 }
266
267 fn consume_values(_: Vec<Value>) {}
268
269 fn consume_value(_: Value) {}
270
271 fn consume_value_opts(_: Vec<Option<Value>>) {}
272
273 fn consume_value_opt(_: Option<Value>) {}
274
275 fn drop_value_opt<Mode: ExecutionMode>(
276 idx: (usize, usize),
277 value: Option<Value>,
278 ty: &Type,
279 ) -> Result<(), ExecutionError> {
280 match value {
281 Some(v) => drop_value::<Mode>(idx, v, ty),
282 None => Ok(()),
283 }
284 }
285
286 fn drop_value<Mode: ExecutionMode>(
287 (i, j): (usize, usize),
288 value: Value,
289 ty: &Type,
290 ) -> Result<(), ExecutionError> {
291 let abilities = ty.abilities();
292 if !abilities.has_drop() && !Mode::allow_arbitrary_values() {
293 let msg = if abilities.has_copy() {
294 "The value has copy, but not drop. \
295 Its last usage must be by-value so it can be taken."
296 } else {
297 "Unused value without drop"
298 };
299 return Err(ExecutionError::new_with_source(
300 ExecutionErrorKind::UnusedValueWithoutDrop {
301 result_idx: i as u16,
302 secondary_idx: j as u16,
303 },
304 msg,
305 ));
306 }
307 consume_value(value);
308 Ok(())
309 }
310
311 fn arguments(context: &mut Context, xs: &[T::Argument]) -> Result<Vec<Value>, ExecutionError> {
312 xs.iter().map(|x| argument(context, x)).collect()
313 }
314
315 fn argument(context: &mut Context, sp!(_, x): &T::Argument) -> Result<Value, ExecutionError> {
316 match &x.0 {
317 T::Argument__::Use(T::Usage::Move(location)) => move_value(context, *location),
318 T::Argument__::Use(T::Usage::Copy { location, .. }) => copy_value(context, *location),
319 T::Argument__::Borrow(_, location) => borrow_location(context, *location),
320 T::Argument__::Read(usage) => read_ref(context, usage),
321 T::Argument__::Freeze(usage) => freeze_ref(context, usage),
322 }
323 }
324
325 fn move_value(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
326 let Some(value) = context.location(l).take() else {
327 invariant_violation!("memory safety should have failed")
328 };
329 Ok(value)
330 }
331
332 fn copy_value(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
333 assert_invariant!(
334 context.location(l).is_some(),
335 "memory safety should have failed"
336 );
337 Ok(Value)
338 }
339
340 fn borrow_location(context: &mut Context, l: T::Location) -> Result<Value, ExecutionError> {
341 assert_invariant!(
342 context.location(l).is_some(),
343 "memory safety should have failed"
344 );
345 Ok(Value)
346 }
347
348 fn read_ref(context: &mut Context, u: &T::Usage) -> Result<Value, ExecutionError> {
349 let value = match u {
350 T::Usage::Move(l) => move_value(context, *l)?,
351 T::Usage::Copy { location, .. } => copy_value(context, *location)?,
352 };
353 consume_value(value);
354 Ok(Value)
355 }
356
357 fn freeze_ref(context: &mut Context, u: &T::Usage) -> Result<Value, ExecutionError> {
358 let value = match u {
359 T::Usage::Move(l) => move_value(context, *l)?,
360 T::Usage::Copy { location, .. } => copy_value(context, *location)?,
361 };
362 consume_value(value);
363 Ok(Value)
364 }
365}