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:
thiolliere
2019-04-30 09:41:33 +02:00
committed by Sergei Pepyakin
parent f14580535e
commit c7d9ca379d
12 changed files with 1040 additions and 133 deletions
+1 -1
View File
@@ -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> {
+12
View File
@@ -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,
+7 -1
View File
@@ -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
);
});
}
+1 -1
View File
@@ -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,
};
+30 -2
View File
@@ -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.
+112 -49
View File
@@ -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) => {
+68 -25
View File
@@ -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(_));
});
}
}
+146 -9
View File
@@ -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);
}
}
+190
View File
@@ -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)
}
+439 -45
View File
@@ -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());
}
);
}
+9
View File
@@ -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.