mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-23 22:25:42 +00:00
Contract accounting removal (#2230)
* first partial implementation * update rent allowance * fmt Co-Authored-By: thiolliere <gui.thiolliere@gmail.com> * remove comments * reward surcharge claims * remove rent allowance in param + code_hash changed * Fix bug * fix tests * fmt * impl getter setter rent allowance * fmt Co-Authored-By: thiolliere <gui.thiolliere@gmail.com> * comments * doc + be->le * doc * doc * fix improve fast return * renamings * rename + COMPLEXITY * COMPLEXITY * add test * etrinsic claim surcharge delay configurable * comment addressed * move and rewrite of pay_rent * remove child trie * fmt * use derive * arithmetic operation * fix * fix storage root + checked_mul + test * WIP: test * WIP * add tests and fix * fmt * typo and doc suggestions Co-Authored-By: thiolliere <gui.thiolliere@gmail.com> * WIP * address some comments divide tests + some docs * use br_table * remove unused function * Bump the runtime version * insert_with * Add some comments. * Refactor * Shuffle and fix comments * More comment fixes. * dues limited * Add comment * Handicap * Docs. * Apply suggestions from code review Co-Authored-By: pepyakin <s.pepyakin@gmail.com> * Coalesce block_passed in a block * Fix build * Paid → Ok * match → if * Imrpove handicap description
This commit is contained in:
committed by
Sergei Pepyakin
parent
f14580535e
commit
c7d9ca379d
@@ -159,7 +159,7 @@ impl<H: Hasher> Externalities<H> for TestExternalities<H> where H::Out: Ord + He
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn child_storage_root(&mut self, _storage_key: ChildStorageKey<H>) -> Vec<u8> {
|
fn child_storage_root(&mut self, _storage_key: ChildStorageKey<H>) -> Vec<u8> {
|
||||||
unimplemented!()
|
vec![]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn storage_changes_root(&mut self, parent: H::Out, parent_num: u64) -> Option<H::Out> {
|
fn storage_changes_root(&mut self, parent: H::Out, parent_num: u64) -> Option<H::Out> {
|
||||||
|
|||||||
@@ -152,6 +152,12 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
|
|||||||
burn: Permill::from_percent(50),
|
burn: Permill::from_percent(50),
|
||||||
}),
|
}),
|
||||||
contract: Some(ContractConfig {
|
contract: Some(ContractConfig {
|
||||||
|
signed_claim_handicap: 2,
|
||||||
|
rent_byte_price: 4,
|
||||||
|
rent_deposit_offset: 1000,
|
||||||
|
storage_size_offset: 8,
|
||||||
|
surcharge_reward: 150,
|
||||||
|
tombstone_deposit: 16,
|
||||||
transaction_base_fee: 1 * CENTS,
|
transaction_base_fee: 1 * CENTS,
|
||||||
transaction_byte_fee: 10 * MILLICENTS,
|
transaction_byte_fee: 10 * MILLICENTS,
|
||||||
transfer_fee: 1 * CENTS,
|
transfer_fee: 1 * CENTS,
|
||||||
@@ -239,6 +245,12 @@ pub fn testnet_genesis(
|
|||||||
const ENDOWMENT: u128 = 1 << 20;
|
const ENDOWMENT: u128 = 1 << 20;
|
||||||
|
|
||||||
let mut contract_config = ContractConfig {
|
let mut contract_config = ContractConfig {
|
||||||
|
signed_claim_handicap: 2,
|
||||||
|
rent_byte_price: 4,
|
||||||
|
rent_deposit_offset: 1000,
|
||||||
|
storage_size_offset: 8,
|
||||||
|
surcharge_reward: 150,
|
||||||
|
tombstone_deposit: 16,
|
||||||
transaction_base_fee: 1,
|
transaction_base_fee: 1,
|
||||||
transaction_byte_fee: 0,
|
transaction_byte_fee: 0,
|
||||||
transfer_fee: 0,
|
transfer_fee: 0,
|
||||||
|
|||||||
@@ -745,7 +745,13 @@ mod tests {
|
|||||||
|
|
||||||
runtime_io::with_externalities(&mut t, || {
|
runtime_io::with_externalities(&mut t, || {
|
||||||
// Verify that the contract constructor worked well and code of TRANSFER contract is actually deployed.
|
// Verify that the contract constructor worked well and code of TRANSFER contract is actually deployed.
|
||||||
assert_eq!(&contract::CodeHashOf::<Runtime>::get(addr).unwrap(), &transfer_ch);
|
assert_eq!(
|
||||||
|
&contract::ContractInfoOf::<Runtime>::get(addr)
|
||||||
|
.and_then(|c| c.get_alive())
|
||||||
|
.unwrap()
|
||||||
|
.code_hash,
|
||||||
|
&transfer_ch
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
|
|||||||
impl_name: create_runtime_str!("substrate-node"),
|
impl_name: create_runtime_str!("substrate-node"),
|
||||||
authoring_version: 10,
|
authoring_version: 10,
|
||||||
spec_version: 67,
|
spec_version: 67,
|
||||||
impl_version: 67,
|
impl_version: 68,
|
||||||
apis: RUNTIME_API_VERSIONS,
|
apis: RUNTIME_API_VERSIONS,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ execution contexts operate on the AccountDb. All changes are flushed into underl
|
|||||||
|
|
||||||
Today `AccountDb` is implemented as a cascade of overlays with the direct storage at the bottom. Each overlay is represented by a `Map`. On a commit from an overlay to an overlay, maps are merged. On commit from an overlay to the bottommost `AccountDb` all changes are flushed to the storage. On revert, the overlay is just discarded.
|
Today `AccountDb` is implemented as a cascade of overlays with the direct storage at the bottom. Each overlay is represented by a `Map`. On a commit from an overlay to an overlay, maps are merged. On commit from an overlay to the bottommost `AccountDb` all changes are flushed to the storage. On revert, the overlay is just discarded.
|
||||||
|
|
||||||
## get_storage, get_code, get_balance
|
## get_storage, get_code_hash, get_rent_allowance, get_balance, contract_exists
|
||||||
|
|
||||||
These functions check the local cache for a requested value and, if it is there, the value is returned. Otherwise, these functions will ask an underlying `AccountDb` for the value. This means that the number of lookups is proportional to the depth of the overlay cascade. If the value can't be found before reaching the bottommost `AccountDb`, then a DB read will be performed (in case `get_balance` the function `free_balance` will be invoked).
|
These functions check the local cache for a requested value and, if it is there, the value is returned. Otherwise, these functions will ask an underlying `AccountDb` for the value. This means that the number of lookups is proportional to the depth of the overlay cascade. If the value can't be found before reaching the bottommost `AccountDb`, then a DB read will be performed (in case `get_balance` the function `free_balance` will be invoked).
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ These functions return an owned value as its result, so memory usage depends on
|
|||||||
|
|
||||||
**complexity**: The memory complexity is proportional to the size of the value. The computational complexity is proportional to the depth of the overlay cascade and the size of the value; the cost is dominated by the DB read though.
|
**complexity**: The memory complexity is proportional to the size of the value. The computational complexity is proportional to the depth of the overlay cascade and the size of the value; the cost is dominated by the DB read though.
|
||||||
|
|
||||||
## set_storage, set_code, set_balance
|
## set_storage, set_balance, set_rent_allowance
|
||||||
|
|
||||||
These functions only modify the local `Map`.
|
These functions only modify the local `Map`.
|
||||||
|
|
||||||
@@ -105,6 +105,12 @@ While these functions only modify the local `Map`, if changes made by them are c
|
|||||||
|
|
||||||
**complexity**: Each lookup has a logarithmical computing time to the number of already inserted entries. No additional memory is required.
|
**complexity**: Each lookup has a logarithmical computing time to the number of already inserted entries. No additional memory is required.
|
||||||
|
|
||||||
|
## create_contract
|
||||||
|
|
||||||
|
Calls `contract_exists` and if it doesn't exist, do not modify the local `Map` similarly to `set_rent_allowance`.
|
||||||
|
|
||||||
|
**complexity**: The computational complexity is proportional to the depth of the overlay cascade and the size of the value; the cost is dominated by the DB read though. No additional memory is required.
|
||||||
|
|
||||||
## commit
|
## commit
|
||||||
|
|
||||||
In this function, all cached values will be inserted into the underlying `AccountDb` or into the storage.
|
In this function, all cached values will be inserted into the underlying `AccountDb` or into the storage.
|
||||||
@@ -327,3 +333,25 @@ This function copies slice of data from the scratch buffer to the sandbox memory
|
|||||||
1. Storing a specified slice of the scratch buffer into the sandbox memory (see sandboxing memory set)
|
1. Storing a specified slice of the scratch buffer into the sandbox memory (see sandboxing memory set)
|
||||||
|
|
||||||
**complexity**: The computing complexity of this function is proportional to the length of the slice. No additional memory is required.
|
**complexity**: The computing complexity of this function is proportional to the length of the slice. No additional memory is required.
|
||||||
|
|
||||||
|
## ext_set_rent_allowance
|
||||||
|
|
||||||
|
This function receives the following argument:
|
||||||
|
|
||||||
|
- `value` buffer of a marshaled `Balance`,
|
||||||
|
|
||||||
|
It consists of the following steps:
|
||||||
|
|
||||||
|
1. Loading `value` buffer from the sandbox memory and then decoding it.
|
||||||
|
2. Invoking `set_rent_allowance` AccountDB function.
|
||||||
|
|
||||||
|
**complexity**: Complexity is proportional to the size of the `value`. This function induces a DB write of size proportional to the `value` size (if flushed to the storage), so should be priced accordingly.
|
||||||
|
|
||||||
|
## ext_rent_allowance
|
||||||
|
|
||||||
|
It consists of the following steps:
|
||||||
|
|
||||||
|
1. Invoking `get_rent_allowance` AccountDB function.
|
||||||
|
2. Serializing the rent allowance of the current contract into the scratch buffer.
|
||||||
|
|
||||||
|
**complexity**: Assuming that the rent allowance is of constant size, this function has constant complexity.
|
||||||
|
|||||||
@@ -16,21 +16,28 @@
|
|||||||
|
|
||||||
//! Auxilliaries to help with managing partial changes to accounts state.
|
//! Auxilliaries to help with managing partial changes to accounts state.
|
||||||
|
|
||||||
use super::{CodeHash, CodeHashOf, Trait, TrieId, AccountInfoOf, BalanceOf, AccountInfo, TrieIdGenerator};
|
use super::{
|
||||||
|
AliveContractInfo, BalanceOf, CodeHash, ContractInfo, ContractInfoOf, Module, Trait, TrieId,
|
||||||
|
TrieIdGenerator,
|
||||||
|
};
|
||||||
use crate::exec::StorageKey;
|
use crate::exec::StorageKey;
|
||||||
use system;
|
|
||||||
use rstd::cell::RefCell;
|
use rstd::cell::RefCell;
|
||||||
use rstd::collections::btree_map::{BTreeMap, Entry};
|
use rstd::collections::btree_map::{BTreeMap, Entry};
|
||||||
use rstd::prelude::*;
|
use rstd::prelude::*;
|
||||||
use runtime_io::blake2_256;
|
use runtime_io::blake2_256;
|
||||||
use runtime_primitives::traits::Zero;
|
use runtime_primitives::traits::Zero;
|
||||||
use srml_support::{StorageMap, traits::{UpdateBalanceOutcome,
|
use srml_support::traits::{Currency, Imbalance, SignedImbalance, UpdateBalanceOutcome};
|
||||||
SignedImbalance, Currency, Imbalance}, storage::child};
|
use srml_support::{storage::child, StorageMap};
|
||||||
|
use system;
|
||||||
|
|
||||||
|
// Note: we don't provide Option<Contract> because we can't create
|
||||||
|
// the trie_id in the overlay, thus we provide an overlay on the fields
|
||||||
|
// specifically.
|
||||||
pub struct ChangeEntry<T: Trait> {
|
pub struct ChangeEntry<T: Trait> {
|
||||||
balance: Option<BalanceOf<T>>,
|
balance: Option<BalanceOf<T>>,
|
||||||
/// In the case the outer option is None, the code_hash remains untouched, while providing `Some(None)` signifies a removing of the code in question
|
/// If None, the code_hash remains untouched.
|
||||||
code: Option<Option<CodeHash<T>>>,
|
code_hash: Option<CodeHash<T>>,
|
||||||
|
rent_allowance: Option<BalanceOf<T>>,
|
||||||
storage: BTreeMap<StorageKey, Option<Vec<u8>>>,
|
storage: BTreeMap<StorageKey, Option<Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,8 +45,9 @@ pub struct ChangeEntry<T: Trait> {
|
|||||||
impl<T: Trait> Default for ChangeEntry<T> {
|
impl<T: Trait> Default for ChangeEntry<T> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ChangeEntry {
|
ChangeEntry {
|
||||||
|
rent_allowance: Default::default(),
|
||||||
balance: Default::default(),
|
balance: Default::default(),
|
||||||
code: Default::default(),
|
code_hash: Default::default(),
|
||||||
storage: Default::default(),
|
storage: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,10 +59,15 @@ pub trait AccountDb<T: Trait> {
|
|||||||
/// Account is used when overlayed otherwise trie_id must be provided.
|
/// Account is used when overlayed otherwise trie_id must be provided.
|
||||||
/// This is for performance reason.
|
/// This is for performance reason.
|
||||||
///
|
///
|
||||||
/// Trie id can be None iff account doesn't have an associated trie id in <AccountInfoOf<T>>.
|
/// Trie id is None iff account doesn't have an associated trie id in <ContractInfoOf<T>>.
|
||||||
/// Because DirectAccountDb bypass the lookup for this association.
|
/// Because DirectAccountDb bypass the lookup for this association.
|
||||||
fn get_storage(&self, account: &T::AccountId, trie_id: Option<&TrieId>, location: &StorageKey) -> Option<Vec<u8>>;
|
fn get_storage(&self, account: &T::AccountId, trie_id: Option<&TrieId>, location: &StorageKey) -> Option<Vec<u8>>;
|
||||||
fn get_code(&self, account: &T::AccountId) -> Option<CodeHash<T>>;
|
/// If account has an alive contract then return the code hash associated.
|
||||||
|
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>>;
|
||||||
|
/// If account has an alive contract then return the rent allowance associated.
|
||||||
|
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>>;
|
||||||
|
/// Returns false iff account has no alive contract nor tombstone.
|
||||||
|
fn contract_exists(&self, account: &T::AccountId) -> bool;
|
||||||
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T>;
|
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T>;
|
||||||
|
|
||||||
fn commit(&mut self, change_set: ChangeSet<T>);
|
fn commit(&mut self, change_set: ChangeSet<T>);
|
||||||
@@ -65,8 +78,14 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
|
|||||||
fn get_storage(&self, _account: &T::AccountId, trie_id: Option<&TrieId>, location: &StorageKey) -> Option<Vec<u8>> {
|
fn get_storage(&self, _account: &T::AccountId, trie_id: Option<&TrieId>, location: &StorageKey) -> Option<Vec<u8>> {
|
||||||
trie_id.and_then(|id| child::get_raw(id, &blake2_256(location)))
|
trie_id.and_then(|id| child::get_raw(id, &blake2_256(location)))
|
||||||
}
|
}
|
||||||
fn get_code(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
|
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
|
||||||
<CodeHashOf<T>>::get(account)
|
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.code_hash))
|
||||||
|
}
|
||||||
|
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>> {
|
||||||
|
<ContractInfoOf<T>>::get(account).and_then(|i| i.as_alive().map(|i| i.rent_allowance))
|
||||||
|
}
|
||||||
|
fn contract_exists(&self, account: &T::AccountId) -> bool {
|
||||||
|
<ContractInfoOf<T>>::exists(account)
|
||||||
}
|
}
|
||||||
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
|
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
|
||||||
T::Currency::free_balance(account)
|
T::Currency::free_balance(account)
|
||||||
@@ -84,42 +103,58 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if changed.code.is_some() || !changed.storage.is_empty() {
|
|
||||||
let mut info = if !<AccountInfoOf<T>>::exists(&address) {
|
if changed.code_hash.is_some()
|
||||||
let info = AccountInfo {
|
|| changed.rent_allowance.is_some()
|
||||||
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
|
|| !changed.storage.is_empty()
|
||||||
storage_size: 0,
|
{
|
||||||
};
|
let old_info = match <ContractInfoOf<T>>::get(&address) {
|
||||||
<AccountInfoOf<T>>::insert(&address, &info);
|
Some(ContractInfo::Alive(alive)) => Some(alive),
|
||||||
info
|
None => None,
|
||||||
} else {
|
// Cannot commit changes to tombstone contract
|
||||||
<AccountInfoOf<T>>::get(&address).unwrap()
|
Some(ContractInfo::Tombstone(_)) => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(code) = changed.code {
|
let mut new_info = if let Some(info) = old_info.clone() {
|
||||||
if let Some(code) = code {
|
info
|
||||||
<CodeHashOf<T>>::insert(&address, code);
|
} else if let Some(code_hash) = changed.code_hash {
|
||||||
} else {
|
AliveContractInfo::<T> {
|
||||||
<CodeHashOf<T>>::remove(&address);
|
code_hash,
|
||||||
|
storage_size: <Module<T>>::storage_size_offset(),
|
||||||
|
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
|
||||||
|
deduct_block: <system::Module<T>>::block_number(),
|
||||||
|
rent_allowance: <BalanceOf<T>>::zero(),
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// No contract exist and no code_hash provided
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(rent_allowance) = changed.rent_allowance {
|
||||||
|
new_info.rent_allowance = rent_allowance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(code_hash) = changed.code_hash {
|
||||||
|
new_info.code_hash = code_hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut new_storage_size = info.storage_size;
|
|
||||||
for (k, v) in changed.storage.into_iter() {
|
for (k, v) in changed.storage.into_iter() {
|
||||||
if let Some(value) = child::get_raw(&info.trie_id[..], &blake2_256(&k)) {
|
if let Some(value) = child::get_raw(&new_info.trie_id[..], &blake2_256(&k)) {
|
||||||
new_storage_size -= value.len() as u64;
|
new_info.storage_size -= value.len() as u64;
|
||||||
}
|
}
|
||||||
if let Some(value) = v {
|
if let Some(value) = v {
|
||||||
new_storage_size += value.len() as u64;
|
new_info.storage_size += value.len() as u64;
|
||||||
child::put_raw(&info.trie_id[..], &blake2_256(&k), &value[..]);
|
child::put_raw(&new_info.trie_id[..], &blake2_256(&k), &value[..]);
|
||||||
} else {
|
} else {
|
||||||
child::kill(&info.trie_id[..], &blake2_256(&k));
|
child::kill(&new_info.trie_id[..], &blake2_256(&k));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_storage_size != info.storage_size {
|
if old_info
|
||||||
info.storage_size = new_storage_size;
|
.map(|old_info| old_info != new_info)
|
||||||
<AccountInfoOf<T>>::insert(&address, info);
|
.unwrap_or(true)
|
||||||
|
{
|
||||||
|
<ContractInfoOf<T>>::insert(&address, ContractInfo::Alive(new_info));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,9 +175,7 @@ pub struct OverlayAccountDb<'a, T: Trait + 'a> {
|
|||||||
underlying: &'a AccountDb<T>,
|
underlying: &'a AccountDb<T>,
|
||||||
}
|
}
|
||||||
impl<'a, T: Trait> OverlayAccountDb<'a, T> {
|
impl<'a, T: Trait> OverlayAccountDb<'a, T> {
|
||||||
pub fn new(
|
pub fn new(underlying: &'a AccountDb<T>) -> OverlayAccountDb<'a, T> {
|
||||||
underlying: &'a AccountDb<T>,
|
|
||||||
) -> OverlayAccountDb<'a, T> {
|
|
||||||
OverlayAccountDb {
|
OverlayAccountDb {
|
||||||
local: RefCell::new(ChangeSet::new()),
|
local: RefCell::new(ChangeSet::new()),
|
||||||
underlying,
|
underlying,
|
||||||
@@ -166,12 +199,31 @@ impl<'a, T: Trait> OverlayAccountDb<'a, T> {
|
|||||||
.insert(location, value);
|
.insert(location, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_code(&mut self, account: &T::AccountId, code: Option<CodeHash<T>>) {
|
/// Return an error if contract already exists (either if it is alive or tombstone)
|
||||||
|
pub fn create_contract(
|
||||||
|
&mut self,
|
||||||
|
account: &T::AccountId,
|
||||||
|
code_hash: CodeHash<T>,
|
||||||
|
) -> Result<(), &'static str> {
|
||||||
|
if self.contract_exists(account) {
|
||||||
|
return Err("Alive contract or tombstone already exists");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut local = self.local.borrow_mut();
|
||||||
|
let contract = local.entry(account.clone()).or_insert_with(|| Default::default());
|
||||||
|
|
||||||
|
contract.code_hash = Some(code_hash);
|
||||||
|
contract.rent_allowance = Some(<BalanceOf<T>>::zero());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
/// Assume contract exists
|
||||||
|
pub fn set_rent_allowance(&mut self, account: &T::AccountId, rent_allowance: BalanceOf<T>) {
|
||||||
self.local
|
self.local
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.entry(account.clone())
|
.entry(account.clone())
|
||||||
.or_insert(Default::default())
|
.or_insert(Default::default())
|
||||||
.code = Some(code);
|
.rent_allowance = Some(rent_allowance);
|
||||||
}
|
}
|
||||||
pub fn set_balance(&mut self, account: &T::AccountId, balance: BalanceOf<T>) {
|
pub fn set_balance(&mut self, account: &T::AccountId, balance: BalanceOf<T>) {
|
||||||
self.local
|
self.local
|
||||||
@@ -191,12 +243,26 @@ impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
|
|||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_else(|| self.underlying.get_storage(account, trie_id, location))
|
.unwrap_or_else(|| self.underlying.get_storage(account, trie_id, location))
|
||||||
}
|
}
|
||||||
fn get_code(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
|
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
|
||||||
self.local
|
self.local
|
||||||
.borrow()
|
.borrow()
|
||||||
.get(account)
|
.get(account)
|
||||||
.and_then(|a| a.code.clone())
|
.and_then(|changes| changes.code_hash)
|
||||||
.unwrap_or_else(|| self.underlying.get_code(account))
|
.or_else(|| self.underlying.get_code_hash(account))
|
||||||
|
}
|
||||||
|
fn get_rent_allowance(&self, account: &T::AccountId) -> Option<BalanceOf<T>> {
|
||||||
|
self.local
|
||||||
|
.borrow()
|
||||||
|
.get(account)
|
||||||
|
.and_then(|changes| changes.rent_allowance)
|
||||||
|
.or_else(|| self.underlying.get_rent_allowance(account))
|
||||||
|
}
|
||||||
|
fn contract_exists(&self, account: &T::AccountId) -> bool {
|
||||||
|
self.local
|
||||||
|
.borrow()
|
||||||
|
.get(account)
|
||||||
|
.map(|a| a.code_hash.is_some())
|
||||||
|
.unwrap_or_else(|| self.underlying.contract_exists(account))
|
||||||
}
|
}
|
||||||
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
|
fn get_balance(&self, account: &T::AccountId) -> BalanceOf<T> {
|
||||||
self.local
|
self.local
|
||||||
@@ -212,12 +278,9 @@ impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
|
|||||||
match local.entry(address) {
|
match local.entry(address) {
|
||||||
Entry::Occupied(e) => {
|
Entry::Occupied(e) => {
|
||||||
let mut value = e.into_mut();
|
let mut value = e.into_mut();
|
||||||
if changed.balance.is_some() {
|
value.balance = changed.balance.or(value.balance);
|
||||||
value.balance = changed.balance;
|
value.code_hash = changed.code_hash.or(value.code_hash);
|
||||||
}
|
value.rent_allowance = changed.rent_allowance.or(value.rent_allowance);
|
||||||
if changed.code.is_some() {
|
|
||||||
value.code = changed.code;
|
|
||||||
}
|
|
||||||
value.storage.extend(changed.storage.into_iter());
|
value.storage.extend(changed.storage.into_iter());
|
||||||
}
|
}
|
||||||
Entry::Vacant(e) => {
|
Entry::Vacant(e) => {
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait, TrieId, BalanceOf, AccountInfoOf};
|
use super::{CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait,
|
||||||
|
TrieId, BalanceOf, ContractInfoOf};
|
||||||
use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb};
|
use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb};
|
||||||
use crate::gas::{GasMeter, Token, approx_gas_for_balance};
|
use crate::gas::{GasMeter, Token, approx_gas_for_balance};
|
||||||
|
|
||||||
@@ -107,6 +108,12 @@ pub trait Ext {
|
|||||||
|
|
||||||
/// Deposit an event.
|
/// Deposit an event.
|
||||||
fn deposit_event(&mut self, data: Vec<u8>);
|
fn deposit_event(&mut self, data: Vec<u8>);
|
||||||
|
|
||||||
|
/// Set rent allowance of the contract
|
||||||
|
fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<Self::T>);
|
||||||
|
|
||||||
|
/// Rent allowance of the contract
|
||||||
|
fn rent_allowance(&self) -> BalanceOf<Self::T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loader is a companion of the `Vm` trait. It loads an appropriate abstract
|
/// Loader is a companion of the `Vm` trait. It loads an appropriate abstract
|
||||||
@@ -249,7 +256,8 @@ where
|
|||||||
/// The specified `origin` address will be used as `sender` for
|
/// The specified `origin` address will be used as `sender` for
|
||||||
pub fn top_level(origin: T::AccountId, cfg: &'a Config<T>, vm: &'a V, loader: &'a L) -> Self {
|
pub fn top_level(origin: T::AccountId, cfg: &'a Config<T>, vm: &'a V, loader: &'a L) -> Self {
|
||||||
ExecutionContext {
|
ExecutionContext {
|
||||||
self_trie_id: <AccountInfoOf<T>>::get(&origin).map(|s| s.trie_id),
|
self_trie_id: <ContractInfoOf<T>>::get(&origin)
|
||||||
|
.and_then(|i| i.as_alive().map(|i| i.trie_id.clone())),
|
||||||
self_account: origin,
|
self_account: origin,
|
||||||
overlay: OverlayAccountDb::<T>::new(&DirectAccountDb),
|
overlay: OverlayAccountDb::<T>::new(&DirectAccountDb),
|
||||||
depth: 0,
|
depth: 0,
|
||||||
@@ -263,7 +271,8 @@ where
|
|||||||
|
|
||||||
fn nested(&self, overlay: OverlayAccountDb<'a, T>, dest: T::AccountId) -> Self {
|
fn nested(&self, overlay: OverlayAccountDb<'a, T>, dest: T::AccountId) -> Self {
|
||||||
ExecutionContext {
|
ExecutionContext {
|
||||||
self_trie_id: <AccountInfoOf<T>>::get(&dest).map(|s| s.trie_id),
|
self_trie_id: <ContractInfoOf<T>>::get(&dest)
|
||||||
|
.and_then(|i| i.as_alive().map(|i| i.trie_id.clone())),
|
||||||
self_account: dest,
|
self_account: dest,
|
||||||
overlay,
|
overlay,
|
||||||
depth: self.depth + 1,
|
depth: self.depth + 1,
|
||||||
@@ -295,7 +304,11 @@ where
|
|||||||
return Err("not enough gas to pay base call fee");
|
return Err("not enough gas to pay base call fee");
|
||||||
}
|
}
|
||||||
|
|
||||||
let dest_code_hash = self.overlay.get_code(&dest);
|
// Assumption: pay_rent doesn't collide with overlay because
|
||||||
|
// pay_rent will be done on first call and dest contract and balance
|
||||||
|
// cannot be changed before the first call
|
||||||
|
crate::rent::pay_rent::<T>(&dest);
|
||||||
|
|
||||||
let mut output_data = Vec::new();
|
let mut output_data = Vec::new();
|
||||||
|
|
||||||
let (change_set, events, calls) = {
|
let (change_set, events, calls) = {
|
||||||
@@ -315,7 +328,7 @@ where
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(dest_code_hash) = dest_code_hash {
|
if let Some(dest_code_hash) = self.overlay.get_code_hash(&dest) {
|
||||||
let executable = self.loader.load_main(&dest_code_hash)?;
|
let executable = self.loader.load_main(&dest_code_hash)?;
|
||||||
output_data = self
|
output_data = self
|
||||||
.vm
|
.vm
|
||||||
@@ -369,15 +382,11 @@ where
|
|||||||
&self.self_account,
|
&self.self_account,
|
||||||
);
|
);
|
||||||
|
|
||||||
if self.overlay.get_code(&dest).is_some() {
|
|
||||||
// It should be enough to check only the code.
|
|
||||||
return Err("contract already exists");
|
|
||||||
}
|
|
||||||
|
|
||||||
let (change_set, events, calls) = {
|
let (change_set, events, calls) = {
|
||||||
let mut overlay = OverlayAccountDb::new(&self.overlay);
|
let mut overlay = OverlayAccountDb::new(&self.overlay);
|
||||||
|
|
||||||
overlay.set_code(&dest, Some(code_hash.clone()));
|
overlay.create_contract(&dest, code_hash.clone())?;
|
||||||
|
|
||||||
let mut nested = self.nested(overlay, dest.clone());
|
let mut nested = self.nested(overlay, dest.clone());
|
||||||
|
|
||||||
// Send funds unconditionally here. If the `endowment` is below existential_deposit
|
// Send funds unconditionally here. If the `endowment` is below existential_deposit
|
||||||
@@ -625,6 +634,15 @@ where
|
|||||||
fn deposit_event(&mut self, data: Vec<u8>) {
|
fn deposit_event(&mut self, data: Vec<u8>) {
|
||||||
self.ctx.events.push(RawEvent::Contract(self.ctx.self_account.clone(), data));
|
self.ctx.events.push(RawEvent::Contract(self.ctx.self_account.clone(), data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<T>) {
|
||||||
|
self.ctx.overlay.set_rent_allowance(&self.ctx.self_account, rent_allowance)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rent_allowance(&self) -> BalanceOf<T> {
|
||||||
|
self.ctx.overlay.get_rent_allowance(&self.ctx.self_account)
|
||||||
|
.unwrap_or(<BalanceOf<T>>::zero()) // Must never be triggered actually
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// These tests exercise the executive layer.
|
/// These tests exercise the executive layer.
|
||||||
@@ -763,7 +781,7 @@ mod tests {
|
|||||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
ctx.overlay.set_code(&BOB, Some(exec_ch));
|
ctx.overlay.create_contract(&BOB, exec_ch).unwrap();
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
ctx.call(BOB, value, &mut gas_meter, &data, EmptyOutputBuf::new()),
|
ctx.call(BOB, value, &mut gas_meter, &data, EmptyOutputBuf::new()),
|
||||||
@@ -998,7 +1016,7 @@ mod tests {
|
|||||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
|
||||||
ctx.overlay.set_code(&BOB, Some(return_ch));
|
ctx.overlay.create_contract(&BOB, return_ch).unwrap();
|
||||||
|
|
||||||
let result = ctx.call(
|
let result = ctx.call(
|
||||||
dest,
|
dest,
|
||||||
@@ -1026,7 +1044,7 @@ mod tests {
|
|||||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
ctx.overlay.set_code(&BOB, Some(input_data_ch));
|
ctx.overlay.create_contract(&BOB, input_data_ch).unwrap();
|
||||||
|
|
||||||
let result = ctx.call(
|
let result = ctx.call(
|
||||||
BOB,
|
BOB,
|
||||||
@@ -1038,7 +1056,7 @@ mod tests {
|
|||||||
assert_matches!(result, Ok(_));
|
assert_matches!(result, Ok(_));
|
||||||
});
|
});
|
||||||
|
|
||||||
// This one tests passing the input data into a contract via call.
|
// This one tests passing the input data into a contract via instantiate.
|
||||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
@@ -1085,7 +1103,7 @@ mod tests {
|
|||||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
ctx.overlay.set_code(&BOB, Some(recurse_ch));
|
ctx.overlay.create_contract(&BOB, recurse_ch).unwrap();
|
||||||
|
|
||||||
let result = ctx.call(
|
let result = ctx.call(
|
||||||
BOB,
|
BOB,
|
||||||
@@ -1132,8 +1150,8 @@ mod tests {
|
|||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
|
|
||||||
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
|
||||||
ctx.overlay.set_code(&dest, Some(bob_ch));
|
ctx.overlay.create_contract(&dest, bob_ch).unwrap();
|
||||||
ctx.overlay.set_code(&CHARLIE, Some(charlie_ch));
|
ctx.overlay.create_contract(&CHARLIE, charlie_ch).unwrap();
|
||||||
|
|
||||||
let result = ctx.call(
|
let result = ctx.call(
|
||||||
dest,
|
dest,
|
||||||
@@ -1175,8 +1193,8 @@ mod tests {
|
|||||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
ctx.overlay.set_code(&BOB, Some(bob_ch));
|
ctx.overlay.create_contract(&BOB, bob_ch).unwrap();
|
||||||
ctx.overlay.set_code(&CHARLIE, Some(charlie_ch));
|
ctx.overlay.create_contract(&CHARLIE, charlie_ch).unwrap();
|
||||||
|
|
||||||
let result = ctx.call(
|
let result = ctx.call(
|
||||||
BOB,
|
BOB,
|
||||||
@@ -1242,7 +1260,7 @@ mod tests {
|
|||||||
|
|
||||||
// Check that the newly created account has the expected code hash and
|
// Check that the newly created account has the expected code hash and
|
||||||
// there are instantiation event.
|
// there are instantiation event.
|
||||||
assert_eq!(ctx.overlay.get_code(&created_contract_address).unwrap(), dummy_ch);
|
assert_eq!(ctx.overlay.get_code_hash(&created_contract_address).unwrap(), dummy_ch);
|
||||||
assert_eq!(&ctx.events, &[
|
assert_eq!(&ctx.events, &[
|
||||||
RawEvent::Transfer(ALICE, created_contract_address, 100),
|
RawEvent::Transfer(ALICE, created_contract_address, 100),
|
||||||
RawEvent::Instantiated(ALICE, created_contract_address),
|
RawEvent::Instantiated(ALICE, created_contract_address),
|
||||||
@@ -1283,7 +1301,7 @@ mod tests {
|
|||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
ctx.overlay.set_balance(&ALICE, 1000);
|
ctx.overlay.set_balance(&ALICE, 1000);
|
||||||
ctx.overlay.set_code(&BOB, Some(creator_ch));
|
ctx.overlay.create_contract(&BOB, creator_ch).unwrap();
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
ctx.call(BOB, 20, &mut GasMeter::<Test>::with_limit(1000, 1), &[], EmptyOutputBuf::new()),
|
ctx.call(BOB, 20, &mut GasMeter::<Test>::with_limit(1000, 1), &[], EmptyOutputBuf::new()),
|
||||||
@@ -1294,7 +1312,7 @@ mod tests {
|
|||||||
|
|
||||||
// Check that the newly created account has the expected code hash and
|
// Check that the newly created account has the expected code hash and
|
||||||
// there are instantiation event.
|
// there are instantiation event.
|
||||||
assert_eq!(ctx.overlay.get_code(&created_contract_address).unwrap(), dummy_ch);
|
assert_eq!(ctx.overlay.get_code_hash(&created_contract_address).unwrap(), dummy_ch);
|
||||||
assert_eq!(&ctx.events, &[
|
assert_eq!(&ctx.events, &[
|
||||||
RawEvent::Transfer(ALICE, BOB, 20),
|
RawEvent::Transfer(ALICE, BOB, 20),
|
||||||
RawEvent::Transfer(BOB, created_contract_address, 15),
|
RawEvent::Transfer(BOB, created_contract_address, 15),
|
||||||
@@ -1334,7 +1352,7 @@ mod tests {
|
|||||||
let cfg = Config::preload();
|
let cfg = Config::preload();
|
||||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
ctx.overlay.set_balance(&ALICE, 1000);
|
ctx.overlay.set_balance(&ALICE, 1000);
|
||||||
ctx.overlay.set_code(&BOB, Some(creator_ch));
|
ctx.overlay.create_contract(&BOB, creator_ch).unwrap();
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
ctx.call(BOB, 20, &mut GasMeter::<Test>::with_limit(1000, 1), &[], EmptyOutputBuf::new()),
|
ctx.call(BOB, 20, &mut GasMeter::<Test>::with_limit(1000, 1), &[], EmptyOutputBuf::new()),
|
||||||
@@ -1349,4 +1367,29 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rent_allowance() {
|
||||||
|
let vm = MockVm::new();
|
||||||
|
let mut loader = MockLoader::empty();
|
||||||
|
let rent_allowance_ch = loader.insert(|ctx| {
|
||||||
|
assert_eq!(ctx.ext.rent_allowance(), 0);
|
||||||
|
ctx.ext.set_rent_allowance(10);
|
||||||
|
assert_eq!(ctx.ext.rent_allowance(), 10);
|
||||||
|
VmExecResult::Ok
|
||||||
|
});
|
||||||
|
|
||||||
|
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||||
|
let cfg = Config::preload();
|
||||||
|
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||||
|
|
||||||
|
let result = ctx.instantiate(
|
||||||
|
0,
|
||||||
|
&mut GasMeter::<Test>::with_limit(10000, 1),
|
||||||
|
&rent_allowance_ch,
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
assert_matches!(result, Ok(_));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ mod gas;
|
|||||||
mod account_db;
|
mod account_db;
|
||||||
mod exec;
|
mod exec;
|
||||||
mod wasm;
|
mod wasm;
|
||||||
|
mod rent;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
@@ -96,7 +97,7 @@ use substrate_primitives::crypto::UncheckedFrom;
|
|||||||
use rstd::prelude::*;
|
use rstd::prelude::*;
|
||||||
use rstd::marker::PhantomData;
|
use rstd::marker::PhantomData;
|
||||||
use parity_codec::{Codec, Encode, Decode};
|
use parity_codec::{Codec, Encode, Decode};
|
||||||
use runtime_primitives::traits::{Hash, As, SimpleArithmetic,Bounded, StaticLookup};
|
use runtime_primitives::traits::{Hash, As, SimpleArithmetic, Bounded, StaticLookup, Zero};
|
||||||
use srml_support::dispatch::{Result, Dispatchable};
|
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 srml_support::traits::{OnFreeBalanceZero, OnUnbalanced, Currency};
|
||||||
@@ -117,14 +118,94 @@ pub trait ComputeDispatchFee<Call, Balance> {
|
|||||||
fn compute_dispatch_fee(call: &Call) -> Balance;
|
fn compute_dispatch_fee(call: &Call) -> Balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Encode,Decode,Clone,Debug)]
|
/// Information for managing an acocunt and its sub trie abstraction.
|
||||||
|
/// This is the required info to cache for an account
|
||||||
|
#[derive(Encode, Decode)]
|
||||||
|
pub enum ContractInfo<T: Trait> {
|
||||||
|
Alive(AliveContractInfo<T>),
|
||||||
|
Tombstone(TombstoneContractInfo<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Trait> ContractInfo<T> {
|
||||||
|
/// If contract is alive then return some alive info
|
||||||
|
pub fn get_alive(self) -> Option<AliveContractInfo<T>> {
|
||||||
|
if let ContractInfo::Alive(alive) = self {
|
||||||
|
Some(alive)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// If contract is alive then return some reference to alive info
|
||||||
|
pub fn as_alive(&self) -> Option<&AliveContractInfo<T>> {
|
||||||
|
if let ContractInfo::Alive(ref alive) = self {
|
||||||
|
Some(alive)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// If contract is alive then return some mutable reference to alive info
|
||||||
|
pub fn as_alive_mut(&mut self) -> Option<&mut AliveContractInfo<T>> {
|
||||||
|
if let ContractInfo::Alive(ref mut alive) = self {
|
||||||
|
Some(alive)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If contract is tombstone then return some alive info
|
||||||
|
pub fn get_tombstone(self) -> Option<TombstoneContractInfo<T>> {
|
||||||
|
if let ContractInfo::Tombstone(tombstone) = self {
|
||||||
|
Some(tombstone)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// If contract is tombstone then return some reference to tombstone info
|
||||||
|
pub fn as_tombstone(&self) -> Option<&TombstoneContractInfo<T>> {
|
||||||
|
if let ContractInfo::Tombstone(ref tombstone) = self {
|
||||||
|
Some(tombstone)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// If contract is tombstone then return some mutable reference to tombstone info
|
||||||
|
pub fn as_tombstone_mut(&mut self) -> Option<&mut TombstoneContractInfo<T>> {
|
||||||
|
if let ContractInfo::Tombstone(ref mut tombstone) = self {
|
||||||
|
Some(tombstone)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AliveContractInfo<T> = RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as system::Trait>::BlockNumber>;
|
||||||
|
|
||||||
/// Information for managing an account and its sub trie abstraction.
|
/// Information for managing an account and its sub trie abstraction.
|
||||||
/// This is the required info to cache for an account.
|
/// This is the required info to cache for an account.
|
||||||
pub struct AccountInfo {
|
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||||
/// Unique ID for the subtree encoded as a byte.
|
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
|
||||||
|
pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
|
||||||
|
/// Unique ID for the subtree encoded as a bytes vector.
|
||||||
pub trie_id: TrieId,
|
pub trie_id: TrieId,
|
||||||
/// The size of stored value in octet.
|
/// The size of stored value in octet.
|
||||||
pub storage_size: u64,
|
pub storage_size: u64,
|
||||||
|
/// The code associated with a given account.
|
||||||
|
pub code_hash: CodeHash,
|
||||||
|
pub rent_allowance: Balance,
|
||||||
|
pub deduct_block: BlockNumber,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Encode, Decode)]
|
||||||
|
pub struct TombstoneContractInfo<T: Trait>(T::Hash);
|
||||||
|
|
||||||
|
impl<T: Trait> TombstoneContractInfo<T> {
|
||||||
|
fn new(storage_root: Vec<u8>, storage_size: u64, code_hash: CodeHash<T>) -> 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[..]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a trie id (trie id must be unique and collision resistant depending upon its context).
|
/// Get a trie id (trie id must be unique and collision resistant depending upon its context).
|
||||||
@@ -384,6 +465,39 @@ decl_module! {
|
|||||||
result.map(|_| ())
|
result.map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allows block producers to claim a small reward for evicting a contract. If a block producer
|
||||||
|
/// fails to do so, a regular users will be allowed to claim the reward.
|
||||||
|
///
|
||||||
|
/// If contract is not evicted as a result of this call, no actions are taken and
|
||||||
|
/// the sender is not eligible for the reward.
|
||||||
|
fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option<T::AccountId>) {
|
||||||
|
let origin = origin.into();
|
||||||
|
let (signed, rewarded) = match origin {
|
||||||
|
Some(system::RawOrigin::Signed(ref account)) if aux_sender.is_none() => {
|
||||||
|
(true, account)
|
||||||
|
},
|
||||||
|
Some(system::RawOrigin::Inherent) if aux_sender.is_some() => {
|
||||||
|
(false, aux_sender.as_ref().expect("checked above"))
|
||||||
|
},
|
||||||
|
_ => return Err("Invalid surcharge claim: origin must be signed or \
|
||||||
|
inherent and auxiliary sender only provided on inherent")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add some advantage for block producers (who send unsigned extrinsics) by
|
||||||
|
// adding a handicap: for signed extrinsics we use a slightly older block number
|
||||||
|
// for the eviction check. This can be viewed as if we pushed regular users back in past.
|
||||||
|
let handicap = if signed {
|
||||||
|
<Module<T>>::signed_claim_handicap()
|
||||||
|
} else {
|
||||||
|
Zero::zero()
|
||||||
|
};
|
||||||
|
|
||||||
|
// If poking the contract has lead to eviction of the contract, give out the rewards.
|
||||||
|
if rent::try_evict::<T>(&dest, handicap) == rent::RentOutcome::Evicted {
|
||||||
|
T::Currency::deposit_into_existing(rewarded, Self::surcharge_reward())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_finalize() {
|
fn on_finalize() {
|
||||||
<GasSpent<T>>::kill();
|
<GasSpent<T>>::kill();
|
||||||
}
|
}
|
||||||
@@ -420,6 +534,29 @@ decl_event! {
|
|||||||
|
|
||||||
decl_storage! {
|
decl_storage! {
|
||||||
trait Store for Module<T: Trait> as Contract {
|
trait Store for Module<T: Trait> as Contract {
|
||||||
|
/// Number of block delay an extrinsic claim surcharge has.
|
||||||
|
///
|
||||||
|
/// When claim surchage is called by an extrinsic the rent is checked
|
||||||
|
/// for current_block - delay
|
||||||
|
SignedClaimHandicap get(signed_claim_handicap) config(): T::BlockNumber;
|
||||||
|
/// The minimum amount required to generate a tombstone.
|
||||||
|
TombstoneDeposit get(tombstone_deposit) config(): BalanceOf<T>;
|
||||||
|
/// Size of a contract at the time of creation. This is a simple way to ensure
|
||||||
|
/// that empty contracts eventually gets deleted.
|
||||||
|
StorageSizeOffset get(storage_size_offset) config(): u64;
|
||||||
|
/// Price of a byte of storage per one block interval. Should be greater than 0.
|
||||||
|
RentByteFee get(rent_byte_price) config(): BalanceOf<T>;
|
||||||
|
/// The amount of funds a contract should deposit in order to offset
|
||||||
|
/// the cost of one byte.
|
||||||
|
///
|
||||||
|
/// Let's suppose the deposit is 1,000 EDG/byte and the rent is 1 EDG/byte/day, then a contract
|
||||||
|
/// with 1,000,000 EDG that uses 1,000 bytes of storage would pay no rent.
|
||||||
|
/// But if the balance reduced to 500,000 EDG and the storage stayed the same at 1,000,
|
||||||
|
/// then it would pay 500 EDG/day.
|
||||||
|
RentDepositOffset get(rent_deposit_offset) config(): BalanceOf<T>;
|
||||||
|
/// Reward that is received by the party whose touch has led
|
||||||
|
/// to removal of a contract.
|
||||||
|
SurchargeReward get(surcharge_reward) config(): BalanceOf<T>;
|
||||||
/// The fee required to make a transfer.
|
/// The fee required to make a transfer.
|
||||||
TransferFee get(transfer_fee) config(): BalanceOf<T>;
|
TransferFee get(transfer_fee) config(): BalanceOf<T>;
|
||||||
/// The fee required to create an account.
|
/// The fee required to create an account.
|
||||||
@@ -444,8 +581,6 @@ decl_storage! {
|
|||||||
GasSpent get(gas_spent): T::Gas;
|
GasSpent get(gas_spent): T::Gas;
|
||||||
/// Current cost schedule for contracts.
|
/// Current cost schedule for contracts.
|
||||||
CurrentSchedule get(current_schedule) config(): Schedule<T::Gas> = Schedule::default();
|
CurrentSchedule get(current_schedule) config(): Schedule<T::Gas> = Schedule::default();
|
||||||
/// The code associated with a given account.
|
|
||||||
pub CodeHashOf: map T::AccountId => Option<CodeHash<T>>;
|
|
||||||
/// A mapping from an original code hash to the original code, untouched by instrumentation.
|
/// A mapping from an original code hash to the original code, untouched by instrumentation.
|
||||||
pub PristineCode: map CodeHash<T> => Option<Vec<u8>>;
|
pub PristineCode: map CodeHash<T> => Option<Vec<u8>>;
|
||||||
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
|
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
|
||||||
@@ -453,14 +588,16 @@ decl_storage! {
|
|||||||
/// The subtrie counter.
|
/// The subtrie counter.
|
||||||
pub AccountCounter: u64 = 0;
|
pub AccountCounter: u64 = 0;
|
||||||
/// The code associated with a given account.
|
/// The code associated with a given account.
|
||||||
pub AccountInfoOf: map T::AccountId => Option<AccountInfo>;
|
pub ContractInfoOf: map T::AccountId => Option<ContractInfo<T>>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
|
impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
|
||||||
fn on_free_balance_zero(who: &T::AccountId) {
|
fn on_free_balance_zero(who: &T::AccountId) {
|
||||||
<CodeHashOf<T>>::remove(who);
|
if let Some(ContractInfo::Alive(info)) = <ContractInfoOf<T>>::get(who) {
|
||||||
<AccountInfoOf<T>>::get(who).map(|info| child::kill_storage(&info.trie_id));
|
child::kill_storage(&info.trie_id);
|
||||||
|
}
|
||||||
|
<ContractInfoOf<T>>::remove(who);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
// Copyright 2019 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Substrate.
|
||||||
|
|
||||||
|
// Substrate is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Substrate is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
use crate::{BalanceOf, ContractInfo, ContractInfoOf, Module, TombstoneContractInfo, Trait};
|
||||||
|
use runtime_primitives::traits::{As, Bounded, CheckedDiv, CheckedMul, Saturating, Zero};
|
||||||
|
use srml_support::traits::{Currency, ExistenceRequirement, Imbalance, WithdrawReason};
|
||||||
|
use srml_support::StorageMap;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[must_use]
|
||||||
|
pub enum RentOutcome {
|
||||||
|
/// Exempted from rent iff:
|
||||||
|
/// * rent is offset completely by the `rent_deposit_offset`,
|
||||||
|
/// * or rent has already been paid for this block number,
|
||||||
|
/// * or account doesn't have a contract,
|
||||||
|
/// * or account has a tombstone.
|
||||||
|
Exempted,
|
||||||
|
/// Evicted iff:
|
||||||
|
/// * rent exceed rent allowance,
|
||||||
|
/// * or can't withdraw the rent,
|
||||||
|
/// * or go below subsistence threshold.
|
||||||
|
Evicted,
|
||||||
|
/// The outstanding dues were paid or were able to be paid.
|
||||||
|
Ok,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evict and optionally pay dues (or check account can pay them otherwise) at the current
|
||||||
|
/// block number (modulo `handicap`, read on).
|
||||||
|
///
|
||||||
|
/// `pay_rent` gives an ability to pay or skip paying rent.
|
||||||
|
/// `handicap` gives a way to check or pay the rent up to a moment in the past instead
|
||||||
|
/// of current block.
|
||||||
|
///
|
||||||
|
/// NOTE: This function acts eagerly, all modification are committed into the storage.
|
||||||
|
fn try_evict_or_and_pay_rent<T: Trait>(
|
||||||
|
account: &T::AccountId,
|
||||||
|
handicap: T::BlockNumber,
|
||||||
|
pay_rent: bool,
|
||||||
|
) -> RentOutcome {
|
||||||
|
let contract = match <ContractInfoOf<T>>::get(account) {
|
||||||
|
None | Some(ContractInfo::Tombstone(_)) => return RentOutcome::Exempted,
|
||||||
|
Some(ContractInfo::Alive(contract)) => contract,
|
||||||
|
};
|
||||||
|
|
||||||
|
// How much block has passed since the last deduction for the contract.
|
||||||
|
let blocks_passed = {
|
||||||
|
// Calculate an effective block number, i.e. after adjusting for handicap.
|
||||||
|
let effective_block_number = <system::Module<T>>::block_number().saturating_sub(handicap);
|
||||||
|
let n = effective_block_number.saturating_sub(contract.deduct_block);
|
||||||
|
if n.is_zero() {
|
||||||
|
// Rent has already been paid
|
||||||
|
return RentOutcome::Exempted;
|
||||||
|
}
|
||||||
|
n
|
||||||
|
};
|
||||||
|
|
||||||
|
let balance = T::Currency::free_balance(account);
|
||||||
|
|
||||||
|
// An amount of funds to charge per block for storage taken up by the contract.
|
||||||
|
let fee_per_block = {
|
||||||
|
let free_storage = balance
|
||||||
|
.checked_div(&<Module<T>>::rent_deposit_offset())
|
||||||
|
.unwrap_or(<BalanceOf<T>>::sa(0));
|
||||||
|
|
||||||
|
let effective_storage_size =
|
||||||
|
<BalanceOf<T>>::sa(contract.storage_size).saturating_sub(free_storage);
|
||||||
|
|
||||||
|
effective_storage_size
|
||||||
|
.checked_mul(&<Module<T>>::rent_byte_price())
|
||||||
|
.unwrap_or(<BalanceOf<T>>::max_value())
|
||||||
|
};
|
||||||
|
|
||||||
|
if fee_per_block.is_zero() {
|
||||||
|
// The rent deposit offset reduced the fee to 0. This means that the contract
|
||||||
|
// gets the rent for free.
|
||||||
|
return RentOutcome::Exempted;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The minimal amount of funds required for a contract not to be evicted.
|
||||||
|
let subsistence_threshold = T::Currency::minimum_balance() + <Module<T>>::tombstone_deposit();
|
||||||
|
|
||||||
|
let dues = fee_per_block
|
||||||
|
.checked_mul(&<BalanceOf<T>>::sa(blocks_passed.as_()))
|
||||||
|
.unwrap_or(<BalanceOf<T>>::max_value());
|
||||||
|
|
||||||
|
let dues_limited = dues.min(contract.rent_allowance);
|
||||||
|
let rent_allowance_exceeded = dues > contract.rent_allowance;
|
||||||
|
let is_below_subsistence = balance < subsistence_threshold;
|
||||||
|
let go_below_subsistence = balance.saturating_sub(dues_limited) < subsistence_threshold;
|
||||||
|
let can_withdraw_rent = T::Currency::ensure_can_withdraw(
|
||||||
|
account,
|
||||||
|
dues_limited,
|
||||||
|
WithdrawReason::Fee,
|
||||||
|
balance.saturating_sub(dues_limited),
|
||||||
|
)
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
if !rent_allowance_exceeded && can_withdraw_rent && !go_below_subsistence {
|
||||||
|
// Collect dues
|
||||||
|
|
||||||
|
if pay_rent {
|
||||||
|
let imbalance = T::Currency::withdraw(
|
||||||
|
account,
|
||||||
|
dues,
|
||||||
|
WithdrawReason::Fee,
|
||||||
|
ExistenceRequirement::KeepAlive,
|
||||||
|
)
|
||||||
|
.expect(
|
||||||
|
"Withdraw has been checked above;
|
||||||
|
go_below_subsistence is false and subsistence > existencial_deposit;
|
||||||
|
qed",
|
||||||
|
);
|
||||||
|
|
||||||
|
<ContractInfoOf<T>>::mutate(account, |contract| {
|
||||||
|
contract
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|c| c.as_alive_mut())
|
||||||
|
.expect("Dead or inexistent account has been exempt above; qed")
|
||||||
|
.rent_allowance -= imbalance.peek(); // rent_allowance is not exceeded
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
RentOutcome::Ok
|
||||||
|
} else {
|
||||||
|
// Evict
|
||||||
|
|
||||||
|
if can_withdraw_rent && !go_below_subsistence {
|
||||||
|
T::Currency::withdraw(
|
||||||
|
account,
|
||||||
|
dues,
|
||||||
|
WithdrawReason::Fee,
|
||||||
|
ExistenceRequirement::KeepAlive,
|
||||||
|
)
|
||||||
|
.expect("Can withdraw and don't go below subsistence");
|
||||||
|
} else if !is_below_subsistence {
|
||||||
|
T::Currency::make_free_balance_be(account, subsistence_threshold);
|
||||||
|
} else {
|
||||||
|
T::Currency::make_free_balance_be(account, <BalanceOf<T>>::zero());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !is_below_subsistence {
|
||||||
|
// The contract has funds above subsistence deposit and that means it can afford to
|
||||||
|
// leave tombstone.
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
contract.code_hash,
|
||||||
|
);
|
||||||
|
<ContractInfoOf<T>>::insert(account, ContractInfo::Tombstone(tombstone));
|
||||||
|
runtime_io::kill_child_storage(&contract.trie_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
RentOutcome::Evicted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make account paying the rent for the current block number
|
||||||
|
///
|
||||||
|
/// NOTE: This function acts eagerly.
|
||||||
|
pub fn pay_rent<T: Trait>(account: &T::AccountId) {
|
||||||
|
let _ = try_evict_or_and_pay_rent::<T>(account, Zero::zero(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evict the account if it should be evicted at the given block number.
|
||||||
|
///
|
||||||
|
/// `handicap` gives a way to check or pay the rent up to a moment in the past instead
|
||||||
|
/// of current block. E.g. if the contract is going to be evicted at the current block,
|
||||||
|
/// `handicap=1` can defer the eviction for 1 block.
|
||||||
|
///
|
||||||
|
/// NOTE: This function acts eagerly.
|
||||||
|
pub fn try_evict<T: Trait>(account: &T::AccountId, handicap: T::BlockNumber) -> RentOutcome {
|
||||||
|
try_evict_or_and_pay_rent::<T>(account, handicap, false)
|
||||||
|
}
|
||||||
@@ -19,27 +19,28 @@
|
|||||||
|
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
use runtime_io::with_externalities;
|
use crate::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb};
|
||||||
use runtime_primitives::testing::{Digest, DigestItem, H256, Header, UintAuthorityId};
|
|
||||||
use runtime_primitives::traits::{BlakeTwo256, IdentityLookup};
|
|
||||||
use runtime_primitives::BuildStorage;
|
|
||||||
use runtime_io;
|
|
||||||
use srml_support::{storage::child, StorageMap, assert_ok, impl_outer_event, impl_outer_dispatch,
|
|
||||||
impl_outer_origin, traits::Currency};
|
|
||||||
use substrate_primitives::Blake2Hasher;
|
|
||||||
use system::{self, Phase, EventRecord};
|
|
||||||
use {wabt, balances, consensus};
|
|
||||||
use hex_literal::hex;
|
|
||||||
use assert_matches::assert_matches;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ContractAddressFor, GenesisConfig, Module, RawEvent,
|
ComputeDispatchFee, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, Module,
|
||||||
Trait, ComputeDispatchFee, TrieIdGenerator, TrieId,
|
RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter, TrieIdGenerator,
|
||||||
AccountInfo, AccountInfoOf, TrieIdFromParentCounter
|
};
|
||||||
|
use assert_matches::assert_matches;
|
||||||
|
use hex_literal::*;
|
||||||
|
use parity_codec::{Decode, Encode, KeyedVec};
|
||||||
|
use runtime_io;
|
||||||
|
use runtime_io::with_externalities;
|
||||||
|
use runtime_primitives::testing::{Digest, DigestItem, Header, UintAuthorityId, H256};
|
||||||
|
use runtime_primitives::traits::{As, 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,
|
||||||
};
|
};
|
||||||
use substrate_primitives::storage::well_known_keys;
|
|
||||||
use parity_codec::{Encode, Decode, KeyedVec};
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use crate::account_db::{DirectAccountDb, OverlayAccountDb, AccountDb};
|
use substrate_primitives::storage::well_known_keys;
|
||||||
|
use substrate_primitives::Blake2Hasher;
|
||||||
|
use system::{self, EventRecord, Phase};
|
||||||
|
use {balances, consensus, wabt};
|
||||||
|
|
||||||
mod contract {
|
mod contract {
|
||||||
// Re-export contents of the root. This basically
|
// Re-export contents of the root. This basically
|
||||||
@@ -206,8 +207,14 @@ impl ExtBuilder {
|
|||||||
);
|
);
|
||||||
t.extend(
|
t.extend(
|
||||||
GenesisConfig::<Test> {
|
GenesisConfig::<Test> {
|
||||||
transaction_base_fee: 0,
|
signed_claim_handicap: 2,
|
||||||
transaction_byte_fee: 0,
|
rent_byte_price: 4,
|
||||||
|
rent_deposit_offset: 10_000,
|
||||||
|
storage_size_offset: 8,
|
||||||
|
surcharge_reward: 150,
|
||||||
|
tombstone_deposit: 16,
|
||||||
|
transaction_base_fee: 2,
|
||||||
|
transaction_byte_fee: 6,
|
||||||
transfer_fee: self.transfer_fee,
|
transfer_fee: self.transfer_fee,
|
||||||
creation_fee: self.creation_fee,
|
creation_fee: self.creation_fee,
|
||||||
contract_fee: 21,
|
contract_fee: 21,
|
||||||
@@ -231,13 +238,7 @@ fn refunds_unused_gas() {
|
|||||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||||
Balances::deposit_creating(&0, 100_000_000);
|
Balances::deposit_creating(&0, 100_000_000);
|
||||||
|
|
||||||
assert_ok!(Contract::call(
|
assert_ok!(Contract::call(Origin::signed(0), 1, 0, 100_000, Vec::new()));
|
||||||
Origin::signed(0),
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
100_000,
|
|
||||||
Vec::new()
|
|
||||||
));
|
|
||||||
|
|
||||||
assert_eq!(Balances::free_balance(&0), 100_000_000 - (2 * 135));
|
assert_eq!(Balances::free_balance(&0), 100_000_000 - (2 * 135));
|
||||||
});
|
});
|
||||||
@@ -256,10 +257,13 @@ fn account_removal_removes_storage() {
|
|||||||
// Set up two accounts with free balance above the existential threshold.
|
// Set up two accounts with free balance above the existential threshold.
|
||||||
{
|
{
|
||||||
Balances::deposit_creating(&1, 110);
|
Balances::deposit_creating(&1, 110);
|
||||||
AccountInfoOf::<Test>::insert(1, &AccountInfo {
|
ContractInfoOf::<Test>::insert(1, &ContractInfo::Alive(RawAliveContractInfo {
|
||||||
trie_id: trie_id1.clone(),
|
trie_id: trie_id1.clone(),
|
||||||
storage_size: 0,
|
storage_size: Contract::storage_size_offset(),
|
||||||
});
|
deduct_block: System::block_number(),
|
||||||
|
code_hash: H256::repeat_byte(1),
|
||||||
|
rent_allowance: 40,
|
||||||
|
}));
|
||||||
|
|
||||||
let mut overlay = OverlayAccountDb::<Test>::new(&DirectAccountDb);
|
let mut overlay = OverlayAccountDb::<Test>::new(&DirectAccountDb);
|
||||||
overlay.set_storage(&1, key1.clone(), Some(b"1".to_vec()));
|
overlay.set_storage(&1, key1.clone(), Some(b"1".to_vec()));
|
||||||
@@ -267,10 +271,13 @@ fn account_removal_removes_storage() {
|
|||||||
DirectAccountDb.commit(overlay.into_change_set());
|
DirectAccountDb.commit(overlay.into_change_set());
|
||||||
|
|
||||||
Balances::deposit_creating(&2, 110);
|
Balances::deposit_creating(&2, 110);
|
||||||
AccountInfoOf::<Test>::insert(2, &AccountInfo {
|
ContractInfoOf::<Test>::insert(2, &ContractInfo::Alive(RawAliveContractInfo {
|
||||||
trie_id: trie_id2.clone(),
|
trie_id: trie_id2.clone(),
|
||||||
storage_size: 0,
|
storage_size: Contract::storage_size_offset(),
|
||||||
});
|
deduct_block: System::block_number(),
|
||||||
|
code_hash: H256::repeat_byte(2),
|
||||||
|
rent_allowance: 40,
|
||||||
|
}));
|
||||||
|
|
||||||
let mut overlay = OverlayAccountDb::<Test>::new(&DirectAccountDb);
|
let mut overlay = OverlayAccountDb::<Test>::new(&DirectAccountDb);
|
||||||
overlay.set_storage(&2, key1.clone(), Some(b"3".to_vec()));
|
overlay.set_storage(&2, key1.clone(), Some(b"3".to_vec()));
|
||||||
@@ -287,7 +294,6 @@ fn account_removal_removes_storage() {
|
|||||||
// Verify that all entries from account 1 is removed, while
|
// Verify that all entries from account 1 is removed, while
|
||||||
// entries from account 2 is in place.
|
// entries from account 2 is in place.
|
||||||
{
|
{
|
||||||
// let a: <Test as system::Trait>::AccountId = 1;
|
|
||||||
assert!(<AccountDb<Test>>::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key1).is_none());
|
assert!(<AccountDb<Test>>::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key1).is_none());
|
||||||
assert!(<AccountDb<Test>>::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key2).is_none());
|
assert!(<AccountDb<Test>>::get_storage(&DirectAccountDb, &1, Some(&trie_id1), key2).is_none());
|
||||||
|
|
||||||
@@ -342,11 +348,7 @@ fn instantiate_and_call_and_deposit_event() {
|
|||||||
|| {
|
|| {
|
||||||
Balances::deposit_creating(&ALICE, 1_000_000);
|
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
|
||||||
assert_ok!(Contract::put_code(
|
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||||
Origin::signed(ALICE),
|
|
||||||
100_000,
|
|
||||||
wasm,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Check at the end to get hash on error easily
|
// Check at the end to get hash on error easily
|
||||||
let creation = Contract::create(
|
let creation = Contract::create(
|
||||||
@@ -387,7 +389,7 @@ fn instantiate_and_call_and_deposit_event() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
assert_ok!(creation);
|
assert_ok!(creation);
|
||||||
assert!(AccountInfoOf::<Test>::exists(BOB));
|
assert!(ContractInfoOf::<Test>::exists(BOB));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -424,11 +426,7 @@ fn dispatch_call() {
|
|||||||
|| {
|
|| {
|
||||||
Balances::deposit_creating(&ALICE, 1_000_000);
|
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
|
||||||
assert_ok!(Contract::put_code(
|
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||||
Origin::signed(ALICE),
|
|
||||||
100_000,
|
|
||||||
wasm,
|
|
||||||
));
|
|
||||||
|
|
||||||
// Let's keep this assert even though it's redundant. If you ever need to update the
|
// Let's keep this assert even though it's redundant. If you ever need to update the
|
||||||
// wasm source this test will fail and will show you the actual hash.
|
// wasm source this test will fail and will show you the actual hash.
|
||||||
@@ -506,3 +504,399 @@ fn dispatch_call() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CODE_SET_RENT: &str = r#"
|
||||||
|
(module
|
||||||
|
(import "env" "ext_dispatch_call" (func $ext_dispatch_call (param i32 i32)))
|
||||||
|
(import "env" "ext_set_storage" (func $ext_set_storage (param i32 i32 i32 i32)))
|
||||||
|
(import "env" "ext_set_rent_allowance" (func $ext_set_rent_allowance (param i32 i32)))
|
||||||
|
(import "env" "ext_input_size" (func $ext_input_size (result i32)))
|
||||||
|
(import "env" "ext_input_copy" (func $ext_input_copy (param i32 i32 i32)))
|
||||||
|
(import "env" "memory" (memory 1 1))
|
||||||
|
|
||||||
|
;; insert a value of 4 bytes into storage
|
||||||
|
(func $call_0
|
||||||
|
(call $ext_set_storage
|
||||||
|
(i32.const 1)
|
||||||
|
(i32.const 1)
|
||||||
|
(i32.const 0)
|
||||||
|
(i32.const 4)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; remove the value inserted by call_1
|
||||||
|
(func $call_1
|
||||||
|
(call $ext_set_storage
|
||||||
|
(i32.const 1)
|
||||||
|
(i32.const 0)
|
||||||
|
(i32.const 0)
|
||||||
|
(i32.const 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; transfer 50 to ALICE
|
||||||
|
(func $call_2
|
||||||
|
(call $ext_dispatch_call
|
||||||
|
(i32.const 8)
|
||||||
|
(i32.const 11)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; do nothing
|
||||||
|
(func $call_else)
|
||||||
|
|
||||||
|
(func $assert (param i32)
|
||||||
|
(block $ok
|
||||||
|
(br_if $ok
|
||||||
|
(get_local 0)
|
||||||
|
)
|
||||||
|
(unreachable)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Dispatch the call according to input size
|
||||||
|
(func (export "call")
|
||||||
|
(local $input_size i32)
|
||||||
|
(set_local $input_size
|
||||||
|
(call $ext_input_size)
|
||||||
|
)
|
||||||
|
(block $IF_ELSE
|
||||||
|
(block $IF_2
|
||||||
|
(block $IF_1
|
||||||
|
(block $IF_0
|
||||||
|
(br_table $IF_0 $IF_1 $IF_2 $IF_ELSE
|
||||||
|
(get_local $input_size)
|
||||||
|
)
|
||||||
|
(unreachable)
|
||||||
|
)
|
||||||
|
(call $call_0)
|
||||||
|
return
|
||||||
|
)
|
||||||
|
(call $call_1)
|
||||||
|
return
|
||||||
|
)
|
||||||
|
(call $call_2)
|
||||||
|
return
|
||||||
|
)
|
||||||
|
(call $call_else)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Set into storage a 4 bytes value
|
||||||
|
;; Set call set_rent_allowance with input
|
||||||
|
(func (export "deploy")
|
||||||
|
(local $input_size i32)
|
||||||
|
(call $ext_set_storage
|
||||||
|
(i32.const 0)
|
||||||
|
(i32.const 1)
|
||||||
|
(i32.const 0)
|
||||||
|
(i32.const 4)
|
||||||
|
)
|
||||||
|
(set_local $input_size
|
||||||
|
(call $ext_input_size)
|
||||||
|
)
|
||||||
|
(call $ext_input_copy
|
||||||
|
(i32.const 0)
|
||||||
|
(i32.const 0)
|
||||||
|
(get_local $input_size)
|
||||||
|
)
|
||||||
|
(call $ext_set_rent_allowance
|
||||||
|
(i32.const 0)
|
||||||
|
(get_local $input_size)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
;; Encoding of 10 in balance
|
||||||
|
(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")
|
||||||
|
)
|
||||||
|
"#;
|
||||||
|
const HASH_SET_RENT: [u8; 32] = hex!("a51c2a6f3f68936d4ae9abdb93b28eedcbd0f6f39770e168f9025f0c1e7094ef");
|
||||||
|
|
||||||
|
|
||||||
|
/// Input data for each call in set_rent code
|
||||||
|
mod call {
|
||||||
|
pub fn set_storage_4_byte() -> Vec<u8> { vec![] }
|
||||||
|
pub fn remove_storage_4_byte() -> Vec<u8> { vec![0] }
|
||||||
|
pub fn transfer() -> Vec<u8> { vec![0, 0] }
|
||||||
|
pub fn null() -> Vec<u8> { vec![0, 0, 0] }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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() {
|
||||||
|
// 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)));
|
||||||
|
assert_eq!(&encoded[..], &hex!("00000300000000000000C8")[..]);
|
||||||
|
|
||||||
|
let 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, 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)),
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::ApplyExtrinsic(0),
|
||||||
|
event: MetaEvent::contract(RawEvent::CodeStored(HASH_SET_RENT.into())),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn storage_size() {
|
||||||
|
let wasm = wabt::wat2wasm(CODE_SET_RENT).unwrap();
|
||||||
|
|
||||||
|
// Storage size
|
||||||
|
with_externalities(
|
||||||
|
&mut ExtBuilder::default().existential_deposit(50).build(),
|
||||||
|
|| {
|
||||||
|
// Create
|
||||||
|
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||||
|
assert_ok!(Contract::create(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
30_000,
|
||||||
|
100_000, HASH_SET_RENT.into(),
|
||||||
|
<Test as balances::Trait>::Balance::sa(1_000u64).encode() // rent allowance
|
||||||
|
));
|
||||||
|
let bob_contract = super::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();
|
||||||
|
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();
|
||||||
|
assert_eq!(bob_contract.storage_size, Contract::storage_size_offset() + 4);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deduct_blocks() {
|
||||||
|
let wasm = wabt::wat2wasm(CODE_SET_RENT).unwrap();
|
||||||
|
|
||||||
|
with_externalities(
|
||||||
|
&mut ExtBuilder::default().existential_deposit(50).build(),
|
||||||
|
|| {
|
||||||
|
// Create
|
||||||
|
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||||
|
assert_ok!(Contract::create(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
30_000,
|
||||||
|
100_000, HASH_SET_RENT.into(),
|
||||||
|
<Test as balances::Trait>::Balance::sa(1_000u64).encode() // rent allowance
|
||||||
|
));
|
||||||
|
|
||||||
|
// Check creation
|
||||||
|
let bob_contract = super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().unwrap();
|
||||||
|
assert_eq!(bob_contract.rent_allowance, 1_000);
|
||||||
|
|
||||||
|
// 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()));
|
||||||
|
|
||||||
|
// Check result
|
||||||
|
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();
|
||||||
|
assert_eq!(bob_contract.rent_allowance, 1_000 - rent);
|
||||||
|
assert_eq!(Balances::free_balance(BOB), 30_000 - rent);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn call_contract_removals() {
|
||||||
|
removals(|| Contract::call(Origin::signed(ALICE), BOB, 0, 100_000, call::null()).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inherent_claim_surcharge_contract_removals() {
|
||||||
|
removals(|| Contract::claim_surcharge(Origin::INHERENT, BOB, Some(ALICE)).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn signed_claim_surcharge_contract_removals() {
|
||||||
|
removals(|| Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn claim_surcharge_malus() {
|
||||||
|
// Test surcharge malus for inherent
|
||||||
|
claim_surcharge(4, || Contract::claim_surcharge(Origin::INHERENT, BOB, Some(ALICE)).is_ok(), true);
|
||||||
|
claim_surcharge(3, || Contract::claim_surcharge(Origin::INHERENT, BOB, Some(ALICE)).is_ok(), true);
|
||||||
|
claim_surcharge(2, || Contract::claim_surcharge(Origin::INHERENT, BOB, Some(ALICE)).is_ok(), true);
|
||||||
|
claim_surcharge(1, || Contract::claim_surcharge(Origin::INHERENT, BOB, Some(ALICE)).is_ok(), false);
|
||||||
|
|
||||||
|
// Test surcharge malus for signed
|
||||||
|
claim_surcharge(4, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), true);
|
||||||
|
claim_surcharge(3, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
|
||||||
|
claim_surcharge(2, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
|
||||||
|
claim_surcharge(1, || Contract::claim_surcharge(Origin::signed(ALICE), BOB, None).is_ok(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Claim surcharge with the given trigger_call at the given blocks.
|
||||||
|
/// if removes is true then assert that the contract is a tombstonedead
|
||||||
|
fn claim_surcharge(blocks: u64, trigger_call: impl Fn() -> bool, removes: bool) {
|
||||||
|
let wasm = wabt::wat2wasm(CODE_SET_RENT).unwrap();
|
||||||
|
|
||||||
|
with_externalities(
|
||||||
|
&mut ExtBuilder::default().existential_deposit(50).build(),
|
||||||
|
|| {
|
||||||
|
// Create
|
||||||
|
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||||
|
assert_ok!(Contract::create(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
100,
|
||||||
|
100_000, HASH_SET_RENT.into(),
|
||||||
|
<Test as balances::Trait>::Balance::sa(1_000u64).encode() // rent allowance
|
||||||
|
));
|
||||||
|
|
||||||
|
// Advance blocks
|
||||||
|
System::initialize(&blocks, &[0u8; 32].into(), &[0u8; 32].into());
|
||||||
|
|
||||||
|
// Trigger rent through call
|
||||||
|
assert!(trigger_call());
|
||||||
|
|
||||||
|
if removes {
|
||||||
|
assert!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_tombstone().is_some());
|
||||||
|
} else {
|
||||||
|
assert!(super::ContractInfoOf::<Test>::get(BOB).unwrap().get_alive().is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test for all kind of removals for the given trigger:
|
||||||
|
/// * if balance is reached and balance > subsistence threshold
|
||||||
|
/// * if allowance is exceeded
|
||||||
|
/// * if balance is reached and balance < subsistence threshold
|
||||||
|
fn removals(trigger_call: impl Fn() -> bool) {
|
||||||
|
let wasm = wabt::wat2wasm(CODE_SET_RENT).unwrap();
|
||||||
|
|
||||||
|
// Balance reached and superior to subsistence threshold
|
||||||
|
with_externalities(
|
||||||
|
&mut ExtBuilder::default().existential_deposit(50).build(),
|
||||||
|
|| {
|
||||||
|
// Create
|
||||||
|
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
|
||||||
|
assert_ok!(Contract::create(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
100,
|
||||||
|
100_000, HASH_SET_RENT.into(),
|
||||||
|
<Test as balances::Trait>::Balance::sa(1_000u64).encode() // rent allowance
|
||||||
|
));
|
||||||
|
|
||||||
|
// Trigger rent must have no effect
|
||||||
|
assert!(trigger_call());
|
||||||
|
assert_eq!(super::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());
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Allowance exceeded
|
||||||
|
with_externalities(
|
||||||
|
&mut ExtBuilder::default().existential_deposit(50).build(),
|
||||||
|
|| {
|
||||||
|
// Create
|
||||||
|
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
|
||||||
|
assert_ok!(Contract::create(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
1_000,
|
||||||
|
100_000, HASH_SET_RENT.into(),
|
||||||
|
<Test as balances::Trait>::Balance::sa(100u64).encode() // rent allowance
|
||||||
|
));
|
||||||
|
|
||||||
|
// Trigger rent must have no effect
|
||||||
|
assert!(trigger_call());
|
||||||
|
assert_eq!(super::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());
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Balance reached and inferior to subsistence threshold
|
||||||
|
with_externalities(
|
||||||
|
&mut ExtBuilder::default().existential_deposit(50).build(),
|
||||||
|
|| {
|
||||||
|
// Create
|
||||||
|
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
|
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm.clone()));
|
||||||
|
assert_ok!(Contract::create(
|
||||||
|
Origin::signed(ALICE),
|
||||||
|
50+Balances::minimum_balance(),
|
||||||
|
100_000, HASH_SET_RENT.into(),
|
||||||
|
<Test as balances::Trait>::Balance::sa(1_000u64).encode() // rent allowance
|
||||||
|
));
|
||||||
|
|
||||||
|
// Trigger rent must have no effect
|
||||||
|
assert!(trigger_call());
|
||||||
|
assert_eq!(super::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);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ mod tests {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct MockExt {
|
pub struct MockExt {
|
||||||
storage: HashMap<StorageKey, Vec<u8>>,
|
storage: HashMap<StorageKey, Vec<u8>>,
|
||||||
|
rent_allowance: u64,
|
||||||
creates: Vec<CreateEntry>,
|
creates: Vec<CreateEntry>,
|
||||||
transfers: Vec<TransferEntry>,
|
transfers: Vec<TransferEntry>,
|
||||||
dispatches: Vec<DispatchEntry>,
|
dispatches: Vec<DispatchEntry>,
|
||||||
@@ -281,6 +282,14 @@ mod tests {
|
|||||||
fn deposit_event(&mut self, data: Vec<u8>) {
|
fn deposit_event(&mut self, data: Vec<u8>) {
|
||||||
self.events.push(data)
|
self.events.push(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_rent_allowance(&mut self, rent_allowance: u64) {
|
||||||
|
self.rent_allowance = rent_allowance;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rent_allowance(&self) -> u64 {
|
||||||
|
self.rent_allowance
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute<E: Ext>(
|
fn execute<E: Ext>(
|
||||||
|
|||||||
@@ -629,6 +629,31 @@ define_env!(Env, <E: Ext>,
|
|||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Set rent allowance of the contract
|
||||||
|
//
|
||||||
|
// - value_ptr: a pointer to the buffer with value, how much to allow for rent
|
||||||
|
// Should be decodable as a `T::Balance`. Traps otherwise.
|
||||||
|
// - value_len: length of the value buffer.
|
||||||
|
ext_set_rent_allowance(ctx, value_ptr: u32, value_len: u32) => {
|
||||||
|
let value = {
|
||||||
|
let value_buf = read_sandbox_memory(ctx, value_ptr, value_len)?;
|
||||||
|
BalanceOf::<<E as Ext>::T>::decode(&mut &value_buf[..])
|
||||||
|
.ok_or_else(|| sandbox::HostError)?
|
||||||
|
};
|
||||||
|
ctx.ext.set_rent_allowance(value);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
|
// Stores the rent allowance into the scratch buffer.
|
||||||
|
//
|
||||||
|
// The data is encoded as T::Balance. The current contents of the scratch buffer are overwritten.
|
||||||
|
ext_rent_allowance(ctx) => {
|
||||||
|
ctx.scratch_buf = ctx.ext.rent_allowance().encode();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
|
||||||
// Prints utf8 encoded string from the data buffer.
|
// Prints utf8 encoded string from the data buffer.
|
||||||
// Only available on `--dev` chains.
|
// Only available on `--dev` chains.
|
||||||
// This function may be removed at any time, superseded by a more general contract debugging feature.
|
// This function may be removed at any time, superseded by a more general contract debugging feature.
|
||||||
|
|||||||
Reference in New Issue
Block a user