use std::{
collections::BTreeMap,
convert::{TryFrom, TryInto},
ffi::OsStr,
fs,
ops::Deref,
path::{Path, PathBuf},
rc::Rc,
sync::Arc,
};
use filesize::PathExt;
use lmdb::DatabaseFlags;
use log::LevelFilter;
use num_rational::Ratio;
use num_traits::CheckedMul;
use casper_execution_engine::{
core::{
engine_state::{
self,
engine_config::RefundHandling,
era_validators::GetEraValidatorsRequest,
execute_request::ExecuteRequest,
execution_result::ExecutionResult,
run_genesis_request::RunGenesisRequest,
step::{EvictItem, StepRequest, StepSuccess},
BalanceResult, EngineConfig, EngineConfigBuilder, EngineState, Error, GenesisSuccess,
GetBidsRequest, PruneConfig, PruneResult, QueryRequest, QueryResult, RewardItem,
StepError, SystemContractRegistry, UpgradeConfig, UpgradeSuccess,
DEFAULT_MAX_QUERY_DEPTH,
},
execution,
},
shared::{
additive_map::AdditiveMap,
execution_journal::ExecutionJournal,
logging::{self, Settings, Style},
newtypes::CorrelationId,
system_config::{
auction_costs::AuctionCosts, handle_payment_costs::HandlePaymentCosts,
mint_costs::MintCosts,
},
transform::Transform,
utils::OS_PAGE_SIZE,
},
storage::{
global_state::{
in_memory::InMemoryGlobalState, lmdb::LmdbGlobalState, scratch::ScratchGlobalState,
CommitProvider, StateProvider, StateReader,
},
transaction_source::lmdb::LmdbEnvironment,
trie::{merkle_proof::TrieMerkleProof, Trie},
trie_store::lmdb::LmdbTrieStore,
},
};
use casper_hashing::Digest;
use casper_types::{
account::{Account, AccountHash},
bytesrepr::{self, FromBytes},
runtime_args,
system::{
auction::{
Bids, EraValidators, UnbondingPurse, UnbondingPurses, ValidatorWeights, WithdrawPurses,
ARG_ERA_END_TIMESTAMP_MILLIS, ARG_EVICTED_VALIDATORS, AUCTION_DELAY_KEY, ERA_ID_KEY,
METHOD_RUN_AUCTION, UNBONDING_DELAY_KEY,
},
mint::{ROUND_SEIGNIORAGE_RATE_KEY, TOTAL_SUPPLY_KEY},
AUCTION, HANDLE_PAYMENT, MINT, STANDARD_PAYMENT,
},
CLTyped, CLValue, Contract, ContractHash, ContractPackage, ContractPackageHash, ContractWasm,
DeployHash, DeployInfo, EraId, Gas, Key, KeyTag, Motes, ProtocolVersion, PublicKey,
RuntimeArgs, StoredValue, Transfer, TransferAddr, URef, U512,
};
use crate::{
chainspec_config::{ChainspecConfig, CoreConfig, PRODUCTION_CHAINSPEC_PATH},
utils, ExecuteRequestBuilder, StepRequestBuilder, DEFAULT_GAS_PRICE, DEFAULT_PROPOSER_ADDR,
DEFAULT_PROTOCOL_VERSION, SYSTEM_ADDR,
};
const DEFAULT_LMDB_PAGES: usize = 256_000_000;
const DEFAULT_MAX_READERS: u32 = 512;
const GLOBAL_STATE_DIR: &str = "global_state";
pub type InMemoryWasmTestBuilder = WasmTestBuilder<InMemoryGlobalState>;
pub type LmdbWasmTestBuilder = WasmTestBuilder<LmdbGlobalState>;
pub struct WasmTestBuilder<S> {
engine_state: Rc<EngineState<S>>,
exec_results: Vec<Vec<Rc<ExecutionResult>>>,
upgrade_results: Vec<Result<UpgradeSuccess, engine_state::Error>>,
prune_results: Vec<Result<PruneResult, engine_state::Error>>,
genesis_hash: Option<Digest>,
post_state_hash: Option<Digest>,
transforms: Vec<ExecutionJournal>,
system_account: Option<Account>,
genesis_transforms: Option<AdditiveMap<Key, Transform>>,
scratch_engine_state: Option<EngineState<ScratchGlobalState>>,
system_contract_registry: Option<SystemContractRegistry>,
global_state_dir: Option<PathBuf>,
}
impl<S> WasmTestBuilder<S> {
fn initialize_logging() {
let log_settings = Settings::new(LevelFilter::Error).with_style(Style::HumanReadable);
let _ = logging::initialize(log_settings);
}
}
impl Default for InMemoryWasmTestBuilder {
fn default() -> Self {
Self::new_with_chainspec(&*PRODUCTION_CHAINSPEC_PATH, None)
}
}
impl<S> Clone for WasmTestBuilder<S> {
fn clone(&self) -> Self {
WasmTestBuilder {
engine_state: Rc::clone(&self.engine_state),
exec_results: self.exec_results.clone(),
upgrade_results: self.upgrade_results.clone(),
prune_results: self.prune_results.clone(),
genesis_hash: self.genesis_hash,
post_state_hash: self.post_state_hash,
transforms: self.transforms.clone(),
system_account: None,
genesis_transforms: self.genesis_transforms.clone(),
scratch_engine_state: None,
system_contract_registry: self.system_contract_registry.clone(),
global_state_dir: self.global_state_dir.clone(),
}
}
}
impl InMemoryWasmTestBuilder {
pub fn new_with_config(engine_config: EngineConfig) -> Self {
Self::initialize_logging();
let global_state = InMemoryGlobalState::empty().expect("should create global state");
let genesis_hash = global_state.empty_root();
let engine_state = EngineState::new(global_state, engine_config);
WasmTestBuilder {
exec_results: Vec::new(),
upgrade_results: Vec::new(),
prune_results: Vec::new(),
engine_state: Rc::new(engine_state),
genesis_hash: Some(genesis_hash),
post_state_hash: Some(genesis_hash),
transforms: Vec::new(),
system_account: None,
genesis_transforms: None,
scratch_engine_state: None,
system_contract_registry: None,
global_state_dir: None,
}
}
pub fn new(
global_state: InMemoryGlobalState,
engine_config: EngineConfig,
maybe_post_state_hash: Option<Digest>,
) -> Self {
Self::initialize_logging();
let engine_state = EngineState::new(global_state, engine_config);
WasmTestBuilder {
engine_state: Rc::new(engine_state),
exec_results: Vec::new(),
upgrade_results: Vec::new(),
prune_results: Vec::new(),
genesis_hash: maybe_post_state_hash,
post_state_hash: maybe_post_state_hash,
transforms: Vec::new(),
system_account: None,
genesis_transforms: None,
scratch_engine_state: None,
system_contract_registry: None,
global_state_dir: None,
}
}
pub fn new_with_chainspec<P: AsRef<Path>>(
chainspec_path: P,
post_state_hash: Option<Digest>,
) -> Self {
let chainspec_config = ChainspecConfig::from_chainspec_path(chainspec_path)
.expect("must build chainspec configuration");
let ChainspecConfig {
core_config,
wasm_config,
system_costs_config,
} = chainspec_config;
let CoreConfig {
validator_slots: _,
auction_delay: _,
locked_funds_period: _,
vesting_schedule_period,
unbonding_delay: _,
round_seigniorage_rate: _,
max_associated_keys,
max_runtime_call_stack_height,
minimum_delegation_amount,
strict_argument_checking,
max_delegators_per_validator,
refund_handling,
fee_handling,
} = core_config;
let engine_config = EngineConfigBuilder::new()
.with_max_query_depth(DEFAULT_MAX_QUERY_DEPTH)
.with_max_associated_keys(max_associated_keys)
.with_max_runtime_call_stack_height(max_runtime_call_stack_height)
.with_minimum_delegation_amount(minimum_delegation_amount)
.with_strict_argument_checking(strict_argument_checking)
.with_vesting_schedule_period_millis(vesting_schedule_period.millis())
.with_max_delegators_per_validator(max_delegators_per_validator)
.with_wasm_config(wasm_config)
.with_system_config(system_costs_config)
.with_refund_handling(refund_handling)
.with_fee_handling(fee_handling)
.build();
let global_state = InMemoryGlobalState::empty().expect("should create global state");
Self::new(global_state, engine_config, post_state_hash)
}
}
impl LmdbWasmTestBuilder {
pub fn upgrade_with_upgrade_request_using_scratch(
&mut self,
engine_config: EngineConfig,
upgrade_config: &mut UpgradeConfig,
) -> &mut Self {
let pre_state_hash = self.post_state_hash.expect("should have state hash");
upgrade_config.with_pre_state_hash(pre_state_hash);
let engine_state = Rc::get_mut(&mut self.engine_state).unwrap();
engine_state.update_config(engine_config);
let scratch_state = self.engine_state.get_scratch_engine_state();
let pre_state_hash = upgrade_config.pre_state_hash();
let mut result = scratch_state
.commit_upgrade(CorrelationId::new(), upgrade_config.clone())
.unwrap();
result.post_state_hash = self
.engine_state
.write_scratch_to_db(pre_state_hash, scratch_state.into_inner())
.unwrap();
self.engine_state.flush_environment().unwrap();
let result = Ok(result);
if let Ok(UpgradeSuccess {
post_state_hash,
execution_effect: _,
}) = result
{
self.post_state_hash = Some(post_state_hash);
if let Ok(StoredValue::CLValue(cl_registry)) =
self.query(self.post_state_hash, Key::SystemContractRegistry, &[])
{
let registry = CLValue::into_t::<SystemContractRegistry>(cl_registry).unwrap();
self.system_contract_registry = Some(registry);
}
}
self.upgrade_results.push(result);
self
}
pub fn new_with_config<T: AsRef<OsStr> + ?Sized>(
data_dir: &T,
engine_config: EngineConfig,
) -> Self {
Self::initialize_logging();
let page_size = *OS_PAGE_SIZE;
let global_state_dir = Self::global_state_dir(data_dir);
Self::create_global_state_dir(&global_state_dir);
let environment = Arc::new(
LmdbEnvironment::new(
&global_state_dir,
page_size * DEFAULT_LMDB_PAGES,
DEFAULT_MAX_READERS,
true,
)
.expect("should create LmdbEnvironment"),
);
let trie_store = Arc::new(
LmdbTrieStore::new(&environment, None, DatabaseFlags::empty())
.expect("should create LmdbTrieStore"),
);
let global_state =
LmdbGlobalState::empty(environment, trie_store).expect("should create LmdbGlobalState");
let engine_state = EngineState::new(global_state, engine_config);
WasmTestBuilder {
engine_state: Rc::new(engine_state),
exec_results: Vec::new(),
upgrade_results: Vec::new(),
prune_results: Vec::new(),
genesis_hash: None,
post_state_hash: None,
transforms: Vec::new(),
system_account: None,
genesis_transforms: None,
scratch_engine_state: None,
system_contract_registry: None,
global_state_dir: Some(global_state_dir),
}
}
pub fn new_with_chainspec<T: AsRef<OsStr> + ?Sized, P: AsRef<Path>>(
data_dir: &T,
chainspec_path: P,
) -> Self {
let chainspec_config = ChainspecConfig::from_chainspec_path(chainspec_path)
.expect("must build chainspec configuration");
Self::new_with_config(data_dir, EngineConfig::from(chainspec_config))
}
pub fn new_with_production_chainspec<T: AsRef<OsStr> + ?Sized>(data_dir: &T) -> Self {
Self::new_with_chainspec(data_dir, &*PRODUCTION_CHAINSPEC_PATH)
}
pub fn flush_environment(&self) {
let engine_state = &*self.engine_state;
engine_state.flush_environment().unwrap();
}
pub fn new<T: AsRef<OsStr> + ?Sized>(data_dir: &T) -> Self {
Self::new_with_config(data_dir, Default::default())
}
pub fn open<T: AsRef<OsStr> + ?Sized>(
data_dir: &T,
engine_config: EngineConfig,
post_state_hash: Digest,
) -> Self {
let global_state_path = Self::global_state_dir(data_dir);
Self::open_raw(global_state_path, engine_config, post_state_hash)
}
pub fn open_raw<T: AsRef<Path>>(
global_state_dir: T,
engine_config: EngineConfig,
post_state_hash: Digest,
) -> Self {
Self::initialize_logging();
let page_size = *OS_PAGE_SIZE;
Self::create_global_state_dir(&global_state_dir);
let environment = Arc::new(
LmdbEnvironment::new(
&global_state_dir,
page_size * DEFAULT_LMDB_PAGES,
DEFAULT_MAX_READERS,
true,
)
.expect("should create LmdbEnvironment"),
);
let trie_store =
Arc::new(LmdbTrieStore::open(&environment, None).expect("should open LmdbTrieStore"));
let global_state =
LmdbGlobalState::empty(environment, trie_store).expect("should create LmdbGlobalState");
let engine_state = EngineState::new(global_state, engine_config);
let mut builder = WasmTestBuilder {
engine_state: Rc::new(engine_state),
exec_results: Vec::new(),
upgrade_results: Vec::new(),
prune_results: Vec::new(),
genesis_hash: None,
post_state_hash: Some(post_state_hash),
transforms: Vec::new(),
system_account: None,
genesis_transforms: None,
scratch_engine_state: None,
system_contract_registry: None,
global_state_dir: None,
};
builder.system_contract_registry =
builder.query_system_contract_registry(Some(post_state_hash));
builder
}
fn create_global_state_dir<T: AsRef<Path>>(global_state_path: T) {
fs::create_dir_all(&global_state_path).unwrap_or_else(|_| {
panic!(
"Expected to create {}",
global_state_path.as_ref().display()
)
});
}
fn global_state_dir<T: AsRef<OsStr> + ?Sized>(data_dir: &T) -> PathBuf {
let mut path = PathBuf::from(data_dir);
path.push(GLOBAL_STATE_DIR);
path
}
pub fn lmdb_on_disk_size(&self) -> Option<u64> {
if let Some(path) = self.global_state_dir.as_ref() {
let mut path = path.clone();
path.push("data.lmdb");
return path.as_path().size_on_disk().ok();
}
None
}
pub fn scratch_exec_and_commit(&mut self, mut exec_request: ExecuteRequest) -> &mut Self {
if self.scratch_engine_state.is_none() {
self.scratch_engine_state = Some(self.engine_state.get_scratch_engine_state());
}
let cached_state = self
.scratch_engine_state
.as_ref()
.expect("scratch state should exist");
let exec_request = {
let hash = self.post_state_hash.expect("expected post_state_hash");
exec_request.parent_state_hash = hash;
exec_request
};
let mut exec_results = Vec::new();
let maybe_exec_results = cached_state.run_execute(CorrelationId::new(), exec_request);
for execution_result in maybe_exec_results.unwrap() {
let journal = execution_result.execution_journal().clone();
let transforms: AdditiveMap<Key, Transform> = journal.clone().into();
let _post_state_hash = cached_state
.apply_effect(
CorrelationId::new(),
self.post_state_hash.expect("requires a post_state_hash"),
transforms,
)
.expect("should commit");
self.transforms.push(journal);
exec_results.push(Rc::new(execution_result))
}
self.exec_results.push(exec_results);
self
}
pub fn write_scratch_to_db(&mut self) -> &mut Self {
let prestate_hash = self.post_state_hash.expect("Should have genesis hash");
if let Some(scratch) = self.scratch_engine_state.take() {
let new_state_root = self
.engine_state
.write_scratch_to_db(prestate_hash, scratch.into_inner())
.unwrap();
self.post_state_hash = Some(new_state_root);
}
self
}
pub fn step_with_scratch(&mut self, step_request: StepRequest) -> &mut Self {
if self.scratch_engine_state.is_none() {
self.scratch_engine_state = Some(self.engine_state.get_scratch_engine_state());
}
let cached_state = self
.scratch_engine_state
.as_ref()
.expect("scratch state should exist");
cached_state
.commit_step(CorrelationId::new(), step_request)
.expect("unable to run step request against scratch global state");
self
}
}
impl<S> WasmTestBuilder<S>
where
S: StateProvider + CommitProvider,
engine_state::Error: From<S::Error>,
S::Error: Into<execution::Error>,
{
pub fn run_genesis(&mut self, run_genesis_request: &RunGenesisRequest) -> &mut Self {
let GenesisSuccess {
post_state_hash,
execution_effect,
} = self
.engine_state
.commit_genesis(
CorrelationId::new(),
run_genesis_request.genesis_config_hash(),
run_genesis_request.protocol_version(),
run_genesis_request.ee_config(),
run_genesis_request.chainspec_registry().clone(),
)
.expect("Unable to get genesis response");
let transforms = execution_effect.transforms;
self.system_contract_registry = self.query_system_contract_registry(Some(post_state_hash));
self.genesis_hash = Some(post_state_hash);
self.post_state_hash = Some(post_state_hash);
self.genesis_transforms = Some(transforms);
self.system_account = self.get_account(*SYSTEM_ADDR);
self
}
fn query_system_contract_registry(
&mut self,
post_state_hash: Option<Digest>,
) -> Option<SystemContractRegistry> {
match self.query(post_state_hash, Key::SystemContractRegistry, &[]) {
Ok(StoredValue::CLValue(cl_registry)) => {
let system_contract_registry =
CLValue::into_t::<SystemContractRegistry>(cl_registry).unwrap();
Some(system_contract_registry)
}
Ok(_) => None,
Err(_) => None,
}
}
pub fn query(
&self,
maybe_post_state: Option<Digest>,
base_key: Key,
path: &[String],
) -> Result<StoredValue, String> {
let post_state = maybe_post_state
.or(self.post_state_hash)
.expect("builder must have a post-state hash");
let query_request = QueryRequest::new(post_state, base_key, path.to_vec());
let query_result = self
.engine_state
.run_query(CorrelationId::new(), query_request)
.expect("should get query response");
if let QueryResult::Success { value, .. } = query_result {
return Ok(value.deref().clone());
}
Err(format!("{:?}", query_result))
}
pub fn query_dictionary_item(
&self,
maybe_post_state: Option<Digest>,
dictionary_seed_uref: URef,
dictionary_item_key: &str,
) -> Result<StoredValue, String> {
let dictionary_address =
Key::dictionary(dictionary_seed_uref, dictionary_item_key.as_bytes());
let empty_path: Vec<String> = vec![];
self.query(maybe_post_state, dictionary_address, &empty_path)
}
pub fn query_with_proof(
&self,
maybe_post_state: Option<Digest>,
base_key: Key,
path: &[String],
) -> Result<(StoredValue, Vec<TrieMerkleProof<Key, StoredValue>>), String> {
let post_state = maybe_post_state
.or(self.post_state_hash)
.expect("builder must have a post-state hash");
let path_vec: Vec<String> = path.to_vec();
let query_request = QueryRequest::new(post_state, base_key, path_vec);
let query_result = self
.engine_state
.run_query(CorrelationId::new(), query_request)
.expect("should get query response");
if let QueryResult::Success { value, proofs } = query_result {
return Ok((value.deref().clone(), proofs));
}
panic! {"{:?}", query_result};
}
pub fn total_supply(&self, maybe_post_state: Option<Digest>) -> U512 {
let mint_key: Key = self
.get_system_contract_hash(MINT)
.cloned()
.expect("should have mint_contract_hash")
.into();
let result = self.query(maybe_post_state, mint_key, &[TOTAL_SUPPLY_KEY.to_string()]);
let total_supply: U512 = if let Ok(StoredValue::CLValue(total_supply)) = result {
total_supply.into_t().expect("total supply should be U512")
} else {
panic!("mint should track total supply");
};
total_supply
}
pub fn base_round_reward(&mut self, maybe_post_state: Option<Digest>) -> U512 {
let mint_key: Key = self.get_mint_contract_hash().into();
let mint_contract = self
.query(maybe_post_state, mint_key, &[])
.expect("must get mint stored value")
.as_contract()
.expect("must convert to mint contract")
.clone();
let mint_named_keys = mint_contract.named_keys().clone();
let total_supply_uref = *mint_named_keys
.get(TOTAL_SUPPLY_KEY)
.expect("must track total supply")
.as_uref()
.expect("must get uref");
let round_seigniorage_rate_uref = *mint_named_keys
.get(ROUND_SEIGNIORAGE_RATE_KEY)
.expect("must track round seigniorage rate");
let total_supply = self
.query(maybe_post_state, Key::URef(total_supply_uref), &[])
.expect("must read value under total supply URef")
.as_cl_value()
.expect("must convert into CL value")
.clone()
.into_t::<U512>()
.expect("must convert into U512");
let rate = self
.query(maybe_post_state, round_seigniorage_rate_uref, &[])
.expect("must read value")
.as_cl_value()
.expect("must conver to cl value")
.clone()
.into_t::<Ratio<U512>>()
.expect("must conver to ratio");
rate.checked_mul(&Ratio::from(total_supply))
.map(|ratio| ratio.to_integer())
.expect("must get base round reward")
}
pub fn exec(&mut self, exec_request: ExecuteRequest) -> &mut Self {
self.try_exec(exec_request).expect("should execute")
}
pub fn try_exec(&mut self, mut exec_request: ExecuteRequest) -> Result<&mut Self, Error> {
let exec_request = {
let hash = self.post_state_hash.expect("expected post_state_hash");
exec_request.parent_state_hash = hash;
exec_request
};
let execution_results = self
.engine_state
.run_execute(CorrelationId::new(), exec_request)?;
self.transforms.extend(
execution_results
.iter()
.map(|res| res.execution_journal().clone()),
);
self.exec_results
.push(execution_results.into_iter().map(Rc::new).collect());
Ok(self)
}
pub fn commit(&mut self) -> &mut Self {
let prestate_hash = self.post_state_hash.expect("Should have genesis hash");
let effects = self.transforms.last().cloned().unwrap_or_default();
self.commit_transforms(prestate_hash, effects.into())
}
pub fn commit_transforms(
&mut self,
pre_state_hash: Digest,
effects: AdditiveMap<Key, Transform>,
) -> &mut Self {
let post_state_hash = self
.engine_state
.apply_effect(CorrelationId::new(), pre_state_hash, effects)
.expect("should commit");
self.post_state_hash = Some(post_state_hash);
self
}
pub fn upgrade_with_upgrade_request(
&mut self,
engine_config: EngineConfig,
upgrade_config: &mut UpgradeConfig,
) -> &mut Self {
self.upgrade_with_upgrade_request_and_config(Some(engine_config), upgrade_config)
}
pub fn upgrade_with_upgrade_request_and_config(
&mut self,
engine_config: Option<EngineConfig>,
upgrade_config: &mut UpgradeConfig,
) -> &mut Self {
let engine_config = engine_config.unwrap_or_else(|| self.engine_state.config().clone());
let pre_state_hash = self.post_state_hash.expect("should have state hash");
upgrade_config.with_pre_state_hash(pre_state_hash);
let engine_state_mut =
Rc::get_mut(&mut self.engine_state).expect("should have unique ownership");
engine_state_mut.update_config(engine_config);
let result = engine_state_mut.commit_upgrade(CorrelationId::new(), upgrade_config.clone());
if let Ok(UpgradeSuccess {
post_state_hash,
execution_effect: _,
}) = result
{
self.post_state_hash = Some(post_state_hash);
self.system_contract_registry =
self.query_system_contract_registry(Some(post_state_hash));
}
self.upgrade_results.push(result);
self
}
pub fn run_auction(
&mut self,
era_end_timestamp_millis: u64,
evicted_validators: Vec<PublicKey>,
) -> &mut Self {
let auction = self.get_auction_contract_hash();
let run_request = ExecuteRequestBuilder::contract_call_by_hash(
*SYSTEM_ADDR,
auction,
METHOD_RUN_AUCTION,
runtime_args! {
ARG_ERA_END_TIMESTAMP_MILLIS => era_end_timestamp_millis,
ARG_EVICTED_VALIDATORS => evicted_validators,
},
)
.build();
self.exec(run_request).commit().expect_success()
}
pub fn step(&mut self, step_request: StepRequest) -> Result<StepSuccess, StepError> {
let step_result = self
.engine_state
.commit_step(CorrelationId::new(), step_request);
if let Ok(StepSuccess {
post_state_hash, ..
}) = &step_result
{
self.post_state_hash = Some(*post_state_hash);
}
step_result
}
pub fn expect_success(&mut self) -> &mut Self {
let exec_results = self
.get_last_exec_results()
.expect("Expected to be called after run()");
let exec_result = exec_results
.get(0)
.expect("Unable to get first deploy result");
if exec_result.is_failure() {
panic!(
"Expected successful execution result, but instead got: {:#?}",
exec_result,
);
}
self
}
pub fn expect_failure(&mut self) -> &mut Self {
let exec_results = self
.get_last_exec_results()
.expect("Expected to be called after run()");
let exec_result = exec_results
.get(0)
.expect("Unable to get first deploy result");
if exec_result.is_success() {
panic!(
"Expected failed execution result, but instead got: {:?}",
exec_result,
);
}
self
}
pub fn is_error(&self) -> bool {
self.get_last_exec_results()
.expect("Expected to be called after run()")
.get(0)
.expect("Unable to get first execution result")
.is_failure()
}
pub fn get_error(&self) -> Option<engine_state::Error> {
self.get_last_exec_results()
.expect("Expected to be called after run()")
.get(0)
.expect("Unable to get first deploy result")
.as_error()
.cloned()
}
pub fn get_execution_journals(&self) -> Vec<ExecutionJournal> {
self.transforms.clone()
}
pub fn get_genesis_account(&self) -> &Account {
self.system_account
.as_ref()
.expect("Unable to obtain genesis account. Please run genesis first.")
}
pub fn get_mint_contract_hash(&self) -> ContractHash {
self.get_system_contract_hash(MINT)
.cloned()
.expect("Unable to obtain mint contract. Please run genesis first.")
}
pub fn get_handle_payment_contract_hash(&self) -> ContractHash {
self.get_system_contract_hash(HANDLE_PAYMENT)
.cloned()
.expect("Unable to obtain handle payment contract. Please run genesis first.")
}
pub fn get_standard_payment_contract_hash(&self) -> ContractHash {
self.get_system_contract_hash(STANDARD_PAYMENT)
.cloned()
.expect("Unable to obtain standard payment contract. Please run genesis first.")
}
fn get_system_contract_hash(&self, contract_name: &str) -> Option<&ContractHash> {
self.system_contract_registry
.as_ref()
.and_then(|registry| registry.get(contract_name))
}
pub fn get_auction_contract_hash(&self) -> ContractHash {
self.get_system_contract_hash(AUCTION)
.cloned()
.expect("Unable to obtain auction contract. Please run genesis first.")
}
pub fn get_genesis_transforms(&self) -> &AdditiveMap<Key, Transform> {
self.genesis_transforms
.as_ref()
.expect("should have genesis transforms")
}
pub fn get_genesis_hash(&self) -> Digest {
self.genesis_hash
.expect("Genesis hash should be present. Should be called after run_genesis.")
}
pub fn get_post_state_hash(&self) -> Digest {
self.post_state_hash.expect("Should have post-state hash.")
}
pub fn get_engine_state(&self) -> &EngineState<S> {
&self.engine_state
}
pub fn get_last_exec_results(&self) -> Option<Vec<Rc<ExecutionResult>>> {
let exec_results = self.exec_results.last()?;
Some(exec_results.iter().map(Rc::clone).collect())
}
pub fn get_exec_result_owned(&self, index: usize) -> Option<Vec<Rc<ExecutionResult>>> {
let exec_results = self.exec_results.get(index)?;
Some(exec_results.iter().map(Rc::clone).collect())
}
pub fn get_exec_results_count(&self) -> usize {
self.exec_results.len()
}
pub fn get_upgrade_result(
&self,
index: usize,
) -> Option<&Result<UpgradeSuccess, engine_state::Error>> {
self.upgrade_results.get(index)
}
pub fn expect_upgrade_success(&mut self) -> &mut Self {
let result = self
.upgrade_results
.last()
.expect("Expected to be called after a system upgrade.")
.as_ref();
result.unwrap_or_else(|_| panic!("Expected success, got: {:?}", result));
self
}
pub fn get_handle_payment_contract(&self) -> Contract {
let handle_payment_contract: Key = self
.get_system_contract_hash(HANDLE_PAYMENT)
.cloned()
.expect("should have handle payment contract uref")
.into();
self.query(None, handle_payment_contract, &[])
.and_then(|v| v.try_into().map_err(|error| format!("{:?}", error)))
.expect("should find handle payment URef")
}
pub fn get_purse_balance(&self, purse: URef) -> U512 {
let base_key = Key::Balance(purse.addr());
self.query(None, base_key, &[])
.and_then(|v| CLValue::try_from(v).map_err(|error| format!("{:?}", error)))
.and_then(|cl_value| cl_value.into_t().map_err(|error| format!("{:?}", error)))
.expect("should parse balance into a U512")
}
pub fn get_purse_balance_result(&self, purse: URef) -> BalanceResult {
let correlation_id = CorrelationId::new();
let state_root_hash: Digest = self.post_state_hash.expect("should have post_state_hash");
self.engine_state
.get_purse_balance(correlation_id, state_root_hash, purse)
.expect("should get purse balance")
}
pub fn get_public_key_balance_result(&self, public_key: PublicKey) -> BalanceResult {
let correlation_id = CorrelationId::new();
let state_root_hash: Digest = self.post_state_hash.expect("should have post_state_hash");
self.engine_state
.get_balance(correlation_id, state_root_hash, public_key)
.expect("should get purse balance using public key")
}
pub fn get_proposer_purse_balance(&self) -> U512 {
let proposer_account = self
.get_account(*DEFAULT_PROPOSER_ADDR)
.expect("proposer account should exist");
self.get_purse_balance(proposer_account.main_purse())
}
pub fn get_account(&self, account_hash: AccountHash) -> Option<Account> {
match self.query(None, Key::Account(account_hash), &[]) {
Ok(account_value) => match account_value {
StoredValue::Account(account) => Some(account),
_ => None,
},
Err(_) => None,
}
}
pub fn get_expected_account(&self, account_hash: AccountHash) -> Account {
self.get_account(account_hash).expect("account to exist")
}
pub fn get_contract(&self, contract_hash: ContractHash) -> Option<Contract> {
let contract_value: StoredValue = self
.query(None, contract_hash.into(), &[])
.expect("should have contract value");
if let StoredValue::Contract(contract) = contract_value {
Some(contract)
} else {
None
}
}
pub fn get_contract_wasm(&self, contract_hash: ContractHash) -> Option<ContractWasm> {
let contract_value: StoredValue = self
.query(None, contract_hash.into(), &[])
.expect("should have contract value");
if let StoredValue::ContractWasm(contract_wasm) = contract_value {
Some(contract_wasm)
} else {
None
}
}
pub fn get_contract_package(
&self,
contract_package_hash: ContractPackageHash,
) -> Option<ContractPackage> {
let contract_value: StoredValue = self
.query(None, contract_package_hash.into(), &[])
.expect("should have package value");
if let StoredValue::ContractPackage(package) = contract_value {
Some(package)
} else {
None
}
}
pub fn get_transfer(&self, transfer: TransferAddr) -> Option<Transfer> {
let transfer_value: StoredValue = self
.query(None, Key::Transfer(transfer), &[])
.expect("should have transfer value");
if let StoredValue::Transfer(transfer) = transfer_value {
Some(transfer)
} else {
None
}
}
pub fn get_deploy_info(&self, deploy_hash: DeployHash) -> Option<DeployInfo> {
let deploy_info_value: StoredValue = self
.query(None, Key::DeployInfo(deploy_hash), &[])
.expect("should have deploy info value");
if let StoredValue::DeployInfo(deploy_info) = deploy_info_value {
Some(deploy_info)
} else {
None
}
}
pub fn exec_costs(&self, index: usize) -> Vec<Gas> {
let exec_results = self
.get_exec_result_owned(index)
.expect("should have exec response");
utils::get_exec_costs(exec_results)
}
pub fn last_exec_gas_cost(&self) -> Gas {
let exec_results = self
.get_last_exec_results()
.expect("Expected to be called after run()");
let exec_result = exec_results.get(0).expect("should have result");
exec_result.cost()
}
pub fn last_exec_result(&self) -> &ExecutionResult {
let exec_results = self
.exec_results
.last()
.expect("Expected to be called after run()");
exec_results.get(0).expect("should have result").as_ref()
}
pub fn assert_error(&self, expected_error: Error) {
match self.get_error() {
Some(error) => assert_eq!(format!("{:?}", expected_error), format!("{:?}", error)),
None => panic!("expected error ({:?}) got success", expected_error),
}
}
pub fn exec_error_message(&self, index: usize) -> Option<String> {
let response = self.get_exec_result_owned(index)?;
Some(utils::get_error_message(response))
}
pub fn get_era_validators(&mut self) -> EraValidators {
let correlation_id = CorrelationId::new();
let state_hash = self.get_post_state_hash();
let request = GetEraValidatorsRequest::new(state_hash, *DEFAULT_PROTOCOL_VERSION);
let system_contract_registry = self
.system_contract_registry
.clone()
.expect("System contract registry not found. Please run genesis first.");
self.engine_state
.get_era_validators(correlation_id, Some(system_contract_registry), request)
.expect("get era validators should not error")
}
pub fn get_validator_weights(&mut self, era_id: EraId) -> Option<ValidatorWeights> {
let mut result = self.get_era_validators();
result.remove(&era_id)
}
pub fn get_bids(&mut self) -> Bids {
let get_bids_request = GetBidsRequest::new(self.get_post_state_hash());
let get_bids_result = self
.engine_state
.get_bids(CorrelationId::new(), get_bids_request)
.unwrap();
get_bids_result.into_success().unwrap()
}
pub fn get_unbonds(&mut self) -> UnbondingPurses {
let correlation_id = CorrelationId::new();
let state_root_hash = self.get_post_state_hash();
let tracking_copy = self
.engine_state
.tracking_copy(state_root_hash)
.unwrap()
.unwrap();
let reader = tracking_copy.reader();
let unbond_keys = reader
.keys_with_prefix(correlation_id, &[KeyTag::Unbond as u8])
.unwrap_or_default();
let mut ret = BTreeMap::new();
for key in unbond_keys.into_iter() {
let read_result = reader.read(correlation_id, &key);
if let (Key::Unbond(account_hash), Ok(Some(StoredValue::Unbonding(unbonding_purses)))) =
(key, read_result)
{
ret.insert(account_hash, unbonding_purses);
}
}
ret
}
pub fn get_withdraw_purses(&mut self) -> WithdrawPurses {
let correlation_id = CorrelationId::new();
let state_root_hash = self.get_post_state_hash();
let tracking_copy = self
.engine_state
.tracking_copy(state_root_hash)
.unwrap()
.unwrap();
let reader = tracking_copy.reader();
let withdraws_keys = reader
.keys_with_prefix(correlation_id, &[KeyTag::Withdraw as u8])
.unwrap_or_default();
let mut ret = BTreeMap::new();
for key in withdraws_keys.into_iter() {
let read_result = reader.read(correlation_id, &key);
if let (Key::Withdraw(account_hash), Ok(Some(StoredValue::Withdraw(withdraw_purses)))) =
(key, read_result)
{
ret.insert(account_hash, withdraw_purses);
}
}
ret
}
pub fn get_balance_keys(&self) -> Vec<Key> {
self.get_keys(KeyTag::Balance).unwrap_or_default()
}
pub fn get_keys(&self, tag: KeyTag) -> Result<Vec<Key>, S::Error> {
let correlation_id = CorrelationId::new();
let state_root_hash = self.get_post_state_hash();
let tracking_copy = self
.engine_state
.tracking_copy(state_root_hash)
.unwrap()
.unwrap();
let reader = tracking_copy.reader();
reader.keys_with_prefix(correlation_id, &[tag as u8])
}
pub fn get_value<T>(&mut self, contract_hash: ContractHash, name: &str) -> T
where
T: FromBytes + CLTyped,
{
let contract = self
.get_contract(contract_hash)
.expect("should have contract");
let key = contract
.named_keys()
.get(name)
.expect("should have named key");
let stored_value = self.query(None, *key, &[]).expect("should query");
let cl_value = stored_value
.as_cl_value()
.cloned()
.expect("should be cl value");
let result: T = cl_value.into_t().expect("should convert");
result
}
pub fn get_era(&mut self) -> EraId {
let auction_contract = self.get_auction_contract_hash();
self.get_value(auction_contract, ERA_ID_KEY)
}
pub fn get_auction_delay(&mut self) -> u64 {
let auction_contract = self.get_auction_contract_hash();
self.get_value(auction_contract, AUCTION_DELAY_KEY)
}
pub fn get_unbonding_delay(&mut self) -> u64 {
let auction_contract = self.get_auction_contract_hash();
self.get_value(auction_contract, UNBONDING_DELAY_KEY)
}
pub fn get_system_auction_hash(&self) -> ContractHash {
let correlation_id = CorrelationId::new();
let state_root_hash = self.get_post_state_hash();
self.engine_state
.get_system_auction_hash(correlation_id, state_root_hash)
.expect("should have auction hash")
}
pub fn get_system_mint_hash(&self) -> ContractHash {
let correlation_id = CorrelationId::new();
let state_root_hash = self.get_post_state_hash();
self.engine_state
.get_system_mint_hash(correlation_id, state_root_hash)
.expect("should have auction hash")
}
pub fn get_system_handle_payment_hash(&self) -> ContractHash {
let correlation_id = CorrelationId::new();
let state_root_hash = self.get_post_state_hash();
self.engine_state
.get_handle_payment_hash(correlation_id, state_root_hash)
.expect("should have handle payment hash")
}
pub fn get_system_standard_payment_hash(&self) -> ContractHash {
let correlation_id = CorrelationId::new();
let state_root_hash = self.get_post_state_hash();
self.engine_state
.get_standard_payment_hash(correlation_id, state_root_hash)
.expect("should have standard payment hash")
}
pub fn clear_results(&mut self) -> &mut Self {
self.exec_results = Vec::new();
self.upgrade_results = Vec::new();
self.transforms = Vec::new();
self
}
pub fn advance_eras_by(
&mut self,
num_eras: u64,
reward_items: impl IntoIterator<Item = RewardItem>,
evict_items: impl IntoIterator<Item = EvictItem>,
) {
let step_request_builder = StepRequestBuilder::new()
.with_protocol_version(ProtocolVersion::V1_0_0)
.with_reward_items(reward_items)
.with_evict_items(evict_items)
.with_run_auction(true);
for _ in 0..num_eras {
let step_request = step_request_builder
.clone()
.with_parent_state_hash(self.get_post_state_hash())
.with_next_era_id(self.get_era().successor())
.build();
self.step(step_request)
.expect("failed to execute step request");
}
}
pub fn advance_eras_by_default_auction_delay(
&mut self,
reward_items: impl IntoIterator<Item = RewardItem>,
evict_items: impl IntoIterator<Item = EvictItem>,
) {
let auction_delay = self.get_auction_delay();
self.advance_eras_by(auction_delay + 1, reward_items, evict_items);
}
pub fn advance_era(
&mut self,
reward_items: impl IntoIterator<Item = RewardItem>,
evict_items: impl IntoIterator<Item = EvictItem>,
) {
self.advance_eras_by(1, reward_items, evict_items);
}
pub fn get_trie(&mut self, state_hash: Digest) -> Option<Trie<Key, StoredValue>> {
self.engine_state
.get_trie_full(CorrelationId::default(), state_hash)
.unwrap()
.map(|bytes| bytesrepr::deserialize(bytes.into_inner().into()).unwrap())
}
pub fn get_auction_costs(&self) -> AuctionCosts {
*self.engine_state.config().system_config().auction_costs()
}
pub fn get_mint_costs(&self) -> MintCosts {
*self.engine_state.config().system_config().mint_costs()
}
pub fn get_handle_payment_costs(&self) -> HandlePaymentCosts {
*self
.engine_state
.config()
.system_config()
.handle_payment_costs()
}
pub fn commit_prune(&mut self, prune_config: PruneConfig) -> &mut Self {
let result = self
.engine_state
.commit_prune(CorrelationId::new(), prune_config);
if let Ok(PruneResult::Success { post_state_hash }) = &result {
self.post_state_hash = Some(*post_state_hash);
}
self.prune_results.push(result);
self
}
pub fn get_prune_result(
&self,
index: usize,
) -> Option<&Result<PruneResult, engine_state::Error>> {
self.prune_results.get(index)
}
pub fn expect_prune_success(&mut self) -> &mut Self {
let result = self
.prune_results
.last()
.expect("Expected to be called after a system upgrade.")
.as_ref();
let prune_result = result.unwrap_or_else(|_| panic!("Expected success, got: {:?}", result));
match prune_result {
PruneResult::RootNotFound => panic!("Root not found"),
PruneResult::DoesNotExist => panic!("Does not exists"),
PruneResult::Success { .. } => {}
}
self
}
#[deprecated(
since = "2.1.0",
note = "Use `get_execution_journals` that returns transforms in the order they were created."
)]
pub fn get_transforms(&self) -> Vec<AdditiveMap<Key, Transform>> {
self.transforms
.clone()
.into_iter()
.map(|journal| journal.into_iter().collect())
.collect()
}
#[deprecated(
since = "2.3.0",
note = "use `get_last_exec_results` or `get_exec_result_owned` instead"
)]
pub fn get_exec_results(&self) -> &Vec<Vec<Rc<ExecutionResult>>> {
&self.exec_results
}
#[deprecated(since = "2.3.0", note = "use `get_exec_result_owned` instead")]
pub fn get_exec_result(&self, index: usize) -> Option<&Vec<Rc<ExecutionResult>>> {
self.exec_results.get(index)
}
#[deprecated(since = "2.3.0", note = "use `get_withdraw_purses` instead")]
pub fn get_withdraws(&mut self) -> UnbondingPurses {
let withdraw_purses = self.get_withdraw_purses();
let unbonding_purses: UnbondingPurses = withdraw_purses
.iter()
.map(|(key, withdraw_purse)| {
(
key.to_owned(),
withdraw_purse
.iter()
.map(|withdraw_purse| withdraw_purse.to_owned().into())
.collect::<Vec<UnbondingPurse>>(),
)
})
.collect::<BTreeMap<AccountHash, Vec<UnbondingPurse>>>();
unbonding_purses
}
pub fn calculate_refund_amount(&self, payment_amount: U512) -> U512 {
let gas_amount = Motes::from_gas(self.last_exec_gas_cost(), DEFAULT_GAS_PRICE)
.expect("should create motes from gas");
let refund_ratio = match self.engine_state.config().refund_handling() {
RefundHandling::Refund { refund_ratio } | RefundHandling::Burn { refund_ratio } => {
*refund_ratio
}
};
let (numer, denom) = refund_ratio.into();
let refund_ratio = Ratio::new_raw(U512::from(numer), U512::from(denom));
let refundable_amount = Ratio::from(payment_amount) - Ratio::from(gas_amount.value());
(refundable_amount * refund_ratio).to_integer()
}
}