diff --git a/substrate/runtime/contract/src/gas.rs b/substrate/runtime/contract/src/gas.rs
index 2f2b82d6b0142..b405e31d7fd59 100644
--- a/substrate/runtime/contract/src/gas.rs
+++ b/substrate/runtime/contract/src/gas.rs
@@ -14,8 +14,9 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
-use {Trait, Module};
+use {Trait, Module, GasSpent};
use runtime_primitives::traits::{As, CheckedMul, CheckedSub, Zero};
+use runtime_support::StorageValue;
use staking;
#[must_use]
@@ -35,6 +36,8 @@ impl GasMeterResult {
}
pub struct GasMeter {
+ limit: T::Gas,
+ /// Amount of gas left from initial gas limit. Can reach zero.
gas_left: T::Gas,
gas_price: T::Balance,
}
@@ -42,6 +45,7 @@ impl GasMeter {
#[cfg(test)]
pub fn with_limit(gas_limit: T::Gas, gas_price: T::Balance) -> GasMeter {
GasMeter {
+ limit: gas_limit,
gas_left: gas_limit,
gas_price,
}
@@ -101,6 +105,7 @@ impl GasMeter {
} else {
self.gas_left = self.gas_left - amount;
let mut nested = GasMeter {
+ limit: amount,
gas_left: amount,
gas_price: self.gas_price,
};
@@ -117,6 +122,11 @@ impl GasMeter {
pub fn gas_left(&self) -> T::Gas {
self.gas_left
}
+
+ /// Returns how much gas was spent.
+ fn spent(&self) -> T::Gas {
+ self.limit - self.gas_left
+ }
}
/// Buy the given amount of gas.
@@ -127,6 +137,14 @@ pub fn buy_gas(
transactor: &T::AccountId,
gas_limit: T::Gas,
) -> Result, &'static str> {
+ // Check if the specified amount of gas is available in the current block.
+ // This cannot underflow since `gas_spent` is never greater than `block_gas_limit`.
+ let gas_available = >::block_gas_limit() - >::gas_spent();
+ if gas_limit > gas_available {
+ return Err("block gas limit is reached");
+ }
+
+ // Buy the specified amount of gas.
let gas_price = >::gas_price();
let b = >::free_balance(transactor);
let cost = >::as_(gas_limit.clone())
@@ -138,6 +156,7 @@ pub fn buy_gas(
>::set_free_balance(transactor, b - cost);
>::decrease_total_stake_by(cost);
Ok(GasMeter {
+ limit: gas_limit,
gas_left: gas_limit,
gas_price,
})
@@ -145,6 +164,13 @@ pub fn buy_gas(
/// Refund the unused gas.
pub fn refund_unused_gas(transactor: &T::AccountId, gas_meter: GasMeter) {
+ // Increase total spent gas.
+ // This cannot overflow, since `gas_spent` is never greater than `block_gas_limit`, which
+ // also has T::Gas type.
+ let gas_spent = >::gas_spent() + gas_meter.spent();
+ >::put(gas_spent);
+
+ // Refund gas left by the price it was bought.
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
index 89f94b40b94ef..08f09324a5ad7 100644
--- a/substrate/runtime/contract/src/genesis_config.rs
+++ b/substrate/runtime/contract/src/genesis_config.rs
@@ -16,7 +16,7 @@
//! Build the contract module part of the genesis block storage.
-use {Trait, ContractFee, CallBaseFee, CreateBaseFee, GasPrice, MaxDepth};
+use {Trait, ContractFee, CallBaseFee, CreateBaseFee, GasPrice, MaxDepth, BlockGasLimit};
use runtime_primitives;
use runtime_io::{self, twox_128};
@@ -34,6 +34,7 @@ pub struct GenesisConfig {
pub create_base_fee: T::Gas,
pub gas_price: T::Balance,
pub max_depth: u32,
+ pub block_gas_limit: T::Gas,
}
impl runtime_primitives::BuildStorage for GenesisConfig {
@@ -43,7 +44,8 @@ impl runtime_primitives::BuildStorage for GenesisConfig {
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()
+ twox_128(>::key()).to_vec() => self.max_depth.encode(),
+ twox_128(>::key()).to_vec() => self.block_gas_limit.encode()
];
Ok(r.into())
}
diff --git a/substrate/runtime/contract/src/lib.rs b/substrate/runtime/contract/src/lib.rs
index bdeebcf7b29fd..814e945ea3102 100644
--- a/substrate/runtime/contract/src/lib.rs
+++ b/substrate/runtime/contract/src/lib.rs
@@ -25,13 +25,31 @@
//! 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).
+//! Gas is bought upfront up to the, specified in transaction, limit. Any unused gas 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.
+//!
+//! # Interaction with the system
+//!
+//! ## Finalization
+//!
+//! This module requires performing some finalization steps at the end of the block. If not performed
+//! the module will have incorrect behavior.
+//!
+//! Call [`Module::execute`] at the end of the block. The order in relation to
+//! the other module doesn't matter.
+//!
+//! ## Account killing
+//!
+//! When `staking` module determines that account is dead (e.g. account's balance fell below
+//! exsistential deposit) then it reaps the account. That will lead to deletion of the associated
+//! code and storage of the account.
+//!
+//! [`Module::execute`]: struct.Module.html#impl-Executable
#![cfg_attr(not(feature = "std"), no_std)]
@@ -90,16 +108,16 @@ use account_db::{AccountDb, OverlayAccountDb};
use double_map::StorageDoubleMap;
use codec::Codec;
-use runtime_primitives::traits::{As, RefInto, SimpleArithmetic};
+use runtime_primitives::traits::{As, RefInto, SimpleArithmetic, Executable};
use runtime_support::dispatch::Result;
-use runtime_support::{Parameter, StorageMap};
+use runtime_support::{Parameter, StorageMap, StorageValue};
pub trait Trait: system::Trait + staking::Trait + consensus::Trait {
/// Function type to get the contract address given the creator.
type DetermineContractAddress: ContractAddressFor;
// As is needed for wasm-utils
- type Gas: Parameter + Codec + SimpleArithmetic + Copy + As + As + As;
+ type Gas: Parameter + Default + Codec + SimpleArithmetic + Copy + As + As + As;
}
pub trait ContractAddressFor {
@@ -144,9 +162,13 @@ decl_storage! {
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 maximum amount of gas that could be expended per block.
+ BlockGasLimit get(block_gas_limit): b"con:block_gas_limit" => required T::Gas;
+ // Gas spent so far in this block.
+ GasSpent get(gas_spent): b"con:gas_spent" => default T::Gas;
// 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.
+ CodeOf: b"con:cod:" => default map [ T::AccountId => Vec ]; // TODO Vec values should be optimised to not do a length prefix.
}
// TODO: consider storing upper-bound for contract's gas limit in fixed-length runtime
@@ -253,3 +275,10 @@ impl staking::OnFreeBalanceZero for Module {
>::remove_prefix(who.clone());
}
}
+
+/// Finalization hook for the smart-contract module.
+impl Executable for Module {
+ fn execute() {
+ >::kill();
+ }
+}
diff --git a/substrate/runtime/contract/src/tests.rs b/substrate/runtime/contract/src/tests.rs
index 1f3d221ffe2c2..5f1014243a046 100644
--- a/substrate/runtime/contract/src/tests.rs
+++ b/substrate/runtime/contract/src/tests.rs
@@ -77,61 +77,90 @@ impl ContractAddressFor for DummyContractAddressFor {
}
}
-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],
- }.build_storage()
+struct ExtBuilder {
+ existential_deposit: u64,
+ gas_price: u64,
+ block_gas_limit: u64,
+}
+impl Default for ExtBuilder {
+ fn default() -> Self {
+ Self {
+ existential_deposit: 0,
+ gas_price: 2,
+ block_gas_limit: 100_000_000,
+ }
+ }
+}
+impl ExtBuilder {
+ fn existential_deposit(mut self, existential_deposit: u64) -> Self {
+ self.existential_deposit = existential_deposit;
+ self
+ }
+ fn gas_price(mut self, gas_price: u64) -> Self {
+ self.gas_price = gas_price;
+ self
+ }
+ fn block_gas_limit(mut self, block_gas_limit: u64) -> Self {
+ self.block_gas_limit = block_gas_limit;
+ self
+ }
+ fn build(self) -> 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(
- staking::GenesisConfig:: {
- sessions_per_era: 1,
- current_era: 0,
- balances: vec![],
- intentions: vec![],
- validator_count: 2,
- minimum_validator_count: 0,
- 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,
- offline_slash_grace: 0,
- }.build_storage()
+ );
+ t.extend(
+ session::GenesisConfig:: {
+ session_length: 1,
+ validators: vec![10, 20],
+ }.build_storage()
.unwrap(),
- );
- t.extend(
- timestamp::GenesisConfig::::default()
- .build_storage()
+ );
+ t.extend(
+ staking::GenesisConfig:: {
+ sessions_per_era: 1,
+ current_era: 0,
+ balances: vec![],
+ intentions: vec![],
+ validator_count: 2,
+ minimum_validator_count: 0,
+ bonding_duration: 0,
+ transaction_base_fee: 0,
+ transaction_byte_fee: 0,
+ existential_deposit: self.existential_deposit,
+ transfer_fee: 0,
+ creation_fee: 0,
+ reclaim_rebate: 0,
+ early_era_slash: 0,
+ session_reward: 0,
+ offline_slash_grace: 0,
+ }.build_storage()
.unwrap(),
- );
- t.extend(
- GenesisConfig:: {
- contract_fee: 21,
- call_base_fee: 135,
- create_base_fee: 175,
- gas_price,
- max_depth: 100,
- }.build_storage()
+ );
+ t.extend(
+ timestamp::GenesisConfig::::default()
+ .build_storage()
+ .unwrap(),
+ );
+ t.extend(
+ GenesisConfig:: {
+ contract_fee: 21,
+ call_base_fee: 135,
+ create_base_fee: 175,
+ gas_price: self.gas_price,
+ max_depth: 100,
+ block_gas_limit: self.block_gas_limit,
+ }.build_storage()
.unwrap(),
- );
- t.into()
+ );
+ t.into()
+ }
}
const CODE_TRANSFER: &str = r#"
@@ -163,7 +192,7 @@ fn contract_transfer() {
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
- with_externalities(&mut new_test_ext(0, 2), || {
+ with_externalities(&mut ExtBuilder::default().build(), || {
>::insert(1, code_transfer.to_vec());
Staking::set_free_balance(&0, 100_000_000);
@@ -198,7 +227,7 @@ fn contract_transfer_oog() {
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
- with_externalities(&mut new_test_ext(0, 2), || {
+ with_externalities(&mut ExtBuilder::default().build(), || {
>::insert(1, code_transfer.to_vec());
Staking::set_free_balance(&0, 100_000_000);
@@ -219,16 +248,9 @@ fn contract_transfer_oog() {
// 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,
- );
+ assert_eq!(Staking::free_balance(&1), 11);
+ assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 0);
});
-
}
#[test]
@@ -237,7 +259,7 @@ fn contract_transfer_max_depth() {
let code_transfer = wabt::wat2wasm(CODE_TRANSFER).unwrap();
- with_externalities(&mut new_test_ext(0, 2), || {
+ with_externalities(&mut ExtBuilder::default().build(), || {
>::insert(CONTRACT_SHOULD_TRANSFER_TO, code_transfer.to_vec());
Staking::set_free_balance(&0, 100_000_000);
@@ -258,10 +280,7 @@ fn contract_transfer_max_depth() {
// 2 * 135 * 100 - base gas fee for call (by transaction) multiplied by max depth (100).
100_000_000 - (2 * 135 * 100) - (2 * 6 * 100),
);
- assert_eq!(
- Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO),
- 11,
- );
+ assert_eq!(Staking::free_balance(&CONTRACT_SHOULD_TRANSFER_TO), 11);
});
}
@@ -340,7 +359,7 @@ fn contract_create() {
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), || {
+ with_externalities(&mut ExtBuilder::default().build(), || {
Staking::set_free_balance(&0, 100_000_000);
Staking::increase_total_stake_by(100_000_000);
Staking::set_free_balance(&1, 0);
@@ -389,7 +408,7 @@ 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), || {
+ with_externalities(&mut ExtBuilder::default().gas_price(3).build(), || {
let derived_address = ::DetermineContractAddress::contract_address_for(
&code_ctor_transfer,
&0,
@@ -434,29 +453,29 @@ const CODE_NOP: &'static str = r#"
fn refunds_unused_gas() {
let code_nop = wabt::wat2wasm(CODE_NOP).unwrap();
- with_externalities(&mut new_test_ext(0, 2), || {
+ with_externalities(&mut ExtBuilder::default().build(), || {
>::insert(1, code_nop.to_vec());
Staking::set_free_balance(&0, 100_000_000);
Staking::increase_total_stake_by(100_000_000);
- assert_ok!(Contract::call(&0, 1, 0, 100_000, Vec::new(),));
+ assert_ok!(Contract::call(&0, 1, 0, 100_000, Vec::new()));
- assert_eq!(Staking::free_balance(&0), 100_000_000 - 4 - (2 * 135),);
+ 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), || {
+ with_externalities(&mut ExtBuilder::default().build(), || {
>::insert(1, vec![]);
Staking::set_free_balance(&0, 100_000_000);
Staking::increase_total_stake_by(100_000_000);
- assert_ok!(Contract::call(&0, 1, 0, 100_000, Vec::new(),));
+ assert_ok!(Contract::call(&0, 1, 0, 100_000, Vec::new()));
- assert_eq!(Staking::free_balance(&0), 100_000_000 - (2 * 135),);
+ assert_eq!(Staking::free_balance(&0), 100_000_000 - (2 * 135));
});
}
@@ -464,11 +483,11 @@ fn call_with_zero_value() {
fn create_with_zero_endowment() {
let code_nop = wabt::wat2wasm(CODE_NOP).unwrap();
- with_externalities(&mut new_test_ext(0, 2), || {
+ with_externalities(&mut ExtBuilder::default().build(), || {
Staking::set_free_balance(&0, 100_000_000);
Staking::increase_total_stake_by(100_000_000);
- assert_ok!(Contract::create(&0, 0, 100_000, code_nop, Vec::new(),));
+ assert_ok!(Contract::create(&0, 0, 100_000, code_nop, Vec::new()));
assert_eq!(
Staking::free_balance(&0),
@@ -481,42 +500,45 @@ fn create_with_zero_endowment() {
#[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);
- Staking::increase_total_stake_by(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);
- Staking::increase_total_stake_by(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())
- );
- }
- });
+ with_externalities(
+ &mut ExtBuilder::default().existential_deposit(100).build(),
+ || {
+ // Setup two accounts with free balance above than exsistential threshold.
+ {
+ Staking::set_free_balance(&1, 110);
+ Staking::increase_total_stake_by(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);
+ Staking::increase_total_stake_by(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#"
@@ -531,7 +553,7 @@ const CODE_UNREACHABLE: &'static str = r#"
#[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), || {
+ with_externalities(&mut ExtBuilder::default().gas_price(4).build(), || {
>::insert(1, code_unreachable.to_vec());
Staking::set_free_balance(&0, 100_000_000);
@@ -542,6 +564,48 @@ fn top_level_call_refunds_even_if_fails() {
"vm execute returned error while call"
);
- assert_eq!(Staking::free_balance(&0), 100_000_000 - (4 * 3) - (4 * 135),);
+ assert_eq!(Staking::free_balance(&0), 100_000_000 - (4 * 3) - (4 * 135));
});
}
+
+const CODE_LOOP: &'static str = r#"
+(module
+ (func (export "call")
+ (loop
+ (br 0)
+ )
+ )
+)
+"#;
+
+#[test]
+fn block_gas_limit() {
+ let code_loop = wabt::wat2wasm(CODE_LOOP).unwrap();
+ with_externalities(
+ &mut ExtBuilder::default().block_gas_limit(100_000).build(),
+ || {
+ >::insert(1, code_loop.to_vec());
+
+ Staking::set_free_balance(&0, 100_000_000);
+ Staking::increase_total_stake_by(100_000_000);
+
+ // Spend 50_000 units of gas (OOG).
+ assert_err!(
+ Contract::call(&0, 1, 0, 50_000, Vec::new()),
+ "vm execute returned error while call"
+ );
+
+ // Ensure we can't spend more gas than available in block gas limit.
+ assert_err!(
+ Contract::call(&0, 1, 0, 50_001, Vec::new()),
+ "block gas limit is reached"
+ );
+
+ // However, we can spend another 50_000
+ assert_err!(
+ Contract::call(&0, 1, 0, 50_000, Vec::new()),
+ "vm execute returned error while call"
+ );
+ },
+ );
+}