diff --git a/Cargo.lock b/Cargo.lock index 0c6f0cfe49c10..dafb6e8161046 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2900,9 +2900,19 @@ dependencies = [ "assert_matches 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "parity-wasm 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", "pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.70 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-codec 0.1.0", + "substrate-runtime-consensus 0.1.0", + "substrate-runtime-io 0.1.0", + "substrate-runtime-primitives 0.1.0", "substrate-runtime-sandbox 0.1.0", + "substrate-runtime-session 0.1.0", + "substrate-runtime-staking 0.1.0", "substrate-runtime-std 0.1.0", + "substrate-runtime-support 0.1.0", + "substrate-runtime-system 0.1.0", + "substrate-runtime-timestamp 0.1.0", "wabt 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -3047,7 +3057,6 @@ dependencies = [ "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", - "substrate-runtime-contract 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-sandbox 0.1.0", diff --git a/demo/cli/src/lib.rs b/demo/cli/src/lib.rs index 60e6b00d7ee15..08834541f52bf 100644 --- a/demo/cli/src/lib.rs +++ b/demo/cli/src/lib.rs @@ -135,7 +135,6 @@ pub fn run(args: I) -> error::Result<()> where transaction_byte_fee: 1, transfer_fee: 0, creation_fee: 0, - contract_fee: 0, reclaim_rebate: 0, existential_deposit: 500, balances: vec![(god_key.clone().into(), 1u64 << 63)].into_iter().collect(), diff --git a/demo/executor/src/lib.rs b/demo/executor/src/lib.rs index 8a47fbf69c769..24ec4e5f573c9 100644 --- a/demo/executor/src/lib.rs +++ b/demo/executor/src/lib.rs @@ -205,7 +205,6 @@ mod tests { existential_deposit: 0, transfer_fee: 0, creation_fee: 0, - contract_fee: 0, reclaim_rebate: 0, early_era_slash: 0, session_reward: 0, @@ -248,7 +247,7 @@ mod tests { construct_block( 1, [69u8; 32].into(), - hex!("786071057714fdd6ea4595eecd4a0f327908d65f462ff5bca0f700fafce588c9").into(), + hex!("b97d52254fc967bb94bed485de6a738e9fad05decfda3453711677b8becf6d0a").into(), vec![BareExtrinsic { signed: alice(), index: 0, @@ -261,7 +260,7 @@ mod tests { construct_block( 2, block1().1, - hex!("a7f1259cc6b2fa758542f2996e737f8f0de9dec3a9d32641da348178f48b9fc2").into(), + hex!("a1f018d2faa339f72f5ee29050b4670d971e2e271cc06c41ee9cbe1f4c6feec9").into(), vec![ BareExtrinsic { signed: bob(), @@ -281,7 +280,7 @@ mod tests { construct_block( 1, [69u8; 32].into(), - hex!("d95fc2cf4541b97ed2cd381fe7a486af8aebad9ed0480c30e9cca184bb207e95").into(), + hex!("41d07010f49aa29b2c9aca542cbaa6f59aafd3dda53cdf711c51ddb7d386912e").into(), vec![BareExtrinsic { signed: alice(), index: 0, diff --git a/demo/runtime/src/lib.rs b/demo/runtime/src/lib.rs index 94c2610aa824d..3f5165ef2998b 100644 --- a/demo/runtime/src/lib.rs +++ b/demo/runtime/src/lib.rs @@ -130,8 +130,8 @@ pub type Session = session::Module; impl staking::Trait for Concrete { type Balance = Balance; - type DetermineContractAddress = BlakeTwo256; type AccountIndex = AccountIndex; + type OnAccountKill = (); } /// Staking module for this concrete runtime. diff --git a/demo/runtime/wasm/Cargo.lock b/demo/runtime/wasm/Cargo.lock index 395c160082021..d5eac31f66037 100644 --- a/demo/runtime/wasm/Cargo.lock +++ b/demo/runtime/wasm/Cargo.lock @@ -516,16 +516,6 @@ dependencies = [ name = "pwasm-libc" version = "0.1.0" -[[package]] -name = "pwasm-utils" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "quote" version = "0.6.3" @@ -759,17 +749,6 @@ dependencies = [ "substrate-runtime-system 0.1.0", ] -[[package]] -name = "substrate-runtime-contract" -version = "0.1.0" -dependencies = [ - "parity-wasm 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "substrate-codec 0.1.0", - "substrate-runtime-sandbox 0.1.0", - "substrate-runtime-std 0.1.0", -] - [[package]] name = "substrate-runtime-council" version = "0.1.0" @@ -902,7 +881,6 @@ dependencies = [ "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", - "substrate-runtime-contract 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-sandbox 0.1.0", @@ -1215,7 +1193,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0" "checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892" "checksum proc-macro2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1fa93823f53cfd0f5ac117b189aed6cfdfb2cfc0a9d82e956dd7927595ed7d46" -"checksum pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd695333cfae6e9dbe2703a6d040e252b57a6fc3b9a65c712615ac042b2e0c5" "checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035" "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm index 7d546485a1c9a..11a41785736d2 100644 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.compact.wasm differ diff --git a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm index 4dad2b4000977..54e26234cd5ef 100755 Binary files a/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm and b/demo/runtime/wasm/target/wasm32-unknown-unknown/release/demo_runtime.wasm differ diff --git a/polkadot/runtime/src/lib.rs b/polkadot/runtime/src/lib.rs index 9ccafe92a9aff..7c366e30e7a8a 100644 --- a/polkadot/runtime/src/lib.rs +++ b/polkadot/runtime/src/lib.rs @@ -110,7 +110,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: ver_str!("polkadot"), impl_name: ver_str!("parity-polkadot"), authoring_version: 1, - spec_version: 1, + spec_version: 2, impl_version: 0, }; @@ -168,8 +168,8 @@ pub type Session = session::Module; impl staking::Trait for Concrete { type Balance = Balance; - type DetermineContractAddress = BlakeTwo256; type AccountIndex = AccountIndex; + type OnAccountKill = (); } /// Staking module for this concrete runtime. pub type Staking = staking::Module; diff --git a/polkadot/runtime/wasm/Cargo.lock b/polkadot/runtime/wasm/Cargo.lock index aa789a6e692c2..b90d8e14e89d8 100644 --- a/polkadot/runtime/wasm/Cargo.lock +++ b/polkadot/runtime/wasm/Cargo.lock @@ -516,16 +516,6 @@ dependencies = [ name = "pwasm-libc" version = "0.1.0" -[[package]] -name = "pwasm-utils" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parity-wasm 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "quote" version = "0.6.3" @@ -759,17 +749,6 @@ dependencies = [ "substrate-runtime-system 0.1.0", ] -[[package]] -name = "substrate-runtime-contract" -version = "0.1.0" -dependencies = [ - "parity-wasm 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", - "pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "substrate-codec 0.1.0", - "substrate-runtime-sandbox 0.1.0", - "substrate-runtime-std 0.1.0", -] - [[package]] name = "substrate-runtime-council" version = "0.1.0" @@ -902,7 +881,6 @@ dependencies = [ "substrate-keyring 0.1.0", "substrate-primitives 0.1.0", "substrate-runtime-consensus 0.1.0", - "substrate-runtime-contract 0.1.0", "substrate-runtime-io 0.1.0", "substrate-runtime-primitives 0.1.0", "substrate-runtime-sandbox 0.1.0", @@ -1215,7 +1193,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum proc-macro-hack 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ba8d4f9257b85eb6cdf13f055cea3190520aab1409ca2ab43493ea4820c25f0" "checksum proc-macro-hack-impl 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d5cb6f960ad471404618e9817c0e5d10b1ae74cfdf01fab89ea0641fe7fb2892" "checksum proc-macro2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1fa93823f53cfd0f5ac117b189aed6cfdfb2cfc0a9d82e956dd7927595ed7d46" -"checksum pwasm-utils 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "efd695333cfae6e9dbe2703a6d040e252b57a6fc3b9a65c712615ac042b2e0c5" "checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035" "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm index 8ce040fb8a26a..d67b5d7585cfc 100644 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.compact.wasm differ diff --git a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm index a0ab536128cb9..214dcdae5f2b9 100755 Binary files a/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm and b/polkadot/runtime/wasm/target/wasm32-unknown-unknown/release/polkadot_runtime.wasm differ diff --git a/polkadot/service/src/chain_spec.rs b/polkadot/service/src/chain_spec.rs index 0c734f5712f33..dcf487de0bd2d 100644 --- a/polkadot/service/src/chain_spec.rs +++ b/polkadot/service/src/chain_spec.rs @@ -57,7 +57,6 @@ fn staging_testnet_config_genesis() -> GenesisConfig { existential_deposit: 500, transfer_fee: 0, creation_fee: 0, - contract_fee: 0, reclaim_rebate: 0, early_era_slash: 10000, session_reward: 100, @@ -133,7 +132,6 @@ fn testnet_genesis(initial_authorities: Vec) -> GenesisConfig { existential_deposit: 500, transfer_fee: 0, creation_fee: 0, - contract_fee: 0, reclaim_rebate: 0, balances: endowed_accounts.iter().map(|&k|(k, (1u128 << 60))).collect(), validator_count: 2, diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm index ee39736edf763..bd930a1e4cc7a 100644 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.compact.wasm differ diff --git a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm index a48cfe38df266..15312bde3b2a0 100755 Binary files a/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm and b/substrate/executor/wasm/target/wasm32-unknown-unknown/release/runtime_test.wasm differ diff --git a/substrate/runtime/contract/Cargo.toml b/substrate/runtime/contract/Cargo.toml index f8226d9c35670..23291d4d67e48 100644 --- a/substrate/runtime/contract/Cargo.toml +++ b/substrate/runtime/contract/Cargo.toml @@ -4,9 +4,19 @@ version = "0.1.0" authors = ["Parity Technologies "] [dependencies] +serde = { version = "1.0", default_features = false } +serde_derive = { version = "1.0", optional = true } substrate-codec = { path = "../../codec", default_features = false } +substrate-runtime-consensus = { path = "../../runtime/consensus", default_features = false } +substrate-runtime-primitives = { path = "../../runtime/primitives" } +substrate-runtime-io = { path = "../../runtime-io", default_features = false } substrate-runtime-std = { path = "../../runtime-std", default_features = false } substrate-runtime-sandbox = { path = "../../runtime-sandbox", default_features = false } +substrate-runtime-staking = { path = "../../runtime/staking", default_features = false } +substrate-runtime-support = { path = "../../runtime-support", default_features = false } +substrate-runtime-system = { path = "../../runtime/system", default_features = false } +substrate-runtime-session = { path = "../session", default_features = false } +substrate-runtime-timestamp = { path = "../timestamp", default_features = false } parity-wasm = { version = "0.31", default_features = false } pwasm-utils = { version = "0.3", default_features = false } @@ -17,9 +27,19 @@ assert_matches = "1.1" [features] default = ["std"] std = [ + "serde_derive", + "serde/std", "substrate-codec/std", + "substrate-runtime-primitives/std", + "substrate-runtime-consensus/std", + "substrate-runtime-io/std", "substrate-runtime-std/std", "substrate-runtime-sandbox/std", + "substrate-runtime-staking/std", + "substrate-runtime-support/std", + "substrate-runtime-system/std", + "substrate-runtime-timestamp/std", + "substrate-runtime-session/std", "parity-wasm/std", "pwasm-utils/std", ] diff --git a/substrate/runtime/staking/src/account_db.rs b/substrate/runtime/contract/src/account_db.rs similarity index 54% rename from substrate/runtime/staking/src/account_db.rs rename to substrate/runtime/contract/src/account_db.rs index b41ce55458e65..efa5c150c1ddd 100644 --- a/substrate/runtime/staking/src/account_db.rs +++ b/substrate/runtime/contract/src/account_db.rs @@ -1,27 +1,29 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// This file is part of Substrate Demo. +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. -// Substrate Demo is free software: you can redistribute it and/or modify +// 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 Demo is distributed in the hope that it will be useful, +// 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 Demo. If not, see . +// along with Substrate. If not, see . //! Auxilliaries to help with managing partial changes to accounts state. -use rstd::prelude::*; +use super::{CodeOf, StorageOf, Trait}; +use double_map::StorageDoubleMap; use rstd::cell::RefCell; use rstd::collections::btree_map::{BTreeMap, Entry}; +use rstd::prelude::*; use runtime_support::StorageMap; -use double_map::StorageDoubleMap; -use super::*; +use staking; +use system; pub struct ChangeEntry { balance: Option, @@ -40,23 +42,14 @@ impl Default for ChangeEntry { } } -impl ChangeEntry { - pub fn contract_created(b: T::Balance, c: Vec) -> Self { - ChangeEntry { balance: Some(b), code: Some(c), storage: Default::default() } - } - pub fn balance_changed(b: T::Balance) -> Self { - ChangeEntry { balance: Some(b), code: None, storage: Default::default() } - } -} - -pub type State = BTreeMap<::AccountId, ChangeEntry>; +pub type ChangeSet = BTreeMap<::AccountId, ChangeEntry>; pub trait AccountDb { fn get_storage(&self, account: &T::AccountId, location: &[u8]) -> Option>; fn get_code(&self, account: &T::AccountId) -> Vec; fn get_balance(&self, account: &T::AccountId) -> T::Balance; - fn merge(&mut self, state: State); + fn commit(&mut self, change_set: ChangeSet); } pub struct DirectAccountDb; @@ -68,37 +61,18 @@ impl AccountDb for DirectAccountDb { >::get(account) } fn get_balance(&self, account: &T::AccountId) -> T::Balance { - >::get(account) + staking::Module::::free_balance(account) } - fn merge(&mut self, s: State) { - let ed = >::existential_deposit(); + fn commit(&mut self, s: ChangeSet) { for (address, changed) in s.into_iter() { if let Some(balance) = changed.balance { - // If the balance is too low, then the account is reaped. - // NOTE: There are two balances for every account: `reserved_balance` and - // `free_balance`. This contract subsystem only cares about the latter: whenever - // the term "balance" is used *here* it should be assumed to mean "free balance" - // in the rest of the module. - // Free balance can never be less than ED. If that happens, it gets reduced to zero - // and the account information relevant to this subsystem is deleted (i.e. the - // account is reaped). - // NOTE: This is orthogonal to the `Bondage` value that an account has, a high - // value of which makes even the `free_balance` unspendable. - // TODO: enforce this for the other balance-altering functions. - if balance < ed { - >::on_free_too_low(&address); + if let staking::UpdateBalanceOutcome::AccountKilled = + staking::Module::::set_free_balance_creating(&address, balance) + { + // Account killed. This will ultimately lead to calling `OnAccountKill` callback + // which will make removal of CodeOf and StorageOf for this account. + // In order to avoid writing over the deleted properties we `continue` here. continue; - } else { - if !>::exists(&address) { - let outcome = >::new_account(&address, balance); - let credit = match outcome { - NewAccountOutcome::GoodHint => balance + >::reclaim_rebate(), - _ => balance, - }; - >::insert(&address, credit); - } else { - >::insert(&address, balance); - } } } if let Some(code) = changed.code { @@ -116,22 +90,27 @@ impl AccountDb for DirectAccountDb { } pub struct OverlayAccountDb<'a, T: Trait + 'a> { - local: RefCell>, + local: RefCell>, underlying: &'a AccountDb, } impl<'a, T: Trait> OverlayAccountDb<'a, T> { pub fn new(underlying: &'a AccountDb) -> OverlayAccountDb<'a, T> { OverlayAccountDb { - local: RefCell::new(State::new()), + local: RefCell::new(ChangeSet::new()), underlying, } } - pub fn into_state(self) -> State { + pub fn into_change_set(self) -> ChangeSet { self.local.into_inner() } - fn set_storage(&mut self, account: &T::AccountId, location: Vec, value: Option>) { + pub fn set_storage( + &mut self, + account: &T::AccountId, + location: Vec, + value: Option>, + ) { self.local .borrow_mut() .entry(account.clone()) @@ -139,6 +118,13 @@ impl<'a, T: Trait> OverlayAccountDb<'a, T> { .storage .insert(location, value); } + pub fn set_code(&mut self, account: &T::AccountId, code: Vec) { + self.local + .borrow_mut() + .entry(account.clone()) + .or_insert(Default::default()) + .code = Some(code); + } pub fn set_balance(&mut self, account: &T::AccountId, balance: T::Balance) { self.local .borrow_mut() @@ -171,7 +157,7 @@ impl<'a, T: Trait> AccountDb for OverlayAccountDb<'a, T> { .and_then(|a| a.balance) .unwrap_or_else(|| self.underlying.get_balance(account)) } - fn merge(&mut self, s: State) { + fn commit(&mut self, s: ChangeSet) { let mut local = self.local.borrow_mut(); for (address, changed) in s.into_iter() { @@ -193,33 +179,3 @@ impl<'a, T: Trait> AccountDb for OverlayAccountDb<'a, T> { } } } - -pub(crate) struct StakingExt<'a, 'b: 'a, T: Trait + 'b> { - pub account_db: &'a mut OverlayAccountDb<'b, T>, - pub account: T::AccountId, -} -impl<'a, 'b: 'a, T: Trait> contract::Ext for StakingExt<'a, 'b, T> { - type AccountId = T::AccountId; - type Balance = T::Balance; - - fn get_storage(&self, key: &[u8]) -> Option> { - self.account_db.get_storage(&self.account, key) - } - fn set_storage(&mut self, key: &[u8], value: Option>) { - self.account_db.set_storage(&self.account, key.to_vec(), value); - } - fn create(&mut self, code: &[u8], value: Self::Balance) { - if let Ok(Some(commit_state)) = - Module::::effect_create(&self.account, code, value, self.account_db) - { - self.account_db.merge(commit_state); - } - } - fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance) { - if let Ok(Some(commit_state)) = - Module::::effect_transfer(&self.account, to, value, self.account_db) - { - self.account_db.merge(commit_state); - } - } -} diff --git a/substrate/runtime/staking/src/double_map.rs b/substrate/runtime/contract/src/double_map.rs similarity index 100% rename from substrate/runtime/staking/src/double_map.rs rename to substrate/runtime/contract/src/double_map.rs diff --git a/substrate/runtime/contract/src/exec.rs b/substrate/runtime/contract/src/exec.rs new file mode 100644 index 0000000000000..403fe050e6e13 --- /dev/null +++ b/substrate/runtime/contract/src/exec.rs @@ -0,0 +1,259 @@ +// Copyright 2018 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 . + +use super::{CodeOf, ContractAddressFor, Module, Trait}; +use account_db::{AccountDb, OverlayAccountDb}; +use gas::GasMeter; +use vm; + +use rstd::prelude::*; +use runtime_primitives::traits::{Zero, CheckedAdd, CheckedSub}; +use runtime_support::StorageMap; +use staking; +use system; + +pub struct CreateReceipt { + pub address: T::AccountId, +} + +pub struct CallReceipt { + pub return_data: Vec, +} + +pub struct ExecutionContext<'a, T: Trait + 'a> { + // typically should be dest + pub self_account: T::AccountId, + pub overlay: OverlayAccountDb<'a, T>, + pub depth: usize, +} + +impl<'a, T: Trait> ExecutionContext<'a, T> { + /// Make a call to the specified address. + pub fn call( + &mut self, + caller: T::AccountId, + dest: T::AccountId, + value: T::Balance, + gas_meter: &mut GasMeter, + _data: &[u8], + ) -> Result { + let dest_code = >::get(&dest); + + // TODO: check the new depth + + let call_base_fee = >::call_base_fee(); + if gas_meter.charge(call_base_fee).is_out_of_gas() { + return Err("not enough gas to pay base call fee"); + } + + let (exec_result, change_set) = { + let mut overlay = OverlayAccountDb::new(&self.overlay); + + if value > T::Balance::zero() { + transfer( + gas_meter, + false, + &self.self_account, + &dest, + value, + &mut overlay, + )?; + } + + let mut nested = ExecutionContext { + overlay: overlay, + self_account: dest.clone(), + depth: self.depth + 1, + }; + let exec_result = if !dest_code.is_empty() { + vm::execute( + &dest_code, + &mut CallContext { + ctx: &mut nested, + _caller: caller, + }, + gas_meter, + ).map_err(|_| "vm execute returned error while call")? + } else { + // that was a plain transfer + vm::ExecutionResult { + return_data: Vec::new(), + } + }; + + (exec_result, nested.overlay.into_change_set()) + }; + + self.overlay.commit(change_set); + + Ok(CallReceipt { + return_data: exec_result.return_data, + }) + } + + pub fn create( + &mut self, + caller: T::AccountId, + endowment: T::Balance, + gas_meter: &mut GasMeter, + ctor: &[u8], + _data: &[u8], + ) -> Result, &'static str> { + let create_base_fee = >::create_base_fee(); + if gas_meter.charge(create_base_fee).is_out_of_gas() { + return Err("not enough gas to pay base create fee"); + } + + let dest = T::DetermineContractAddress::contract_address_for(ctor, &self.self_account); + if >::exists(&dest) { + // TODO: Is it enough? + return Err("contract already exists"); + } + + let change_set = { + let mut overlay = OverlayAccountDb::new(&self.overlay); + + if endowment > T::Balance::zero() { + transfer( + gas_meter, + true, + &self.self_account, + &dest, + endowment, + &mut overlay, + )?; + } + + let mut nested = ExecutionContext { + overlay: overlay, + self_account: dest.clone(), + depth: self.depth + 1, + }; + let exec_result = { + vm::execute( + ctor, + &mut CallContext { + ctx: &mut nested, + _caller: caller, + }, + gas_meter, + ).map_err(|_| "vm execute returned error while create")? + }; + + nested.overlay.set_code(&dest, exec_result.return_data); + nested.overlay.into_change_set() + }; + + self.overlay.commit(change_set); + + Ok(CreateReceipt { + address: dest, + }) + } +} + +fn transfer( + gas_meter: &mut GasMeter, + contract_create: bool, + transactor: &T::AccountId, + dest: &T::AccountId, + value: T::Balance, + overlay: &mut OverlayAccountDb, +) -> Result<(), &'static str> { + let would_create = overlay.get_balance(transactor).is_zero(); + + let fee: T::Balance = if contract_create { + >::contract_fee() + } else { + if would_create { + >::creation_fee() + } else { + >::transfer_fee() + } + }; + + if gas_meter.charge_by_balance(fee).is_out_of_gas() { + return Err("not enough gas to pay transfer fee"); + } + + let from_balance = overlay.get_balance(transactor); + let new_from_balance = match from_balance.checked_sub(&value) { + Some(b) => b, + None => return Err("balance too low to send value"), + }; + if would_create && value < >::existential_deposit() { + return Err("value too low to create account"); + } + if >::bondage(transactor) > >::block_number() { + return Err("bondage too high to send value"); + } + + let to_balance = overlay.get_balance(dest); + let new_to_balance = match to_balance.checked_add(&value) { + Some(b) => b, + None => return Err("destination balance too high to receive value"), + }; + + if transactor != dest { + overlay.set_balance(transactor, new_from_balance); + overlay.set_balance(dest, new_to_balance); + } + + Ok(()) +} + +struct CallContext<'a, 'b: 'a, T: Trait + 'b> { + ctx: &'a mut ExecutionContext<'b, T>, + _caller: T::AccountId, +} + +impl<'a, 'b: 'a, T: Trait + 'b> vm::Ext for CallContext<'a, 'b, T> { + fn get_storage(&self, key: &[u8]) -> Option> { + self.ctx.overlay.get_storage(&self.ctx.self_account, key) + } + + fn set_storage(&mut self, key: &[u8], value: Option>) { + self.ctx + .overlay + .set_storage(&self.ctx.self_account, key.to_vec(), value) + } + + fn create( + &mut self, + code: &[u8], + endowment: T::Balance, + gas_meter: &mut GasMeter, + data: &[u8], + ) -> Result, ()> { + let caller = self.ctx.self_account.clone(); + self.ctx + .create(caller, endowment, gas_meter, code, &data) + .map_err(|_| ()) + } + + fn call( + &mut self, + to: &T::AccountId, + value: T::Balance, + gas_meter: &mut GasMeter, + data: &[u8], + ) -> Result { + let caller = self.ctx.self_account.clone(); + self.ctx + .call(caller, to.clone(), value, gas_meter, data) + .map_err(|_| ()) + } +} diff --git a/substrate/runtime/contract/src/gas.rs b/substrate/runtime/contract/src/gas.rs new file mode 100644 index 0000000000000..662ddc4fbd3ad --- /dev/null +++ b/substrate/runtime/contract/src/gas.rs @@ -0,0 +1,150 @@ +// Copyright 2018 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 . + +use {Trait, Module}; +use runtime_primitives::traits::{As, CheckedMul, CheckedSub, Zero}; +use staking; + +#[must_use] +#[derive(Debug, PartialEq, Eq)] +pub enum GasMeterResult { + Proceed, + OutOfGas, +} + +impl GasMeterResult { + pub fn is_out_of_gas(&self) -> bool { + match *self { + GasMeterResult::OutOfGas => true, + GasMeterResult::Proceed => false, + } + } +} + +pub struct GasMeter { + gas_left: T::Gas, + gas_price: T::Balance, +} +impl GasMeter { + #[cfg(test)] + pub fn with_limit(gas_limit: T::Gas, gas_price: T::Balance) -> GasMeter { + GasMeter { + gas_left: gas_limit, + gas_price, + } + } + + /// Account for used gas. + /// + /// Returns `OutOfGas` if there is not enough gas or addition of the specified + /// amount of gas has lead to overflow. On success returns `Proceed`. + /// + /// NOTE that `amount` is always consumed, i.e. if there is not enough gas + /// then the counter will be set to zero. + pub fn charge(&mut self, amount: T::Gas) -> GasMeterResult { + let new_value = match self.gas_left.checked_sub(&amount) { + None => None, + Some(val) if val.is_zero() => None, + Some(val) => Some(val), + }; + + // We always consume the gas even if there is not enough gas. + self.gas_left = new_value.unwrap_or_else(Zero::zero); + + match new_value { + Some(_) => GasMeterResult::Proceed, + None => GasMeterResult::OutOfGas, + } + } + + /// Account for used gas expressed in balance units. + /// + /// Same as [`charge`], but amount to be charged is converted from units of balance to + /// units of gas. + /// + /// [`charge`]: #method.charge + pub fn charge_by_balance(&mut self, amount: T::Balance) -> GasMeterResult { + let amount_in_gas: T::Balance = amount / self.gas_price; + let amount_in_gas: T::Gas = >::sa(amount_in_gas); + self.charge(amount_in_gas) + } + + /// Allocate some amount of gas and perform some work with + /// a newly created nested gas meter. + /// + /// Invokes `f` with either the gas meter that has `amount` gas left or + /// with `None`, if this gas meter has not enough gas to allocate given `amount`. + /// + /// All unused gas in the nested gas meter is returned to this gas meter. + pub fn with_nested>) -> R>( + &mut self, + amount: T::Gas, + f: F, + ) -> R { + // NOTE that it is ok to allocate all available gas since it still ensured + // by `charge` that it doesn't reach zero. + if self.gas_left < amount { + f(None) + } else { + self.gas_left = self.gas_left - amount; + let mut nested = GasMeter { + gas_left: amount, + gas_price: self.gas_price, + }; + + let r = f(Some(&mut nested)); + + self.gas_left = self.gas_left + nested.gas_left; + + r + } + } + + /// Returns how much gas left from the initial budget. + pub fn gas_left(&self) -> T::Gas { + self.gas_left + } +} + +/// Buy the given amount of gas. +/// +/// Cost is calculated by multiplying the gas cost (taken from the storage) by the `gas_limit`. +/// The funds are deducted from `transactor`. +pub fn buy_gas( + transactor: &T::AccountId, + gas_limit: T::Gas, +) -> Result, &'static str> { + let gas_price = >::gas_price(); + let b = >::free_balance(transactor); + let cost = >::as_(gas_limit.clone()) + .checked_mul(&gas_price) + .ok_or("overflow multiplying gas limit by price")?; + if b < cost + >::existential_deposit() { + return Err("not enough funds for transaction fee"); + } + >::set_free_balance(transactor, b - cost); + Ok(GasMeter { + gas_left: gas_limit, + gas_price, + }) +} + +/// Refund the unused gas. +pub fn refund_unused_gas(transactor: &T::AccountId, gas_meter: GasMeter) { + let b = >::free_balance(transactor); + let refund = >::as_(gas_meter.gas_left) * gas_meter.gas_price; + >::set_free_balance(transactor, b + refund); +} diff --git a/substrate/runtime/contract/src/genesis_config.rs b/substrate/runtime/contract/src/genesis_config.rs new file mode 100644 index 0000000000000..d841f22806a6a --- /dev/null +++ b/substrate/runtime/contract/src/genesis_config.rs @@ -0,0 +1,48 @@ +// Copyright 2018 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 . + +//! Build the contract module part of the genesis block storage. + +use {Trait, ContractFee, CallBaseFee, CreateBaseFee, GasPrice, MaxDepth}; + +use runtime_primitives; +use runtime_io::{self, twox_128}; +use runtime_support::StorageValue; +use codec::Encode; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct GenesisConfig { + pub contract_fee: T::Balance, + pub call_base_fee: T::Gas, + pub create_base_fee: T::Gas, + pub gas_price: T::Balance, + pub max_depth: u32, +} + +impl runtime_primitives::BuildStorage for GenesisConfig { + fn build_storage(self) -> Result { + let r: runtime_io::TestExternalities = map![ + twox_128(>::key()).to_vec() => self.contract_fee.encode(), + twox_128(>::key()).to_vec() => self.call_base_fee.encode(), + twox_128(>::key()).to_vec() => self.create_base_fee.encode(), + twox_128(>::key()).to_vec() => self.gas_price.encode(), + twox_128(>::key()).to_vec() => self.max_depth.encode() + ]; + Ok(r) + } +} diff --git a/substrate/runtime/contract/src/lib.rs b/substrate/runtime/contract/src/lib.rs index 92156444660f2..123574b27c0ac 100644 --- a/substrate/runtime/contract/src/lib.rs +++ b/substrate/runtime/contract/src/lib.rs @@ -14,637 +14,241 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -//! Crate for executing smart-contracts. +//! Smart-contract module for runtime; Allows deployment and execution of smart-contracts +//! expressed in WebAssembly. //! -//! It provides an means for executing contracts represented in WebAssembly (Wasm for short). -//! Contracts are able to create other contracts, transfer funds to each other and operate on a simple key-value storage. +//! This module provides an ability to create smart-contract accounts and send them messages. +//! A smart-contract is an account with associated code and storage. When such an account receives a message, +//! the code associated with that account gets executed. +//! +//! The code is allowed to alter the storage entries of the associated account, +//! create smart-contracts or send messages to existing smart-contracts. +//! +//! For any actions invoked by the smart-contracts fee must be paid. The fee is paid in gas. +//! Gas is bought upfront. Any unused is refunded after the transaction (regardless of the +//! execution outcome). If all gas is used, then changes made for the specific call or create +//! are reverted (including balance transfers). +//! +//! Failures are typically not cascading. That, for example, means that if contract A calls B and B errors +//! somehow, then A can decide if it should proceed or error. +//! TODO: That is not the case now, since call/create externalities traps on any error now. #![cfg_attr(not(feature = "std"), no_std)] -#![warn(missing_docs)] + +#[cfg(feature = "std")] +#[macro_use] +extern crate serde_derive; + +#[cfg(feature = "std")] +extern crate serde; extern crate parity_wasm; extern crate pwasm_utils; -extern crate substrate_runtime_std as rstd; -extern crate substrate_runtime_sandbox as sandbox; extern crate substrate_codec as codec; +extern crate substrate_runtime_io as runtime_io; +extern crate substrate_runtime_sandbox as sandbox; -#[cfg(test)] -#[macro_use] -extern crate assert_matches; - -#[cfg(test)] -extern crate wabt; +#[cfg_attr(feature = "std", macro_use)] +extern crate substrate_runtime_std as rstd; -use rstd::prelude::*; -use codec::{Codec, Decode}; +extern crate substrate_runtime_consensus as consensus; +extern crate substrate_runtime_staking as staking; +extern crate substrate_runtime_system as system; -use parity_wasm::elements::{self, External, MemoryType}; -use pwasm_utils::rules; +#[cfg(test)] +extern crate substrate_runtime_timestamp as timestamp; +#[cfg(test)] +extern crate substrate_runtime_session as session; -/// An interface that provides an access to the external environment in which the -/// smart-contract is executed. -/// -/// This interface is specialised to an account of the executing code, so all -/// operations are implicitly performed on that account. -pub trait Ext { - /// The indentifier of an account. - type AccountId: Codec + Clone; - /// The balance of an account. - type Balance: Codec; - - /// Returns the storage entry of the executing account by the given key. - fn get_storage(&self, key: &[u8]) -> Option>; - - /// Sets the storage entry by the given key to the specified value. - fn set_storage(&mut self, key: &[u8], value: Option>); - - // TODO: Return the address of the created contract. - /// Create a new account for a contract. - /// - /// The newly created account will be associated with the `code`. `value` specifies the amount of value - /// transfered from this to the newly created account. - fn create(&mut self, code: &[u8], value: Self::Balance); +#[macro_use] +extern crate substrate_runtime_support as runtime_support; - /// Transfer some funds to the specified account. - fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance); -} +extern crate substrate_runtime_primitives as runtime_primitives; -/// Error that can occur while preparing or executing wasm smart-contract. -#[derive(Debug, PartialEq, Eq)] -pub enum Error { - /// Error happened while serializing the module. - Serialization, +#[cfg(test)] +#[macro_use] +extern crate assert_matches; - /// Error happened while deserializing the module. - Deserialization, +#[cfg(test)] +extern crate wabt; - /// Internal memory declaration has been found in the module. - InternalMemoryDeclared, +mod account_db; +mod double_map; +mod exec; +mod vm; +mod gas; +mod genesis_config; - /// Gas instrumentation failed. - /// - /// This most likely indicates the module isn't valid. - GasInstrumentation, +#[cfg(test)] +mod tests; - /// Stack instrumentation failed. - /// - /// This most likely indicates the module isn't valid. - StackHeightInstrumentation, +pub use genesis_config::GenesisConfig; +use exec::ExecutionContext; +use account_db::{AccountDb, OverlayAccountDb}; +use double_map::StorageDoubleMap; - /// Error happened during invocation of the contract's entrypoint. - /// - /// Most likely because of trap. - Invoke, +use codec::Codec; +use runtime_primitives::traits::{As, RefInto, SimpleArithmetic}; +use runtime_support::dispatch::Result; +use runtime_support::{Parameter, StorageMap}; - /// Error happened during instantiation. - /// - /// This might indicate that `start` function trapped, or module isn't - /// instantiable and/or unlinkable. - Instantiate, +pub trait Trait: system::Trait + staking::Trait + consensus::Trait { + /// Function type to get the contract address given the creator. + type DetermineContractAddress: ContractAddressFor; - /// Memory creation error. - /// - /// This might happen when the memory import has invalid descriptor or - /// requested too much resources. - Memory, + // As is needed for wasm-utils + type Gas: Parameter + Codec + SimpleArithmetic + Copy + As + As + As; } -struct Runtime<'a, T: Ext + 'a> { - ext: &'a mut T, - memory: sandbox::Memory, - gas_used: u64, - gas_limit: u64, -} -impl<'a, T: Ext + 'a> Runtime<'a, T> { - fn memory(&self) -> &sandbox::Memory { - &self.memory - } - fn ext(&self) -> &T { - self.ext - } - fn ext_mut(&mut self) -> &mut T { - self.ext - } - /// Account for used gas. - /// - /// Returns `false` if there is not enough gas or addition of the specified - /// amount of gas has lead to overflow. On success returns `true`. - /// - /// Intuition about the return value sense is to answer the question 'are we allowed to continue?' - fn charge_gas(&mut self, amount: u64) -> bool { - match self.gas_used.checked_add(amount) { - None => false, - Some(val) if val > self.gas_limit => false, - Some(val) => { - self.gas_used = val; - true - } - } - } +pub trait ContractAddressFor { + fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId; } -/// Execute the given code as a contract. -pub fn execute<'a, T: Ext>( - code: &[u8], - ext: &'a mut T, - gas_limit: u64, -) -> Result<(), Error> { - // ext_gas(amount: u32) - // - // Account for used gas. Traps if gas used is greater than gas limit. - // - // - amount: How much gas is used. - fn ext_gas(e: &mut Runtime, args: &[sandbox::TypedValue]) -> Result { - let amount = args[0].as_i32().unwrap() as u32; - if e.charge_gas(amount as u64) { - Ok(sandbox::ReturnValue::Unit) - } else { - Err(sandbox::HostError) - } +decl_module! { + /// Contracts module. + pub struct Module; + + #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + pub enum Call where aux: T::PublicAux { + // TODO: Change AccountId to staking::Address + fn call( + aux, + dest: T::AccountId, + value: T::Balance, + gas_limit: T::Gas, + data: Vec + ) -> Result = 0; + + fn create( + aux, + value: T::Balance, + gas_limit: T::Gas, + ctor: Vec, + data: Vec + ) -> Result = 1; } - - // ext_put_storage(location_ptr: u32, value_non_null: u32, value_ptr: u32); - // - // Change the value at the given location in storage or remove it. - // - // - location_ptr: pointer into the linear - // memory where the location of the requested value is placed. - // - value_non_null: if set to 0, then the entry - // at the given location will be removed. - // - value_ptr: pointer into the linear memory - // where the value to set is placed. If `value_non_null` is set to 0, then this parameter is ignored. - fn ext_set_storage( - e: &mut Runtime, - args: &[sandbox::TypedValue], - ) -> Result { - let location_ptr = args[0].as_i32().unwrap() as u32; - let value_non_null = args[1].as_i32().unwrap() as u32; - let value_ptr = args[2].as_i32().unwrap() as u32; - - let mut location = [0; 32]; - - e.memory().get(location_ptr, &mut location)?; - - let value = if value_non_null != 0 { - let mut value = [0; 32]; - e.memory().get(value_ptr, &mut value)?; - Some(value.to_vec()) - } else { - None - }; - e.ext_mut().set_storage( - &location, - value, - ); - - Ok(sandbox::ReturnValue::Unit) - } - - // ext_get_storage(location_ptr: u32, dest_ptr: u32); - // - // Retrieve the value at the given location from the strorage. - // If there is no entry at the given location then all-zero-value - // will be returned. - // - // - location_ptr: pointer into the linear - // memory where the location of the requested value is placed. - // - dest_ptr: pointer where contents of the specified storage location - // should be placed. - fn ext_get_storage(e: &mut Runtime, args: &[sandbox::TypedValue]) -> Result { - let location_ptr = args[0].as_i32().unwrap() as u32; - let dest_ptr = args[1].as_i32().unwrap() as u32; - - let mut location = [0; 32]; - e.memory().get(location_ptr, &mut location)?; - - if let Some(value) = e.ext().get_storage(&location) { - e.memory().set(dest_ptr, &value)?; - } else { - e.memory().set(dest_ptr, &[0u8; 32])?; - } - - Ok(sandbox::ReturnValue::Unit) - } - - // ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) - fn ext_transfer(e: &mut Runtime, args: &[sandbox::TypedValue]) -> Result { - let transfer_to_ptr = args[0].as_i32().unwrap() as u32; - let transfer_to_len = args[1].as_i32().unwrap() as u32; - let value_ptr = args[2].as_i32().unwrap() as u32; - let value_len = args[3].as_i32().unwrap() as u32; - - let mut transfer_to = Vec::new(); - transfer_to.resize(transfer_to_len as usize, 0); - e.memory().get(transfer_to_ptr, &mut transfer_to)?; - let transfer_to = T::AccountId::decode(&mut &transfer_to[..]).unwrap(); - - let mut value_buf = Vec::new(); - value_buf.resize(value_len as usize, 0); - e.memory().get(value_ptr, &mut value_buf)?; - let value = T::Balance::decode(&mut &value_buf[..]).unwrap(); - - e.ext_mut().transfer(&transfer_to, value); - - Ok(sandbox::ReturnValue::Unit) - } - - // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) - fn ext_create(e: &mut Runtime, args: &[sandbox::TypedValue]) -> Result { - let code_ptr = args[0].as_i32().unwrap() as u32; - let code_len = args[1].as_i32().unwrap() as u32; - let value_ptr = args[2].as_i32().unwrap() as u32; - let value_len = args[3].as_i32().unwrap() as u32; - - let mut value_buf = Vec::new(); - value_buf.resize(value_len as usize, 0); - e.memory().get(value_ptr, &mut value_buf)?; - let value = T::Balance::decode(&mut &value_buf[..]).unwrap(); - - let mut code = Vec::new(); - code.resize(code_len as usize, 0u8); - e.memory().get(code_ptr, &mut code)?; - - e.ext_mut().create(&code, value); - - Ok(sandbox::ReturnValue::Unit) - } - - let PreparedContract { - instrumented_code, - memory, - } = prepare_contract(code)?; - - let mut imports = sandbox::EnvironmentDefinitionBuilder::new(); - imports.add_host_func("env", "gas", ext_gas::); - imports.add_host_func("env", "ext_set_storage", ext_set_storage::); - imports.add_host_func("env", "ext_get_storage", ext_get_storage::); - imports.add_host_func("env", "ext_transfer", ext_transfer::); - imports.add_host_func("env", "ext_create", ext_create::); - // TODO: ext_balance, ext_address, ext_callvalue, etc. - imports.add_memory("env", "memory", memory.clone()); - - let mut runtime = Runtime { - ext, - memory, - gas_limit, - gas_used: 0, - }; - - let mut instance = - sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) - .map_err(|_| Error::Instantiate)?; - instance - .invoke(b"call", &[], &mut runtime) - .map(|_| ()) - .map_err(|_| Error::Invoke) } -#[derive(Clone)] -struct Config { - /// Gas cost of a growing memory by single page. - grow_mem_cost: u32, - - /// Gas cost of a regular operation. - regular_op_cost: u32, - - /// How tall the stack is allowed to grow? - /// - /// See https://wiki.parity.io/WebAssembly-StackHeight to find out - /// how the stack frame cost is calculated. - max_stack_height: u32, - - //// What is the maximal memory pages amount is allowed to have for - /// a contract. - max_memory_pages: u32, +decl_storage! { + trait Store for Module; + + // The fee required to create a contract. At least as big as staking's ReclaimRebate. + ContractFee get(contract_fee): b"con:contract_fee" => required T::Balance; + // The fee charged for a call into a contract. + CallBaseFee get(call_base_fee): b"con:base_call_fee" => required T::Gas; + // The fee charged for a create of a contract. + CreateBaseFee get(create_base_fee): b"con:base_create_fee" => required T::Gas; + // The price of one unit of gas. + GasPrice get(gas_price): b"con:gas_price" => required T::Balance; + // The maximum nesting level of a call/create stack. + MaxDepth get(max_depth): b"con:max_depth" => required u32; + + // The code associated with an account. + pub CodeOf: b"con:cod:" => default map [ T::AccountId => Vec ]; // TODO Vec values should be optimised to not do a length prefix. } -impl Default for Config { - fn default() -> Config { - Config { - grow_mem_cost: 1, - regular_op_cost: 1, - max_stack_height: 64 * 1024, - max_memory_pages: 16, - } - } -} +// TODO: consider storing upper-bound for contract's gas limit in fixed-length runtime +// code in contract itself and use that. -struct ContractModule { - // An `Option` is used here for loaning (`take()`-ing) the module. - // Invariant: Can't be `None` (i.e. on enter and on exit from the function - // the value *must* be `Some`). - module: Option, - config: Config, +/// The storage items associated with an account/key. +/// +/// TODO: keys should also be able to take AsRef to ensure Vecs can be passed as &[u8] +pub(crate) struct StorageOf(::rstd::marker::PhantomData); +impl double_map::StorageDoubleMap for StorageOf { + const PREFIX: &'static [u8] = b"con:sto:"; + type Key1 = T::AccountId; + type Key2 = Vec; + type Value = Vec; } -impl ContractModule { - fn new(original_code: &[u8], config: Config) -> Result { - let module = - elements::deserialize_buffer(original_code).map_err(|_| Error::Deserialization)?; - Ok(ContractModule { - module: Some(module), - config, - }) - } +impl Module { + /// Make a call to a specified account, optionally transferring some balance. + fn call( + aux: &::PublicAux, + dest: T::AccountId, + value: T::Balance, + gas_limit: T::Gas, + data: Vec, + ) -> Result { + let aux = aux.ref_into(); + + // Pay for the gas upfront. + // + // NOTE: it is very important to avoid any state changes before + // paying for the gas. + let mut gas_meter = gas::buy_gas::(aux, gas_limit)?; + + let mut ctx = ExecutionContext { + self_account: aux.clone(), + depth: 0, + overlay: OverlayAccountDb::::new(&account_db::DirectAccountDb), + }; + let result = ctx.call(aux.clone(), dest, value, &mut gas_meter, &data); - /// Ensures that module doesn't declare internal memories. - /// - /// In this runtime we only allow wasm module to import memory from the environment. - /// Memory section contains declarations of internal linear memories, so if we find one - /// we reject such a module. - fn ensure_no_internal_memory(&self) -> Result<(), Error> { - let module = self.module - .as_ref() - .expect("On entry to the function `module` can't be None; qed"); - if module - .memory_section() - .map_or(false, |ms| ms.entries().len() > 0) - { - return Err(Error::InternalMemoryDeclared); + if let Ok(_) = result { + // Commit all changes that made it thus far into the persistant storage. + account_db::DirectAccountDb.commit(ctx.overlay.into_change_set()); } - Ok(()) - } - - fn inject_gas_metering(&mut self) -> Result<(), Error> { - let gas_rules = rules::Set::new(self.config.regular_op_cost, Default::default()) - .with_grow_cost(self.config.grow_mem_cost) - .with_forbidden_floats(); - - let module = self.module - .take() - .expect("On entry to the function `module` can't be `None`; qed"); - - let contract_module = pwasm_utils::inject_gas_counter(module, &gas_rules) - .map_err(|_| Error::GasInstrumentation)?; - - self.module = Some(contract_module); - Ok(()) - } - fn inject_stack_height_metering(&mut self) -> Result<(), Error> { - let module = self.module - .take() - .expect("On entry to the function `module` can't be `None`; qed"); + // Refund cost of the unused gas. + // + // NOTE: this should go after the commit to the storage, since the storage changes + // can alter the balance of the caller. + gas::refund_unused_gas::(aux, gas_meter); - let contract_module = - pwasm_utils::stack_height::inject_limiter(module, self.config.max_stack_height) - .map_err(|_| Error::StackHeightInstrumentation)?; - - self.module = Some(contract_module); - Ok(()) - } - - /// Find the memory import entry and return it's descriptor. - fn find_mem_import(&self) -> Option<&MemoryType> { - let import_section = self.module - .as_ref() - .expect("On entry to the function `module` can't be `None`; qed") - .import_section()?; - for import in import_section.entries() { - if let ("env", "memory", &External::Memory(ref memory_type)) = - (import.module(), import.field(), import.external()) - { - return Some(memory_type); - } - } - None + result.map(|_| ()) } - fn into_wasm_code(mut self) -> Result, Error> { - elements::serialize( - self.module - .take() - .expect("On entry to the function `module` can't be `None`; qed"), - ).map_err(|_| Error::Serialization) - } -} - -struct PreparedContract { - instrumented_code: Vec, - memory: sandbox::Memory, -} - -fn prepare_contract(original_code: &[u8]) -> Result { - let config = Config::default(); - - let mut contract_module = ContractModule::new(original_code, config.clone())?; - contract_module.ensure_no_internal_memory()?; - contract_module.inject_gas_metering()?; - contract_module.inject_stack_height_metering()?; - - // Inspect the module to extract the initial and maximum page count. - let memory = match contract_module.find_mem_import() { - Some(memory_type) => { - let limits = memory_type.limits(); - match (limits.initial(), limits.maximum()) { - (initial, Some(maximum)) if initial > maximum => { - // Requested initial number of pages should not exceed the requested maximum. - return Err(Error::Memory); - } - (_, Some(maximum)) if maximum > config.max_memory_pages => { - // Maximum number of pages should not exceed the configured maximum. - return Err(Error::Memory); - } - (_, None) => { - // Maximum number of pages should be always declared. - // This isn't a hard requirement and can be treated as a maxiumum set - // to configured maximum. - return Err(Error::Memory) - } - (initial, maximum) => sandbox::Memory::new( - initial, - maximum, - ) - } - }, - - // If none memory imported then just crate an empty placeholder. - // Any access to it will lead to out of bounds trap. - None => sandbox::Memory::new(0, Some(0)), - }.map_err(|_| Error::Memory)?; - - Ok(PreparedContract { - instrumented_code: contract_module.into_wasm_code()?, - memory, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::fmt; - use wabt; - use std::collections::HashMap; - - #[derive(Debug, PartialEq, Eq)] - struct CreateEntry { - code: Vec, - endownment: u64, - } - #[derive(Debug, PartialEq, Eq)] - struct TransferEntry { - to: u64, - value: u64, - } - #[derive(Default)] - struct MockExt { - storage: HashMap, Vec>, - creates: Vec, - transfers: Vec, - } - impl Ext for MockExt { - type AccountId = u64; - type Balance = u64; - - fn get_storage(&self, key: &[u8]) -> Option> { - self.storage.get(key).cloned() - } - fn set_storage(&mut self, key: &[u8], value: Option>) { - *self.storage.entry(key.to_vec()).or_insert(Vec::new()) = value.unwrap_or(Vec::new()); - } - fn create(&mut self, code: &[u8], value: Self::Balance) { - self.creates.push( - CreateEntry { - code: code.to_vec(), - endownment: value, - } - ); - } - fn transfer(&mut self, to: &Self::AccountId, value: Self::Balance) { - self.transfers.push( - TransferEntry { - to: *to, - value, - } - ); - } - } + /// Create a new contract, optionally transfering some balance to the created account. + /// + /// Creation is executed as follows:ExecutionContext + /// + /// - the destination address is computed based on the sender and hash of the code. + /// - account is created at the computed address. + /// - the `ctor_code` is executed in the context of the newly created account. Buffer returned + /// after the execution is saved as the `code` of the account. That code will be invoked + /// upon any message received by this account. + fn create( + aux: &::PublicAux, + endowment: T::Balance, + gas_limit: T::Gas, + ctor_code: Vec, + data: Vec, + ) -> Result { + let aux = aux.ref_into(); + + // Pay for the gas upfront. + // + // NOTE: it is very important to avoid any state changes before + // paying for the gas. + let mut gas_meter = gas::buy_gas::(aux, gas_limit)?; + + let mut ctx = ExecutionContext { + self_account: aux.clone(), + depth: 0, + overlay: OverlayAccountDb::::new(&account_db::DirectAccountDb), + }; + let result = ctx.create(aux.clone(), endowment, &mut gas_meter, &ctor_code, &data); - impl fmt::Debug for PreparedContract { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "PreparedContract {{ .. }}") + if let Ok(_) = result { + // Commit all changes that made it thus far into the persistant storage. + account_db::DirectAccountDb.commit(ctx.overlay.into_change_set()); } - } - - fn parse_and_prepare_wat(wat: &str) -> Result { - let wasm = wabt::Wat2Wasm::new() - .validate(false) - .convert(wat) - .unwrap(); - prepare_contract(wasm.as_ref()) - } - - #[test] - fn internal_memory_declaration() { - let r = parse_and_prepare_wat( - r#"(module (memory 1 1))"#, - ); - assert_matches!(r, Err(Error::InternalMemoryDeclared)); - } - #[test] - fn memory() { - // This test assumes that maximum page number is configured to a certain number. - assert_eq!(Config::default().max_memory_pages, 16); - - let r = parse_and_prepare_wat( - r#"(module (import "env" "memory" (memory 1 1)))"#, - ); - assert_matches!(r, Ok(_)); - - // No memory import - let r = parse_and_prepare_wat( - r#"(module)"#, - ); - assert_matches!(r, Ok(_)); - - // incorrect import name. That's kinda ok, since this will fail - // at later stage when imports will be resolved. - let r = parse_and_prepare_wat( - r#"(module (import "vne" "memory" (memory 1 1)))"#, - ); - assert_matches!(r, Ok(_)); - - // initial exceed maximum - let r = parse_and_prepare_wat( - r#"(module (import "env" "memory" (memory 16 1)))"#, - ); - assert_matches!(r, Err(Error::Memory)); - - // no maximum - let r = parse_and_prepare_wat( - r#"(module (import "env" "memory" (memory 1)))"#, - ); - assert_matches!(r, Err(Error::Memory)); - - // requested maximum exceed configured maximum - let r = parse_and_prepare_wat( - r#"(module (import "env" "memory" (memory 1 17)))"#, - ); - assert_matches!(r, Err(Error::Memory)); - } + // Refund cost of the unused gas. + // + // NOTE: this should go after the commit to the storage, since the storage changes + // can alter the balance of the caller. + gas::refund_unused_gas::(aux, gas_meter); - const CODE_TRANSFER: &str = r#" -(module - ;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) - (import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32))) - - (import "env" "memory" (memory 1 1)) - - (func (export "call") - (call $ext_transfer - (i32.const 4) ;; Pointer to "Transfer to" address. - (i32.const 8) ;; Length of "Transfer to" address. - (i32.const 12) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. - ) - ) - - ;; Destination AccountId to transfer the funds. - ;; Represented by u64 (8 bytes long) in little endian. - (data (i32.const 4) "\02\00\00\00\00\00\00\00") - - ;; Amount of value to transfer. - ;; Represented by u64 (8 bytes long) in little endian. - (data (i32.const 12) "\06\00\00\00\00\00\00\00") -) -"#; - - #[test] - fn contract_transfer() { - let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); - - let mut mock_ext = MockExt::default(); - execute(&code_transfer, &mut mock_ext, 50_000).unwrap(); - - assert_eq!(&mock_ext.transfers, &[TransferEntry { - to: 2, - value: 6, - }]); + result.map(|_| ()) } +} - const CODE_MEM: &str = -r#" -(module - ;; Internal memory is not allowed. - (memory 1 1) - - (func (export "call") - nop - ) -) -"#; - - #[test] - fn contract_internal_mem() { - let code_mem = wabt::wat2wasm(CODE_MEM).unwrap(); - - let mut mock_ext = MockExt::default(); - - assert_matches!( - execute(&code_mem, &mut mock_ext, 100_000), - Err(_) - ); +impl staking::OnAccountKill for Module { + fn on_account_kill(who: &T::AccountId) { + >::remove(who); + >::remove_prefix(who.clone()); } } diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs new file mode 100644 index 0000000000000..503fb780c3010 --- /dev/null +++ b/substrate/runtime/contract/src/tests.rs @@ -0,0 +1,493 @@ +// Copyright 2018 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 . + +use double_map::StorageDoubleMap; +use runtime_io::with_externalities; +use runtime_primitives::testing::{Digest, H256, Header}; +use runtime_primitives::traits::{BlakeTwo256, HasPublicAux, Identity}; +use runtime_primitives::BuildStorage; +use runtime_support::StorageMap; +use wabt; +use { + consensus, runtime_io, session, staking, system, timestamp, CodeOf, ContractAddressFor, + GenesisConfig, Module, StorageOf, Trait, +}; + +#[derive(Clone, Eq, PartialEq)] +pub struct Test; +impl HasPublicAux for Test { + type PublicAux = u64; +} +impl consensus::Trait for Test { + type PublicAux = ::PublicAux; + type SessionKey = u64; +} +impl system::Trait for Test { + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type Digest = Digest; + type AccountId = u64; + type Header = Header; +} +impl timestamp::Trait for Test { + const TIMESTAMP_SET_POSITION: u32 = 0; + type Moment = u64; +} +impl staking::Trait for Test { + type Balance = u64; + type AccountIndex = u64; + type OnAccountKill = Contract; +} +impl session::Trait for Test { + type ConvertAccountIdToSessionKey = Identity; + type OnSessionChange = Staking; +} +impl Trait for Test { + type Gas = u64; + type DetermineContractAddress = DummyContractAddressFor; +} + +type Staking = staking::Module; +type Contract = Module; + +pub struct DummyContractAddressFor; +impl ContractAddressFor for DummyContractAddressFor { + fn contract_address_for(_code: &[u8], origin: &u64) -> u64 { + origin + 1 + } +} + +fn new_test_ext(existential_deposit: u64, gas_price: u64) -> runtime_io::TestExternalities { + let mut t = system::GenesisConfig::::default() + .build_storage() + .unwrap(); + t.extend( + consensus::GenesisConfig:: { + code: vec![], + authorities: vec![], + }.build_storage() + .unwrap(), + ); + t.extend( + session::GenesisConfig:: { + session_length: 1, + validators: vec![10, 20], + broken_percent_late: 100, + }.build_storage() + .unwrap(), + ); + t.extend( + staking::GenesisConfig:: { + sessions_per_era: 1, + current_era: 0, + balances: vec![], + intentions: vec![], + validator_count: 2, + bonding_duration: 0, + transaction_base_fee: 0, + transaction_byte_fee: 0, + existential_deposit: existential_deposit, + transfer_fee: 0, + creation_fee: 0, + reclaim_rebate: 0, + early_era_slash: 0, + session_reward: 0, + }.build_storage() + .unwrap(), + ); + t.extend( + timestamp::GenesisConfig::::default() + .build_storage() + .unwrap(), + ); + t.extend( + GenesisConfig:: { + contract_fee: 21, + call_base_fee: 135, + create_base_fee: 175, + gas_price, + max_depth: 1024, + }.build_storage() + .unwrap(), + ); + t +} + +const CODE_TRANSFER: &str = r#" +(module + ;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) + (import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (call $ext_transfer + (i32.const 4) ;; Pointer to "Transfer to" address. + (i32.const 8) ;; Length of "Transfer to" address. + (i32.const 12) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + ) + ) + ;; Destination AccountId to transfer the funds. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 4) "\09\00\00\00\00\00\00\00") + ;; Amount of value to transfer. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 12) "\06\00\00\00\00\00\00\00") +) +"#; + +#[test] +fn contract_transfer() { + const CONTRACT_SHOULD_TRANSFER_VALUE: u64 = 6; + const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9; + + let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + + with_externalities(&mut new_test_ext(0, 2), || { + >::insert(1, code_transfer.to_vec()); + + Staking::set_free_balance(&0, 100_000_000); + Staking::set_free_balance(&1, 11); + + assert_ok!(Contract::call(&0, 1, 3, 100_000, Vec::new())); + + assert_eq!( + Staking::free_balance(&0), + // 3 - value sent with the transaction + // 2 * 6 - gas used by the contract (6) multiplied by gas price (2) + // 2 * 135 - base gas fee for call (by transaction) + // 2 * 135 - base gas fee for call (by the contract) + 100_000_000 - 3 - (2 * 6) - (2 * 135) - (2 * 135), + ); + assert_eq!( + Staking::free_balance(&1), + 11 + 3 - CONTRACT_SHOULD_TRANSFER_VALUE, + ); + assert_eq!( + Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), + CONTRACT_SHOULD_TRANSFER_VALUE, + ); + }); +} + +#[test] +fn contract_transfer_oog() { + const CONTRACT_SHOULD_TRANSFER_TO: u64 = 9; + + let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + + with_externalities(&mut new_test_ext(0, 2), || { + >::insert(1, code_transfer.to_vec()); + + Staking::set_free_balance(&0, 100_000_000); + Staking::set_free_balance(&1, 11); + + assert_err!( + Contract::call(&0, 1, 3, 276, Vec::new()), + "vm execute returned error while call" + ); + + assert_eq!( + Staking::free_balance(&0), + // 3 - value sent with the transaction + // 2 * 6 - gas used by the contract (6) multiplied by gas price (2) + // 2 * 135 - base gas fee for call (by transaction) + // 2 * 135 - base gas fee for call (by contract) + 100_000_000 - (2 * 6) - (2 * 135) - (2 * 135), + ); + assert_eq!( + Staking::free_balance(&1), + 11, + ); + assert_eq!( + Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), + 0, + ); + }); + +} + +/// Convert a byte slice to a string with hex values. +/// +/// Each value is preceeded with a `\` character. +fn escaped_bytestring(bytes: &[u8]) -> String { + use std::fmt::Write; + let mut result = String::new(); + for b in bytes { + write!(result, "\\{:02x}", b).unwrap(); + } + result +} + +/// Create a constructor for the specified code. +/// +/// When constructor is executed, it will call `ext_return` with code that +/// specified in `child_bytecode`. +fn code_ctor(child_bytecode: &[u8]) -> String { + format!( + r#" +(module + ;; ext_return(data_ptr: u32, data_len: u32) -> ! + (import "env" "ext_return" (func $ext_return (param i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (call $ext_return + (i32.const 4) + (i32.const {code_len}) + ) + ;; ext_return is diverging, i.e. doesn't return. + unreachable + ) + (data (i32.const 4) "{escaped_bytecode}") +) +"#, + escaped_bytecode = escaped_bytestring(child_bytecode), + code_len = child_bytecode.len(), + ) +} + +/// Returns code that uses `ext_create` runtime call. +/// +/// Takes bytecode of the contract that needs to be deployed. +fn code_create(constructor: &[u8]) -> String { + format!( + r#" +(module + ;; ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) + (import "env" "ext_create" (func $ext_create (param i32 i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (call $ext_create + (i32.const 12) ;; Pointer to `code` + (i32.const {code_len}) ;; Length of `code` + (i32.const 4) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer + ) + ) + ;; Amount of value to transfer. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 4) "\03\00\00\00\00\00\00\00") + ;; Embedded wasm code. + (data (i32.const 12) "{escaped_constructor}") +) +"#, + escaped_constructor = escaped_bytestring(constructor), + code_len = constructor.len(), + ) +} + +#[test] +fn contract_create() { + let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + let code_ctor_transfer = wabt::wat2wasm(&code_ctor(&code_transfer)).unwrap(); + let code_create = wabt::wat2wasm(&code_create(&code_ctor_transfer)).unwrap(); + + with_externalities(&mut new_test_ext(0, 2), || { + Staking::set_free_balance(&0, 100_000_000); + Staking::set_free_balance(&1, 0); + Staking::set_free_balance(&9, 30); + + >::insert(1, code_create.to_vec()); + + // When invoked, the contract at address `1` must create a contract with 'transfer' code. + assert_ok!(Contract::call(&0, 1, 11, 100_000, Vec::new())); + + let derived_address = ::DetermineContractAddress::contract_address_for( + &code_ctor_transfer, + &1, + ); + + // 11 - value sent with the transaction + // 2 * 128 - gas spent by the deployer contract (128) multiplied by gas price (2) + // 2 * 135 - base gas fee for call (top level) + // 2 * 175 - base gas fee for create (by contract) + // ((21 / 2) * 2) - price per account creation + let expected_gas_after_create = + 100_000_000 - 11 - (2 * 128) - (2 * 135) - (2 * 175) - ((21 / 2) * 2); + assert_eq!(Staking::free_balance(&0), expected_gas_after_create); + assert_eq!(Staking::free_balance(&1), 8); + assert_eq!(Staking::free_balance(&derived_address), 3); + + // Initiate transfer to the newly created contract. + assert_ok!(Contract::call(&0, derived_address, 22, 100_000, Vec::new())); + + assert_eq!( + Staking::free_balance(&0), + // 22 - value sent with the transaction + // (2 * 6) - gas used by the contract + // (2 * 135) - base gas fee for call (top level) + // (2 * 135) - base gas fee for call (by transfer contract) + expected_gas_after_create - 22 - (2 * 6) - (2 * 135) - (2 * 135), + ); + assert_eq!(Staking::free_balance(&derived_address), 22 - 3); + assert_eq!(Staking::free_balance(&9), 36); + }); +} + +#[test] +fn top_level_create() { + let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + let code_ctor_transfer = wabt::wat2wasm(&code_ctor(&code_transfer)).unwrap(); + + with_externalities(&mut new_test_ext(0, 3), || { + let derived_address = ::DetermineContractAddress::contract_address_for( + &code_ctor_transfer, + &0, + ); + + Staking::set_free_balance(&0, 100_000_000); + Staking::set_free_balance(&derived_address, 30); + + assert_ok!(Contract::create( + &0, + 11, + 100_000, + code_ctor_transfer.clone(), + Vec::new(), + )); + + // 11 - value sent with the transaction + // (3 * 122) - gas spent by the ctor + // (3 * 175) - base gas fee for create (175) (top level) multipled by gas price (3) + // ((21 / 3) * 3) - price for contract creation + assert_eq!( + Staking::free_balance(&0), + 100_000_000 - 11 - (3 * 122) - (3 * 175) - ((21 / 3) * 3) + ); + assert_eq!(Staking::free_balance(&derived_address), 30 + 11); + + assert_eq!(>::get(&derived_address), code_transfer); + }); +} + +const CODE_NOP: &'static str = r#" +(module + (func (export "call") + nop + ) +) +"#; + +#[test] +fn refunds_unused_gas() { + let code_nop = wabt::wat2wasm(CODE_NOP).unwrap(); + + with_externalities(&mut new_test_ext(0, 2), || { + >::insert(1, code_nop.to_vec()); + + Staking::set_free_balance(&0, 100_000_000); + + assert_ok!(Contract::call(&0, 1, 0, 100_000, Vec::new(),)); + + assert_eq!(Staking::free_balance(&0), 100_000_000 - 4 - (2 * 135),); + }); +} + +#[test] +fn call_with_zero_value() { + with_externalities(&mut new_test_ext(0, 2), || { + >::insert(1, vec![]); + + Staking::set_free_balance(&0, 100_000_000); + + assert_ok!(Contract::call(&0, 1, 0, 100_000, Vec::new(),)); + + assert_eq!(Staking::free_balance(&0), 100_000_000 - (2 * 135),); + }); +} + +#[test] +fn create_with_zero_endowment() { + let code_nop = wabt::wat2wasm(CODE_NOP).unwrap(); + + with_externalities(&mut new_test_ext(0, 2), || { + Staking::set_free_balance(&0, 100_000_000); + + assert_ok!(Contract::create(&0, 0, 100_000, code_nop, Vec::new(),)); + + assert_eq!( + Staking::free_balance(&0), + // 4 - for the gas spent by the constructor + // 2 * 175 - base gas fee for create (175) multiplied by gas price (2) (top level) + 100_000_000 - 4 - (2 * 175), + ); + }); +} + +#[test] +fn account_removal_removes_storage() { + with_externalities(&mut new_test_ext(100, 2), || { + // Setup two accounts with free balance above than exsistential threshold. + { + Staking::set_free_balance(&1, 110); + >::insert(1, b"foo".to_vec(), b"1".to_vec()); + >::insert(1, b"bar".to_vec(), b"2".to_vec()); + + Staking::set_free_balance(&2, 110); + >::insert(2, b"hello".to_vec(), b"3".to_vec()); + >::insert(2, b"world".to_vec(), b"4".to_vec()); + } + + // Transfer funds from account 1 of such amount that after this transfer + // the balance of account 1 is will be below than exsistential threshold. + // + // This should lead to the removal of all storage associated with this account. + assert_ok!(Staking::transfer(&1, 2.into(), 20)); + + // Verify that all entries from account 1 is removed, while + // entries from account 2 is in place. + { + assert_eq!(>::get(1, b"foo".to_vec()), None); + assert_eq!(>::get(1, b"bar".to_vec()), None); + + assert_eq!( + >::get(2, b"hello".to_vec()), + Some(b"3".to_vec()) + ); + assert_eq!( + >::get(2, b"world".to_vec()), + Some(b"4".to_vec()) + ); + } + }); +} + +const CODE_UNREACHABLE: &'static str = r#" +(module + (func (export "call") + nop + unreachable + ) +) +"#; + +#[test] +fn top_level_call_refunds_even_if_fails() { + let code_unreachable = wabt::wat2wasm(CODE_UNREACHABLE).unwrap(); + with_externalities(&mut new_test_ext(0, 4), || { + >::insert(1, code_unreachable.to_vec()); + + Staking::set_free_balance(&0, 100_000_000); + + assert_err!( + Contract::call(&0, 1, 0, 100_000, Vec::new()), + "vm execute returned error while call" + ); + + assert_eq!(Staking::free_balance(&0), 100_000_000 - (4 * 3) - (4 * 135),); + }); +} diff --git a/substrate/runtime/contract/src/vm.rs b/substrate/runtime/contract/src/vm.rs new file mode 100644 index 0000000000000..1e594883eeb15 --- /dev/null +++ b/substrate/runtime/contract/src/vm.rs @@ -0,0 +1,762 @@ +// Copyright 2018 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 . + +//! Crate for executing smart-contracts. +//! +//! It provides an means for executing contracts represented in WebAssembly (Wasm for short). +//! Contracts are able to create other contracts, transfer funds +//! to each other and operate on a simple key-value storage. + +use codec::Decode; +use parity_wasm::elements::{self, External, MemoryType}; +use pwasm_utils; +use pwasm_utils::rules; +use rstd::prelude::*; +use sandbox; +use gas::{GasMeter, GasMeterResult}; +use runtime_primitives::traits::{As, CheckedMul}; +use {Trait}; +use exec::{CallReceipt, CreateReceipt}; + +/// An interface that provides an access to the external environment in which the +/// smart-contract is executed. +/// +/// This interface is specialised to an account of the executing code, so all +/// operations are implicitly performed on that account. +pub trait Ext { + /// Returns the storage entry of the executing account by the given key. + fn get_storage(&self, key: &[u8]) -> Option>; + + /// Sets the storage entry by the given key to the specified value. + fn set_storage(&mut self, key: &[u8], value: Option>); + + // TODO: Return the address of the created contract. + /// Create a new account for a contract. + /// + /// The newly created account will be associated with the `code`. `value` specifies the amount of value + /// transfered from this to the newly created account. + fn create( + &mut self, + code: &[u8], + value: T::Balance, + gas_meter: &mut GasMeter, + data: &[u8], + ) -> Result, ()>; + + /// Call (possibly transfering some amount of funds) into the specified account. + fn call( + &mut self, + to: &T::AccountId, + value: T::Balance, + gas_meter: &mut GasMeter, + data: &[u8], + ) -> Result; +} + +/// Error that can occur while preparing or executing wasm smart-contract. +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + /// Error happened while serializing the module. + Serialization, + + /// Error happened while deserializing the module. + Deserialization, + + /// Internal memory declaration has been found in the module. + InternalMemoryDeclared, + + /// Gas instrumentation failed. + /// + /// This most likely indicates the module isn't valid. + GasInstrumentation, + + /// Stack instrumentation failed. + /// + /// This most likely indicates the module isn't valid. + StackHeightInstrumentation, + + /// Error happened during invocation of the contract's entrypoint. + /// + /// Most likely because of trap. + Invoke, + + /// Error happened during instantiation. + /// + /// This might indicate that `start` function trapped, or module isn't + /// instantiable and/or unlinkable. + Instantiate, + + /// Memory creation error. + /// + /// This might happen when the memory import has invalid descriptor or + /// requested too much resources. + Memory, +} + +/// Enumerates all possible *special* trap conditions. +/// +/// In this runtime traps used not only for signaling about errors but also +/// to just terminate quickly in some cases. +enum SpecialTrap { + // TODO: Can we pass wrapped memory instance instead of copying? + /// Signals that trap was generated in response to call `ext_return` host function. + Return(Vec), +} + +struct Runtime<'a, T: Trait + 'a, E: Ext + 'a> { + ext: &'a mut E, + config: &'a Config, + memory: sandbox::Memory, + gas_meter: &'a mut GasMeter, + special_trap: Option, +} +impl<'a, T: Trait, E: Ext + 'a> Runtime<'a, T, E> { + fn memory(&self) -> &sandbox::Memory { + &self.memory + } + /// Save a data buffer as a result of the execution. + /// + /// This function also charges gas for the returning. + /// + /// Returns `Err` if there is not enough gas. + fn store_return_data(&mut self, data: Vec) -> Result<(), ()> { + let data_len = >::sa(data.len() as u64); + let price = (self.config.return_data_per_byte_cost) + .checked_mul(&data_len) + .ok_or_else(|| ())?; + + match self.gas_meter.charge(price) { + GasMeterResult::Proceed => { + self.special_trap = Some(SpecialTrap::Return(data)); + Ok(()) + } + GasMeterResult::OutOfGas => Err(()), + } + } +} + +fn to_execution_result>( + runtime: Runtime, + run_err: Option, +) -> Result { + // Check the exact type of the error. It could be plain trap or + // special runtime trap the we must recognize. + let return_data = match (run_err, runtime.special_trap) { + // No traps were generated. Proceed normally. + (None, None) => Vec::new(), + // Special case. The trap was the result of the execution `return` host function. + (Some(sandbox::Error::Execution), Some(SpecialTrap::Return(rd))) => rd, + // Any other kind of a trap should result in a failure. + (Some(_), _) => return Err(Error::Invoke), + // Any other case (such as special trap flag without actual trap) signifies + // a logic error. + _ => unreachable!(), + }; + + Ok(ExecutionResult { + return_data, + }) +} + +/// The result of execution of a smart-contract. +#[derive(PartialEq, Eq)] +#[cfg_attr(feature = "std", derive(Debug))] +pub struct ExecutionResult { + /// The result produced by the execution of the contract. + /// + /// The contract can designate some buffer at the execution time via a special function. + /// If contract called this function with non-empty buffer it will be copied here. + /// + /// Note that gas is already charged for returning the data. + pub return_data: Vec, +} + +/// Execute the given code as a contract. +pub fn execute<'a, T: Trait, E: Ext>( + code: &[u8], + ext: &'a mut E, + gas_meter: &mut GasMeter, +) -> Result { + // TODO: Receive data as an argument + + // ext_gas(amount: u32) + // + // Account for used gas. Traps if gas used is greater than gas limit. + // + // - amount: How much gas is used. + fn ext_gas>( + e: &mut Runtime, + args: &[sandbox::TypedValue], + ) -> Result { + let amount = args[0].as_i32().unwrap() as u32; + let amount = >::sa(amount); + + match e.gas_meter.charge(amount) { + GasMeterResult::Proceed => Ok(sandbox::ReturnValue::Unit), + GasMeterResult::OutOfGas => Err(sandbox::HostError), + } + } + + // ext_put_storage(location_ptr: u32, value_non_null: u32, value_ptr: u32); + // + // Change the value at the given location in storage or remove it. + // + // - location_ptr: pointer into the linear + // memory where the location of the requested value is placed. + // - value_non_null: if set to 0, then the entry + // at the given location will be removed. + // - value_ptr: pointer into the linear memory + // where the value to set is placed. If `value_non_null` is set to 0, then this parameter is ignored. + fn ext_set_storage>( + e: &mut Runtime, + args: &[sandbox::TypedValue], + ) -> Result { + let location_ptr = args[0].as_i32().unwrap() as u32; + let value_non_null = args[1].as_i32().unwrap() as u32; + let value_ptr = args[2].as_i32().unwrap() as u32; + + let mut location = [0; 32]; + + e.memory().get(location_ptr, &mut location)?; + + let value = if value_non_null != 0 { + let mut value = [0; 32]; + e.memory().get(value_ptr, &mut value)?; + Some(value.to_vec()) + } else { + None + }; + e.ext.set_storage(&location, value); + + Ok(sandbox::ReturnValue::Unit) + } + + // ext_get_storage(location_ptr: u32, dest_ptr: u32); + // + // Retrieve the value at the given location from the strorage. + // If there is no entry at the given location then all-zero-value + // will be returned. + // + // - location_ptr: pointer into the linear + // memory where the location of the requested value is placed. + // - dest_ptr: pointer where contents of the specified storage location + // should be placed. + fn ext_get_storage>( + e: &mut Runtime, + args: &[sandbox::TypedValue], + ) -> Result { + let location_ptr = args[0].as_i32().unwrap() as u32; + let dest_ptr = args[1].as_i32().unwrap() as u32; + + let mut location = [0; 32]; + e.memory().get(location_ptr, &mut location)?; + + if let Some(value) = e.ext.get_storage(&location) { + e.memory().set(dest_ptr, &value)?; + } else { + e.memory().set(dest_ptr, &[0u8; 32])?; + } + + Ok(sandbox::ReturnValue::Unit) + } + + // ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) + fn ext_transfer>( + e: &mut Runtime, + args: &[sandbox::TypedValue], + ) -> Result { + let transfer_to_ptr = args[0].as_i32().unwrap() as u32; + let transfer_to_len = args[1].as_i32().unwrap() as u32; + let value_ptr = args[2].as_i32().unwrap() as u32; + let value_len = args[3].as_i32().unwrap() as u32; + + let mut transfer_to = Vec::new(); + transfer_to.resize(transfer_to_len as usize, 0); + e.memory().get(transfer_to_ptr, &mut transfer_to)?; + let transfer_to = T::AccountId::decode(&mut &transfer_to[..]).unwrap(); + + let mut value_buf = Vec::new(); + value_buf.resize(value_len as usize, 0); + e.memory().get(value_ptr, &mut value_buf)?; + let value = T::Balance::decode(&mut &value_buf[..]).unwrap(); + + // TODO: Read input data from memory. + let input_data = Vec::new(); + + // TODO: Let user to choose how much gas to allocate for the execution. + let nested_gas_limit = e.gas_meter.gas_left(); + let ext = &mut e.ext; + let call_outcome = e.gas_meter.with_nested(nested_gas_limit, |nested_meter| { + match nested_meter { + Some(nested_meter) => ext.call(&transfer_to, value, nested_meter, &input_data), + // there is not enough gas to allocate for the nested call. + None => Err(()), + } + }); + + match call_outcome { + // TODO: Find a way how to pass return_data back to the this sandbox. + Ok(CallReceipt { .. }) => Ok(sandbox::ReturnValue::Unit), + // TODO: Return a status code value that can be handled by the caller instead of a trap. + Err(_) => Err(sandbox::HostError), + } + } + + // ext_create(code_ptr: u32, code_len: u32, value_ptr: u32, value_len: u32) + fn ext_create>( + e: &mut Runtime, + args: &[sandbox::TypedValue], + ) -> Result { + let code_ptr = args[0].as_i32().unwrap() as u32; + let code_len = args[1].as_i32().unwrap() as u32; + let value_ptr = args[2].as_i32().unwrap() as u32; + let value_len = args[3].as_i32().unwrap() as u32; + + let mut value_buf = Vec::new(); + value_buf.resize(value_len as usize, 0); + e.memory().get(value_ptr, &mut value_buf)?; + let value = T::Balance::decode(&mut &value_buf[..]).unwrap(); + + let mut code = Vec::new(); + code.resize(code_len as usize, 0u8); + e.memory().get(code_ptr, &mut code)?; + + // TODO: Read input data from the sandbox. + let input_data = Vec::new(); + + // TODO: Let user to choose how much gas to allocate for the execution. + let nested_gas_limit = e.gas_meter.gas_left(); + let ext = &mut e.ext; + let create_outcome = e.gas_meter.with_nested(nested_gas_limit, |nested_meter| { + match nested_meter { + Some(nested_meter) => ext.create(&code, value, nested_meter, &input_data), + // there is not enough gas to allocate for the nested call. + None => Err(()), + } + }); + + match create_outcome { + // TODO: Copy an address of the created contract in the sandbox. + Ok(CreateReceipt { .. }) => Ok(sandbox::ReturnValue::Unit), + // TODO: Return a status code value that can be handled by the caller instead of a trap. + Err(_) => Err(sandbox::HostError), + } + } + + // ext_return(data_ptr: u32, data_len: u32) -> ! + fn ext_return>( + e: &mut Runtime, + args: &[sandbox::TypedValue], + ) -> Result { + let data_ptr = args[0].as_i32().unwrap() as u32; + let data_len = args[1].as_i32().unwrap() as u32; + + let mut data_buf = Vec::new(); + data_buf.resize(data_len as usize, 0); + e.memory().get(data_ptr, &mut data_buf)?; + + e.store_return_data(data_buf) + .map_err(|_| sandbox::HostError)?; + + // The trap mechanism is used to immediately terminate the execution. + // This trap should be handled appropriately before returning the result + // to the user of this crate. + Err(sandbox::HostError) + } + + let config = Config::default(); + + let PreparedContract { + instrumented_code, + memory, + } = prepare_contract(code, &config)?; + + let mut imports = sandbox::EnvironmentDefinitionBuilder::new(); + imports.add_host_func("env", "gas", ext_gas::); + imports.add_host_func("env", "ext_set_storage", ext_set_storage::); + imports.add_host_func("env", "ext_get_storage", ext_get_storage::); + // TODO: Rename it to ext_call. + imports.add_host_func("env", "ext_transfer", ext_transfer::); + imports.add_host_func("env", "ext_create", ext_create::); + imports.add_host_func("env", "ext_return", ext_return::); + // TODO: ext_balance, ext_address, ext_callvalue, etc. + imports.add_memory("env", "memory", memory.clone()); + + let mut runtime = Runtime { + ext, + config: &config, + memory, + gas_meter, + special_trap: None, + }; + + let mut instance = sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) + .map_err(|_| Error::Instantiate)?; + + let run_result = instance.invoke(b"call", &[], &mut runtime); + + to_execution_result(runtime, run_result.err()) +} + +// TODO: Extract it to the root of the crate +#[derive(Clone)] +struct Config { + /// Gas cost of a growing memory by single page. + grow_mem_cost: T::Gas, + + /// Gas cost of a regular operation. + regular_op_cost: T::Gas, + + /// Gas cost per one byte returned. + return_data_per_byte_cost: T::Gas, + + /// How tall the stack is allowed to grow? + /// + /// See https://wiki.parity.io/WebAssembly-StackHeight to find out + /// how the stack frame cost is calculated. + max_stack_height: u32, + + //// What is the maximal memory pages amount is allowed to have for + /// a contract. + max_memory_pages: u32, +} + +impl Default for Config { + fn default() -> Config { + Config { + grow_mem_cost: T::Gas::sa(1), + regular_op_cost: T::Gas::sa(1), + return_data_per_byte_cost: T::Gas::sa(1), + max_stack_height: 64 * 1024, + max_memory_pages: 16, + } + } +} + +struct ContractModule<'a, T: Trait + 'a> { + // An `Option` is used here for loaning (`take()`-ing) the module. + // Invariant: Can't be `None` (i.e. on enter and on exit from the function + // the value *must* be `Some`). + module: Option, + config: &'a Config, +} + +impl<'a, T: Trait> ContractModule<'a, T> { + fn new(original_code: &[u8], config: &'a Config) -> Result, Error> { + let module = + elements::deserialize_buffer(original_code).map_err(|_| Error::Deserialization)?; + Ok(ContractModule { + module: Some(module), + config, + }) + } + + /// Ensures that module doesn't declare internal memories. + /// + /// In this runtime we only allow wasm module to import memory from the environment. + /// Memory section contains declarations of internal linear memories, so if we find one + /// we reject such a module. + fn ensure_no_internal_memory(&self) -> Result<(), Error> { + let module = self + .module + .as_ref() + .expect("On entry to the function `module` can't be None; qed"); + if module + .memory_section() + .map_or(false, |ms| ms.entries().len() > 0) + { + return Err(Error::InternalMemoryDeclared); + } + Ok(()) + } + + fn inject_gas_metering(&mut self) -> Result<(), Error> { + let gas_rules = rules::Set::new(self.config.regular_op_cost.as_(), Default::default()) + .with_grow_cost(self.config.grow_mem_cost.as_()) + .with_forbidden_floats(); + + let module = self + .module + .take() + .expect("On entry to the function `module` can't be `None`; qed"); + + let contract_module = pwasm_utils::inject_gas_counter(module, &gas_rules) + .map_err(|_| Error::GasInstrumentation)?; + + self.module = Some(contract_module); + Ok(()) + } + + fn inject_stack_height_metering(&mut self) -> Result<(), Error> { + let module = self + .module + .take() + .expect("On entry to the function `module` can't be `None`; qed"); + + let contract_module = + pwasm_utils::stack_height::inject_limiter(module, self.config.max_stack_height) + .map_err(|_| Error::StackHeightInstrumentation)?; + + self.module = Some(contract_module); + Ok(()) + } + + /// Find the memory import entry and return it's descriptor. + fn find_mem_import(&self) -> Option<&MemoryType> { + let import_section = self + .module + .as_ref() + .expect("On entry to the function `module` can't be `None`; qed") + .import_section()?; + for import in import_section.entries() { + if let ("env", "memory", &External::Memory(ref memory_type)) = + (import.module(), import.field(), import.external()) + { + return Some(memory_type); + } + } + None + } + + fn into_wasm_code(mut self) -> Result, Error> { + elements::serialize( + self.module + .take() + .expect("On entry to the function `module` can't be `None`; qed"), + ).map_err(|_| Error::Serialization) + } +} + +struct PreparedContract { + instrumented_code: Vec, + memory: sandbox::Memory, +} + +fn prepare_contract(original_code: &[u8], config: &Config) -> Result { + let mut contract_module = ContractModule::new(original_code, config)?; + contract_module.ensure_no_internal_memory()?; + contract_module.inject_gas_metering()?; + contract_module.inject_stack_height_metering()?; + + // Inspect the module to extract the initial and maximum page count. + let memory = if let Some(memory_type) = contract_module.find_mem_import() { + let limits = memory_type.limits(); + match (limits.initial(), limits.maximum()) { + (initial, Some(maximum)) if initial > maximum => { + // Requested initial number of pages should not exceed the requested maximum. + return Err(Error::Memory); + } + (_, Some(maximum)) if maximum > config.max_memory_pages => { + // Maximum number of pages should not exceed the configured maximum. + return Err(Error::Memory); + } + (_, None) => { + // Maximum number of pages should be always declared. + // This isn't a hard requirement and can be treated as a maxiumum set + // to configured maximum. + return Err(Error::Memory); + } + (initial, maximum) => sandbox::Memory::new(initial, maximum), + } + } else { + // If none memory imported then just crate an empty placeholder. + // Any access to it will lead to out of bounds trap. + sandbox::Memory::new(0, Some(0)) + }; + let memory = memory.map_err(|_| Error::Memory)?; + + Ok(PreparedContract { + instrumented_code: contract_module.into_wasm_code()?, + memory, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashMap; + use std::fmt; + use wabt; + use gas::GasMeter; + use ::tests::Test; + + #[derive(Debug, PartialEq, Eq)] + struct CreateEntry { + code: Vec, + endowment: u64, + data: Vec, + } + #[derive(Debug, PartialEq, Eq)] + struct TransferEntry { + to: u64, + value: u64, + } + #[derive(Default)] + struct MockExt { + storage: HashMap, Vec>, + creates: Vec, + transfers: Vec, + next_account_id: u64, + } + impl Ext for MockExt { + fn get_storage(&self, key: &[u8]) -> Option> { + self.storage.get(key).cloned() + } + fn set_storage(&mut self, key: &[u8], value: Option>) { + *self.storage.entry(key.to_vec()).or_insert(Vec::new()) = value.unwrap_or(Vec::new()); + } + fn create( + &mut self, + code: &[u8], + endowment: u64, + _gas_meter: &mut GasMeter, + data: &[u8], + ) -> Result, ()> { + self.creates.push(CreateEntry { + code: code.to_vec(), + endowment, + data: data.to_vec(), + }); + let address = self.next_account_id; + self.next_account_id += 1; + + Ok(CreateReceipt { + address, + }) + } + fn call( + &mut self, + to: &u64, + value: u64, + _gas_meter: &mut GasMeter, + _data: &[u8], + ) -> Result { + self.transfers.push(TransferEntry { to: *to, value }); + // Assume for now that it was just a plain transfer. + // TODO: Add tests for different call outcomes. + Ok(CallReceipt { + return_data: Vec::new(), + }) + } + } + + impl fmt::Debug for PreparedContract { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PreparedContract {{ .. }}") + } + } + + fn parse_and_prepare_wat(wat: &str) -> Result { + let wasm = wabt::Wat2Wasm::new().validate(false).convert(wat).unwrap(); + let config = Config::::default(); + prepare_contract(wasm.as_ref(), &config) + } + + #[test] + fn internal_memory_declaration() { + let r = parse_and_prepare_wat(r#"(module (memory 1 1))"#); + assert_matches!(r, Err(Error::InternalMemoryDeclared)); + } + + #[test] + fn memory() { + // This test assumes that maximum page number is configured to a certain number. + assert_eq!(Config::::default().max_memory_pages, 16); + + let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 1 1)))"#); + assert_matches!(r, Ok(_)); + + // No memory import + let r = parse_and_prepare_wat(r#"(module)"#); + assert_matches!(r, Ok(_)); + + // incorrect import name. That's kinda ok, since this will fail + // at later stage when imports will be resolved. + let r = parse_and_prepare_wat(r#"(module (import "vne" "memory" (memory 1 1)))"#); + assert_matches!(r, Ok(_)); + + // initial exceed maximum + let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 16 1)))"#); + assert_matches!(r, Err(Error::Memory)); + + // no maximum + let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 1)))"#); + assert_matches!(r, Err(Error::Memory)); + + // requested maximum exceed configured maximum + let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 1 17)))"#); + assert_matches!(r, Err(Error::Memory)); + } + + const CODE_TRANSFER: &str = r#" +(module + ;; ext_transfer(transfer_to: u32, transfer_to_len: u32, value_ptr: u32, value_len: u32) + (import "env" "ext_transfer" (func $ext_transfer (param i32 i32 i32 i32))) + + (import "env" "memory" (memory 1 1)) + + (func (export "call") + (call $ext_transfer + (i32.const 4) ;; Pointer to "Transfer to" address. + (i32.const 8) ;; Length of "Transfer to" address. + (i32.const 12) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Length of the buffer with value to transfer. + ) + ) + + ;; Destination AccountId to transfer the funds. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 4) "\02\00\00\00\00\00\00\00") + + ;; Amount of value to transfer. + ;; Represented by u64 (8 bytes long) in little endian. + (data (i32.const 12) "\06\00\00\00\00\00\00\00") +) +"#; + + #[test] + fn contract_transfer() { + let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap(); + + let mut mock_ext = MockExt::default(); + execute(&code_transfer, &mut mock_ext, &mut GasMeter::with_limit(50_000, 1)).unwrap(); + + assert_eq!(&mock_ext.transfers, &[TransferEntry { to: 2, value: 6 }]); + } + + const CODE_MEM: &str = r#" +(module + ;; Internal memory is not allowed. + (memory 1 1) + + (func (export "call") + nop + ) +) +"#; + + #[test] + fn contract_internal_mem() { + let code_mem = wabt::wat2wasm(CODE_MEM).unwrap(); + + let mut mock_ext = MockExt::default(); + + assert_matches!( + execute(&code_mem, &mut mock_ext, &mut GasMeter::with_limit(100_000, 1)), + Err(_) + ); + } +} diff --git a/substrate/runtime/council/src/lib.rs b/substrate/runtime/council/src/lib.rs index 5c103227a8e4d..56d87ea59bff5 100644 --- a/substrate/runtime/council/src/lib.rs +++ b/substrate/runtime/council/src/lib.rs @@ -654,8 +654,8 @@ mod tests { } impl staking::Trait for Test { type Balance = u64; - type DetermineContractAddress = staking::DummyContractAddressFor; type AccountIndex = u64; + type OnAccountKill = (); } impl democracy::Trait for Test { type Proposal = Proposal; @@ -689,7 +689,6 @@ mod tests { existential_deposit: 0, transfer_fee: 0, creation_fee: 0, - contract_fee: 0, reclaim_rebate: 0, early_era_slash: 0, session_reward: 0, diff --git a/substrate/runtime/democracy/src/lib.rs b/substrate/runtime/democracy/src/lib.rs index c6410ffdfcc97..9f9c00d056ab5 100644 --- a/substrate/runtime/democracy/src/lib.rs +++ b/substrate/runtime/democracy/src/lib.rs @@ -396,8 +396,8 @@ mod tests { } impl staking::Trait for Test { type Balance = u64; - type DetermineContractAddress = staking::DummyContractAddressFor; type AccountIndex = u64; + type OnAccountKill = (); } impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; @@ -430,7 +430,6 @@ mod tests { existential_deposit: 0, transfer_fee: 0, creation_fee: 0, - contract_fee: 0, reclaim_rebate: 0, early_era_slash: 0, session_reward: 0, diff --git a/substrate/runtime/executive/src/lib.rs b/substrate/runtime/executive/src/lib.rs index 61f52640418c6..0f01b7a7cb960 100644 --- a/substrate/runtime/executive/src/lib.rs +++ b/substrate/runtime/executive/src/lib.rs @@ -257,8 +257,8 @@ mod tests { } impl staking::Trait for Test { type Balance = u64; - type DetermineContractAddress = staking::DummyContractAddressFor; type AccountIndex = u64; + type OnAccountKill = (); } impl timestamp::Trait for Test { const TIMESTAMP_SET_POSITION: u32 = 0; @@ -283,7 +283,6 @@ mod tests { existential_deposit: 0, transfer_fee: 0, creation_fee: 0, - contract_fee: 0, reclaim_rebate: 0, early_era_slash: 0, session_reward: 0, @@ -313,7 +312,7 @@ mod tests { header: Header { parent_hash: [69u8; 32].into(), number: 1, - state_root: hex!("b47a0bfc249af6e00c71a45fcd5619c47b6f71cb4d5c62ab7bf1fe9601d5efc4").into(), + state_root: hex!("8fad93b6b9e5251a2e4913598fd0d74a138c0e486eb1133ff8081b429b0c56f2").into(), extrinsics_root: hex!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421").into(), digest: Digest { logs: vec![], }, }, @@ -347,7 +346,7 @@ mod tests { header: Header { parent_hash: [69u8; 32].into(), number: 1, - state_root: hex!("b47a0bfc249af6e00c71a45fcd5619c47b6f71cb4d5c62ab7bf1fe9601d5efc4").into(), + state_root: hex!("8fad93b6b9e5251a2e4913598fd0d74a138c0e486eb1133ff8081b429b0c56f2").into(), extrinsics_root: [0u8; 32].into(), digest: Digest { logs: vec![], }, }, diff --git a/substrate/runtime/primitives/src/traits.rs b/substrate/runtime/primitives/src/traits.rs index f6fd79077fde9..3c89087d70dc3 100644 --- a/substrate/runtime/primitives/src/traits.rs +++ b/substrate/runtime/primitives/src/traits.rs @@ -25,6 +25,7 @@ use substrate_primitives; use codec::{Codec, Encode}; pub use integer_sqrt::IntegerSquareRoot; pub use num_traits::{Zero, One, Bounded}; +pub use num_traits::ops::checked::{CheckedAdd, CheckedSub, CheckedMul, CheckedDiv}; use rstd::ops::{Add, Sub, Mul, Div, Rem, AddAssign, SubAssign, MulAssign, DivAssign, RemAssign}; /// A lazy value. @@ -131,6 +132,10 @@ pub trait SimpleArithmetic: Mul + MulAssign + Div + DivAssign + Rem + RemAssign + + CheckedAdd + + CheckedSub + + CheckedMul + + CheckedDiv + PartialOrd + Ord {} impl + MulAssign + Div + DivAssign + Rem + RemAssign + + CheckedAdd + + CheckedSub + + CheckedMul + + CheckedDiv + PartialOrd + Ord > SimpleArithmetic for T {} diff --git a/substrate/runtime/session/src/lib.rs b/substrate/runtime/session/src/lib.rs index 6e320a73575f9..8e53428de63a0 100644 --- a/substrate/runtime/session/src/lib.rs +++ b/substrate/runtime/session/src/lib.rs @@ -79,6 +79,7 @@ decl_module! { fn force_new_session(normal_rotation: bool) -> Result = 1; } } + decl_storage! { trait Store for Module; @@ -93,6 +94,9 @@ decl_storage! { // Percent by which the session must necessarily finish late before we early-exit the session. pub BrokenPercentLate get(broken_percent_late): b"ses:broken_percent_late" => required T::Moment; + // New session is being forced is this entry exists; in which case, the boolean value is whether + // the new session should be considered a normal rotation (rewardable) or exceptional (slashable). + pub ForcingNewSession get(forcing_new_session): b"ses:forcing_new_session" => bool; // Block at which the session length last changed. LastLengthChange: b"ses:llc" => T::BlockNumber; // The next key for a given validator. @@ -127,8 +131,8 @@ impl Module { } /// Forces a new session. - fn force_new_session(normal_rotation: bool) -> Result { - Self::rotate_session(normal_rotation); + pub fn force_new_session(normal_rotation: bool) -> Result { + >::put(normal_rotation); Ok(()) } @@ -153,13 +157,16 @@ impl Module { let block_number = >::block_number(); let is_final_block = ((block_number - Self::last_length_change()) % Self::length()).is_zero(); let broken_validation = Self::broken_validation(); - if is_final_block || broken_validation { - Self::rotate_session(!broken_validation); + if let Some(normal_rotation) = Self::forcing_new_session() { + Self::rotate_session(normal_rotation, is_final_block); + >::kill(); + } else if is_final_block || broken_validation { + Self::rotate_session(!broken_validation, is_final_block); } } /// Move onto next session: register the new authority set. - pub fn rotate_session(normal_rotation: bool) { + pub fn rotate_session(normal_rotation: bool, is_final_block: bool) { let now = >::get(); let time_elapsed = now.clone() - Self::current_start(); @@ -168,9 +175,14 @@ impl Module { >::put(now); // Enact era length change. - if let Some(next_len) = >::take() { - let block_number = >::block_number(); + let len_changed = if let Some(next_len) = >::take() { >::put(next_len); + true + } else { + false + }; + if len_changed || !is_final_block { + let block_number = >::block_number(); >::put(block_number); } @@ -354,6 +366,40 @@ mod tests { }); } + #[test] + fn should_work_with_early_exit() { + with_externalities(&mut new_test_ext(), || { + System::set_block_number(1); + assert_ok!(Session::set_length(10)); + assert_eq!(Session::blocks_remaining(), 1); + Session::check_rotate_session(); + + System::set_block_number(2); + assert_eq!(Session::blocks_remaining(), 0); + Session::check_rotate_session(); + assert_eq!(Session::length(), 10); + + System::set_block_number(7); + assert_eq!(Session::current_index(), 1); + assert_eq!(Session::blocks_remaining(), 5); + assert_ok!(Session::force_new_session(false)); + Session::check_rotate_session(); + + System::set_block_number(8); + assert_eq!(Session::current_index(), 2); + assert_eq!(Session::blocks_remaining(), 9); + Session::check_rotate_session(); + + System::set_block_number(17); + assert_eq!(Session::current_index(), 2); + assert_eq!(Session::blocks_remaining(), 0); + Session::check_rotate_session(); + + System::set_block_number(18); + assert_eq!(Session::current_index(), 3); + }); + } + #[test] fn session_length_change_should_work() { with_externalities(&mut new_test_ext(), || { diff --git a/substrate/runtime/staking/Cargo.toml b/substrate/runtime/staking/Cargo.toml index 3c26ad034689f..410667aaec6c7 100644 --- a/substrate/runtime/staking/Cargo.toml +++ b/substrate/runtime/staking/Cargo.toml @@ -11,7 +11,6 @@ safe-mix = { path = "../../../safe-mix", default_features = false} substrate-keyring = { path = "../../keyring", optional = true } substrate-codec = { path = "../../codec", default_features = false } substrate-primitives = { path = "../../primitives", default_features = false } -substrate-runtime-contract = { path = "../contract", default_features = false } substrate-runtime-std = { path = "../../runtime-std", default_features = false } substrate-runtime-io = { path = "../../runtime-io", default_features = false } substrate-runtime-sandbox = { path = "../../runtime-sandbox", default_features = false } @@ -34,7 +33,6 @@ std = [ "substrate-keyring", "substrate-codec/std", "substrate-primitives/std", - "substrate-runtime-contract/std", "substrate-runtime-std/std", "substrate-runtime-io/std", "substrate-runtime-sandbox/std", diff --git a/substrate/runtime/staking/src/genesis_config.rs b/substrate/runtime/staking/src/genesis_config.rs index 74d028c3beef4..2987725b7a235 100644 --- a/substrate/runtime/staking/src/genesis_config.rs +++ b/substrate/runtime/staking/src/genesis_config.rs @@ -24,7 +24,7 @@ use runtime_support::{StorageValue, StorageMap}; use primitives::traits::{Zero, As}; use {runtime_io, primitives}; use super::{Trait, ENUM_SET_SIZE, EnumSet, NextEnumSet, Intentions, CurrentEra, - BondingDuration, ContractFee, CreationFee, TransferFee, ReclaimRebate, + BondingDuration, CreationFee, TransferFee, ReclaimRebate, ExistentialDeposit, TransactionByteFee, TransactionBaseFee, TotalStake, SessionsPerEra, ValidatorCount, FreeBalance, SessionReward, EarlyEraSlash}; @@ -42,7 +42,6 @@ pub struct GenesisConfig { pub transaction_byte_fee: T::Balance, pub transfer_fee: T::Balance, pub creation_fee: T::Balance, - pub contract_fee: T::Balance, pub reclaim_rebate: T::Balance, pub existential_deposit: T::Balance, pub session_reward: T::Balance, @@ -62,7 +61,6 @@ impl GenesisConfig where T::AccountId: From { transaction_byte_fee: T::Balance::sa(0), transfer_fee: T::Balance::sa(0), creation_fee: T::Balance::sa(0), - contract_fee: T::Balance::sa(0), existential_deposit: T::Balance::sa(0), reclaim_rebate: T::Balance::sa(0), session_reward: T::Balance::sa(0), @@ -90,7 +88,6 @@ impl GenesisConfig where T::AccountId: From { transaction_byte_fee: T::Balance::sa(0), transfer_fee: T::Balance::sa(0), creation_fee: T::Balance::sa(0), - contract_fee: T::Balance::sa(0), existential_deposit: T::Balance::sa(0), reclaim_rebate: T::Balance::sa(0), session_reward: T::Balance::sa(0), @@ -112,7 +109,6 @@ impl Default for GenesisConfig { transaction_byte_fee: T::Balance::sa(0), transfer_fee: T::Balance::sa(0), creation_fee: T::Balance::sa(0), - contract_fee: T::Balance::sa(0), existential_deposit: T::Balance::sa(0), reclaim_rebate: T::Balance::sa(0), session_reward: T::Balance::sa(0), @@ -135,7 +131,6 @@ impl primitives::BuildStorage for GenesisConfig { Self::hash(>::key()).to_vec() => self.transaction_byte_fee.encode(), Self::hash(>::key()).to_vec() => self.transfer_fee.encode(), Self::hash(>::key()).to_vec() => self.creation_fee.encode(), - Self::hash(>::key()).to_vec() => self.contract_fee.encode(), Self::hash(>::key()).to_vec() => self.existential_deposit.encode(), Self::hash(>::key()).to_vec() => self.reclaim_rebate.encode(), Self::hash(>::key()).to_vec() => self.current_era.encode(), diff --git a/substrate/runtime/staking/src/lib.rs b/substrate/runtime/staking/src/lib.rs index 4ae6f817cf8bc..b0436c5fa30ae 100644 --- a/substrate/runtime/staking/src/lib.rs +++ b/substrate/runtime/staking/src/lib.rs @@ -36,7 +36,6 @@ extern crate substrate_runtime_std as rstd; extern crate substrate_codec as codec; extern crate substrate_primitives; -extern crate substrate_runtime_contract as contract; extern crate substrate_runtime_io as runtime_io; extern crate substrate_runtime_primitives as primitives; extern crate substrate_runtime_consensus as consensus; @@ -46,31 +45,25 @@ extern crate substrate_runtime_system as system; extern crate substrate_runtime_timestamp as timestamp; #[cfg(test)] use std::fmt::Debug; -use account_db::State; use rstd::prelude::*; use rstd::{cmp, result}; -use rstd::collections::btree_map::BTreeMap; use codec::{Encode, Decode, Codec, Input, Output}; use runtime_support::{StorageValue, StorageMap, Parameter}; use runtime_support::dispatch::Result; use session::OnSessionChange; use primitives::traits::{Zero, One, Bounded, RefInto, SimpleArithmetic, Executable, MakePayment, - As, AuxLookup, Hash as HashT, Member}; + As, AuxLookup, Member, CheckedAdd, CheckedSub}; use address::Address as RawAddress; -use double_map::StorageDoubleMap; -pub mod address; mod mock; + +pub mod address; mod tests; mod genesis_config; -mod account_db; -mod double_map; #[cfg(feature = "std")] pub use genesis_config::GenesisConfig; -pub use account_db::*; - /// Number of account IDs stored per enum set. const ENUM_SET_SIZE: usize = 64; @@ -95,39 +88,26 @@ pub enum LockStatus { Staked, } -pub trait ContractAddressFor { - fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId; +/// The account was the given id was killed. +pub trait OnAccountKill { + /// The account was the given id was killed. + fn on_account_kill(who: &AccountId); } -#[cfg(feature = "std")] -pub struct DummyContractAddressFor; -#[cfg(feature = "std")] -impl ContractAddressFor for DummyContractAddressFor { - fn contract_address_for(_code: &[u8], origin: &u64) -> u64 { - origin + 1 - } -} - -impl ContractAddressFor for Hash where - Hash: HashT, - AccountId: Sized + Codec + From, - Hash::Output: Codec -{ - fn contract_address_for(code: &[u8], origin: &AccountId) -> AccountId { - let mut dest_pre = Hash::hash(code).encode(); - origin.using_encoded(|s| dest_pre.extend(s)); - AccountId::from(Hash::hash(&dest_pre)) - } +impl OnAccountKill for () { + fn on_account_kill(_who: &AccountId) {} } pub trait Trait: system::Trait + session::Trait { /// The balance of an account. type Balance: Parameter + SimpleArithmetic + Codec + Default + Copy + As + As + As; - /// Function type to get the contract address given the creator. - type DetermineContractAddress: ContractAddressFor; /// Type used for storing an account's index; implies the maximum number of accounts the system /// can hold. type AccountIndex: Parameter + Member + Codec + SimpleArithmetic + As + As + As + As + As + Copy; + /// A function which is invoked when the given account is dead. + /// + /// Gives a chance to clean up resources associated with the given account. + type OnAccountKill: OnAccountKill; } decl_module! { @@ -147,7 +127,7 @@ decl_module! { fn set_sessions_per_era(new: T::BlockNumber) -> Result = 0; fn set_bonding_duration(new: T::BlockNumber) -> Result = 1; fn set_validator_count(new: u32) -> Result = 2; - fn force_new_era() -> Result = 3; + fn force_new_era(should_slash: bool) -> Result = 3; } } @@ -175,8 +155,6 @@ decl_storage! { pub TransferFee get(transfer_fee): b"sta:transfer_fee" => required T::Balance; // The fee required to create an account. At least as big as ReclaimRebate. pub CreationFee get(creation_fee): b"sta:creation_fee" => required T::Balance; - // The fee required to create a contract. At least as big as ReclaimRebate. - pub ContractFee get(contract_fee): b"sta:contract_fee" => required T::Balance; // Maximum reward, per validator, that is provided per acceptable session. pub SessionReward get(session_reward): b"sta:session_reward" => required T::Balance; // Slash, per validator that is taken per abnormal era end. @@ -204,12 +182,17 @@ decl_storage! { // The enumeration sets. pub EnumSet get(enum_set): b"sta:enum_set" => default map [ T::AccountIndex => Vec ]; + // We are forcing a new era. + pub ForcingNewEra get(forcing_new_era): b"sta:forcing_new_era" => (); + // The "free" balance of a given account. // // This is the only balance that matters in terms of most operations on tokens. It is // alone used to determine the balance when in the contract execution environment. When this // balance falls below the value of `ExistentialDeposit`, then the "current account" is - // deleted: specifically, `Bondage`, `StorageOf`, `CodeOf` and `FreeBalance`. + // deleted: specifically, `Bondage` and `FreeBalance`. Furthermore, `OnAccountKill` callback + // is invoked, giving a chance to external modules to cleanup data associated with + // the deleted account. // // `system::AccountNonce` is also deleted if `ReservedBalance` is also zero (it also gets // collapsed to zero if it ever becomes less than `ExistentialDeposit`. @@ -231,20 +214,6 @@ decl_storage! { // The block at which the `who`'s funds become entirely liquid. pub Bondage get(bondage): b"sta:bon:" => default map [ T::AccountId => T::BlockNumber ]; - - // The code associated with an account. - pub CodeOf: b"sta:cod:" => default map [ T::AccountId => Vec ]; // TODO Vec values should be optimised to not do a length prefix. -} - -/// The storage items associated with an account/key. -/// -/// TODO: keys should also be able to take AsRef to ensure Vecs can be passed as &[u8] -pub(crate) struct StorageOf(::rstd::marker::PhantomData); -impl double_map::StorageDoubleMap for StorageOf { - type Key1 = T::AccountId; - type Key2 = Vec; - type Value = Vec; - const PREFIX: &'static [u8] = b"sta:sto:"; } enum NewAccountOutcome { @@ -253,6 +222,14 @@ enum NewAccountOutcome { BadHint, } +/// Outcome of a balance update. +pub enum UpdateBalanceOutcome { + /// Account balance was simply updated. + Updated, + /// The update has led to killing of the account. + AccountKilled, +} + impl Module { // PUBLIC IMMUTABLES @@ -292,25 +269,40 @@ impl Module { } } - /// Create a smart-contract account. - pub fn create(aux: &T::PublicAux, code: &[u8], value: T::Balance) -> Result { - // commit anything that made it this far to storage - if let Some(commit) = Self::effect_create(aux.ref_into(), code, value, &DirectAccountDb)? { - >::merge(&mut DirectAccountDb, commit); - } - Ok(()) - } - // PUBLIC DISPATCH /// Transfer some unlocked staking balance to another staker. - /// TODO: probably want to state gas-limit and gas-price. - fn transfer(aux: &T::PublicAux, dest: Address, value: T::Balance) -> Result { + pub fn transfer(aux: &T::PublicAux, dest: Address, value: T::Balance) -> Result { let dest = Self::lookup(dest)?; - // commit anything that made it this far to storage - if let Some(commit) = Self::effect_transfer(aux.ref_into(), &dest, value, &DirectAccountDb)? { - >::merge(&mut DirectAccountDb, commit); + + let transactor = aux.ref_into(); + let from_balance = Self::free_balance(transactor); + let would_create = from_balance.is_zero(); + let fee = if would_create { Self::creation_fee() } else { Self::transfer_fee() }; + let liability = value + fee; + + let new_from_balance = match from_balance.checked_sub(&liability) { + Some(b) => b, + None => return Err("balance too low to send value"), + }; + if would_create && value < Self::existential_deposit() { + return Err("value too low to create account"); + } + if >::get(transactor) > >::block_number() { + return Err("bondage too high to send value"); + } + + let to_balance = Self::free_balance(&dest); + let new_to_balance = match to_balance.checked_add(&value) { + Some(b) => b, + None => return Err("destination balance too high to receive value"), + }; + + if transactor != &dest { + Self::set_free_balance(transactor, new_from_balance); + Self::set_free_balance_creating(&dest, new_to_balance); } + Ok(()) } @@ -414,23 +406,26 @@ impl Module { Ok(()) } - /// Force there to be a new era. This also forces a new session immediately after. - fn force_new_era() -> Result { - >::rotate_session(false); - Ok(()) + /// Force there to be a new era. This also forces a new session immediately after by + /// setting `normal_rotation` to be false. Validators will get slashed. + fn force_new_era(should_slash: bool) -> Result { + >::put(()); + >::force_new_session(!should_slash) } // PUBLIC MUTABLES (DANGEROUS) - /// Set the free balance of an account to some new value. Will enforce ExistentialDeposit law, - /// anulling the account as needed. - pub fn set_reserved_balance(who: &T::AccountId, balance: T::Balance) -> bool { + /// Set the free balance of an account to some new value. + /// + /// Will enforce ExistentialDeposit law, anulling the account as needed. + /// In that case it will return `AccountKilled`. + pub fn set_reserved_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome { if balance < Self::existential_deposit() { Self::on_reserved_too_low(who); - false + UpdateBalanceOutcome::AccountKilled } else { >::insert(who, balance); - true + UpdateBalanceOutcome::Updated } } @@ -439,15 +434,55 @@ impl Module { /// /// Doesn't do any preparatory work for creating a new account, so should only be used when it /// is known that the account already exists. - pub fn set_free_balance(who: &T::AccountId, balance: T::Balance) -> bool { + /// + /// Returns if the account was successfully updated or update has led to killing of the account. + pub fn set_free_balance(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome { // Commented out for no - but consider it instructive. // assert!(!Self::voting_balance(who).is_zero()); if balance < Self::existential_deposit() { Self::on_free_too_low(who); - false + UpdateBalanceOutcome::AccountKilled } else { >::insert(who, balance); - true + UpdateBalanceOutcome::Updated + } + } + + /// Set the free balance on an account to some new value. + /// + /// Same as [`set_free_balance`], but will create a new account. + /// + /// Returns if the account was successfully updated or update has led to killing of the account. + /// + /// [`set_free_balance`]: #method.set_free_balance + pub fn set_free_balance_creating(who: &T::AccountId, balance: T::Balance) -> UpdateBalanceOutcome { + let ed = >::existential_deposit(); + // If the balance is too low, then the account is reaped. + // NOTE: There are two balances for every account: `reserved_balance` and + // `free_balance`. This contract subsystem only cares about the latter: whenever + // the term "balance" is used *here* it should be assumed to mean "free balance" + // in the rest of the module. + // Free balance can never be less than ED. If that happens, it gets reduced to zero + // and the account information relevant to this subsystem is deleted (i.e. the + // account is reaped). + // NOTE: This is orthogonal to the `Bondage` value that an account has, a high + // value of which makes even the `free_balance` unspendable. + // TODO: enforce this for the other balance-altering functions. + if balance < ed { + Self::on_free_too_low(who); + UpdateBalanceOutcome::AccountKilled + } else { + if !>::exists(who) { + let outcome = Self::new_account(&who, balance); + let credit = match outcome { + NewAccountOutcome::GoodHint => balance + >::reclaim_rebate(), + _ => balance, + }; + >::insert(who, credit); + } else { + >::insert(who, balance); + } + UpdateBalanceOutcome::Updated } } @@ -590,7 +625,10 @@ impl Module { } } } - if ((session_index - Self::last_era_length_change()) % Self::sessions_per_era()).is_zero() || !normal_rotation { + if >::take().is_some() + || ((session_index - Self::last_era_length_change()) % Self::sessions_per_era()).is_zero() + || !normal_rotation + { Self::new_era(); } } @@ -738,8 +776,7 @@ impl Module { fn on_free_too_low(who: &T::AccountId) { >::remove(who); >::remove(who); - >::remove(who); - >::remove_prefix(who.clone()); + T::OnAccountKill::on_account_kill(who); if Self::reserved_balance(who).is_zero() { >::remove(who); @@ -753,101 +790,6 @@ impl Module { >::remove(who); } } - - fn effect_create>( - transactor: &T::AccountId, - code: &[u8], - value: T::Balance, - account_db: &DB, - ) -> result::Result>, &'static str> { - let from_balance = account_db.get_balance(transactor); - - let liability = value + Self::contract_fee(); - - if from_balance < liability { - return Err("balance too low to send value"); - } - if value < Self::existential_deposit() { - return Err("value too low to create account"); - } - if >::get(transactor) > >::block_number() { - return Err("bondage too high to send value"); - } - - let dest = T::DetermineContractAddress::contract_address_for(code, transactor); - - // early-out if degenerate. - if &dest == transactor { - return Ok(None); - } - - let mut local = BTreeMap::new(); - // two inserts are safe - // note that we now know that `&dest != transactor` due to early-out before. - local.insert(dest, ChangeEntry::contract_created(value, code.to_vec())); - local.insert(transactor.clone(), ChangeEntry::balance_changed(from_balance - liability)); - Ok(Some(local)) - } - - fn effect_transfer>( - transactor: &T::AccountId, - dest: &T::AccountId, - value: T::Balance, - account_db: &DB, - ) -> result::Result>, &'static str> { - let would_create = account_db.get_balance(transactor).is_zero(); - let fee = if would_create { Self::creation_fee() } else { Self::transfer_fee() }; - let liability = value + fee; - - let from_balance = account_db.get_balance(transactor); - if from_balance < liability { - return Err("balance too low to send value"); - } - if would_create && value < Self::existential_deposit() { - return Err("value too low to create account"); - } - if >::get(transactor) > >::block_number() { - return Err("bondage too high to send value"); - } - - let to_balance = account_db.get_balance(dest); - if to_balance + value <= to_balance { - return Err("destination balance too high to receive value"); - } - - // TODO: an additional fee, based upon gaslimit/gasprice. - let gas_limit = 100_000; - - // TODO: consider storing upper-bound for contract's gas limit in fixed-length runtime - // code in contract itself and use that. - - // Our local overlay: Should be used for any transfers and creates that happen internally. - let mut overlay = OverlayAccountDb::new(account_db); - - if transactor != dest { - overlay.set_balance(transactor, from_balance - liability); - overlay.set_balance(dest, to_balance + value); - } - - let dest_code = overlay.get_code(dest); - let should_commit = if dest_code.is_empty() { - true - } else { - // TODO: logging (logs are just appended into a notable storage-based vector and - // cleared every block). - let mut staking_ext = StakingExt { - account_db: &mut overlay, - account: dest.clone(), - }; - contract::execute(&dest_code, &mut staking_ext, gas_limit).is_ok() - }; - - Ok(if should_commit { - Some(overlay.into_state()) - } else { - None - }) - } } impl Executable for Module { diff --git a/substrate/runtime/staking/src/mock.rs b/substrate/runtime/staking/src/mock.rs index c2cf48dbd19ab..66119cef6bd92 100644 --- a/substrate/runtime/staking/src/mock.rs +++ b/substrate/runtime/staking/src/mock.rs @@ -24,7 +24,6 @@ use primitives::testing::{Digest, Header}; use substrate_primitives::H256; use runtime_io; use {GenesisConfig, Module, Trait, consensus, session, system, timestamp}; -use super::DummyContractAddressFor; // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] @@ -55,8 +54,8 @@ impl timestamp::Trait for Test { } impl Trait for Test { type Balance = u64; - type DetermineContractAddress = DummyContractAddressFor; type AccountIndex = u64; + type OnAccountKill = (); } pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64, current_era: u64, monied: bool, reward: u64) -> runtime_io::TestExternalities { @@ -95,7 +94,6 @@ pub fn new_test_ext(ext_deposit: u64, session_length: u64, sessions_per_era: u64 existential_deposit: ext_deposit, transfer_fee: 0, creation_fee: 0, - contract_fee: 0, reclaim_rebate: 0, session_reward: reward, early_era_slash: if monied { 20 } else { 0 }, diff --git a/substrate/runtime/staking/src/tests.rs b/substrate/runtime/staking/src/tests.rs index 0034c261986de..ef9fa4faa5173 100644 --- a/substrate/runtime/staking/src/tests.rs +++ b/substrate/runtime/staking/src/tests.rs @@ -587,33 +587,17 @@ fn transferring_incomplete_reserved_balance_should_work() { } #[test] -fn account_removal_removes_storage() { - with_externalities(&mut new_test_ext(100, 1, 3, 1, false, 0), || { - // Setup two accounts with free balance above than exsistential threshold. - { - >::insert(1, 110); - >::insert(1, b"foo".to_vec(), b"1".to_vec()); - >::insert(1, b"bar".to_vec(), b"2".to_vec()); - - >::insert(2, 110); - >::insert(2, b"hello".to_vec(), b"3".to_vec()); - >::insert(2, b"world".to_vec(), b"4".to_vec()); - } - - // Transfer funds from account 1 of such amount that after this transfer - // the balance of account 1 is will be below than exsistential threshold. - // - // This should lead to the removal of all storage associated with this account. - assert_ok!(Staking::transfer(&1, 2.into(), 20)); - - // Verify that all entries from account 1 is removed, while - // entries from account 2 is in place. - { - assert_eq!(>::get(1, b"foo".to_vec()), None); - assert_eq!(>::get(1, b"bar".to_vec()), None); - - assert_eq!(>::get(2, b"hello".to_vec()), Some(b"3".to_vec())); - assert_eq!(>::get(2, b"world".to_vec()), Some(b"4".to_vec())); - } +fn transferring_too_high_value_should_not_panic() { + with_externalities(&mut new_test_ext(0, 1, 3, 1, false, 0), || { + >::insert(1, u64::max_value()); + >::insert(2, 1); + + assert_err!( + Staking::transfer(&1, 2.into(), u64::max_value()), + "destination balance too high to receive value" + ); + + assert_eq!(Staking::free_balance(&1), u64::max_value()); + assert_eq!(Staking::free_balance(&2), 1); }); } diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index de3f922c2ea81..d449d647e20ce 100644 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm index d1f2b6fbbf4af..2fe9433d00c92 100755 Binary files a/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm and b/substrate/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.wasm differ