use crate::{
BalanceOf, ContractInfo, ContractInfoOf, GenesisConfig, Module,
RawAliveContractInfo, RawEvent, Config, Schedule, gas::Gas,
Error, ConfigCache, RuntimeReturnCode, storage::Storage,
chain_extension::{
Result as ExtensionResult, Environment, ChainExtension, Ext, SysConfig, RetVal,
UncheckedFrom, InitState, ReturnFlags,
},
exec::AccountIdOf,
};
use assert_matches::assert_matches;
use codec::Encode;
use tp_runtime::{
traits::{BlakeTwo256, Hash, IdentityLookup, Convert},
testing::{Header, H256},
AccountId32, Perbill,
};
use tet_io::hashing::blake2_256;
use fabric_support::{
assert_ok, assert_err, assert_err_ignore_postinfo, impl_outer_dispatch, impl_outer_event,
impl_outer_origin, parameter_types, StorageMap, assert_storage_noop,
traits::{Currency, ReservableCurrency, OnInitialize},
weights::{Weight, PostDispatchInfo, DispatchClass, constants::WEIGHT_PER_SECOND},
dispatch::DispatchErrorWithPostInfo,
storage::child,
};
use fabric_system::{self as system, EventRecord, Phase};
mod contracts {
pub use super::super::*;
pub use fabric_support::impl_outer_event;
}
use noble_balances as balances;
impl_outer_event! {
pub enum MetaEvent for Test {
system<T>,
balances<T>,
contracts<T>,
}
}
impl_outer_origin! {
pub enum Origin for Test where system = fabric_system { }
}
impl_outer_dispatch! {
pub enum Call for Test where origin: Origin {
balances::Balances,
contracts::Contracts,
}
}
#[macro_use]
pub mod test_utils {
use super::{Test, Balances};
use crate::{
ConfigCache,
ContractInfoOf, CodeHash,
storage::Storage,
exec::{StorageKey, AccountIdOf},
};
use fabric_support::{StorageMap, traits::Currency};
pub fn set_storage(addr: &AccountIdOf<Test>, key: &StorageKey, value: Option<Vec<u8>>) {
let contract_info = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
Storage::<Test>::write(addr, &contract_info.trie_id, key, value).unwrap();
}
pub fn get_storage(addr: &AccountIdOf<Test>, key: &StorageKey) -> Option<Vec<u8>> {
let contract_info = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
Storage::<Test>::read(&contract_info.trie_id, key)
}
pub fn place_contract(address: &AccountIdOf<Test>, code_hash: CodeHash<Test>) {
let trie_id = Storage::<Test>::generate_trie_id(address);
set_balance(address, ConfigCache::<Test>::subsistence_threshold_uncached() * 10);
Storage::<Test>::place_contract(&address, trie_id, code_hash).unwrap()
}
pub fn set_balance(who: &AccountIdOf<Test>, amount: u64) {
let imbalance = Balances::deposit_creating(who, amount);
drop(imbalance);
}
pub fn get_balance(who: &AccountIdOf<Test>) -> u64 {
Balances::free_balance(who)
}
macro_rules! assert_return_code {
( $x:expr , $y:expr $(,)? ) => {{
use tetcore_std::convert::TryInto;
assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32);
}}
}
}
thread_local! {
static TEST_EXTENSION: tetcore_std::cell::RefCell<TestExtension> = Default::default();
}
pub struct TestExtension {
enabled: bool,
last_seen_buffer: Vec<u8>,
last_seen_inputs: (u32, u32, u32, u32),
}
impl TestExtension {
fn disable() {
TEST_EXTENSION.with(|e| e.borrow_mut().enabled = false)
}
fn last_seen_buffer() -> Vec<u8> {
TEST_EXTENSION.with(|e| e.borrow().last_seen_buffer.clone())
}
fn last_seen_inputs() -> (u32, u32, u32, u32) {
TEST_EXTENSION.with(|e| e.borrow().last_seen_inputs.clone())
}
}
impl Default for TestExtension {
fn default() -> Self {
Self {
enabled: true,
last_seen_buffer: vec![],
last_seen_inputs: (0, 0, 0, 0),
}
}
}
impl ChainExtension for TestExtension {
fn call<E: Ext>(func_id: u32, env: Environment<E, InitState>) -> ExtensionResult<RetVal>
where
<E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
{
match func_id {
0 => {
let mut env = env.buf_in_buf_out();
let input = env.read(2)?;
env.write(&input, false, None)?;
TEST_EXTENSION.with(|e| e.borrow_mut().last_seen_buffer = input);
Ok(RetVal::Converging(func_id))
},
1 => {
let env = env.only_in();
TEST_EXTENSION.with(|e|
e.borrow_mut().last_seen_inputs = (
env.val0(), env.val1(), env.val2(), env.val3()
)
);
Ok(RetVal::Converging(func_id))
},
2 => {
let mut env = env.buf_in_buf_out();
let weight = env.read(2)?[1].into();
env.charge_weight(weight)?;
Ok(RetVal::Converging(func_id))
},
3 => {
Ok(RetVal::Diverging{
flags: ReturnFlags::REVERT,
data: vec![42, 99],
})
},
_ => {
panic!("Passed unknown func_id to test chain extension: {}", func_id);
}
}
}
fn enabled() -> bool {
TEST_EXTENSION.with(|e| e.borrow().enabled)
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub BlockWeights: fabric_system::limits::BlockWeights =
fabric_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND);
pub static ExistentialDeposit: u64 = 0;
}
impl fabric_system::Config for Test {
type BaseCallFilter = ();
type BlockWeights = BlockWeights;
type BlockLength = ();
type DbWeight = ();
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Call = Call;
type Hashing = BlakeTwo256;
type AccountId = AccountId32;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = MetaEvent;
type BlockHashCount = BlockHashCount;
type Version = ();
type NobleInfo = ();
type AccountData = noble_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
}
impl noble_balances::Config for Test {
type MaxLocks = ();
type Balance = u64;
type Event = MetaEvent;
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
}
parameter_types! {
pub const MinimumPeriod: u64 = 1;
}
impl noble_timestamp::Config for Test {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = MinimumPeriod;
type WeightInfo = ();
}
parameter_types! {
pub const SignedClaimHandicap: u64 = 2;
pub const TombstoneDeposit: u64 = 16;
pub const DepositPerContract: u64 = 8 * DepositPerStorageByte::get();
pub const DepositPerStorageByte: u64 = 10_000;
pub const DepositPerStorageItem: u64 = 10_000;
pub RentFraction: Perbill = Perbill::from_rational_approximation(4u32, 10_000u32);
pub const SurchargeReward: u64 = 500_000;
pub const MaxDepth: u32 = 100;
pub const MaxValueSize: u32 = 16_384;
pub const DeletionQueueDepth: u32 = 1024;
pub const DeletionWeightLimit: Weight = 500_000_000_000;
}
parameter_types! {
pub const TransactionByteFee: u64 = 0;
}
impl Convert<Weight, BalanceOf<Self>> for Test {
fn convert(w: Weight) -> BalanceOf<Self> {
w
}
}
impl Config for Test {
type Time = Timestamp;
type Randomness = Randomness;
type Currency = Balances;
type Event = MetaEvent;
type RentPayment = ();
type SignedClaimHandicap = SignedClaimHandicap;
type TombstoneDeposit = TombstoneDeposit;
type DepositPerContract = DepositPerContract;
type DepositPerStorageByte = DepositPerStorageByte;
type DepositPerStorageItem = DepositPerStorageItem;
type RentFraction = RentFraction;
type SurchargeReward = SurchargeReward;
type MaxDepth = MaxDepth;
type MaxValueSize = MaxValueSize;
type WeightPrice = Self;
type WeightInfo = ();
type ChainExtension = TestExtension;
type DeletionQueueDepth = DeletionQueueDepth;
type DeletionWeightLimit = DeletionWeightLimit;
}
type Balances = noble_balances::Module<Test>;
type Timestamp = noble_timestamp::Module<Test>;
type Contracts = Module<Test>;
type System = fabric_system::Module<Test>;
type Randomness = noble_randomness_collective_flip::Module<Test>;
pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
pub const BOB: AccountId32 = AccountId32::new([2u8; 32]);
pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]);
pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]);
const GAS_LIMIT: Gas = 10_000_000_000;
pub struct ExtBuilder {
existential_deposit: u64,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
existential_deposit: 1,
}
}
}
impl ExtBuilder {
pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
self.existential_deposit = existential_deposit;
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
}
pub fn build(self) -> tet_io::TestExternalities {
self.set_associated_consts();
let mut t = fabric_system::GenesisConfig::default().build_storage::<Test>().unwrap();
noble_balances::GenesisConfig::<Test> {
balances: vec![],
}.assimilate_storage(&mut t).unwrap();
GenesisConfig {
current_schedule: Schedule::<Test> {
enable_println: true,
..Default::default()
},
}.assimilate_storage(&mut t).unwrap();
let mut ext = tet_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}
fn compile_module<T>(
fixture_name: &str,
) -> wat::Result<(Vec<u8>, <T::Hashing as Hash>::Output)>
where
T: fabric_system::Config,
{
let fixture_path = ["fixtures/", fixture_name, ".wat"].concat();
let wasm_binary = wat::parse_file(fixture_path)?;
let code_hash = T::Hashing::hash(&wasm_binary);
Ok((wasm_binary, code_hash))
}
#[test]
fn calling_plain_account_fails() {
ExtBuilder::default().build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 100_000_000);
assert_eq!(
Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, Vec::new()),
Err(
DispatchErrorWithPostInfo {
error: Error::<Test>::NotCallable.into(),
post_info: PostDispatchInfo {
actual_weight: Some(0),
pays_fee: Default::default(),
},
}
)
);
});
}
#[test]
fn account_removal_does_not_remove_storage() {
use self::test_utils::{set_storage, get_storage};
ExtBuilder::default().existential_deposit(100).build().execute_with(|| {
let trie_id1 = Storage::<Test>::generate_trie_id(&ALICE);
let trie_id2 = Storage::<Test>::generate_trie_id(&BOB);
let key1 = &[1; 32];
let key2 = &[2; 32];
{
let alice_contract_info = ContractInfo::Alive(RawAliveContractInfo {
trie_id: trie_id1.clone(),
storage_size: 0,
pair_count: 0,
deduct_block: System::block_number(),
code_hash: H256::repeat_byte(1),
rent_allowance: 40,
rent_payed: 0,
last_write: None,
});
let _ = Balances::deposit_creating(&ALICE, 110);
ContractInfoOf::<Test>::insert(ALICE, &alice_contract_info);
set_storage(&ALICE, &key1, Some(b"1".to_vec()));
set_storage(&ALICE, &key2, Some(b"2".to_vec()));
let bob_contract_info = ContractInfo::Alive(RawAliveContractInfo {
trie_id: trie_id2.clone(),
storage_size: 0,
pair_count: 0,
deduct_block: System::block_number(),
code_hash: H256::repeat_byte(2),
rent_allowance: 40,
rent_payed: 0,
last_write: None,
});
let _ = Balances::deposit_creating(&BOB, 110);
ContractInfoOf::<Test>::insert(BOB, &bob_contract_info);
set_storage(&BOB, &key1, Some(b"3".to_vec()));
set_storage(&BOB, &key2, Some(b"4".to_vec()));
}
assert_ok!(Balances::transfer(Origin::signed(ALICE), BOB, 20));
{
assert_eq!(
get_storage(&ALICE, key1),
Some(b"1".to_vec())
);
assert_eq!(
get_storage(&ALICE, key2),
Some(b"2".to_vec())
);
assert_eq!(
get_storage(&BOB, key1),
Some(b"3".to_vec())
);
assert_eq!(
get_storage(&BOB, key2),
Some(b"4".to_vec())
);
}
});
}
#[test]
fn instantiate_and_call_and_deposit_event() {
let (wasm, code_hash) = compile_module::<Test>("return_from_start_fn").unwrap();
ExtBuilder::default()
.existential_deposit(100)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
let creation = Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
);
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
pretty_assertions::assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::system(fabric_system::Event::NewAccount(ALICE.clone())),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(
noble_balances::RawEvent::Endowed(ALICE, 1_000_000)
),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::system(fabric_system::Event::NewAccount(addr.clone())),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(
noble_balances::RawEvent::Endowed(addr.clone(), subsistence * 3)
),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(
noble_balances::RawEvent::Transfer(ALICE, addr.clone(), subsistence * 3)
),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(
RawEvent::ContractExecution(addr.clone(), vec![1, 2, 3, 4])
),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(RawEvent::Instantiated(ALICE, addr.clone())),
topics: vec![],
}
]);
assert_ok!(creation);
assert!(ContractInfoOf::<Test>::contains_key(&addr));
});
}
#[test]
fn deposit_event_max_value_limit() {
let (wasm, code_hash) = compile_module::<Test>("event_size").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let first_rent = <Test as Config>::RentFraction::get()
.mul_ceil(80_000 - 30_000)
* 1;
let bob_contract = ContractInfoOf::<Test>::get(addr.clone())
.unwrap()
.get_alive()
.unwrap();
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value() - first_rent);
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT * 2, <Test as Config>::MaxValueSize::get().encode(),
));
assert_err_ignore_postinfo!(
Contracts::call(
Origin::signed(ALICE),
addr,
0,
GAS_LIMIT,
(<Test as Config>::MaxValueSize::get() + 1).encode(),
),
Error::<Test>::ValueTooLarge,
);
});
}
#[test]
fn run_out_of_gas() {
let (wasm, code_hash) = compile_module::<Test>("run_out_of_gas").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
assert_err_ignore_postinfo!(
Contracts::call(
Origin::signed(ALICE),
addr, 0,
67_500_000,
vec![],
),
Error::<Test>::OutOfGas,
);
});
}
mod call {
use super::{AccountIdOf, Test};
pub fn set_storage_4_byte() -> Vec<u8> { 0u32.to_le_bytes().to_vec() }
pub fn remove_storage_4_byte() -> Vec<u8> { 1u32.to_le_bytes().to_vec() }
#[allow(dead_code)]
pub fn transfer(to: &AccountIdOf<Test>) -> Vec<u8> {
2u32.to_le_bytes().iter().chain(AsRef::<[u8]>::as_ref(to)).cloned().collect()
}
pub fn null() -> Vec<u8> { 3u32.to_le_bytes().to_vec() }
}
#[test]
fn test_set_rent_code_and_hash() {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::system(fabric_system::Event::NewAccount(ALICE)),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(noble_balances::RawEvent::Endowed(
ALICE, 1_000_000
)),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(RawEvent::CodeStored(code_hash.into())),
topics: vec![],
},
]);
});
}
#[test]
fn storage_size() {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
code_hash.into(),
<Test as noble_balances::Config>::Balance::from(1_000u32).encode(), vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let bob_contract = ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_alive()
.unwrap();
assert_eq!(
bob_contract.storage_size,
4
);
assert_eq!(
bob_contract.pair_count,
1,
);
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
call::set_storage_4_byte()
));
let bob_contract = ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_alive()
.unwrap();
assert_eq!(
bob_contract.storage_size,
4 + 4
);
assert_eq!(
bob_contract.pair_count,
2,
);
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
call::remove_storage_4_byte()
));
let bob_contract = ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_alive()
.unwrap();
assert_eq!(
bob_contract.storage_size,
4
);
assert_eq!(
bob_contract.pair_count,
1,
);
});
}
#[test]
fn empty_kv_pairs() {
let (wasm, code_hash) = compile_module::<Test>("set_empty_storage").unwrap();
ExtBuilder::default()
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let bob_contract = ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_alive()
.unwrap();
assert_eq!(
bob_contract.storage_size,
0,
);
assert_eq!(
bob_contract.pair_count,
1,
);
});
}
fn initialize_block(number: u64) {
System::initialize(
&number,
&[0u8; 32].into(),
&Default::default(),
Default::default(),
);
}
#[test]
fn deduct_blocks() {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT, code_hash.into(),
<Test as noble_balances::Config>::Balance::from(1_000u32).encode(), vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
let rent0 = <Test as Config>::RentFraction::get()
.mul_ceil(80_000 + 40_000 + 10_000 - 30_000)
* 1;
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000 - rent0);
assert_eq!(bob_contract.deduct_block, 1);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent0);
initialize_block(5);
assert_ok!(
Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, call::null())
);
let rent = <Test as Config>::RentFraction::get()
.mul_ceil(80_000 + 40_000 + 10_000 - (30_000 - rent0))
* 4;
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000 - rent0 - rent);
assert_eq!(bob_contract.deduct_block, 5);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent0 - rent);
initialize_block(12);
assert_ok!(
Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, call::null())
);
let rent_2 = <Test as Config>::RentFraction::get()
.mul_ceil(80_000 + 40_000 + 10_000 - (30_000 - rent0 - rent))
* 7;
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000 - rent0 - rent - rent_2);
assert_eq!(bob_contract.deduct_block, 12);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent0 - rent - rent_2);
assert_ok!(
Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, call::null())
);
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 1_000 - rent0 - rent - rent_2);
assert_eq!(bob_contract.deduct_block, 12);
assert_eq!(Balances::free_balance(&addr), 30_000 - rent0 - rent - rent_2)
});
}
#[test]
fn inherent_claim_surcharge_contract_removals() {
removals(|addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok());
}
#[test]
fn signed_claim_surcharge_contract_removals() {
removals(|addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok());
}
#[test]
fn claim_surcharge_malus() {
claim_surcharge(27, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(26, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(25, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), true);
claim_surcharge(24, |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(), false);
claim_surcharge(27, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), true);
claim_surcharge(26, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
claim_surcharge(25, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
claim_surcharge(24, |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(), false);
}
fn claim_surcharge(blocks: u64, trigger_call: impl Fn(AccountIdOf<Test>) -> bool, removes: bool) {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT, code_hash.into(),
<Test as noble_balances::Config>::Balance::from(1_000u32).encode(), vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
initialize_block(blocks);
assert_eq!(trigger_call(addr.clone()), removes);
if removes {
assert!(ContractInfoOf::<Test>::get(&addr).unwrap().get_tombstone().is_some());
} else {
assert!(ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().is_some());
}
});
}
fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
500,
GAS_LIMIT, code_hash.into(),
<Test as noble_balances::Config>::Balance::from(1_000u32).encode(), vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let allowance = ContractInfoOf::<Test>::get(&addr)
.unwrap().get_alive().unwrap().rent_allowance;
let balance = Balances::free_balance(&addr);
let subsistence_threshold = ConfigCache::<Test>::subsistence_threshold_uncached();
assert!(!trigger_call(addr.clone()));
assert_eq!(
ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap().rent_allowance,
allowance,
);
assert_eq!(Balances::free_balance(&addr), balance);
initialize_block(27);
assert!(trigger_call(addr.clone()));
assert!(ContractInfoOf::<Test>::get(&addr).unwrap().get_tombstone().is_some());
assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
initialize_block(30);
assert!(!trigger_call(addr.clone()));
assert!(ContractInfoOf::<Test>::get(&addr).unwrap().get_tombstone().is_some());
assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
});
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
code_hash.into(),
<Test as noble_balances::Config>::Balance::from(1000u32).encode(), vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let allowance = ContractInfoOf::<Test>::get(&addr)
.unwrap().get_alive().unwrap().rent_allowance;
let balance = Balances::free_balance(&addr);
assert!(!trigger_call(addr.clone()));
assert_eq!(
ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_alive()
.unwrap()
.rent_allowance,
allowance,
);
assert_eq!(Balances::free_balance(&addr), balance);
initialize_block(27);
assert!(trigger_call(addr.clone()));
assert!(ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_tombstone()
.is_some());
assert_eq!(Balances::free_balance(&addr), 29000);
initialize_block(20);
assert!(!trigger_call(addr.clone()));
assert!(ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_tombstone()
.is_some());
assert_eq!(Balances::free_balance(&addr), 29000);
});
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
let subsistence_threshold = ConfigCache::<Test>::subsistence_threshold_uncached();
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
subsistence_threshold * 3,
GAS_LIMIT,
code_hash.into(),
<Test as noble_balances::Config>::Balance::from(1_000u32).encode(), vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let allowance = ContractInfoOf::<Test>::get(&addr)
.unwrap().get_alive().unwrap().rent_allowance;
let balance = Balances::free_balance(&addr);
assert!(!trigger_call(addr.clone()));
assert_eq!(
ContractInfoOf::<Test>::get(&addr)
.unwrap()
.get_alive()
.unwrap()
.rent_allowance,
allowance,
);
assert_eq!(
Balances::free_balance(&addr),
balance,
);
Balances::make_free_balance_be(&addr, subsistence_threshold);
assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
initialize_block(10);
assert!(trigger_call(addr.clone()));
assert_matches!(ContractInfoOf::<Test>::get(&addr), Some(ContractInfo::Tombstone(_)));
assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
initialize_block(20);
assert!(!trigger_call(addr.clone()));
assert_matches!(ContractInfoOf::<Test>::get(&addr), Some(ContractInfo::Tombstone(_)));
assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
});
}
#[test]
fn call_removed_contract() {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm.clone()));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT, code_hash.into(),
<Test as noble_balances::Config>::Balance::from(1_000u32).encode(), vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
assert_ok!(
Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, call::null())
);
initialize_block(27);
assert_err_ignore_postinfo!(
Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, call::null()),
Error::<Test>::NotCallable
);
assert_eq!(System::events(), vec![]);
assert_err_ignore_postinfo!(
Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, call::null()),
Error::<Test>::NotCallable
);
assert_ok!(Contracts::claim_surcharge(Origin::none(), addr.clone(), Some(ALICE)));
assert!(ContractInfoOf::<Test>::get(&addr).unwrap().get_tombstone().is_some());
})
}
#[test]
fn default_rent_allowance_on_instantiate() {
let (wasm, code_hash) = compile_module::<Test>("check_default_rent_allowance").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let first_rent = <Test as Config>::RentFraction::get()
.mul_ceil(80_000 - 30_000)
* 1;
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value() - first_rent);
initialize_block(5);
assert_ok!(
Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, call::null())
);
let bob_contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive();
assert!(bob_contract.is_some())
});
}
#[test]
fn restorations_dirty_storage_and_different_storage() {
restoration(true, true);
}
#[test]
fn restorations_dirty_storage() {
restoration(false, true);
}
#[test]
fn restoration_different_storage() {
restoration(true, false);
}
#[test]
fn restoration_success() {
restoration(false, false);
}
fn restoration(test_different_storage: bool, test_restore_to_with_dirty_storage: bool) {
let (set_rent_wasm, set_rent_code_hash) = compile_module::<Test>("set_rent").unwrap();
let (restoration_wasm, restoration_code_hash) = compile_module::<Test>("restoration").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), restoration_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), set_rent_wasm));
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::system(fabric_system::Event::NewAccount(ALICE)),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(noble_balances::RawEvent::Endowed(ALICE, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(RawEvent::CodeStored(restoration_code_hash.into())),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(RawEvent::CodeStored(set_rent_code_hash.into())),
topics: vec![],
},
]);
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
set_rent_code_hash.into(),
<Test as noble_balances::Config>::Balance::from(1_000u32).encode(),
vec![],
));
let addr_bob = Contracts::contract_address(&ALICE, &set_rent_code_hash, &[]);
let bob_contract = ContractInfoOf::<Test>::get(&addr_bob).unwrap().get_alive().unwrap();
assert!(bob_contract.rent_allowance < 5_000);
if test_different_storage {
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr_bob.clone(), 0, GAS_LIMIT,
call::set_storage_4_byte())
);
}
initialize_block(27);
assert_err_ignore_postinfo!(
Contracts::call(
Origin::signed(ALICE), addr_bob.clone(), 0, GAS_LIMIT, call::null()
),
Error::<Test>::NotCallable
);
assert!(System::events().is_empty());
assert!(ContractInfoOf::<Test>::get(&addr_bob).unwrap().get_alive().is_some());
assert_ok!(Contracts::claim_surcharge(Origin::none(), addr_bob.clone(), Some(ALICE)));
assert!(ContractInfoOf::<Test>::get(&addr_bob).unwrap().get_tombstone().is_some());
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(
RawEvent::Evicted(addr_bob.clone(), true)
),
topics: vec![],
},
]);
let _ = Balances::deposit_creating(&CHARLIE, 1_000_000);
assert_ok!(Contracts::instantiate(
Origin::signed(CHARLIE),
30_000,
GAS_LIMIT,
restoration_code_hash.into(),
vec![],
vec![],
));
let addr_django = Contracts::contract_address(&CHARLIE, &restoration_code_hash, &[]);
let django_trie_id = ContractInfoOf::<Test>::get(&addr_django).unwrap()
.get_alive().unwrap().trie_id;
if !test_restore_to_with_dirty_storage {
initialize_block(28);
}
let perform_the_restoration = || {
Contracts::call(
Origin::signed(ALICE),
addr_django.clone(),
0,
GAS_LIMIT,
set_rent_code_hash
.as_ref()
.iter()
.chain(AsRef::<[u8]>::as_ref(&addr_bob))
.cloned()
.collect(),
)
};
let delta_key = {
let mut key = [0u8; 32];
key[0] = 1;
key
};
if test_different_storage || test_restore_to_with_dirty_storage {
let result = perform_the_restoration();
assert!(ContractInfoOf::<Test>::get(&addr_bob).unwrap().get_tombstone().is_some());
let django_contract = ContractInfoOf::<Test>::get(&addr_django).unwrap()
.get_alive().unwrap();
assert_eq!(django_contract.storage_size, 8);
assert_eq!(django_contract.trie_id, django_trie_id);
assert_eq!(django_contract.deduct_block, System::block_number());
assert_eq!(
Storage::<Test>::read(&django_trie_id, &delta_key),
Some(vec![40, 0, 0, 0]),
);
match (test_different_storage, test_restore_to_with_dirty_storage) {
(true, false) => {
assert_err_ignore_postinfo!(
result, Error::<Test>::InvalidTombstone,
);
assert_eq!(System::events(), vec![]);
}
(_, true) => {
assert_err_ignore_postinfo!(
result, Error::<Test>::InvalidContractOrigin,
);
pretty_assertions::assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(RawEvent::Evicted(addr_bob, true)),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::system(fabric_system::Event::NewAccount(CHARLIE)),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(noble_balances::RawEvent::Endowed(CHARLIE, 1_000_000)),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::system(fabric_system::Event::NewAccount(addr_django.clone())),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(noble_balances::RawEvent::Endowed(addr_django.clone(), 30_000)),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::balances(
noble_balances::RawEvent::Transfer(CHARLIE, addr_django.clone(), 30_000)
),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(RawEvent::Instantiated(CHARLIE, addr_django.clone())),
topics: vec![],
},
]);
}
_ => unreachable!(),
}
} else {
assert_ok!(perform_the_restoration());
let bob_contract = ContractInfoOf::<Test>::get(&addr_bob).unwrap()
.get_alive().unwrap();
assert_eq!(bob_contract.rent_allowance, 50);
assert_eq!(bob_contract.storage_size, 4);
assert_eq!(bob_contract.trie_id, django_trie_id);
assert_eq!(bob_contract.deduct_block, System::block_number());
assert!(ContractInfoOf::<Test>::get(&addr_django).is_none());
assert_matches!(Storage::<Test>::read(&django_trie_id, &delta_key), None);
assert_eq!(System::events(), vec![
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::system(system::Event::KilledAccount(addr_django.clone())),
topics: vec![],
},
EventRecord {
phase: Phase::Initialization,
event: MetaEvent::contracts(
RawEvent::Restored(addr_django, addr_bob, bob_contract.code_hash, 50)
),
topics: vec![],
},
]);
}
});
}
#[test]
fn storage_max_value_limit() {
let (wasm, code_hash) = compile_module::<Test>("storage_size").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
ContractInfoOf::<Test>::get(&addr).unwrap().get_alive().unwrap();
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT * 2, <Test as Config>::MaxValueSize::get().encode(),
));
assert_err_ignore_postinfo!(
Contracts::call(
Origin::signed(ALICE),
addr,
0,
GAS_LIMIT,
(<Test as Config>::MaxValueSize::get() + 1).encode(),
),
Error::<Test>::ValueTooLarge,
);
});
}
#[test]
fn deploy_and_call_other_contract() {
let (callee_wasm, callee_code_hash) = compile_module::<Test>("return_with_data").unwrap();
let (caller_wasm, caller_code_hash) = compile_module::<Test>("caller_contract").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), callee_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), caller_wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100_000,
GAS_LIMIT,
caller_code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &caller_code_hash, &[]);
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr,
0,
GAS_LIMIT,
callee_code_hash.as_ref().to_vec(),
));
});
}
#[test]
fn cannot_self_destruct_through_draning() {
let (wasm, code_hash) = compile_module::<Test>("drain").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
assert_matches!(
ContractInfoOf::<Test>::get(&addr),
Some(ContractInfo::Alive(_))
);
assert_ok!(
Contracts::call(
Origin::signed(ALICE),
addr,
0,
GAS_LIMIT,
vec![],
)
);
});
}
#[test]
fn cannot_self_destruct_while_live() {
let (wasm, code_hash) = compile_module::<Test>("self_destruct").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
assert_matches!(
ContractInfoOf::<Test>::get(&addr),
Some(ContractInfo::Alive(_))
);
assert_err_ignore_postinfo!(
Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![0],
),
Error::<Test>::ContractTrapped,
);
assert_matches!(
ContractInfoOf::<Test>::get(&addr),
Some(ContractInfo::Alive(_))
);
});
}
#[test]
fn self_destruct_works() {
let (wasm, code_hash) = compile_module::<Test>("self_destruct").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
assert_matches!(
ContractInfoOf::<Test>::get(&addr),
Some(ContractInfo::Alive(_))
);
assert_matches!(
Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![],
),
Ok(_)
);
assert!(ContractInfoOf::<Test>::get(&addr).is_none());
assert_eq!(Balances::free_balance(DJANGO), 100_000);
});
}
#[test]
fn destroy_contract_and_transfer_funds() {
let (callee_wasm, callee_code_hash) = compile_module::<Test>("self_destruct").unwrap();
let (caller_wasm, caller_code_hash) = compile_module::<Test>("destroy_and_transfer").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), callee_wasm));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), caller_wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
200_000,
GAS_LIMIT,
caller_code_hash.into(),
callee_code_hash.as_ref().to_vec(),
vec![],
));
let addr_bob = Contracts::contract_address(&ALICE, &caller_code_hash, &[]);
let addr_charlie = Contracts::contract_address(
&addr_bob, &callee_code_hash, &[0x47, 0x11]
);
assert_matches!(
ContractInfoOf::<Test>::get(&addr_charlie),
Some(ContractInfo::Alive(_))
);
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr_bob,
0,
GAS_LIMIT,
addr_charlie.encode(),
));
assert!(ContractInfoOf::<Test>::get(&addr_charlie).is_none());
});
}
#[test]
fn cannot_self_destruct_in_constructor() {
let (wasm, code_hash) = compile_module::<Test>("self_destructing_constructor").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_err_ignore_postinfo!(
Contracts::instantiate(
Origin::signed(ALICE),
100_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
),
Error::<Test>::NewContractNotFunded,
);
});
}
#[test]
fn crypto_hashes() {
let (wasm, code_hash) = compile_module::<Test>("crypto_hashes").unwrap();
ExtBuilder::default()
.existential_deposit(50)
.build()
.execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
100_000,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let input = b"_DEAD_BEEF";
use tet_io::hashing::*;
macro_rules! dyn_hash_fn {
($name:ident) => {
Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice())
};
}
let test_cases: &[(Box<dyn Fn(&[u8]) -> Box<[u8]>>, usize)] = &[
(dyn_hash_fn!(sha2_256), 32),
(dyn_hash_fn!(keccak_256), 32),
(dyn_hash_fn!(blake2_256), 32),
(dyn_hash_fn!(blake2_128), 16),
];
for (n, (hash_fn, expected_size)) in test_cases.iter().enumerate() {
let mut params = vec![(n + 1) as u8];
params.extend_from_slice(input);
let result = <Module<Test>>::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
params,
).exec_result.unwrap();
assert!(result.is_success());
let expected = hash_fn(input.as_ref());
assert_eq!(&result.data[..*expected_size], &*expected);
}
})
}
#[test]
fn transfer_return_code() {
let (wasm, code_hash) = compile_module::<Test>("transfer_return_code").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
code_hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
Balances::make_free_balance_be(&addr, subsistence);
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![],
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
Balances::make_free_balance_be(&addr, subsistence + 100);
Balances::reserve(&addr, subsistence + 100).unwrap();
let result = Contracts::bare_call(
ALICE,
addr,
0,
GAS_LIMIT,
vec![],
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
});
}
#[test]
fn call_return_code() {
let (caller_code, caller_hash) = compile_module::<Test>("call_return_code").unwrap();
let (callee_code, callee_hash) = compile_module::<Test>("ok_trap_revert").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
let _ = Balances::deposit_creating(&CHARLIE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), caller_code));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), callee_code));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
caller_hash.into(),
vec![0],
vec![],
),
);
let addr_bob = Contracts::contract_address(&ALICE, &caller_hash, &[]);
Balances::make_free_balance_be(&addr_bob, subsistence);
let result = Contracts::bare_call(
ALICE,
addr_bob.clone(),
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&DJANGO).to_vec(),
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::NotCallable);
assert_ok!(
Contracts::instantiate(
Origin::signed(CHARLIE),
subsistence * 3,
GAS_LIMIT,
callee_hash.into(),
vec![0],
vec![],
),
);
let addr_django = Contracts::contract_address(&CHARLIE, &callee_hash, &[]);
Balances::make_free_balance_be(&addr_django, subsistence);
let result = Contracts::bare_call(
ALICE,
addr_bob.clone(),
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&0u32.to_le_bytes()).cloned().collect(),
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
Balances::make_free_balance_be(&addr_bob, subsistence + 100);
Balances::reserve(&addr_bob, subsistence + 100).unwrap();
let result = Contracts::bare_call(
ALICE,
addr_bob.clone(),
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&0u32.to_le_bytes()).cloned().collect(),
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
Balances::make_free_balance_be(&addr_bob, subsistence + 1000);
let result = Contracts::bare_call(
ALICE,
addr_bob.clone(),
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&1u32.to_le_bytes()).cloned().collect(),
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CalleeReverted);
let result = Contracts::bare_call(
ALICE,
addr_bob,
0,
GAS_LIMIT,
AsRef::<[u8]>::as_ref(&addr_django).iter().chain(&2u32.to_le_bytes()).cloned().collect(),
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);
});
}
#[test]
fn instantiate_return_code() {
let (caller_code, caller_hash) = compile_module::<Test>("instantiate_return_code").unwrap();
let (callee_code, callee_hash) = compile_module::<Test>("ok_trap_revert").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
let _ = Balances::deposit_creating(&CHARLIE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), caller_code));
assert_ok!(Contracts::put_code(Origin::signed(ALICE), callee_code));
let callee_hash = callee_hash.as_ref().to_vec();
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
caller_hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &caller_hash, &[]);
Balances::make_free_balance_be(&addr, subsistence);
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![0; 33],
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
Balances::make_free_balance_be(&addr, subsistence + 100);
Balances::reserve(&addr, subsistence + 100).unwrap();
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![0; 33],
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::TransferFailed);
Balances::make_free_balance_be(&addr, subsistence + 1000);
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![0; 33],
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CodeNotFound);
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect(),
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CalleeReverted);
let result = Contracts::bare_call(
ALICE,
addr,
0,
GAS_LIMIT,
callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect(),
).exec_result.unwrap();
assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);
});
}
#[test]
fn disabled_chain_extension_wont_deploy() {
let (code, _hash) = compile_module::<Test>("chain_extension").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
TestExtension::disable();
assert_eq!(
Contracts::put_code(Origin::signed(ALICE), code),
Err("module uses chain extensions but chain extensions are disabled".into()),
);
});
}
#[test]
fn disabled_chain_extension_errors_on_call() {
let (code, hash) = compile_module::<Test>("chain_extension").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
TestExtension::disable();
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
assert_err_ignore_postinfo!(
Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![],
),
Error::<Test>::NoChainExtension,
);
});
}
#[test]
fn chain_extension_works() {
let (code, hash) = compile_module::<Test>("chain_extension").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![0, 99],
);
let gas_consumed = result.gas_consumed;
assert_eq!(TestExtension::last_seen_buffer(), vec![0, 99]);
assert_eq!(result.exec_result.unwrap().data, vec![0, 99]);
Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![1],
).exec_result.unwrap();
assert_eq!(TestExtension::last_seen_inputs(), (4, 1, 16, 12));
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![2, 42],
);
assert_ok!(result.exec_result);
assert_eq!(result.gas_consumed, gas_consumed + 42);
let result = Contracts::bare_call(
ALICE,
addr.clone(),
0,
GAS_LIMIT,
vec![3],
).exec_result.unwrap();
assert_eq!(result.flags, ReturnFlags::REVERT);
assert_eq!(result.data, vec![42, 99]);
});
}
#[test]
fn lazy_removal_works() {
let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let info = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
let trie = &info.child_trie_info();
child::put(trie, &[99], &42);
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![],
));
assert!(!<ContractInfoOf::<Test>>::contains_key(&addr));
assert_matches!(child::get(trie, &[99]), Some(42));
Contracts::on_initialize(Weight::max_value());
assert_matches!(child::get::<i32>(trie, &[99]), None);
});
}
#[test]
fn lazy_removal_partial_remove_works() {
let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
let extra_keys = 7u32;
let weight_limit = 5_000_000_000;
let (_, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
let vals: Vec<_> = (0..max_keys + extra_keys).map(|i| {
(blake2_256(&i.encode()), (i as u32), (i as u32).encode())
})
.collect();
let mut ext = ExtBuilder::default().existential_deposit(50).build();
let trie = ext.execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let info = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
let trie = &info.child_trie_info();
for val in &vals {
Storage::<Test>::write(
&addr,
&info.trie_id,
&val.0,
Some(val.2.clone()),
).unwrap();
}
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![],
));
assert!(!<ContractInfoOf::<Test>>::contains_key(&addr));
for val in &vals {
assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), Some(val.1));
}
trie.clone()
});
ext.commit_all().unwrap();
ext.execute_with(|| {
let weight_used = Storage::<Test>::process_deletion_queue_batch(weight_limit);
assert_eq!(weight_used, weight_limit);
let mut num_deleted = 0u32;
let mut num_remaining = 0u32;
for val in &vals {
match child::get::<u32>(&trie, &blake2_256(&val.0)) {
None => num_deleted += 1,
Some(x) if x == val.1 => num_remaining += 1,
Some(_) => panic!("Unexpected value in contract storage"),
}
}
assert_eq!(num_deleted + num_remaining, vals.len() as u32);
assert_eq!(num_deleted, max_keys);
assert_eq!(num_remaining, extra_keys);
});
}
#[test]
fn lazy_removal_does_no_run_on_full_block() {
let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let info = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
let trie = &info.child_trie_info();
let max_keys = 30;
let vals: Vec<_> = (0..max_keys).map(|i| {
(blake2_256(&i.encode()), (i as u32), (i as u32).encode())
})
.collect();
for val in &vals {
Storage::<Test>::write(
&addr,
&info.trie_id,
&val.0,
Some(val.2.clone()),
).unwrap();
}
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![],
));
assert!(!<ContractInfoOf::<Test>>::contains_key(&addr));
for val in &vals {
assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), Some(val.1));
}
System::register_extra_weight_unchecked(
<Test as system::Config>::BlockWeights::get().max_block,
DispatchClass::Mandatory,
);
let weight_used = Contracts::on_initialize(Weight::max_value());
let base = <<Test as crate::Config>::WeightInfo as crate::WeightInfo>::on_initialize();
assert_eq!(weight_used, base);
for val in &vals {
assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), Some(val.1));
}
Storage::<Test>::process_deletion_queue_batch(Weight::max_value());
for val in &vals {
assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), None);
}
});
}
#[test]
fn lazy_removal_does_not_use_all_weight() {
let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
let info = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
let trie = &info.child_trie_info();
let weight_limit = 5_000_000_000;
let (weight_per_key, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
let vals: Vec<_> = (0..max_keys - 1).map(|i| {
(blake2_256(&i.encode()), (i as u32), (i as u32).encode())
})
.collect();
for val in &vals {
Storage::<Test>::write(
&addr,
&info.trie_id,
&val.0,
Some(val.2.clone()),
).unwrap();
}
assert_ok!(Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![],
));
assert!(!<ContractInfoOf::<Test>>::contains_key(&addr));
for val in &vals {
assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), Some(val.1));
}
let weight_used = Storage::<Test>::process_deletion_queue_batch(weight_limit);
assert_eq!(weight_used, weight_limit - weight_per_key);
for val in vals {
assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), None);
}
});
}
#[test]
fn deletion_queue_full() {
let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let subsistence = ConfigCache::<Test>::subsistence_threshold_uncached();
let _ = Balances::deposit_creating(&ALICE, 10 * subsistence);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), code));
assert_ok!(
Contracts::instantiate(
Origin::signed(ALICE),
subsistence * 3,
GAS_LIMIT,
hash.into(),
vec![],
vec![],
),
);
let addr = Contracts::contract_address(&ALICE, &hash, &[]);
Storage::<Test>::fill_queue_with_dummies();
assert_err_ignore_postinfo!(
Contracts::call(
Origin::signed(ALICE),
addr.clone(),
0,
GAS_LIMIT,
vec![],
),
Error::<Test>::DeletionQueueFull,
);
<ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
initialize_block(5);
assert_err!(
Contracts::claim_surcharge(Origin::none(), addr.clone(), Some(ALICE)),
Error::<Test>::DeletionQueueFull,
);
<ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
});
}
#[test]
fn not_deployed_if_endowment_too_low_for_first_rent() {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
let first_rent = <Test as Config>::RentFraction::get()
.mul_ceil(80_000u32 + 40_000 + 10_000 - 30_000)
* 1;
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_storage_noop!(assert_err_ignore_postinfo!(
Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT, code_hash.into(),
(BalanceOf::<Test>::from(first_rent) - BalanceOf::<Test>::from(1u32))
.encode(), vec![],
),
Error::<Test>::NewContractNotFunded,
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
assert_matches!(ContractInfoOf::<Test>::get(&addr), None);
});
}
#[test]
fn surcharge_reward_is_capped() {
let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
ExtBuilder::default().existential_deposit(50).build().execute_with(|| {
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm));
assert_ok!(Contracts::instantiate(
Origin::signed(ALICE),
30_000,
GAS_LIMIT, code_hash.into(),
<BalanceOf<Test>>::from(1_000u32).encode(), vec![],
));
let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
let contract = <ContractInfoOf::<Test>>::get(&addr).unwrap().get_alive().unwrap();
let balance = Balances::free_balance(&ALICE);
let reward = <Test as Config>::SurchargeReward::get();
assert_ne!(contract.rent_payed, 0);
assert!(reward > contract.rent_payed);
initialize_block(40);
assert_ok!(Contracts::claim_surcharge(Origin::none(), addr.clone(), Some(ALICE)));
let capped_reward = reward.min(contract.rent_payed);
assert!(Balances::free_balance(&ALICE) > balance + capped_reward);
assert!(Balances::free_balance(&ALICE) < balance + reward);
});
}