use std::time::{Duration, SystemTime};
use tempfile::TempDir;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use cosmwasm_std::{coins, Checksum, Empty};
use cosmwasm_vm::testing::{mock_backend, mock_env, mock_info, MockApi, MockQuerier, MockStorage};
use cosmwasm_vm::{
call_execute, call_instantiate, capabilities_from_csv, Cache, CacheOptions, InstanceOptions,
Size,
};
use clap::{Arg, Command};
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
const ROUNDS: usize = 1024;
const ROUND_LEN: usize = 16;
const DEFAULT_MEMORY_LIMIT: Size = Size::mebi(64);
const DEFAULT_GAS_LIMIT: u64 = u64::MAX;
const DEFAULT_INSTANCE_OPTIONS: InstanceOptions = InstanceOptions {
gas_limit: DEFAULT_GAS_LIMIT,
};
const MEMORY_CACHE_SIZE: Size = Size::mebi(5);
struct Execute {
pub msg: &'static [u8],
pub expect_error: bool,
}
struct Contract {
pub wasm: &'static [u8],
pub instantiate_msg: Option<Vec<u8>>,
pub execute_msgs: Vec<Execute>,
}
fn contracts() -> Vec<Contract> {
let api = MockApi::default();
let verifier = api.addr_make("verifies");
let beneficiary = api.addr_make("benefits");
vec![
Contract {
wasm: include_bytes!("../testdata/cyberpunk.wasm"),
instantiate_msg: Some(b"{}".to_vec()),
execute_msgs: vec![
Execute {
msg: br#"{"unreachable":{}}"#,
expect_error: true,
},
Execute {
msg: br#"{"allocate_large_memory":{"pages":1000}}"#,
expect_error: false,
},
Execute {
msg: br#"{"argon2":{"mem_cost":256,"time_cost":1}}"#,
expect_error: false,
},
Execute {
msg: br#"{"memory_loop":{}}"#,
expect_error: true,
},
],
},
Contract {
wasm: include_bytes!("../testdata/hackatom.wasm"),
instantiate_msg: Some(
format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#)
.into_bytes(),
),
execute_msgs: vec![Execute {
msg: br#"{"release":{"denom":"earth"}}"#,
expect_error: false,
}],
},
Contract {
wasm: include_bytes!("../testdata/hackatom_1.0.wasm"),
instantiate_msg: Some(
format!(r#"{{"verifier": "{verifier}", "beneficiary": "{beneficiary}"}}"#)
.into_bytes(),
),
execute_msgs: vec![Execute {
msg: br#"{"release":{"denom":"earth"}}"#,
expect_error: false,
}],
},
Contract {
wasm: include_bytes!("../testdata/ibc_reflect.wasm"),
instantiate_msg: None,
execute_msgs: vec![],
},
]
}
#[allow(clippy::collapsible_else_if)]
fn app(runtime: u64) {
let start_time = SystemTime::now();
let temp_dir = TempDir::new().unwrap();
let options = CacheOptions::new(
temp_dir.path(),
capabilities_from_csv("iterator,staking,stargate,cosmwasm_1_1,cosmwasm_1_2,cosmwasm_1_3,cosmwasm_1_4,cosmwasm_2_0,cosmwasm_2_1"),
MEMORY_CACHE_SIZE,
DEFAULT_MEMORY_LIMIT,
);
let contracts = contracts();
let checksums = {
let cache: Cache<MockApi, MockStorage, MockQuerier> =
unsafe { Cache::new(options.clone()).unwrap() };
let mut checksums = Vec::<Checksum>::new();
for contract in &contracts {
checksums.push(cache.store_code(contract.wasm, true, true).unwrap());
}
checksums
};
let after = SystemTime::now().duration_since(start_time).unwrap();
eprintln!("Done compiling after {after:?}");
let cache: Cache<MockApi, MockStorage, MockQuerier> = unsafe { Cache::new(options).unwrap() };
for round in 0..ROUNDS {
for _ in 0..ROUND_LEN {
if SystemTime::now().duration_since(start_time).unwrap() > Duration::from_secs(runtime)
{
eprintln!("Round {round}. End time reached. Ending the process");
let metrics = cache.metrics();
eprintln!("Cache metrics: {metrics:?}");
return; }
for idx in 0..contracts.len() {
let mut instance = cache
.get_instance(&checksums[idx], mock_backend(&[]), DEFAULT_INSTANCE_OPTIONS)
.unwrap();
instance.set_debug_handler(|_msg, info| {
let _t = now_rfc3339();
let _gas = info.gas_remaining;
});
if let Some(msg) = &contracts[idx].instantiate_msg {
let info = mock_info("creator", &coins(1000, "earth"));
let contract_result =
call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg)
.unwrap();
assert!(contract_result.into_result().is_ok());
}
for (execution_idx, execute) in contracts[idx].execute_msgs.iter().enumerate() {
let info = mock_info("verifies", &coins(15, "earth"));
let msg = execute.msg;
let res =
call_execute::<_, _, _, Empty>(&mut instance, &mock_env(), &info, msg);
if execute.expect_error {
if res.is_ok() {
panic!(
"Round {round}, Execution {execution_idx}, Contract {idx}. Expected error but got {res:?}"
);
}
} else {
if res.is_err() {
panic!("Round {round}, Execution {execution_idx}, Contract {idx}. Expected no error but got {res:?}");
}
}
}
}
}
}
}
fn now_rfc3339() -> String {
let dt = OffsetDateTime::from(SystemTime::now());
dt.format(&Rfc3339).unwrap_or_default()
}
pub fn main() {
let matches = Command::new("Heap profiling")
.version("0.0.0")
.arg(
Arg::new("runtime")
.long("runtime")
.help("Time in seconds how long the tests should be running")
.value_parser(clap::value_parser!(u64).range(1..10_000))
.default_value("30"),
)
.get_matches();
let runtime = matches
.get_one::<u64>("runtime")
.expect("Error parsing time argument");
#[cfg(feature = "dhat-heap")]
let _profiler = dhat::Profiler::new_heap();
app(*runtime);
}