use ic_management_canister_types::CanisterId;
use pocket_ic::PocketIc;
use std::sync::Arc;
use std::{path::PathBuf, slice::IterMut};
use crate::util::read_canister_bytes;
pub struct FuzzerState {
name: String,
state: Option<Arc<PocketIc>>,
canisters: Vec<CanisterInfo>,
}
#[derive(Clone, Debug)]
pub struct CanisterInfo {
pub id: Option<CanisterId>,
pub name: String,
pub wasm_path: WasmPath,
pub ty: CanisterType,
pub init_args: Vec<u8>,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
pub enum CanisterType {
Coverage,
Support,
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)]
pub enum WasmPath {
EnvVar(String),
Path(PathBuf),
}
impl FuzzerState {
pub fn new(name: &str, canisters: Vec<CanisterInfo>) -> Self {
assert!(
canisters
.iter()
.filter(|c| c.ty == CanisterType::Coverage)
.count()
== 1,
"Only one coverage canister is allowed"
);
Self {
name: name.to_string(),
state: None,
canisters,
}
}
pub fn builder() -> FuzzerBuilder {
FuzzerBuilder::new()
}
pub fn init_state(&mut self, state: PocketIc) {
self.state = Some(Arc::new(state));
}
pub fn setup_canisters(&mut self) {
if self.state.is_none() {
let pic = PocketIc::new();
self.init_state(pic);
}
let pic = self.get_state_machine();
for canister_info in self.canisters.iter_mut() {
let canister_id = pic.create_canister();
pic.add_cycles(canister_id, 20_000_000_000_000);
let wasm_bytes = read_canister_bytes(canister_info.wasm_path.clone());
pic.install_canister(
canister_id,
wasm_bytes,
canister_info.init_args.clone(),
None,
);
canister_info.id = Some(canister_id);
println!(
"Installed canister '{}' at {}",
canister_info.name, canister_id
);
}
}
pub fn name(&self) -> &str {
&self.name
}
pub(crate) fn get_coverage_canister_id(&self) -> CanisterId {
self.canisters
.iter()
.find(|c| c.ty == CanisterType::Coverage)
.unwrap()
.id
.expect("Coverage canister ID not set. Did you call setup_canisters()?")
}
pub(crate) fn get_state_machine(&self) -> Arc<PocketIc> {
self.state
.as_ref()
.expect("PocketIC state not initialized")
.clone()
}
pub fn get_canister_id_by_name(&self, name: &str) -> CanisterId {
self.canisters
.iter()
.find(|c| c.name == name)
.unwrap_or_else(|| panic!("Canister {name} not found"))
.id
.unwrap_or_else(|| panic!("CanisterId is not initialized for {name}"))
}
pub fn get_canister_wasm_path_by_name(&self, name: &str) -> WasmPath {
self.canisters
.iter()
.find(|c| c.name == name)
.unwrap_or_else(|| panic!("Canister {name} not found"))
.wasm_path
.clone()
}
pub fn get_canister_names(&self) -> Vec<String> {
self.canisters.iter().map(|c| c.name.clone()).collect()
}
pub fn get_iter_mut_canister_info(&mut self) -> IterMut<'_, CanisterInfo> {
self.canisters.iter_mut()
}
}
pub struct FuzzerBuilder {
name: String,
canisters: Vec<CanisterInfo>,
}
impl FuzzerBuilder {
pub fn new() -> Self {
Self {
name: "default_fuzzer".to_string(),
canisters: Vec::new(),
}
}
pub fn name(mut self, name: &str) -> Self {
self.name = name.to_string();
self
}
pub fn with_canister(mut self, canister: CanisterInfo) -> Self {
self.canisters.push(canister);
self
}
pub fn build(self) -> FuzzerState {
FuzzerState::new(&self.name, self.canisters)
}
}
impl Default for FuzzerBuilder {
fn default() -> Self {
Self::new()
}
}
pub struct CanisterBuilder {
name: String,
wasm_path: Option<WasmPath>,
ty: CanisterType,
init_args: Vec<u8>,
}
impl CanisterBuilder {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
wasm_path: None,
ty: CanisterType::Support, init_args: Vec::new(),
}
}
pub fn with_wasm_path(mut self, path: impl Into<PathBuf>) -> Self {
self.wasm_path = Some(WasmPath::Path(path.into()));
self
}
pub fn with_wasm_env(mut self, env_var: &str) -> Self {
self.wasm_path = Some(WasmPath::EnvVar(env_var.to_string()));
self
}
pub fn with_init_args(mut self, args: Option<Vec<u8>>) -> Self {
self.init_args = args.unwrap_or_default();
self
}
pub fn as_coverage(mut self) -> Self {
self.ty = CanisterType::Coverage;
self
}
pub fn as_support(mut self) -> Self {
self.ty = CanisterType::Support;
self
}
pub fn build(self) -> CanisterInfo {
CanisterInfo {
id: None,
name: self.name,
wasm_path: self.wasm_path.expect("Wasm path must be set"),
ty: self.ty,
init_args: self.init_args,
}
}
}