mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 04:51:09 +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> {
|
||||
unimplemented!()
|
||||
vec![]
|
||||
}
|
||||
|
||||
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),
|
||||
}),
|
||||
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_byte_fee: 10 * MILLICENTS,
|
||||
transfer_fee: 1 * CENTS,
|
||||
@@ -239,6 +245,12 @@ pub fn testnet_genesis(
|
||||
const ENDOWMENT: u128 = 1 << 20;
|
||||
|
||||
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_byte_fee: 0,
|
||||
transfer_fee: 0,
|
||||
|
||||
@@ -745,7 +745,13 @@ mod tests {
|
||||
|
||||
runtime_io::with_externalities(&mut t, || {
|
||||
// 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"),
|
||||
authoring_version: 10,
|
||||
spec_version: 67,
|
||||
impl_version: 67,
|
||||
impl_version: 68,
|
||||
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.
|
||||
|
||||
## 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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
## set_storage, set_code, set_balance
|
||||
## set_storage, set_balance, set_rent_allowance
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
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)
|
||||
|
||||
**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.
|
||||
|
||||
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 system;
|
||||
use rstd::cell::RefCell;
|
||||
use rstd::collections::btree_map::{BTreeMap, Entry};
|
||||
use rstd::prelude::*;
|
||||
use runtime_io::blake2_256;
|
||||
use runtime_primitives::traits::Zero;
|
||||
use srml_support::{StorageMap, traits::{UpdateBalanceOutcome,
|
||||
SignedImbalance, Currency, Imbalance}, storage::child};
|
||||
use srml_support::traits::{Currency, Imbalance, SignedImbalance, UpdateBalanceOutcome};
|
||||
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> {
|
||||
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
|
||||
code: Option<Option<CodeHash<T>>>,
|
||||
/// If None, the code_hash remains untouched.
|
||||
code_hash: Option<CodeHash<T>>,
|
||||
rent_allowance: Option<BalanceOf<T>>,
|
||||
storage: BTreeMap<StorageKey, Option<Vec<u8>>>,
|
||||
}
|
||||
|
||||
@@ -38,8 +45,9 @@ pub struct ChangeEntry<T: Trait> {
|
||||
impl<T: Trait> Default for ChangeEntry<T> {
|
||||
fn default() -> Self {
|
||||
ChangeEntry {
|
||||
rent_allowance: Default::default(),
|
||||
balance: Default::default(),
|
||||
code: Default::default(),
|
||||
code_hash: 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.
|
||||
/// 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.
|
||||
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 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>> {
|
||||
trie_id.and_then(|id| child::get_raw(id, &blake2_256(location)))
|
||||
}
|
||||
fn get_code(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
|
||||
<CodeHashOf<T>>::get(account)
|
||||
fn get_code_hash(&self, account: &T::AccountId) -> Option<CodeHash<T>> {
|
||||
<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> {
|
||||
T::Currency::free_balance(account)
|
||||
@@ -84,42 +103,58 @@ impl<T: Trait> AccountDb<T> for DirectAccountDb {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if changed.code.is_some() || !changed.storage.is_empty() {
|
||||
let mut info = if !<AccountInfoOf<T>>::exists(&address) {
|
||||
let info = AccountInfo {
|
||||
trie_id: <T as Trait>::TrieIdGenerator::trie_id(&address),
|
||||
storage_size: 0,
|
||||
};
|
||||
<AccountInfoOf<T>>::insert(&address, &info);
|
||||
info
|
||||
} else {
|
||||
<AccountInfoOf<T>>::get(&address).unwrap()
|
||||
|
||||
if changed.code_hash.is_some()
|
||||
|| changed.rent_allowance.is_some()
|
||||
|| !changed.storage.is_empty()
|
||||
{
|
||||
let old_info = match <ContractInfoOf<T>>::get(&address) {
|
||||
Some(ContractInfo::Alive(alive)) => Some(alive),
|
||||
None => None,
|
||||
// Cannot commit changes to tombstone contract
|
||||
Some(ContractInfo::Tombstone(_)) => continue,
|
||||
};
|
||||
|
||||
if let Some(code) = changed.code {
|
||||
if let Some(code) = code {
|
||||
<CodeHashOf<T>>::insert(&address, code);
|
||||
} else {
|
||||
<CodeHashOf<T>>::remove(&address);
|
||||
let mut new_info = if let Some(info) = old_info.clone() {
|
||||
info
|
||||
} else if let Some(code_hash) = changed.code_hash {
|
||||
AliveContractInfo::<T> {
|
||||
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() {
|
||||
if let Some(value) = child::get_raw(&info.trie_id[..], &blake2_256(&k)) {
|
||||
new_storage_size -= value.len() as u64;
|
||||
if let Some(value) = child::get_raw(&new_info.trie_id[..], &blake2_256(&k)) {
|
||||
new_info.storage_size -= value.len() as u64;
|
||||
}
|
||||
if let Some(value) = v {
|
||||
new_storage_size += value.len() as u64;
|
||||
child::put_raw(&info.trie_id[..], &blake2_256(&k), &value[..]);
|
||||
new_info.storage_size += value.len() as u64;
|
||||
child::put_raw(&new_info.trie_id[..], &blake2_256(&k), &value[..]);
|
||||
} 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 {
|
||||
info.storage_size = new_storage_size;
|
||||
<AccountInfoOf<T>>::insert(&address, info);
|
||||
if old_info
|
||||
.map(|old_info| old_info != new_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>,
|
||||
}
|
||||
impl<'a, T: Trait> OverlayAccountDb<'a, T> {
|
||||
pub fn new(
|
||||
underlying: &'a AccountDb<T>,
|
||||
) -> OverlayAccountDb<'a, T> {
|
||||
pub fn new(underlying: &'a AccountDb<T>) -> OverlayAccountDb<'a, T> {
|
||||
OverlayAccountDb {
|
||||
local: RefCell::new(ChangeSet::new()),
|
||||
underlying,
|
||||
@@ -166,12 +199,31 @@ impl<'a, T: Trait> OverlayAccountDb<'a, T> {
|
||||
.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
|
||||
.borrow_mut()
|
||||
.entry(account.clone())
|
||||
.or_insert(Default::default())
|
||||
.code = Some(code);
|
||||
.rent_allowance = Some(rent_allowance);
|
||||
}
|
||||
pub fn set_balance(&mut self, account: &T::AccountId, balance: BalanceOf<T>) {
|
||||
self.local
|
||||
@@ -191,12 +243,26 @@ impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
|
||||
.cloned()
|
||||
.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
|
||||
.borrow()
|
||||
.get(account)
|
||||
.and_then(|a| a.code.clone())
|
||||
.unwrap_or_else(|| self.underlying.get_code(account))
|
||||
.and_then(|changes| changes.code_hash)
|
||||
.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> {
|
||||
self.local
|
||||
@@ -212,12 +278,9 @@ impl<'a, T: Trait> AccountDb<T> for OverlayAccountDb<'a, T> {
|
||||
match local.entry(address) {
|
||||
Entry::Occupied(e) => {
|
||||
let mut value = e.into_mut();
|
||||
if changed.balance.is_some() {
|
||||
value.balance = changed.balance;
|
||||
}
|
||||
if changed.code.is_some() {
|
||||
value.code = changed.code;
|
||||
}
|
||||
value.balance = changed.balance.or(value.balance);
|
||||
value.code_hash = changed.code_hash.or(value.code_hash);
|
||||
value.rent_allowance = changed.rent_allowance.or(value.rent_allowance);
|
||||
value.storage.extend(changed.storage.into_iter());
|
||||
}
|
||||
Entry::Vacant(e) => {
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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::gas::{GasMeter, Token, approx_gas_for_balance};
|
||||
|
||||
@@ -107,6 +108,12 @@ pub trait Ext {
|
||||
|
||||
/// Deposit an event.
|
||||
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
|
||||
@@ -249,7 +256,8 @@ where
|
||||
/// 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 {
|
||||
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,
|
||||
overlay: OverlayAccountDb::<T>::new(&DirectAccountDb),
|
||||
depth: 0,
|
||||
@@ -263,7 +271,8 @@ where
|
||||
|
||||
fn nested(&self, overlay: OverlayAccountDb<'a, T>, dest: T::AccountId) -> Self {
|
||||
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,
|
||||
overlay,
|
||||
depth: self.depth + 1,
|
||||
@@ -295,7 +304,11 @@ where
|
||||
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 (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)?;
|
||||
output_data = self
|
||||
.vm
|
||||
@@ -369,15 +382,11 @@ where
|
||||
&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 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());
|
||||
|
||||
// Send funds unconditionally here. If the `endowment` is below existential_deposit
|
||||
@@ -625,6 +634,15 @@ where
|
||||
fn deposit_event(&mut self, data: Vec<u8>) {
|
||||
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.
|
||||
@@ -763,7 +781,7 @@ mod tests {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
let cfg = Config::preload();
|
||||
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!(
|
||||
ctx.call(BOB, value, &mut gas_meter, &data, EmptyOutputBuf::new()),
|
||||
@@ -998,7 +1016,7 @@ mod tests {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
let cfg = Config::preload();
|
||||
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(
|
||||
dest,
|
||||
@@ -1026,7 +1044,7 @@ mod tests {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
let cfg = Config::preload();
|
||||
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(
|
||||
BOB,
|
||||
@@ -1038,7 +1056,7 @@ mod tests {
|
||||
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(), || {
|
||||
let cfg = Config::preload();
|
||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||
@@ -1085,7 +1103,7 @@ mod tests {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
let cfg = Config::preload();
|
||||
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(
|
||||
BOB,
|
||||
@@ -1132,8 +1150,8 @@ mod tests {
|
||||
let cfg = Config::preload();
|
||||
|
||||
let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader);
|
||||
ctx.overlay.set_code(&dest, Some(bob_ch));
|
||||
ctx.overlay.set_code(&CHARLIE, Some(charlie_ch));
|
||||
ctx.overlay.create_contract(&dest, bob_ch).unwrap();
|
||||
ctx.overlay.create_contract(&CHARLIE, charlie_ch).unwrap();
|
||||
|
||||
let result = ctx.call(
|
||||
dest,
|
||||
@@ -1175,8 +1193,8 @@ mod tests {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
let cfg = Config::preload();
|
||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||
ctx.overlay.set_code(&BOB, Some(bob_ch));
|
||||
ctx.overlay.set_code(&CHARLIE, Some(charlie_ch));
|
||||
ctx.overlay.create_contract(&BOB, bob_ch).unwrap();
|
||||
ctx.overlay.create_contract(&CHARLIE, charlie_ch).unwrap();
|
||||
|
||||
let result = ctx.call(
|
||||
BOB,
|
||||
@@ -1242,7 +1260,7 @@ mod tests {
|
||||
|
||||
// Check that the newly created account has the expected code hash and
|
||||
// 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, &[
|
||||
RawEvent::Transfer(ALICE, created_contract_address, 100),
|
||||
RawEvent::Instantiated(ALICE, created_contract_address),
|
||||
@@ -1283,7 +1301,7 @@ mod tests {
|
||||
let cfg = Config::preload();
|
||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||
ctx.overlay.set_balance(&ALICE, 1000);
|
||||
ctx.overlay.set_code(&BOB, Some(creator_ch));
|
||||
ctx.overlay.create_contract(&BOB, creator_ch).unwrap();
|
||||
|
||||
assert_matches!(
|
||||
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
|
||||
// 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, &[
|
||||
RawEvent::Transfer(ALICE, BOB, 20),
|
||||
RawEvent::Transfer(BOB, created_contract_address, 15),
|
||||
@@ -1334,7 +1352,7 @@ mod tests {
|
||||
let cfg = Config::preload();
|
||||
let mut ctx = ExecutionContext::top_level(ALICE, &cfg, &vm, &loader);
|
||||
ctx.overlay.set_balance(&ALICE, 1000);
|
||||
ctx.overlay.set_code(&BOB, Some(creator_ch));
|
||||
ctx.overlay.create_contract(&BOB, creator_ch).unwrap();
|
||||
|
||||
assert_matches!(
|
||||
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 exec;
|
||||
mod wasm;
|
||||
mod rent;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -96,7 +97,7 @@ use substrate_primitives::crypto::UncheckedFrom;
|
||||
use rstd::prelude::*;
|
||||
use rstd::marker::PhantomData;
|
||||
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::{Parameter, StorageMap, StorageValue, decl_module, decl_event, decl_storage, storage::child};
|
||||
use srml_support::traits::{OnFreeBalanceZero, OnUnbalanced, Currency};
|
||||
@@ -117,14 +118,94 @@ pub trait ComputeDispatchFee<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.
|
||||
/// This is the required info to cache for an account.
|
||||
pub struct AccountInfo {
|
||||
/// Unique ID for the subtree encoded as a byte.
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
#[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,
|
||||
/// The size of stored value in octet.
|
||||
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).
|
||||
@@ -384,6 +465,39 @@ decl_module! {
|
||||
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() {
|
||||
<GasSpent<T>>::kill();
|
||||
}
|
||||
@@ -420,6 +534,29 @@ decl_event! {
|
||||
|
||||
decl_storage! {
|
||||
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.
|
||||
TransferFee get(transfer_fee) config(): BalanceOf<T>;
|
||||
/// The fee required to create an account.
|
||||
@@ -444,8 +581,6 @@ decl_storage! {
|
||||
GasSpent get(gas_spent): T::Gas;
|
||||
/// Current cost schedule for contracts.
|
||||
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.
|
||||
pub PristineCode: map CodeHash<T> => Option<Vec<u8>>;
|
||||
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
|
||||
@@ -453,14 +588,16 @@ decl_storage! {
|
||||
/// The subtrie counter.
|
||||
pub AccountCounter: u64 = 0;
|
||||
/// 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> {
|
||||
fn on_free_balance_zero(who: &T::AccountId) {
|
||||
<CodeHashOf<T>>::remove(who);
|
||||
<AccountInfoOf<T>>::get(who).map(|info| child::kill_storage(&info.trie_id));
|
||||
if let Some(ContractInfo::Alive(info)) = <ContractInfoOf<T>>::get(who) {
|
||||
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)]
|
||||
|
||||
use runtime_io::with_externalities;
|
||||
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::account_db::{AccountDb, DirectAccountDb, OverlayAccountDb};
|
||||
use crate::{
|
||||
ContractAddressFor, GenesisConfig, Module, RawEvent,
|
||||
Trait, ComputeDispatchFee, TrieIdGenerator, TrieId,
|
||||
AccountInfo, AccountInfoOf, TrieIdFromParentCounter
|
||||
ComputeDispatchFee, ContractAddressFor, ContractInfo, ContractInfoOf, GenesisConfig, Module,
|
||||
RawAliveContractInfo, RawEvent, Trait, TrieId, TrieIdFromParentCounter, TrieIdGenerator,
|
||||
};
|
||||
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 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 {
|
||||
// Re-export contents of the root. This basically
|
||||
@@ -206,8 +207,14 @@ impl ExtBuilder {
|
||||
);
|
||||
t.extend(
|
||||
GenesisConfig::<Test> {
|
||||
transaction_base_fee: 0,
|
||||
transaction_byte_fee: 0,
|
||||
signed_claim_handicap: 2,
|
||||
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,
|
||||
creation_fee: self.creation_fee,
|
||||
contract_fee: 21,
|
||||
@@ -231,13 +238,7 @@ fn refunds_unused_gas() {
|
||||
with_externalities(&mut ExtBuilder::default().build(), || {
|
||||
Balances::deposit_creating(&0, 100_000_000);
|
||||
|
||||
assert_ok!(Contract::call(
|
||||
Origin::signed(0),
|
||||
1,
|
||||
0,
|
||||
100_000,
|
||||
Vec::new()
|
||||
));
|
||||
assert_ok!(Contract::call(Origin::signed(0), 1, 0, 100_000, Vec::new()));
|
||||
|
||||
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.
|
||||
{
|
||||
Balances::deposit_creating(&1, 110);
|
||||
AccountInfoOf::<Test>::insert(1, &AccountInfo {
|
||||
ContractInfoOf::<Test>::insert(1, &ContractInfo::Alive(RawAliveContractInfo {
|
||||
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);
|
||||
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());
|
||||
|
||||
Balances::deposit_creating(&2, 110);
|
||||
AccountInfoOf::<Test>::insert(2, &AccountInfo {
|
||||
ContractInfoOf::<Test>::insert(2, &ContractInfo::Alive(RawAliveContractInfo {
|
||||
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);
|
||||
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
|
||||
// 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), key2).is_none());
|
||||
|
||||
@@ -342,11 +348,7 @@ fn instantiate_and_call_and_deposit_event() {
|
||||
|| {
|
||||
Balances::deposit_creating(&ALICE, 1_000_000);
|
||||
|
||||
assert_ok!(Contract::put_code(
|
||||
Origin::signed(ALICE),
|
||||
100_000,
|
||||
wasm,
|
||||
));
|
||||
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||
|
||||
// Check at the end to get hash on error easily
|
||||
let creation = Contract::create(
|
||||
@@ -387,7 +389,7 @@ fn instantiate_and_call_and_deposit_event() {
|
||||
]);
|
||||
|
||||
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);
|
||||
|
||||
assert_ok!(Contract::put_code(
|
||||
Origin::signed(ALICE),
|
||||
100_000,
|
||||
wasm,
|
||||
));
|
||||
assert_ok!(Contract::put_code(Origin::signed(ALICE), 100_000, wasm));
|
||||
|
||||
// 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.
|
||||
@@ -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)]
|
||||
pub struct MockExt {
|
||||
storage: HashMap<StorageKey, Vec<u8>>,
|
||||
rent_allowance: u64,
|
||||
creates: Vec<CreateEntry>,
|
||||
transfers: Vec<TransferEntry>,
|
||||
dispatches: Vec<DispatchEntry>,
|
||||
@@ -281,6 +282,14 @@ mod tests {
|
||||
fn deposit_event(&mut self, data: Vec<u8>) {
|
||||
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>(
|
||||
|
||||
@@ -629,6 +629,31 @@ define_env!(Env, <E: Ext>,
|
||||
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.
|
||||
// Only available on `--dev` chains.
|
||||
// This function may be removed at any time, superseded by a more general contract debugging feature.
|
||||
|
||||
Reference in New Issue
Block a user