mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 09:21:05 +00:00
Contract restoration (#2502)
* partial impl * more checks * improve TestExternalities + tests * make tombstone raw to derive Eq * remove before merge * add test * fmt * update test * doc * bump version * fix testing * update runtime * Fix TrieIdGenerator * fix dummy trie id generator * update test * bump version * format * Update core/state-machine/src/testing.rs Co-Authored-By: cheme <emericchevalier.pro@gmail.com> * document test * Apply suggestions from code review Co-Authored-By: DemiMarie-temp <50585338+DemiMarie-temp@users.noreply.github.com> * refactor * fix * fmt * address review * impl last_write * Fix storage size, test, remove size in tombstone * fix * Update srml/contract/src/lib.rs Co-Authored-By: Sergei Pepyakin <s.pepyakin@gmail.com> * comment * child_storage_root as &[u8]
This commit is contained in:
committed by
Sergei Pepyakin
parent
c357854015
commit
ffce18b994
@@ -25,6 +25,7 @@ assert_matches = "1.1"
|
||||
hex-literal = "0.2.0"
|
||||
consensus = { package = "srml-consensus", path = "../consensus" }
|
||||
balances = { package = "srml-balances", path = "../balances" }
|
||||
hex = "0.3"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
|
||||
@@ -124,6 +124,7 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
|
||||
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
|
||||
deduct_block: <system::Module<T>>::block_number(),
|
||||
rent_allowance: <BalanceOf<T>>::max_value(),
|
||||
last_write: None,
|
||||
}
|
||||
} else {
|
||||
// No contract exist and no code_hash provided
|
||||
@@ -138,6 +139,10 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
|
||||
new_info.code_hash = code_hash;
|
||||
}
|
||||
|
||||
if !changed.storage.is_empty() {
|
||||
new_info.last_write = Some(<system::Module<T>>::block_number());
|
||||
}
|
||||
|
||||
for (k, v) in changed.storage.into_iter() {
|
||||
if let Some(value) = child::get_raw(&new_info.trie_id[..], &blake2_256(&k)) {
|
||||
new_info.storage_size -= value.len() as u32;
|
||||
|
||||
@@ -96,9 +96,14 @@ use serde::{Serialize, Deserialize};
|
||||
use substrate_primitives::crypto::UncheckedFrom;
|
||||
use rstd::{prelude::*, marker::PhantomData, convert::TryFrom};
|
||||
use parity_codec::{Codec, Encode, Decode};
|
||||
use runtime_primitives::traits::{Hash, SimpleArithmetic, Bounded, StaticLookup, Zero};
|
||||
use runtime_io::blake2_256;
|
||||
use runtime_primitives::traits::{
|
||||
Hash, SimpleArithmetic, Bounded, StaticLookup, Zero, MaybeSerializeDebug, Member
|
||||
};
|
||||
use srml_support::dispatch::{Result, Dispatchable};
|
||||
use srml_support::{Parameter, StorageMap, StorageValue, decl_module, decl_event, decl_storage, storage::child};
|
||||
use srml_support::{
|
||||
Parameter, StorageMap, StorageValue, decl_module, decl_event, decl_storage, storage::child
|
||||
};
|
||||
use srml_support::traits::{OnFreeBalanceZero, OnUnbalanced, Currency};
|
||||
use system::{ensure_signed, RawOrigin};
|
||||
use substrate_primitives::storage::well_known_keys::CHILD_STORAGE_KEY_PREFIX;
|
||||
@@ -120,6 +125,7 @@ pub trait ComputeDispatchFee<Call, Balance> {
|
||||
/// Information for managing an acocunt and its sub trie abstraction.
|
||||
/// This is the required info to cache for an account
|
||||
#[derive(Encode, Decode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub enum ContractInfo<T: Trait> {
|
||||
Alive(AliveContractInfo<T>),
|
||||
Tombstone(TombstoneContractInfo<T>),
|
||||
@@ -177,12 +183,14 @@ impl<T: Trait> ContractInfo<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub type AliveContractInfo<T> = RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as system::Trait>::BlockNumber>;
|
||||
pub type AliveContractInfo<T> =
|
||||
RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as system::Trait>::BlockNumber>;
|
||||
|
||||
/// Information for managing an account and its sub trie abstraction.
|
||||
/// This is the required info to cache for an account.
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
|
||||
/// Unique ID for the subtree encoded as a bytes vector.
|
||||
pub trie_id: TrieId,
|
||||
@@ -190,20 +198,32 @@ pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
|
||||
pub storage_size: u32,
|
||||
/// The code associated with a given account.
|
||||
pub code_hash: CodeHash,
|
||||
/// Pay rent at most up to this value.
|
||||
pub rent_allowance: Balance,
|
||||
/// Last block rent has been payed.
|
||||
pub deduct_block: BlockNumber,
|
||||
/// Last block child storage has been written.
|
||||
pub last_write: Option<BlockNumber>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct TombstoneContractInfo<T: Trait>(T::Hash);
|
||||
pub type TombstoneContractInfo<T> =
|
||||
RawTombstoneContractInfo<<T as system::Trait>::Hash, <T as system::Trait>::Hashing>;
|
||||
|
||||
impl<T: Trait> TombstoneContractInfo<T> {
|
||||
fn new(storage_root: Vec<u8>, storage_size: u32, code_hash: CodeHash<T>) -> Self {
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[derive(Encode, Decode, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
|
||||
|
||||
impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>
|
||||
where
|
||||
H: Member + MaybeSerializeDebug + AsRef<[u8]> + AsMut<[u8]> + Copy + Default + rstd::hash::Hash,
|
||||
Hasher: Hash<Output=H>,
|
||||
{
|
||||
fn new(storage_root: &[u8], code_hash: H) -> Self {
|
||||
let mut buf = Vec::new();
|
||||
storage_root.using_encoded(|encoded| buf.extend_from_slice(encoded));
|
||||
storage_size.using_encoded(|encoded| buf.extend_from_slice(encoded));
|
||||
buf.extend_from_slice(code_hash.as_ref());
|
||||
TombstoneContractInfo(T::Hashing::hash(&buf[..]))
|
||||
RawTombstoneContractInfo(Hasher::hash(&buf[..]), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,7 +255,10 @@ where
|
||||
fn trie_id(account_id: &T::AccountId) -> TrieId {
|
||||
// Note that skipping a value due to error is not an issue here.
|
||||
// We only need uniqueness, not sequence.
|
||||
let new_seed = <AccountCounter<T>>::mutate(|v| v.wrapping_add(1));
|
||||
let new_seed = <AccountCounter<T>>::mutate(|v| {
|
||||
*v = v.wrapping_add(1);
|
||||
*v
|
||||
});
|
||||
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(account_id.as_ref());
|
||||
@@ -508,6 +531,84 @@ decl_module! {
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows a contract to restore a tombstone by giving its storage.
|
||||
///
|
||||
/// The contract that wants to restore (i.e. origin of the call, or `msg.sender` in Solidity terms) will compute a
|
||||
/// tombstone with its storage and the given code_hash. If the computed tombstone
|
||||
/// match the destination one, the destination contract is restored with the rent_allowance` specified,
|
||||
/// while the origin sends all its funds to the destination and is removed.
|
||||
fn restore_to(
|
||||
origin,
|
||||
dest: T::AccountId,
|
||||
code_hash: CodeHash<T>,
|
||||
rent_allowance: BalanceOf<T>,
|
||||
delta: Vec<exec::StorageKey>
|
||||
) {
|
||||
let origin = ensure_signed(origin)?;
|
||||
|
||||
let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
|
||||
.and_then(|c| c.get_alive())
|
||||
.ok_or("Cannot restore from inexisting or tombstone contract")?;
|
||||
|
||||
let current_block = <system::Module<T>>::block_number();
|
||||
|
||||
if origin_contract.last_write == Some(current_block) {
|
||||
return Err("Origin TrieId written in the current block");
|
||||
}
|
||||
|
||||
let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
|
||||
.and_then(|c| c.get_tombstone())
|
||||
.ok_or("Cannot restore to inexisting or alive contract")?;
|
||||
|
||||
let last_write = if !delta.is_empty() {
|
||||
Some(current_block)
|
||||
} else {
|
||||
origin_contract.last_write
|
||||
};
|
||||
|
||||
let key_values_taken = delta.iter()
|
||||
.filter_map(|key| {
|
||||
child::get_raw(&origin_contract.trie_id, &blake2_256(key)).map(|value| {
|
||||
child::kill(&origin_contract.trie_id, &blake2_256(key));
|
||||
(key, value)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let tombstone = <TombstoneContractInfo<T>>::new(
|
||||
// This operation is cheap enough because last_write (delta not included)
|
||||
// is not this block as it has been checked earlier.
|
||||
&runtime_io::child_storage_root(&origin_contract.trie_id)[..],
|
||||
code_hash,
|
||||
);
|
||||
|
||||
if tombstone != dest_tombstone {
|
||||
for (key, value) in key_values_taken {
|
||||
child::put_raw(&origin_contract.trie_id, &blake2_256(key), &value);
|
||||
}
|
||||
|
||||
return Err("Tombstones don't match");
|
||||
}
|
||||
|
||||
origin_contract.storage_size -= key_values_taken.iter()
|
||||
.map(|(_, value)| value.len() as u32)
|
||||
.sum::<u32>();
|
||||
|
||||
<ContractInfoOf<T>>::remove(&origin);
|
||||
<ContractInfoOf<T>>::insert(&dest, ContractInfo::Alive(RawAliveContractInfo {
|
||||
trie_id: origin_contract.trie_id,
|
||||
storage_size: origin_contract.storage_size,
|
||||
code_hash,
|
||||
rent_allowance,
|
||||
deduct_block: current_block,
|
||||
last_write,
|
||||
}));
|
||||
|
||||
let origin_free_balance = T::Currency::free_balance(&origin);
|
||||
T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
|
||||
T::Currency::deposit_creating(&dest, origin_free_balance);
|
||||
}
|
||||
|
||||
fn on_finalize() {
|
||||
<GasSpent<T>>::kill();
|
||||
}
|
||||
|
||||
@@ -163,9 +163,8 @@ fn try_evict_or_and_pay_rent<T: Trait>(
|
||||
// Note: this operation is heavy.
|
||||
let child_storage_root = runtime_io::child_storage_root(&contract.trie_id);
|
||||
|
||||
let tombstone = TombstoneContractInfo::new(
|
||||
child_storage_root,
|
||||
contract.storage_size,
|
||||
let tombstone = <TombstoneContractInfo<T>>::new(
|
||||
&child_storage_root[..],
|
||||
contract.code_hash,
|
||||
);
|
||||
<ContractInfoOf<T>>::insert(account, ContractInfo::Tombstone(tombstone));
|
||||
|
||||
@@ -34,7 +34,7 @@ use runtime_primitives::traits::{BlakeTwo256, IdentityLookup};
|
||||
use runtime_primitives::BuildStorage;
|
||||
use srml_support::{
|
||||
assert_ok, impl_outer_dispatch, impl_outer_event, impl_outer_origin, storage::child,
|
||||
traits::Currency, StorageMap,
|
||||
traits::Currency, StorageMap, StorageValue
|
||||
};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use substrate_primitives::storage::well_known_keys;
|
||||
@@ -119,18 +119,21 @@ impl ContractAddressFor<H256, u64> for DummyContractAddressFor {
|
||||
}
|
||||
}
|
||||
|
||||
static KEY_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub struct DummyTrieIdGenerator;
|
||||
impl TrieIdGenerator<u64> for DummyTrieIdGenerator {
|
||||
fn trie_id(account_id: &u64) -> TrieId {
|
||||
use substrate_primitives::storage::well_known_keys;
|
||||
|
||||
let new_seed = <super::AccountCounter<Test>>::mutate(|v| {
|
||||
*v = v.wrapping_add(1);
|
||||
*v
|
||||
});
|
||||
|
||||
// TODO: see https://github.com/paritytech/substrate/issues/2325
|
||||
let mut res = vec![];
|
||||
res.extend_from_slice(well_known_keys::CHILD_STORAGE_KEY_PREFIX);
|
||||
res.extend_from_slice(b"default:");
|
||||
res.extend_from_slice(&KEY_COUNTER.fetch_add(1, Ordering::Relaxed).to_le_bytes());
|
||||
res.extend_from_slice(&new_seed.to_le_bytes());
|
||||
res.extend_from_slice(&account_id.to_le_bytes());
|
||||
res
|
||||
}
|
||||
@@ -146,6 +149,7 @@ impl ComputeDispatchFee<Call, u64> for DummyComputeDispatchFee {
|
||||
const ALICE: u64 = 1;
|
||||
const BOB: u64 = 2;
|
||||
const CHARLIE: u64 = 3;
|
||||
const DJANGO: u64 = 4;
|
||||
|
||||
pub struct ExtBuilder {
|
||||
existential_deposit: u64,
|
||||
@@ -263,6 +267,7 @@ fn account_removal_removes_storage() {
|
||||
deduct_block: System::block_number(),
|
||||
code_hash: H256::repeat_byte(1),
|
||||
rent_allowance: 40,
|
||||
last_write: None,
|
||||
}));
|
||||
|
||||
let mut overlay = OverlayAccountDb::<Test>::new(&DirectAccountDb);
|
||||
@@ -277,6 +282,7 @@ fn account_removal_removes_storage() {
|
||||
deduct_block: System::block_number(),
|
||||
code_hash: H256::repeat_byte(2),
|
||||
rent_allowance: 40,
|
||||
last_write: None,
|
||||
}));
|
||||
|
||||
let mut overlay = OverlayAccountDb::<Test>::new(&DirectAccountDb);
|
||||
@@ -424,7 +430,7 @@ const HASH_DISPATCH_CALL: [u8; 32] = hex!("49dfdcaf9c1553be10634467e95b8e71a3bc1
|
||||
fn dispatch_call() {
|
||||
// This test can fail due to the encoding changes. In case it becomes too annoying
|
||||
// let's rewrite so as we use this module controlled call or we serialize it in runtime.
|
||||
let encoded = parity_codec::Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50)));
|
||||
let encoded = Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50)));
|
||||
assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]);
|
||||
|
||||
let wasm = wabt::wat2wasm(CODE_DISPATCH_CALL).unwrap();
|
||||
@@ -555,7 +561,7 @@ const CODE_SET_RENT: &str = r#"
|
||||
;; transfer 50 to ALICE
|
||||
(func $call_2
|
||||
(call $ext_dispatch_call
|
||||
(i32.const 8)
|
||||
(i32.const 68)
|
||||
(i32.const 11)
|
||||
)
|
||||
)
|
||||
@@ -627,10 +633,12 @@ const CODE_SET_RENT: &str = r#"
|
||||
(data (i32.const 0) "\28")
|
||||
|
||||
;; Encoding of call transfer 50 to CHARLIE
|
||||
(data (i32.const 8) "\00\00\03\00\00\00\00\00\00\00\C8")
|
||||
(data (i32.const 68) "\00\00\03\00\00\00\00\00\00\00\C8")
|
||||
)
|
||||
"#;
|
||||
const HASH_SET_RENT: [u8; 32] = hex!("a51c2a6f3f68936d4ae9abdb93b28eedcbd0f6f39770e168f9025f0c1e7094ef");
|
||||
|
||||
// Use test_hash_and_code test to get the actual hash if the code changed.
|
||||
const HASH_SET_RENT: [u8; 32] = hex!("21d6b1d59aa6038fcad632488e9026893a1bbb48581774c771b8f24320697f05");
|
||||
|
||||
/// Input data for each call in set_rent code
|
||||
mod call {
|
||||
@@ -643,10 +651,10 @@ mod call {
|
||||
/// Test correspondance of set_rent code and its hash.
|
||||
/// Also test that encoded extrinsic in code correspond to the correct transfer
|
||||
#[test]
|
||||
fn set_rent_hash_and_code() {
|
||||
fn test_set_rent_code_and_hash() {
|
||||
// This test can fail due to the encoding changes. In case it becomes too annoying
|
||||
// let's rewrite so as we use this module controlled call or we serialize it in runtime.
|
||||
let encoded = parity_codec::Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50)));
|
||||
let encoded = Encode::encode(&Call::Balances(balances::Call::transfer(CHARLIE, 50)));
|
||||
assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]);
|
||||
|
||||
let wasm = wabt::wat2wasm(CODE_SET_RENT).unwrap();
|
||||
@@ -657,7 +665,8 @@ fn set_rent_hash_and_code() {
|
||||
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||
|
||||
// If you ever need to update the wasm source this test will fail and will show you the actual hash.
|
||||
// If you ever need to update the wasm source this test will fail
|
||||
// and will show you the actual hash.
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
@@ -691,15 +700,15 @@ fn storage_size() {
|
||||
100_000, HASH_SET_RENT.into(),
|
||||
<Test as balances::Trait>::Balance::from(1_000u32).encode() // rent allowance
|
||||
));
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.storage_size, Contract::storage_size_offset() + 4);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::set_storage_4_byte()));
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.storage_size, Contract::storage_size_offset() + 4 + 4);
|
||||
|
||||
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::remove_storage_4_byte()));
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.storage_size, Contract::storage_size_offset() + 4);
|
||||
}
|
||||
);
|
||||
@@ -723,7 +732,7 @@ fn deduct_blocks() {
|
||||
));
|
||||
|
||||
// Check creation
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.rent_allowance, 1_000);
|
||||
|
||||
// Advance 4 blocks
|
||||
@@ -736,7 +745,7 @@ fn deduct_blocks() {
|
||||
let rent = (8 + 4 - 3) // storage size = size_offset + deploy_set_storage - deposit_offset
|
||||
* 4 // rent byte price
|
||||
* 4; // blocks to rent
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.rent_allowance, 1_000 - rent);
|
||||
assert_eq!(bob_contract.deduct_block, 5);
|
||||
assert_eq!(Balances::free_balance(BOB), 30_000 - rent);
|
||||
@@ -751,7 +760,7 @@ fn deduct_blocks() {
|
||||
let rent_2 = (8 + 4 - 2) // storage size = size_offset + deploy_set_storage - deposit_offset
|
||||
* 4 // rent byte price
|
||||
* 7; // blocks to rent
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2);
|
||||
assert_eq!(bob_contract.deduct_block, 12);
|
||||
assert_eq!(Balances::free_balance(BOB), 30_000 - rent - rent_2);
|
||||
@@ -759,7 +768,7 @@ fn deduct_blocks() {
|
||||
// Second call on same block should have no effect on rent
|
||||
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
|
||||
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.rent_allowance, 1_000 - rent - rent_2);
|
||||
assert_eq!(bob_contract.deduct_block, 12);
|
||||
assert_eq!(Balances::free_balance(BOB), 30_000 - rent - rent_2);
|
||||
@@ -822,9 +831,9 @@ fn claim_surcharge(blocks: u64, trigger_call: impl Fn() -> bool, removes: bool)
|
||||
assert!(trigger_call());
|
||||
|
||||
if removes {
|
||||
assert!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
} else {
|
||||
assert!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().is_some());
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().is_some());
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -853,21 +862,21 @@ fn removals(trigger_call: impl Fn() -> bool) {
|
||||
|
||||
// Trigger rent must have no effect
|
||||
assert!(trigger_call());
|
||||
assert_eq!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000);
|
||||
assert_eq!(ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000);
|
||||
|
||||
// Advance blocks
|
||||
System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into());
|
||||
|
||||
// Trigger rent through call
|
||||
assert!(trigger_call());
|
||||
assert!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
|
||||
// Advance blocks
|
||||
System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into());
|
||||
|
||||
// Trigger rent must have no effect
|
||||
assert!(trigger_call());
|
||||
assert!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
}
|
||||
);
|
||||
|
||||
@@ -887,21 +896,21 @@ fn removals(trigger_call: impl Fn() -> bool) {
|
||||
|
||||
// Trigger rent must have no effect
|
||||
assert!(trigger_call());
|
||||
assert_eq!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 100);
|
||||
assert_eq!(ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 100);
|
||||
|
||||
// Advance blocks
|
||||
System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into());
|
||||
|
||||
// Trigger rent through call
|
||||
assert!(trigger_call());
|
||||
assert!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
|
||||
// Advance blocks
|
||||
System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into());
|
||||
|
||||
// Trigger rent must have no effect
|
||||
assert!(trigger_call());
|
||||
assert!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
}
|
||||
);
|
||||
|
||||
@@ -921,25 +930,25 @@ fn removals(trigger_call: impl Fn() -> bool) {
|
||||
|
||||
// Trigger rent must have no effect
|
||||
assert!(trigger_call());
|
||||
assert_eq!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000);
|
||||
assert_eq!(ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000);
|
||||
|
||||
// Transfer funds
|
||||
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::transfer()));
|
||||
assert_eq!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000);
|
||||
assert_eq!(ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap().rent_allowance, 1_000);
|
||||
|
||||
// Advance blocks
|
||||
System::initialize(&10, &[0u8; 32].into(), &[0u8; 32].into());
|
||||
|
||||
// Trigger rent through call
|
||||
assert!(trigger_call());
|
||||
assert!(super::ContractInfoOf::<Test>::get(BOB).is_none());
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).is_none());
|
||||
|
||||
// Advance blocks
|
||||
System::initialize(&20, &[0u8; 32].into(), &[0u8; 32].into());
|
||||
|
||||
// Trigger rent must have no effect
|
||||
assert!(trigger_call());
|
||||
assert!(super::ContractInfoOf::<Test>::get(BOB).is_none());
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).is_none());
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1014,7 +1023,7 @@ fn default_rent_allowance_on_create() {
|
||||
));
|
||||
|
||||
// Check creation
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.rent_allowance, <BalanceOf<Test>>::max_value());
|
||||
|
||||
// Advance blocks
|
||||
@@ -1024,8 +1033,196 @@ fn default_rent_allowance_on_create() {
|
||||
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
|
||||
|
||||
// Check contract is still alive
|
||||
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive();
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive();
|
||||
assert!(bob_contract.is_some())
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const CODE_RESTORATION: &str = r#"
|
||||
(module
|
||||
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32)))
|
||||
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
|
||||
(import "env" "memory" (memory 1 1))
|
||||
|
||||
(func (export "call")
|
||||
(call $ext_dispatch_call
|
||||
(i32.const 200) ;; Pointer to the start of encoded call buffer
|
||||
(i32.const 115) ;; Length of the buffer
|
||||
)
|
||||
)
|
||||
(func (export "deploy")
|
||||
;; Data to restore
|
||||
(call $ext_set_storage
|
||||
(i32.const 0)
|
||||
(i32.const 1)
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
)
|
||||
|
||||
;; ACL
|
||||
(call $ext_set_storage
|
||||
(i32.const 100)
|
||||
(i32.const 1)
|
||||
(i32.const 0)
|
||||
(i32.const 4)
|
||||
)
|
||||
)
|
||||
|
||||
;; Data to restore
|
||||
(data (i32.const 0) "\28")
|
||||
|
||||
;; ACL
|
||||
(data (i32.const 100) "\01")
|
||||
|
||||
;; Call
|
||||
(data (i32.const 200) "\01\05\02\00\00\00\00\00\00\00\21\d6\b1\d5\9a\a6\03\8f\ca\d6\32\48\8e\90"
|
||||
"\26\89\3a\1b\bb\48\58\17\74\c7\71\b8\f2\43\20\69\7f\05\32\00\00\00\00\00\00\00\08\01\00\00"
|
||||
"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\01"
|
||||
"\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
|
||||
"\00"
|
||||
)
|
||||
)
|
||||
"#;
|
||||
const HASH_RESTORATION: [u8; 32] = hex!("b393bfa8de97b02f08ba1580b46100a80c9489a97c8d870691c9ff7236b29bc7");
|
||||
|
||||
#[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 acl_key = {
|
||||
let mut s = [0u8; 32];
|
||||
s[0] = 1;
|
||||
s
|
||||
};
|
||||
|
||||
// This test can fail due to the encoding changes. In case it becomes too annoying
|
||||
// let's rewrite so as we use this module controlled call or we serialize it in runtime.
|
||||
let encoded = hex::encode(Encode::encode(&Call::Contract(super::Call::restore_to(
|
||||
BOB,
|
||||
HASH_SET_RENT.into(),
|
||||
<Test as balances::Trait>::Balance::from(50u32),
|
||||
vec![acl_key, acl_key],
|
||||
))));
|
||||
|
||||
let literal = "0105020000000000000021d6b1d59aa6038fcad632488e9026893a1bbb48581774c771b8f243206\
|
||||
97f053200000000000000080100000000000000000000000000000000000000000000000000000000000000010\
|
||||
0000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
assert_eq!(encoded, literal);
|
||||
assert_eq!(115, hex::decode(literal).unwrap().len());
|
||||
|
||||
let restoration_wasm = wabt::wat2wasm(CODE_RESTORATION).unwrap();
|
||||
let set_rent_wasm = wabt::wat2wasm(CODE_SET_RENT).unwrap();
|
||||
|
||||
with_externalities(
|
||||
&mut ExtBuilder::default().existential_deposit(50).build(),
|
||||
|| {
|
||||
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, restoration_wasm));
|
||||
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, set_rent_wasm));
|
||||
|
||||
// If you ever need to update the wasm source this test will fail
|
||||
// and will show you the actual hash.
|
||||
assert_eq!(System::events(), vec![
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::balances(balances::RawEvent::NewAccount(1, 1_000_000)),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::CodeStored(HASH_RESTORATION.into())),
|
||||
topics: vec![],
|
||||
},
|
||||
EventRecord {
|
||||
phase: Phase::ApplyExtrinsic(0),
|
||||
event: MetaEvent::contract(RawEvent::CodeStored(HASH_SET_RENT.into())),
|
||||
topics: vec![],
|
||||
},
|
||||
]);
|
||||
|
||||
assert_ok!(Contract::create(
|
||||
Origin::signed(ALICE),
|
||||
30_000,
|
||||
100_000, HASH_SET_RENT.into(),
|
||||
<Test as balances::Trait>::Balance::from(0u32).encode()
|
||||
));
|
||||
|
||||
// Check creation
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||
assert_eq!(bob_contract.rent_allowance, 0);
|
||||
|
||||
if test_different_storage {
|
||||
assert_ok!(Contract::call(
|
||||
Origin::signed(ALICE),
|
||||
BOB, 0, 100_000,
|
||||
call::set_storage_4_byte())
|
||||
);
|
||||
}
|
||||
|
||||
// Advance 4 blocks
|
||||
System::initialize(&5, &[0u8; 32].into(), &[0u8; 32].into());
|
||||
|
||||
// Trigger rent through call
|
||||
assert_ok!(Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()));
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
|
||||
Balances::deposit_creating(&CHARLIE, 1_000_000);
|
||||
assert_ok!(Contract::create(
|
||||
Origin::signed(CHARLIE),
|
||||
30_000,
|
||||
100_000, HASH_RESTORATION.into(),
|
||||
<Test as balances::Trait>::Balance::from(0u32).encode()
|
||||
));
|
||||
|
||||
let django_trie_id = ContractInfoOf::<Test>::get(DJANGO).unwrap()
|
||||
.get_alive().unwrap().trie_id;
|
||||
|
||||
if !test_restore_to_with_dirty_storage {
|
||||
// Advance 1 blocks
|
||||
System::initialize(&6, &[0u8; 32].into(), &[0u8; 32].into());
|
||||
}
|
||||
|
||||
assert_ok!(Contract::call(
|
||||
Origin::signed(ALICE),
|
||||
DJANGO, 0, 100_000,
|
||||
vec![],
|
||||
));
|
||||
|
||||
if test_different_storage || test_restore_to_with_dirty_storage {
|
||||
assert!(ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||
let django_contract = ContractInfoOf::<Test>::get(DJANGO).unwrap()
|
||||
.get_alive().unwrap();
|
||||
assert_eq!(django_contract.storage_size, 16);
|
||||
assert_eq!(django_contract.trie_id, django_trie_id);
|
||||
assert_eq!(django_contract.deduct_block, System::block_number());
|
||||
} else {
|
||||
let bob_contract = ContractInfoOf::<Test>::get(BOB).unwrap()
|
||||
.get_alive().unwrap();
|
||||
assert_eq!(bob_contract.rent_allowance, 50);
|
||||
assert_eq!(bob_contract.storage_size, 12);
|
||||
assert_eq!(bob_contract.trie_id, django_trie_id);
|
||||
assert_eq!(bob_contract.deduct_block, System::block_number());
|
||||
assert!(ContractInfoOf::<Test>::get(DJANGO).is_none());
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -474,7 +474,7 @@ mod tests {
|
||||
header: Header {
|
||||
parent_hash: [69u8; 32].into(),
|
||||
number: 1,
|
||||
state_root: hex!("ac2840371d51ff2e036c8fc05af7313b7a030f735c38b2f03b94cbe87bfbb7c9").into(),
|
||||
state_root: hex!("5ba497e45e379d80a4524f9509d224e9c175d0fa30f3491481e7e44a6a758adf").into(),
|
||||
extrinsics_root: hex!("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").into(),
|
||||
digest: Digest { logs: vec![], },
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user