transaction_fuzzer/
account_universe.rsuse crate::executor::{ExecutionResult, Executor};
use once_cell::sync::Lazy;
use proptest::{prelude::*, strategy::Union};
use std::{fmt, sync::Arc};
use sui_types::{storage::ObjectStore, transaction::Transaction};
mod account;
mod helpers;
mod transfer_gen;
mod universe;
pub use account::*;
pub use transfer_gen::*;
pub use universe::*;
static UNIVERSE_SIZE: Lazy<usize> = Lazy::new(|| {
use std::env;
match env::var("UNIVERSE_SIZE") {
Ok(s) => match s.parse::<usize>() {
Ok(val) => val,
Err(err) => {
panic!("Could not parse universe size, aborting: {:?}", err);
}
},
Err(env::VarError::NotPresent) => 30,
Err(err) => {
panic!(
"Could not read universe size from the environment, aborting: {:?}",
err
);
}
}
});
pub fn default_num_accounts() -> usize {
*UNIVERSE_SIZE
}
pub fn default_num_transactions() -> usize {
*UNIVERSE_SIZE * 2
}
pub trait AUTransactionGen: fmt::Debug {
fn apply(
&self,
universe: &mut AccountUniverse,
exec: &mut Executor,
) -> (Transaction, ExecutionResult);
fn arced(self) -> Arc<dyn AUTransactionGen>
where
Self: 'static + Sized,
{
Arc::new(self)
}
}
impl AUTransactionGen for Arc<dyn AUTransactionGen> {
fn apply(
&self,
universe: &mut AccountUniverse,
exec: &mut Executor,
) -> (Transaction, ExecutionResult) {
(**self).apply(universe, exec)
}
}
pub fn log_balance_strategy(min_balance: u64, max_balance: u64) -> impl Strategy<Value = u64> {
assert!(max_balance >= min_balance, "minimum to make sense");
let mut strategies = vec![];
let mut lower_bound: u64 = 0;
let mut upper_bound: u64 = min_balance;
loop {
strategies.push(lower_bound..upper_bound);
if upper_bound >= max_balance {
break;
}
lower_bound = upper_bound;
upper_bound = (upper_bound * 2).min(max_balance);
}
Union::new(strategies)
}
pub fn run_and_assert_universe(
universe: AccountUniverseGen,
transaction_gens: Vec<impl AUTransactionGen + Clone>,
executor: &mut Executor,
) -> Result<(), TestCaseError> {
let mut universe = universe.setup(executor);
let (transactions, expected_values): (Vec<_>, Vec<_>) = transaction_gens
.iter()
.map(|transaction_gen| transaction_gen.clone().apply(&mut universe, executor))
.unzip();
let outputs = executor.execute_transactions(transactions);
prop_assert_eq!(outputs.len(), expected_values.len());
for (idx, (output, expected)) in outputs.iter().zip(&expected_values).enumerate() {
prop_assert!(
output == expected,
"unexpected status for transaction {} expected {:#?} but got {:#?}",
idx,
expected,
output
);
}
assert_accounts_match(&universe, executor)
}
pub fn assert_accounts_match(
universe: &AccountUniverse,
executor: &Executor,
) -> Result<(), TestCaseError> {
let state = executor.state.clone();
let backing_package_store = state.get_backing_package_store();
let object_store = state.get_object_store();
let epoch_store = state.load_epoch_store_one_call_per_task();
let mut layout_resolver = epoch_store
.executor()
.type_layout_resolver(Box::new(backing_package_store.as_ref()));
for (idx, account) in universe.accounts().iter().enumerate() {
for (balance_idx, acc_object) in account.current_coins.iter().enumerate() {
let object = object_store.get_object(&acc_object.id()).unwrap();
let total_sui_value =
object.get_total_sui(layout_resolver.as_mut()).unwrap() - object.storage_rebate;
let account_balance_i = account.current_balances[balance_idx];
prop_assert_eq!(
account_balance_i,
total_sui_value,
"account {} should have correct balance {} for object {} but got {}",
idx,
total_sui_value,
acc_object.id(),
account_balance_i
);
}
}
Ok(())
}