use super::{
EnvInstance,
OnInstance,
};
use crate::{
types::Environment,
Result,
};
use core::fmt::Debug;
use std::panic::UnwindSafe;
pub use super::call_data::CallData;
pub use ink_engine::{
ext::ChainSpec,
ChainExtension,
};
use ink_primitives::{
AccountIdMapper,
Address,
H256,
U256,
};
#[derive(Clone)]
pub struct EmittedEvent {
pub topics: Vec<Vec<u8>>,
pub data: Vec<u8>,
}
#[cfg(feature = "unstable-hostfn")] pub fn set_account_balance(addr: Address, new_balance: U256) {
let min = ChainSpec::default().minimum_balance;
if new_balance < min && new_balance != U256::zero() {
panic!(
"Balance must be at least [{}]. Use 0 as balance to reap the account.",
min
);
}
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.set_balance(addr, new_balance);
})
}
pub fn get_account_balance<T>(addr: Address) -> Result<U256> {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.get_balance(addr).map_err(Into::into)
})
}
pub fn register_chain_extension<E>(extension: E)
where
E: ink_engine::ChainExtension + 'static,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
instance
.engine
.chain_extension_handler
.register(Box::new(extension));
})
}
pub fn set_clear_storage_disabled(_disable: bool) {
unimplemented!(
"off-chain environment does not yet support `set_clear_storage_disabled`"
);
}
pub fn advance_block<T>()
where
T: Environment,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.advance_block();
})
}
pub fn set_caller(caller: Address) {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.set_caller(caller);
})
}
pub fn set_callee(callee: Address) {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.set_callee(callee);
})
}
pub fn set_contract(contract: Address) {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.set_contract(contract);
})
}
#[cfg(feature = "unstable-hostfn")]
pub fn is_contract(contract: Address) -> bool {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.is_contract(&contract)
})
}
pub fn callee() -> Address {
<EnvInstance as OnInstance>::on_instance(|instance| {
let callee = instance.engine.get_callee();
scale::Decode::decode(&mut &callee[..])
.unwrap_or_else(|err| panic!("encoding failed: {err}"))
})
}
pub fn get_contract_storage_rw(addr: Address) -> (usize, usize) {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.get_contract_storage_rw(addr)
})
}
pub fn set_value_transferred(value: U256) {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.set_value_transferred(value);
})
}
#[allow(clippy::arithmetic_side_effects)] pub fn transfer_in(value: U256) {
<EnvInstance as OnInstance>::on_instance(|instance| {
let caller = instance.engine.exec_context.caller;
let caller_old_balance = instance.engine.get_balance(caller).unwrap_or_default();
let callee = instance.engine.get_callee();
let contract_old_balance =
instance.engine.get_balance(callee).unwrap_or_default();
instance
.engine
.set_balance(caller, caller_old_balance - value);
instance
.engine
.set_balance(callee, contract_old_balance + value);
instance.engine.set_value_transferred(value);
});
}
pub fn count_used_storage_cells<T>(addr: Address) -> Result<usize>
where
T: Environment,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
instance
.engine
.count_used_storage_cells(&addr)
.map_err(Into::into)
})
}
pub fn set_block_timestamp<T>(value: T::Timestamp)
where
T: Environment<Timestamp = u64>,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.set_block_timestamp(value);
})
}
pub fn set_block_number<T>(value: T::BlockNumber)
where
T: Environment<BlockNumber = u32>,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.set_block_number(value);
})
}
pub fn run_test<T, F>(f: F) -> Result<()>
where
T: Environment,
F: FnOnce(DefaultAccounts) -> Result<()>,
{
let default_accounts = default_accounts();
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.engine.initialize_or_reset();
let alice = default_accounts.alice;
instance.engine.set_callee(alice);
let substantial = 1_000_000.into();
let some = 1_000.into();
instance.engine.set_balance(alice, substantial);
instance.engine.set_balance(default_accounts.bob, some);
instance.engine.set_balance(default_accounts.charlie, some);
instance
.engine
.set_balance(default_accounts.django, 0.into());
instance.engine.set_balance(default_accounts.eve, 0.into());
instance
.engine
.set_balance(default_accounts.frank, 0.into());
});
f(default_accounts)
}
pub fn default_accounts() -> DefaultAccounts {
DefaultAccounts {
alice: AccountIdMapper::to_address(&[0x01; 32]),
bob: AccountIdMapper::to_address(&[0x02; 32]),
charlie: AccountIdMapper::to_address(&[0x03; 32]),
django: AccountIdMapper::to_address(&[0x04; 32]),
eve: AccountIdMapper::to_address(&[0x05; 32]),
frank: AccountIdMapper::to_address(&[0x06; 32]),
}
}
pub struct DefaultAccounts {
pub alice: Address,
pub bob: Address,
pub charlie: Address,
pub django: Address,
pub eve: Address,
pub frank: Address,
}
pub fn recorded_events() -> impl Iterator<Item = EmittedEvent> {
<EnvInstance as OnInstance>::on_instance(|instance| {
instance
.engine
.get_emitted_events()
.map(|evt: ink_engine::test_api::EmittedEvent| evt.into())
})
}
pub fn assert_contract_termination<T, F>(
should_terminate: F,
expected_beneficiary: Address,
expected_value_transferred_to_beneficiary: U256,
) where
T: Environment,
F: FnMut() + UnwindSafe,
<T as Environment>::AccountId: Debug,
<T as Environment>::Balance: Debug,
{
let value_any = ::std::panic::catch_unwind(should_terminate)
.expect_err("contract did not terminate");
let encoded_input = value_any
.downcast_ref::<Vec<u8>>()
.expect("panic object can not be cast");
let (value_transferred, beneficiary): (U256, Address) =
scale::Decode::decode(&mut &encoded_input[..])
.unwrap_or_else(|err| panic!("input can not be decoded: {err}"));
assert_eq!(value_transferred, expected_value_transferred_to_beneficiary);
assert_eq!(beneficiary, expected_beneficiary);
}
#[macro_export]
macro_rules! pay_with_call {
($contract:ident . $message:ident ( $( $params:expr ),* ) , $amount:expr) => {{
$crate::test::transfer_in($amount);
$contract.$message($ ($params) ,*)
}}
}
pub fn get_return_value() -> Vec<u8> {
<EnvInstance as OnInstance>::on_instance(|instance| instance.get_return_value())
}
pub fn upload_code<E, ContractRef>() -> H256
where
E: Environment,
ContractRef: crate::ContractReverseReference,
<ContractRef as crate::ContractReverseReference>::Type:
crate::reflect::ContractMessageDecoder,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
instance.upload_code::<ContractRef>()
})
}