use anyhow::{Context, Result, anyhow};
use cid::Cid;
use fvm::call_manager::DefaultCallManager;
use fvm::engine::EnginePool;
use fvm::executor::DefaultExecutor;
use fvm::externs::Externs;
use fvm::machine::{DefaultMachine, Machine, MachineContext, NetworkConfig};
use fvm::state_tree::{ActorState, StateTree};
use fvm::{init_actor, system_actor};
use fvm_ipld_blockstore::{Block, Blockstore, MemoryBlockstore};
use fvm_ipld_encoding::{CborStore, ser};
use fvm_shared::address::{Address, Protocol};
use fvm_shared::econ::TokenAmount;
use fvm_shared::state::StateTreeVersion;
use fvm_shared::version::NetworkVersion;
use fvm_shared::{ActorID, IPLD_RAW};
use k256::SecretKey;
use k256::elliptic_curve::sec1::ToEncodedPoint;
use lazy_static::lazy_static;
use multihash_codetable::Code;
use crate::builtin::{
fetch_builtin_code_cid, set_burnt_funds_account, set_eam_actor, set_init_actor, set_sys_actor,
};
use crate::custom_kernel::DefaultCustomKernel;
use crate::dummy::DummyExterns;
use crate::error::Error::{FailedToFlushTree, NoManifestInformation};
const DEFAULT_BASE_FEE: u64 = 100;
lazy_static! {
pub static ref INITIAL_ACCOUNT_BALANCE: TokenAmount = TokenAmount::from_atto(10000);
}
pub trait Store: Blockstore + Sized + 'static {}
pub type IntegrationExecutor<B, E> =
DefaultExecutor<DefaultCustomKernel<DefaultCallManager<DefaultMachine<B, E>>>>;
pub type Account = (ActorID, Address);
#[derive(Clone, Debug, Default)]
pub struct ExecutionOptions {
pub debug: bool,
pub trace: bool,
pub events: bool,
}
pub struct Tester<B: Blockstore + 'static, E: Externs + 'static> {
nv: NetworkVersion,
builtin_actors: Cid,
accounts_code_cid: Cid,
placeholder_code_cid: Cid,
code_cids: Vec<Cid>,
pub executor: Option<IntegrationExecutor<B, E>>,
pub state_tree: Option<StateTree<B>>,
pub options: Option<ExecutionOptions>,
pub ready: bool,
}
impl<B, E> Tester<B, E>
where
B: Blockstore,
E: Externs,
{
pub fn new(
nv: NetworkVersion,
stv: StateTreeVersion,
builtin_actors: Cid,
blockstore: B,
) -> Result<Self> {
let (manifest_version, manifest_data_cid): (u32, Cid) =
match blockstore.get_cbor(&builtin_actors)? {
Some((manifest_version, manifest_data)) => (manifest_version, manifest_data),
None => return Err(NoManifestInformation(builtin_actors).into()),
};
let (sys_code_cid, init_code_cid, accounts_code_cid, placeholder_code_cid, eam_code_cid) =
fetch_builtin_code_cid(&blockstore, &manifest_data_cid, manifest_version)?;
let init_state = init_actor::State::new_test(&blockstore);
let mut state_tree = StateTree::new(blockstore, stv).map_err(anyhow::Error::from)?;
let sys_state = system_actor::State { builtin_actors };
set_sys_actor(&mut state_tree, sys_state, sys_code_cid)?;
set_init_actor(&mut state_tree, init_code_cid, init_state)?;
set_burnt_funds_account(&mut state_tree, accounts_code_cid)?;
set_eam_actor(&mut state_tree, eam_code_cid)?;
Ok(Tester {
nv,
builtin_actors,
executor: None,
code_cids: vec![],
state_tree: Some(state_tree),
accounts_code_cid,
placeholder_code_cid,
options: None,
ready: false,
})
}
pub fn create_accounts<const N: usize>(&mut self) -> Result<[Account; N]> {
use rand::SeedableRng;
let rng = &mut rand_chacha::ChaCha8Rng::seed_from_u64(8);
let mut ret: [Account; N] = [(0, Address::default()); N];
for account in ret.iter_mut().take(N) {
let priv_key = SecretKey::random(rng);
*account = self.make_secp256k1_account(priv_key, INITIAL_ACCOUNT_BALANCE.clone())?;
}
Ok(ret)
}
pub fn create_account(&mut self) -> Result<Account> {
let accounts: [Account; 1] = self.create_accounts()?;
Ok(accounts[0])
}
pub fn set_account_sequence(&mut self, id: ActorID, new_sequence: u64) -> anyhow::Result<()> {
let state_tree = self
.state_tree
.as_mut()
.ok_or_else(|| anyhow!("Expected state tree in set_account_sequence."))?;
let mut state = state_tree
.get_actor(id)?
.ok_or_else(|| anyhow!("Can't set sequence of account that doesn't exist."))?;
state.sequence = new_sequence;
state_tree.set_actor(id, state);
Ok(())
}
pub fn create_placeholder(
&mut self,
address: &Address,
init_balance: TokenAmount,
) -> Result<()> {
assert_eq!(address.protocol(), Protocol::Delegated);
let state_tree = self
.state_tree
.as_mut()
.ok_or_else(|| anyhow!("unable get state tree"))?;
let id = state_tree.register_new_address(address).unwrap();
let state: [u8; 32] = [0; 32];
let cid = state_tree.store().put_cbor(&state, Code::Blake2b256)?;
let actor_state = ActorState {
code: self.placeholder_code_cid,
state: cid,
sequence: 0,
balance: init_balance,
delegated_address: Some(*address),
};
state_tree.set_actor(id, actor_state);
Ok(())
}
pub fn set_state<S: ser::Serialize>(&mut self, state: &S) -> Result<Cid> {
let state_cid = self
.state_tree
.as_mut()
.unwrap()
.store()
.put_cbor(state, Code::Blake2b256)?;
Ok(state_cid)
}
pub fn set_actor_from_bin(
&mut self,
wasm_bin: &[u8],
state_cid: Cid,
actor_address: Address,
balance: TokenAmount,
) -> Result<Cid> {
let actor_id = match actor_address.id() {
Ok(id) => id,
Err(_) => self
.state_tree
.as_mut()
.unwrap()
.register_new_address(&actor_address)
.unwrap(),
};
let code_cid = put_wasm_code(self.state_tree.as_mut().unwrap().store(), wasm_bin)?;
self.code_cids.push(code_cid);
let actor_state = ActorState::new(
code_cid,
state_cid,
balance,
1,
match actor_address.protocol() {
Protocol::ID | Protocol::Actor => None,
_ => Some(actor_address),
},
);
self.state_tree
.as_mut()
.unwrap()
.set_actor(actor_id, actor_state);
Ok(code_cid)
}
pub fn instantiate_machine(&mut self, externs: E) -> Result<()> {
self.instantiate_machine_with_config(externs, |_| (), |_| ())?;
self.ready = true;
Ok(())
}
pub fn instantiate_machine_with_config<F, G>(
&mut self,
externs: E,
configure_nc: F,
configure_mc: G,
) -> Result<()>
where
F: FnOnce(&mut NetworkConfig),
G: FnOnce(&mut MachineContext),
{
let mut state_tree = self.state_tree.take().unwrap();
let state_root = state_tree
.flush()
.map_err(anyhow::Error::from)
.context(FailedToFlushTree)?;
let blockstore = state_tree.into_store();
let mut nc = NetworkConfig::new(self.nv);
nc.override_actors(self.builtin_actors);
nc.enable_actor_debugging();
configure_nc(&mut nc);
let mut mc = nc.for_epoch(0, 0, state_root);
mc.set_base_fee(TokenAmount::from_atto(DEFAULT_BASE_FEE))
.enable_tracing();
configure_mc(&mut mc);
let engine = EnginePool::new((&mc.network.clone()).into())?;
engine.acquire().preload_all(&blockstore, &self.code_cids)?;
let machine = DefaultMachine::new(&mc, blockstore, externs)?;
let executor = DefaultExecutor::<
DefaultCustomKernel<DefaultCallManager<DefaultMachine<B, E>>>,
>::new(engine, machine)?;
self.executor = Some(executor);
self.ready = true;
Ok(())
}
pub fn blockstore(&self) -> &dyn Blockstore {
if let Some(executor) = &self.executor {
executor.blockstore()
} else {
self.state_tree.as_ref().unwrap().store()
}
}
pub fn make_secp256k1_account(
&mut self,
priv_key: SecretKey,
init_balance: TokenAmount,
) -> Result<Account> {
let pub_key = priv_key.public_key();
let pub_key_addr = Address::new_secp256k1(pub_key.to_encoded_point(false).as_bytes())?;
let state_tree = self
.state_tree
.as_mut()
.ok_or_else(|| anyhow!("unable get state tree"))?;
let assigned_addr = state_tree.register_new_address(&pub_key_addr).unwrap();
let state = fvm::account_actor::State {
address: pub_key_addr,
};
let cid = state_tree.store().put_cbor(&state, Code::Blake2b256)?;
let actor_state = ActorState {
code: self.accounts_code_cid,
state: cid,
sequence: 0,
balance: init_balance,
delegated_address: None,
};
state_tree.set_actor(assigned_addr, actor_state);
Ok((assigned_addr, pub_key_addr))
}
}
pub type BasicTester = Tester<MemoryBlockstore, DummyExterns>;
pub type BasicExecutor = IntegrationExecutor<MemoryBlockstore, DummyExterns>;
pub struct BasicAccount {
pub account: Account,
pub seqno: u64,
}
impl BasicTester {
pub fn new_basic_tester(bundle_path: String, options: ExecutionOptions) -> Result<BasicTester> {
let blockstore = MemoryBlockstore::default();
let bundle_cid = crate::bundle::import_bundle_from_path(&blockstore, bundle_path.as_str())?;
let mut tester = Tester::new(
NetworkVersion::V20,
StateTreeVersion::V5,
bundle_cid,
blockstore,
)?;
tester.options = Some(options);
tester.ready = false;
Ok(tester)
}
pub fn create_basic_account(&mut self) -> Result<BasicAccount> {
let accounts: [Account; 1] = self.create_accounts()?;
Ok(BasicAccount {
account: accounts[0],
seqno: 0,
})
}
pub fn create_basic_accounts<const N: usize>(&mut self) -> Result<[BasicAccount; N]> {
let accounts: [Account; N] = self.create_accounts()?;
Ok(accounts.map(|a| BasicAccount {
account: a,
seqno: 0,
}))
}
pub fn with_executor<F, T>(&mut self, f: F) -> Result<T>
where
F: FnOnce(&mut BasicExecutor) -> Result<T>,
{
self.prepare_execution()?;
f(self.executor.as_mut().unwrap())
}
fn prepare_execution(&mut self) -> Result<()> {
if !self.ready {
if let Some(options) = self.options.clone() {
self.instantiate_machine_with_config(
DummyExterns,
|cfg| cfg.actor_debugging = options.debug,
|mc| mc.tracing = options.trace,
)?;
} else {
self.instantiate_machine(DummyExterns)?;
}
self.ready = true
}
Ok(())
}
}
fn put_wasm_code(blockstore: &impl Blockstore, wasm_binary: &[u8]) -> Result<Cid> {
let cid = blockstore.put(
Code::Blake2b256,
&Block {
codec: IPLD_RAW,
data: wasm_binary,
},
)?;
Ok(cid)
}