use std::fmt;
use std::sync::Arc;
use vapory_types::{H256, U256, H160};
use {trie_vm_factories, journaldb, trie, tetsy_kvdb_memorydb};
use tetsy_kvdb::{self, KeyValueDB};
use {state_db, vapcore_trace, db, vapcore_spec};
use vapcore_pod::PodState;
use types::{
errors::VapcoreError,
log_entry,
receipt,
transaction
};
use vapjson::spec::ForkSpec;
use trie_vm_factories::Factories;
use vvm::FinalizationResult;
use tetsy_vm::{self, ActionParams, CreateContractAddress};
use vaptrie;
use account_state::{CleanupMode, State};
use mashina::{
executive,
substate::Substate,
};
use executive_state::ExecutiveState;
#[derive(Debug)]
pub enum VvmTestError {
Trie(Box<vaptrie::TrieError>),
Vvm(tetsy_vm::Error),
ClientError(VapcoreError),
PostCondition(String),
}
impl<E: Into<VapcoreError>> From<E> for VvmTestError {
fn from(err: E) -> Self {
VvmTestError::ClientError(err.into())
}
}
impl fmt::Display for VvmTestError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
use self::VvmTestError::*;
match *self {
Trie(ref err) => write!(fmt, "Trie: {}", err),
Vvm(ref err) => write!(fmt, "VVM: {}", err),
ClientError(ref err) => write!(fmt, "{}", err),
PostCondition(ref err) => write!(fmt, "{}", err),
}
}
}
pub struct VvmTestClient<'a> {
state: State<state_db::StateDB>,
spec: &'a vapcore_spec::Spec,
dump_state: fn(&State<state_db::StateDB>) -> Option<PodState>,
}
fn no_dump_state(_: &State<state_db::StateDB>) -> Option<PodState> {
None
}
fn dump_state(state: &State<state_db::StateDB>) -> Option<PodState> {
state.to_pod_full().ok()
}
impl<'a> fmt::Debug for VvmTestClient<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("VvmTestClient")
.field("state", &self.state)
.field("spec", &self.spec.name)
.finish()
}
}
impl<'a> VvmTestClient<'a> {
pub fn fork_spec_from_json(spec: &ForkSpec) -> Option<vapcore_spec::Spec> {
match *spec {
ForkSpec::Frontier => Some(vapcore_spec::new_frontier_test()),
ForkSpec::Homestead => Some(vapcore_spec::new_homestead_test()),
ForkSpec::EIP150 => Some(vapcore_spec::new_eip150_test()),
ForkSpec::EIP158 => Some(vapcore_spec::new_eip161_test()),
ForkSpec::Byzantium => Some(vapcore_spec::new_byzantium_test()),
ForkSpec::Constantinople => Some(vapcore_spec::new_constantinople_test()),
ForkSpec::ConstantinopleFix => Some(vapcore_spec::new_constantinople_fix_test()),
ForkSpec::Istanbul => Some(vapcore_spec::new_istanbul_test()),
ForkSpec::EIP158ToByzantiumAt5 => Some(vapcore_spec::new_transition_test()),
ForkSpec::FrontierToHomesteadAt5 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 => None,
}
}
pub fn set_dump_state(&mut self) {
self.dump_state = dump_state;
}
pub fn new_with_trie(spec: &'a vapcore_spec::Spec, trie_spec: trie::TrieSpec) -> Result<Self, VvmTestError> {
let factories = Self::factories(trie_spec);
let state = Self::state_from_spec(spec, &factories)?;
Ok(VvmTestClient {
state,
spec,
dump_state: no_dump_state,
})
}
pub fn new(spec: &'a vapcore_spec::Spec) -> Result<Self, VvmTestError> {
Self::new_with_trie(spec, trie::TrieSpec::Secure)
}
pub fn from_pod_state_with_trie(spec: &'a vapcore_spec::Spec, pod_state: PodState, trie_spec: trie::TrieSpec) -> Result<Self, VvmTestError> {
let factories = Self::factories(trie_spec);
let state = Self::state_from_pod(spec, &factories, pod_state)?;
Ok(VvmTestClient {
state,
spec,
dump_state: no_dump_state,
})
}
pub fn from_pod_state(spec: &'a vapcore_spec::Spec, pod_state: PodState) -> Result<Self, VvmTestError> {
Self::from_pod_state_with_trie(spec, pod_state, trie::TrieSpec::Secure)
}
fn factories(trie_spec: trie::TrieSpec) -> Factories {
Factories {
vm: trie_vm_factories::VmFactory::new(5 * 1024),
trie: trie::TrieFactory::new(trie_spec, vaptrie::Layout),
accountdb: Default::default(),
}
}
fn state_from_spec(spec: &'a vapcore_spec::Spec, factories: &Factories) -> Result<State<state_db::StateDB>, VvmTestError> {
let db = Arc::new(tetsy_kvdb_memorydb::create(db::NUM_COLUMNS));
let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE);
let mut state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024);
state_db = spec.ensure_db_good(state_db, factories)?;
let genesis = spec.genesis_header();
{
let mut batch = tetsy_kvdb::DBTransaction::new();
state_db.journal_under(&mut batch, 0, &genesis.hash())?;
db.write(batch)?;
}
State::from_existing(
state_db,
*genesis.state_root(),
spec.engine.account_start_nonce(0),
factories.clone()
).map_err(VvmTestError::Trie)
}
fn state_from_pod(spec: &'a vapcore_spec::Spec, factories: &Factories, pod_state: PodState) -> Result<State<state_db::StateDB>, VvmTestError> {
let db = Arc::new(tetsy_kvdb_memorydb::create(db::NUM_COLUMNS));
let journal_db = journaldb::new(db.clone(), journaldb::Algorithm::EarlyMerge, db::COL_STATE);
let state_db = state_db::StateDB::new(journal_db, 5 * 1024 * 1024);
let mut state = State::new(
state_db,
spec.engine.account_start_nonce(0),
factories.clone(),
);
state.populate_from(pod_state);
state.commit()?;
Ok(state)
}
pub fn state(&self) -> &State<state_db::StateDB> {
&self.state
}
pub fn call<T: vapcore_trace::Tracer, V: vapcore_trace::VMTracer>(
&mut self,
params: ActionParams,
tracer: &mut T,
vm_tracer: &mut V,
) -> Result<FinalizationResult, VvmTestError>
{
let genesis = self.spec.genesis_header();
let info = tetsy_vm::EnvInfo {
number: genesis.number(),
author: *genesis.author(),
timestamp: genesis.timestamp(),
difficulty: *genesis.difficulty(),
last_hashes: Arc::new([H256::zero(); 256].to_vec()),
gas_used: 0.into(),
gas_limit: *genesis.gas_limit(),
};
self.call_envinfo(params, tracer, vm_tracer, info)
}
pub fn call_envinfo<T: vapcore_trace::Tracer, V: vapcore_trace::VMTracer>(
&mut self,
params: ActionParams,
tracer: &mut T,
vm_tracer: &mut V,
info: tetsy_vm::EnvInfo,
) -> Result<FinalizationResult, VvmTestError>
{
let mut substate = Substate::new();
let mashina = self.spec.engine.mashina();
let schedule = mashina.schedule(info.number);
let mut executive = executive::Executive::new(&mut self.state, &info, &mashina, &schedule);
executive.call(
params,
&mut substate,
tracer,
vm_tracer,
).map_err(VvmTestError::Vvm)
}
pub fn transact<T: vapcore_trace::Tracer, V: vapcore_trace::VMTracer>(
&mut self,
env_info: &tetsy_vm::EnvInfo,
transaction: transaction::SignedTransaction,
tracer: T,
vm_tracer: V,
) -> std::result::Result<TransactSuccess<T::Output, V::Output>, TransactErr> {
let initial_gas = transaction.gas;
let is_ok = transaction.verify_basic(true, None);
if let Err(error) = is_ok {
return Err(
TransactErr{
state_root: *self.state.root(),
error: error.into(),
end_state: (self.dump_state)(&self.state),
});
}
let result = self.state.apply_with_tracing(&env_info, self.spec.engine.mashina(), &transaction, tracer, vm_tracer);
let scheme = CreateContractAddress::FromSenderAndNonce;
let schedule = self.spec.engine.mashina().schedule(env_info.number);
self.state.add_balance(&env_info.author, &0.into(), if schedule.no_empty {
CleanupMode::NoEmpty
} else {
CleanupMode::ForceCreate
}).ok();
self.state.kill_garbage(
&vec![env_info.author].into_iter().collect(),
schedule.kill_empty,
&None,
false
).ok();
self.state.commit().ok();
let state_root = *self.state.root();
let end_state = (self.dump_state)(&self.state);
match result {
Ok(result) => {
Ok(TransactSuccess {
state_root,
gas_left: initial_gas - result.receipt.gas_used,
outcome: result.receipt.outcome,
output: result.output,
trace: result.trace,
vm_trace: result.vm_trace,
logs: result.receipt.logs,
contract_address: if let transaction::Action::Create = transaction.action {
Some(executive::contract_address(scheme, &transaction.sender(), &transaction.nonce, &transaction.data).0)
} else {
None
},
end_state,
}
)},
Err(e) => Err(TransactErr {state_root, error: e.into(), end_state}),
}
}
}
#[allow(dead_code)]
pub struct TransactSuccess<T, V> {
pub state_root: H256,
pub gas_left: U256,
pub output: Vec<u8>,
pub trace: Vec<T>,
pub vm_trace: Option<V>,
pub contract_address: Option<H160>,
pub logs: Vec<log_entry::LogEntry>,
pub outcome: receipt::TransactionOutcome,
pub end_state: Option<PodState>,
}
#[allow(dead_code)]
pub struct TransactErr {
pub state_root: H256,
pub error: VapcoreError,
pub end_state: Option<PodState>,
}