1use std::{cmp, str::FromStr};
5
6use move_core_types::identifier::Identifier;
7use once_cell::sync::Lazy;
8use proptest::collection::vec;
9use proptest::prelude::*;
10use sui_protocol_config::ProtocolConfig;
11use sui_types::base_types::{ObjectID, ObjectRef, SuiAddress};
12use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
13use sui_types::transaction::{Argument, CallArg, Command, ProgrammableTransaction};
14
15static PROTOCOL_CONFIG: Lazy<ProtocolConfig> =
16 Lazy::new(ProtocolConfig::get_for_max_version_UNSAFE);
17
18prop_compose! {
19 pub fn gen_transfer()
20 (x in arg_len_strategy())
21 (args in vec(gen_argument(), x..=x), arg_to in gen_argument()) -> Command {
22 Command::TransferObjects(args, arg_to)
23 }
24}
25
26prop_compose! {
27 pub fn gen_split_coins()
28 (x in arg_len_strategy())
29 (args in vec(gen_argument(), x..=x), arg_to in gen_argument()) -> Command {
30 Command::SplitCoins(arg_to, args)
31 }
32}
33
34prop_compose! {
35 pub fn gen_merge_coins()
36 (x in arg_len_strategy())
37 (args in vec(gen_argument(), x..=x), arg_from in gen_argument()) -> Command {
38 Command::MergeCoins(arg_from, args)
39 }
40}
41
42prop_compose! {
43 pub fn gen_move_vec()
44 (x in arg_len_strategy())
45 (args in vec(gen_argument(), x..=x)) -> Command {
46 Command::MakeMoveVec(None, args)
47 }
48}
49
50prop_compose! {
51 pub fn gen_programmable_transaction()
52 (len in command_len_strategy())
53 (commands in vec(gen_command(), len..=len)) -> ProgrammableTransaction {
54 let mut builder = ProgrammableTransactionBuilder::new();
55 for command in commands {
56 builder.command(command);
57 }
58 builder.finish()
59 }
60}
61
62pub fn gen_command() -> impl Strategy<Value = Command> {
63 prop_oneof![
64 gen_transfer(),
65 gen_split_coins(),
66 gen_merge_coins(),
67 gen_move_vec(),
68 ]
69}
70
71pub fn gen_argument() -> impl Strategy<Value = Argument> {
72 prop_oneof![
73 Just(Argument::GasCoin),
74 u16_with_boundaries_strategy().prop_map(Argument::Input),
75 u16_with_boundaries_strategy().prop_map(Argument::Result),
76 (
77 u16_with_boundaries_strategy(),
78 u16_with_boundaries_strategy()
79 )
80 .prop_map(|(a, b)| Argument::NestedResult(a, b))
81 ]
82}
83
84pub fn u16_with_boundaries_strategy() -> impl Strategy<Value = u16> {
85 prop_oneof![
86 5 => 0u16..u16::MAX - 1,
87 1 => Just(u16::MAX - 1),
88 1 => Just(u16::MAX),
89 ]
90}
91
92pub fn arg_len_strategy() -> impl Strategy<Value = usize> {
93 let max_args = PROTOCOL_CONFIG.max_arguments() as usize;
94 1usize..max_args
95}
96
97pub fn command_len_strategy() -> impl Strategy<Value = usize> {
98 let max_commands = PROTOCOL_CONFIG.max_programmable_tx_commands() as usize;
99 prop_oneof![
101 10 => 1usize..10,
102 1 => 10..=max_commands,
103 ]
104}
105
106pub const MAX_ARG_LEN_INPUT_MATCH: usize = 64;
111pub const MAX_COMMANDS_INPUT_MATCH: usize = 24;
112pub const MAX_ITERATIONS_INPUT_MATCH: u32 = 10;
113pub const MAX_SPLIT_AMOUNT: u64 = 1000;
114pub const MAX_COINS_TO_MERGE: u64 = (MAX_ARG_LEN_INPUT_MATCH - 1) as u64;
117pub const MAX_VECTOR_COINS: usize = MAX_ARG_LEN_INPUT_MATCH;
120
121#[derive(Debug)]
124pub enum CommandSketch {
125 TransferObjects(u64),
127 SplitCoins(Vec<u64>),
129 MergeCoins(u64),
131 MakeMoveVec(Vec<u64>),
133}
134
135prop_compose! {
136 pub fn gen_transfer_input_match()
137 (x in arg_len_strategy_input_match()) -> CommandSketch {
138 CommandSketch::TransferObjects(x as u64)
139 }
140}
141
142prop_compose! {
143 pub fn gen_split_coins_input_match()
144 (x in arg_len_strategy_input_match())
145 (args in vec(1..MAX_SPLIT_AMOUNT, x..=x)) -> CommandSketch {
146 CommandSketch::SplitCoins(args)
147 }
148}
149
150prop_compose! {
151 pub fn gen_merge_coins_input_match()
152 (coins_to_merge in 1..MAX_COINS_TO_MERGE) -> CommandSketch {
153 CommandSketch::MergeCoins(coins_to_merge)
154 }
155}
156
157prop_compose! {
158 pub fn gen_move_vec_input_match()
159 (vec_size in 1..MAX_VECTOR_COINS)
160 (args in vec(1u64..7u64, vec_size..=vec_size)) -> CommandSketch {
161 CommandSketch::MakeMoveVec(args)
165 }
166}
167
168pub fn gen_command_input_match() -> impl Strategy<Value = CommandSketch> {
169 prop_oneof![
170 gen_transfer_input_match(),
171 gen_split_coins_input_match(),
172 gen_merge_coins_input_match(),
173 gen_move_vec_input_match(),
174 ]
175}
176
177pub fn arg_len_strategy_input_match() -> impl Strategy<Value = usize> {
178 prop_oneof![
179 20 => 1usize..10,
180 10 => 10usize..MAX_ARG_LEN_INPUT_MATCH
181 ]
182}
183
184prop_compose! {
185 pub fn gen_many_input_match(recipient: SuiAddress, package: ObjectID, cap: ObjectRef)
186 (mut command_sketches in vec(gen_command_input_match(), 1..=MAX_COMMANDS_INPUT_MATCH)) -> ProgrammableTransaction {
187 let mut builder = ProgrammableTransactionBuilder::new();
188 let mut prev_cmd_num = -1;
189 let first_cmd_sketch = command_sketches.pop().unwrap();
191 let (first_cmd, cmd_inc) = gen_input(&mut builder, None, &first_cmd_sketch, prev_cmd_num, recipient, package, cap);
192 builder.command(first_cmd);
193 prev_cmd_num += cmd_inc + 1;
194 let mut prev_cmd = first_cmd_sketch;
195 for cmd_sketch in command_sketches {
196 let (cmd, cmd_inc) = gen_input(&mut builder, Some(&prev_cmd), &cmd_sketch, prev_cmd_num, recipient, package, cap);
197 builder.command(cmd);
198 prev_cmd_num += cmd_inc + 1;
199 prev_cmd = cmd_sketch;
200 }
201 builder.finish()
202 }
203}
204
205fn gen_input(
206 builder: &mut ProgrammableTransactionBuilder,
207 prev_command: Option<&CommandSketch>,
208 cmd: &CommandSketch,
209 prev_cmd_num: i64,
210 recipient: SuiAddress,
211 package: ObjectID,
212 cap: ObjectRef,
213) -> (Command, i64) {
214 match cmd {
215 CommandSketch::TransferObjects(_) => gen_transfer_input(
216 builder,
217 prev_command,
218 cmd,
219 prev_cmd_num,
220 recipient,
221 package,
222 cap,
223 ),
224 CommandSketch::SplitCoins(_) => {
225 gen_split_coins_input(builder, cmd, prev_cmd_num, package, cap)
226 }
227 CommandSketch::MergeCoins(_) => {
228 gen_merge_coins_input(builder, prev_command, cmd, prev_cmd_num, package, cap)
229 }
230 CommandSketch::MakeMoveVec(_) => {
231 gen_move_vec_input(builder, prev_command, cmd, prev_cmd_num, package, cap)
232 }
233 }
234}
235
236pub fn gen_transfer_input(
237 builder: &mut ProgrammableTransactionBuilder,
238 prev_command: Option<&CommandSketch>,
239 cmd: &CommandSketch,
240 prev_cmd_num: i64,
241 recipient: SuiAddress,
242 package: ObjectID,
243 cap: ObjectRef,
244) -> (Command, i64) {
245 let CommandSketch::TransferObjects(args_len) = cmd else {
246 panic!("Should be TransferObjects command");
247 };
248 let mut coins = vec![];
249 let coins_needed = *args_len as usize;
251
252 let cmd_inc = gen_transfer_or_move_vec_input_internal(
253 builder,
254 prev_cmd_num,
255 package,
256 cap,
257 prev_command,
258 coins_needed,
259 &mut coins,
260 );
261 assert!(coins.len() == *args_len as usize);
262
263 let next_cmd = Command::TransferObjects(coins, builder.pure(recipient).unwrap());
264 (next_cmd, cmd_inc)
265}
266
267pub fn gen_split_coins_input(
268 builder: &mut ProgrammableTransactionBuilder,
269 cmd: &CommandSketch,
270 prev_cmd_num: i64,
271 package: ObjectID,
272 cap: ObjectRef,
273) -> (Command, i64) {
274 let CommandSketch::SplitCoins(split_amounts) = cmd else {
275 panic!("Should be SplitCoins command");
276 };
277 let mut cmd_inc = 0;
278 let mut split_args = vec![];
279
280 create_input_calls(
285 builder,
286 package,
287 cap,
288 prev_cmd_num,
289 MAX_SPLIT_AMOUNT * split_amounts.len() as u64,
290 1,
291 );
292 cmd_inc += 2; for s in split_amounts {
295 split_args.push(builder.pure(*s).unwrap());
296 }
297
298 let coin_arg = Argument::Result((prev_cmd_num + cmd_inc) as u16);
299 let next_cmd = Command::SplitCoins(coin_arg, split_args);
300 (next_cmd, cmd_inc)
301}
302
303pub fn gen_merge_coins_input(
304 builder: &mut ProgrammableTransactionBuilder,
305 prev_command: Option<&CommandSketch>,
306 cmd: &CommandSketch,
307 prev_cmd_num: i64,
308 package: ObjectID,
309 cap: ObjectRef,
310) -> (Command, i64) {
311 let CommandSketch::MergeCoins(coins_to_merge) = cmd else {
312 panic!("Should be MergeCoins command");
313 };
314 let mut cmd_inc = 0;
315 let mut coins = vec![];
316 let coins_needed = *coins_to_merge as usize + 1;
318
319 let output_coin = if let Some(prev_cmd) = prev_command {
320 match prev_cmd {
321 CommandSketch::TransferObjects(_) | CommandSketch::MergeCoins(_) => {
322 create_input_calls(builder, package, cap, prev_cmd_num, 7, coins_needed as u64);
324 cmd_inc += 2; for i in 0..coins_needed - 1 {
326 coins.push(Argument::NestedResult(
327 (prev_cmd_num + cmd_inc) as u16,
328 i as u16,
329 ));
330 }
331 Argument::NestedResult((prev_cmd_num + cmd_inc) as u16, *coins_to_merge as u16)
332 }
333 CommandSketch::SplitCoins(output) | CommandSketch::MakeMoveVec(output) => {
334 let usable_coins = cmp::min(output.len(), coins_needed);
337 if let CommandSketch::MakeMoveVec(_) = prev_cmd {
338 create_unpack_call(builder, package, prev_cmd_num, output.len() as u64);
339 cmd_inc += 1; };
341 let res_coin = Argument::NestedResult((prev_cmd_num + cmd_inc) as u16, 0);
343
344 cmd_inc = gen_enough_arguments(
345 builder,
346 prev_cmd_num,
347 package,
348 cap,
349 coins_needed,
350 usable_coins,
351 1, output.len(),
353 &mut coins,
354 cmd_inc,
355 );
356 res_coin
357 }
358 }
359 } else {
360 create_input_calls(builder, package, cap, prev_cmd_num, 7, coins_needed as u64);
362 cmd_inc += 2; for i in 0..coins_needed - 1 {
364 coins.push(Argument::NestedResult(
365 (prev_cmd_num + cmd_inc) as u16,
366 i as u16,
367 ));
368 }
369 Argument::NestedResult((prev_cmd_num + cmd_inc) as u16, *coins_to_merge as u16)
370 };
371
372 let next_cmd = Command::MergeCoins(output_coin, coins);
373 (next_cmd, cmd_inc)
374}
375
376pub fn gen_move_vec_input(
377 builder: &mut ProgrammableTransactionBuilder,
378 prev_command: Option<&CommandSketch>,
379 cmd: &CommandSketch,
380 prev_cmd_num: i64,
381 package: ObjectID,
382 cap: ObjectRef,
383) -> (Command, i64) {
384 let CommandSketch::MakeMoveVec(vector_coins) = cmd else {
385 panic!("Should be MakeMoveVec command");
386 };
387 let mut coins = vec![];
388 let coins_needed = vector_coins.len();
390
391 let cmd_inc = gen_transfer_or_move_vec_input_internal(
392 builder,
393 prev_cmd_num,
394 package,
395 cap,
396 prev_command,
397 coins_needed,
398 &mut coins,
399 );
400
401 let next_cmd = Command::MakeMoveVec(None, coins);
402 (next_cmd, cmd_inc)
403}
404
405fn gen_enough_arguments(
409 builder: &mut ProgrammableTransactionBuilder,
410 prev_cmd_num: i64,
411 package: ObjectID,
412 cap: ObjectRef,
413 coins_needed: usize,
414 coins_available: usize,
415 available_coins_used: usize,
416 prev_cmd_out_len: usize,
417 coins: &mut Vec<Argument>,
418 mut cmd_inc: i64,
419) -> i64 {
420 for i in available_coins_used..coins_available {
421 coins.push(Argument::NestedResult(
422 (prev_cmd_num + cmd_inc) as u16,
423 i as u16,
424 ));
425 }
426 if prev_cmd_out_len < coins_needed {
427 let remaining_args_num = (coins_needed - prev_cmd_out_len) as u64;
429 create_input_calls(
430 builder,
431 package,
432 cap,
433 prev_cmd_num + cmd_inc,
434 7,
435 remaining_args_num,
436 );
437 cmd_inc += 2; for i in 0..remaining_args_num {
439 coins.push(Argument::NestedResult(
440 (prev_cmd_num + cmd_inc) as u16,
441 i as u16,
442 ));
443 }
444 }
445 cmd_inc
446}
447
448fn gen_transfer_or_move_vec_input_internal(
451 builder: &mut ProgrammableTransactionBuilder,
452 prev_cmd_num: i64,
453 package: ObjectID,
454 cap: ObjectRef,
455 prev_command: Option<&CommandSketch>,
456 coins_needed: usize,
457 coins: &mut Vec<Argument>,
458) -> i64 {
459 let mut cmd_inc = 0;
460 if let Some(prev_cmd) = prev_command {
461 match prev_cmd {
462 CommandSketch::TransferObjects(_) | CommandSketch::MergeCoins(_) => {
463 create_input_calls(builder, package, cap, prev_cmd_num, 7, coins_needed as u64);
465 cmd_inc += 2; for i in 0..coins_needed {
467 coins.push(Argument::NestedResult(
468 (prev_cmd_num + cmd_inc) as u16,
469 i as u16,
470 ));
471 }
472 }
473 CommandSketch::SplitCoins(output) | CommandSketch::MakeMoveVec(output) => {
474 let usable_coins = cmp::min(output.len(), coins_needed);
477 if let CommandSketch::MakeMoveVec(_) = prev_cmd {
478 create_unpack_call(builder, package, prev_cmd_num, output.len() as u64);
479 cmd_inc += 1; };
481
482 cmd_inc = gen_enough_arguments(
483 builder,
484 prev_cmd_num,
485 package,
486 cap,
487 coins_needed,
488 usable_coins,
489 0, output.len(),
491 coins,
492 cmd_inc,
493 )
494 }
495 }
496 } else {
497 create_input_calls(builder, package, cap, prev_cmd_num, 7, coins_needed as u64);
499 cmd_inc += 2; for i in 0..coins_needed {
501 coins.push(Argument::NestedResult(
502 (prev_cmd_num + cmd_inc) as u16,
503 i as u16,
504 ));
505 }
506 }
507 cmd_inc
508}
509
510fn create_input_calls(
511 builder: &mut ProgrammableTransactionBuilder,
512 package: ObjectID,
513 cap: ObjectRef,
514 prev_cmd_num: i64,
515 coin_value: u64,
516 input_size: u64,
517) {
518 builder
519 .move_call(
520 package,
521 Identifier::from_str("coin_factory").unwrap(),
522 Identifier::from_str("mint_vec").unwrap(),
523 vec![],
524 vec![
525 CallArg::from(cap),
526 CallArg::from(coin_value),
527 CallArg::from(input_size),
528 ],
529 )
530 .unwrap();
531 create_unpack_call(builder, package, prev_cmd_num + 1, input_size);
532}
533
534fn create_unpack_call(
535 builder: &mut ProgrammableTransactionBuilder,
536 package: ObjectID,
537 prev_cmd_num: i64,
538 input_size: u64,
539) {
540 builder.programmable_move_call(
541 package,
542 Identifier::from_str("coin_factory").unwrap(),
543 Identifier::from_str(format!("unpack_{input_size}").as_str()).unwrap(),
544 vec![],
545 vec![Argument::Result(prev_cmd_num as u16)],
546 );
547}