transaction_fuzzer/
lib.rs1pub mod account_universe;
6pub mod config_fuzzer;
7pub mod executor;
8pub mod programmable_transaction_gen;
9pub mod transaction_data_gen;
10pub mod type_arg_fuzzer;
11
12use std::fmt::Debug;
13
14use executor::Executor;
15use iota_protocol_config::ProtocolConfig;
16use iota_types::{
17 base_types::{IotaAddress, ObjectID},
18 crypto::{AccountKeyPair, get_key_pair},
19 digests::TransactionDigest,
20 gas_coin::NANOS_PER_IOTA,
21 object::{MoveObject, OBJECT_START_VERSION, Object, Owner},
22 transaction::GasData,
23};
24use proptest::{collection::vec, prelude::*, test_runner::TestRunner};
25use rand::{SeedableRng, rngs::StdRng};
26
27fn new_gas_coin_with_balance_and_owner(balance: u64, owner: Owner) -> Object {
28 Object::new_move(
29 MoveObject::new_gas_coin(OBJECT_START_VERSION, ObjectID::random(), balance),
30 owner,
31 TransactionDigest::genesis_marker(),
32 )
33}
34
35fn generate_random_gas_data(
38 seed: [u8; 32],
39 gas_coin_owners: Vec<Owner>, owned_by_sender: bool, ) -> GasDataWithObjects {
43 const MAX_GAS_BALANCE: u64 = 4_600_000_000 * NANOS_PER_IOTA;
47
48 let (sender, sender_key): (IotaAddress, AccountKeyPair) = get_key_pair();
49 let mut rng = StdRng::from_seed(seed);
50 let mut gas_objects = vec![];
51 let mut object_refs = vec![];
52
53 let total_gas_balance = rng.gen_range(0..=MAX_GAS_BALANCE);
54 let mut remaining_gas_balance = total_gas_balance;
55 let num_gas_objects = gas_coin_owners.len();
56 let gas_coin_owners = gas_coin_owners
57 .iter()
58 .map(|o| match o {
59 Owner::ObjectOwner(_) | Owner::AddressOwner(_) if owned_by_sender => {
60 Owner::AddressOwner(sender)
61 }
62 _ => *o,
63 })
64 .collect::<Vec<_>>();
65 for owner in gas_coin_owners.iter().take(num_gas_objects - 1) {
66 let gas_balance = rng.gen_range(0..=remaining_gas_balance);
67 let gas_object = new_gas_coin_with_balance_and_owner(gas_balance, *owner);
68 remaining_gas_balance -= gas_balance;
69 object_refs.push(gas_object.compute_object_reference());
70 gas_objects.push(gas_object);
71 }
72 let last_gas_object = new_gas_coin_with_balance_and_owner(
74 remaining_gas_balance,
75 gas_coin_owners[num_gas_objects - 1],
76 );
77 object_refs.push(last_gas_object.compute_object_reference());
78 gas_objects.push(last_gas_object);
79
80 assert_eq!(gas_objects.len(), num_gas_objects);
81 assert_eq!(
82 gas_objects
83 .iter()
84 .map(|o| o.data.try_as_move().unwrap().get_coin_value_unsafe())
85 .sum::<u64>(),
86 total_gas_balance
87 );
88
89 GasDataWithObjects {
90 gas_data: GasData {
91 payment: object_refs,
92 owner: sender,
93 price: rng.gen_range(0..=ProtocolConfig::get_for_max_version_UNSAFE().max_gas_price()),
94 budget: rng.gen_range(0..=ProtocolConfig::get_for_max_version_UNSAFE().max_tx_gas()),
95 },
96 objects: gas_objects,
97 sender_key,
98 }
99}
100
101#[derive(Debug)]
103pub struct GasDataWithObjects {
104 pub gas_data: GasData,
105 pub sender_key: AccountKeyPair,
106 pub objects: Vec<Object>,
107}
108
109#[derive(Debug, Default)]
110pub struct GasDataGenConfig {
111 pub max_num_gas_objects: usize,
112 pub owned_by_sender: bool,
113}
114
115impl GasDataGenConfig {
116 pub fn owned_by_sender_or_immut() -> Self {
117 Self {
118 max_num_gas_objects: ProtocolConfig::get_for_max_version_UNSAFE()
119 .max_gas_payment_objects() as usize,
120 owned_by_sender: true,
121 }
122 }
123
124 pub fn any_owner() -> Self {
125 Self {
126 max_num_gas_objects: ProtocolConfig::get_for_max_version_UNSAFE()
127 .max_gas_payment_objects() as usize,
128 owned_by_sender: false,
129 }
130 }
131}
132
133impl proptest::arbitrary::Arbitrary for GasDataWithObjects {
134 type Parameters = GasDataGenConfig;
135 type Strategy = BoxedStrategy<Self>;
136
137 fn arbitrary_with(params: Self::Parameters) -> Self::Strategy {
138 (
139 any::<[u8; 32]>(),
140 vec(any::<Owner>(), 1..=params.max_num_gas_objects),
141 )
142 .prop_map(move |(seed, owners)| {
143 generate_random_gas_data(seed, owners, params.owned_by_sender)
144 })
145 .boxed()
146 }
147}
148
149#[derive(Clone, Debug)]
150pub struct TestData<D> {
151 pub data: D,
152 pub executor: Executor,
153}
154
155pub fn run_proptest<D>(
158 num_test_cases: u32,
159 strategy: impl Strategy<Value = D>,
160 test_fn: impl Fn(D, Executor) -> Result<(), TestCaseError>,
161) where
162 D: Debug + 'static,
163{
164 let mut runner = TestRunner::new(ProptestConfig {
165 cases: num_test_cases,
166 ..Default::default()
167 });
168 let executor = Executor::new();
169 let strategy_with_authority = strategy.prop_map(|data| TestData {
170 data,
171 executor: executor.clone(),
172 });
173 let result = runner.run(&strategy_with_authority, |test_data| {
174 test_fn(test_data.data, test_data.executor)
175 });
176 if result.is_err() {
177 panic!("test failed: {:?}", result);
178 }
179}