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