srml-contract: Extract schedule (#1044)

* Rename Config → Schedule.

* Fetch and pass config.

* Integrate config everywhere.

* <<<E as Ext>::T as Trait> → <<E::T as Trait>

* Update roots

* Cache existential_deposit

* Update COMPLEXITY.md

* Update roots.
This commit is contained in:
Sergey Pepyakin
2018-11-12 20:44:05 +01:00
committed by Gav Wood
parent 367c99b2bb
commit 1f0f3c8f6b
12 changed files with 164 additions and 133 deletions
+20 -19
View File
@@ -14,14 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
use super::{MaxDepth, ContractAddressFor, Module, Trait, Event, RawEvent};
use super::{ContractAddressFor, Trait, Event, RawEvent, Config};
use account_db::{AccountDb, OverlayAccountDb};
use gas::GasMeter;
use vm;
use rstd::prelude::*;
use runtime_primitives::traits::{Zero, CheckedAdd, CheckedSub};
use runtime_support::StorageValue;
use balances::{self, EnsureAccountLiquid};
// TODO: Add logs
@@ -38,6 +37,7 @@ pub struct ExecutionContext<'a, T: Trait + 'a> {
pub overlay: OverlayAccountDb<'a, T>,
pub depth: usize,
pub events: Vec<Event<T>>,
pub config: &'a Config<T>,
}
impl<'a, T: Trait> ExecutionContext<'a, T> {
@@ -51,12 +51,11 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
data: &[u8],
output_data: &mut Vec<u8>,
) -> Result<CallReceipt, &'static str> {
if self.depth == <MaxDepth<T>>::get() as usize {
if self.depth == self.config.max_depth as usize {
return Err("reached maximum depth, cannot make a call");
}
let call_base_fee = <Module<T>>::call_base_fee();
if gas_meter.charge(call_base_fee).is_out_of_gas() {
if gas_meter.charge(self.config.call_base_fee).is_out_of_gas() {
return Err("not enough gas to pay base call fee");
}
@@ -70,6 +69,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
self_account: dest.clone(),
depth: self.depth + 1,
events: Vec::new(),
config: self.config,
};
if value > T::Balance::zero() {
@@ -92,7 +92,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
ctx: &mut nested,
_caller: caller,
},
&::vm::Config::default(),
&self.config.schedule,
gas_meter,
).map_err(|_| "vm execute returned error while call")?;
}
@@ -114,12 +114,11 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
init_code: &[u8],
data: &[u8],
) -> Result<CreateReceipt<T>, &'static str> {
if self.depth == <MaxDepth<T>>::get() as usize {
if self.depth == self.config.max_depth as usize {
return Err("reached maximum depth, cannot create");
}
let create_base_fee = <Module<T>>::create_base_fee();
if gas_meter.charge(create_base_fee).is_out_of_gas() {
if gas_meter.charge(self.config.create_base_fee).is_out_of_gas() {
return Err("not enough gas to pay base create fee");
}
@@ -138,6 +137,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
self_account: dest.clone(),
depth: self.depth + 1,
events: Vec::new(),
config: self.config,
};
if endowment > T::Balance::zero() {
@@ -160,7 +160,7 @@ impl<'a, T: Trait> ExecutionContext<'a, T> {
ctx: &mut nested,
_caller: caller,
},
&::vm::Config::default(),
&self.config.schedule,
gas_meter,
).map_err(|_| "vm execute returned error while create")?;
@@ -213,14 +213,15 @@ fn transfer<'a, T: Trait>(
// `contract_create` will be `false` but `would_create` will be `true`.
let would_create = to_balance.is_zero();
let fee: T::Balance = if contract_create {
<Module<T>>::contract_fee()
} else {
if would_create {
<balances::Module<T>>::creation_fee()
} else {
<balances::Module<T>>::transfer_fee()
}
let fee: T::Balance = match (contract_create, would_create) {
// If this function is called from `CREATE` routine, then we always
// charge contract account creation fee.
(true, _) => ctx.config.contract_account_create_fee,
// Otherwise the fee depends on whether we create a new account or transfer
// to an existing one.
(false, true) => ctx.config.account_create_fee,
(false, false) => ctx.config.transfer_fee,
};
if gas_meter.charge_by_balance(fee).is_out_of_gas() {
@@ -233,7 +234,7 @@ fn transfer<'a, T: Trait>(
Some(b) => b,
None => return Err("balance too low to send value"),
};
if would_create && value < <balances::Module<T>>::existential_deposit() {
if would_create && value < ctx.config.existential_deposit {
return Err("value too low to create account");
}
<T as balances::Trait>::EnsureAccountLiquid::ensure_account_liquid(transactor)?;
+80 -1
View File
@@ -168,11 +168,13 @@ decl_module! {
// paying for the gas.
let mut gas_meter = gas::buy_gas::<T>(&origin, gas_limit)?;
let cfg = Config::preload();
let mut ctx = ExecutionContext {
self_account: origin.clone(),
depth: 0,
overlay: OverlayAccountDb::<T>::new(&account_db::DirectAccountDb),
events: Vec::new(),
config: &cfg,
};
let mut output_data = Vec::new();
@@ -221,11 +223,13 @@ decl_module! {
// paying for the gas.
let mut gas_meter = gas::buy_gas::<T>(&origin, gas_limit)?;
let cfg = Config::preload();
let mut ctx = ExecutionContext {
self_account: origin.clone(),
depth: 0,
overlay: OverlayAccountDb::<T>::new(&account_db::DirectAccountDb),
events: Vec::new(),
config: &cfg,
};
let result = ctx.create(origin.clone(), endowment, &mut gas_meter, &ctor_code, &data);
@@ -284,7 +288,8 @@ decl_storage! {
BlockGasLimit get(block_gas_limit) config(): T::Gas = T::Gas::sa(1_000_000);
/// Gas spent so far in this block.
GasSpent get(gas_spent): T::Gas;
/// Current cost schedule for contracts.
CurrentSchedule get(current_schedule) config(): Schedule<T::Gas> = Schedule::default();
/// The code associated with an account.
pub CodeOf: map T::AccountId => Vec<u8>; // TODO Vec<u8> values should be optimised to not do a length prefix.
}
@@ -310,3 +315,77 @@ impl<T: Trait> balances::OnFreeBalanceZero<T::AccountId> for Module<T> {
<StorageOf<T>>::remove_prefix(who.clone());
}
}
/// In-memory cache of configuration values.
///
/// We assume that these values can't be changed in the
/// course of transaction execution.
pub struct Config<T: Trait> {
pub schedule: Schedule<T::Gas>,
pub existential_deposit: T::Balance,
pub max_depth: u32,
pub contract_account_create_fee: T::Balance,
pub account_create_fee: T::Balance,
pub transfer_fee: T::Balance,
pub call_base_fee: T::Gas,
pub create_base_fee: T::Gas,
}
impl<T: Trait> Config<T> {
fn preload() -> Config<T> {
Config {
schedule: <Module<T>>::current_schedule(),
existential_deposit: <balances::Module<T>>::existential_deposit(),
max_depth: <Module<T>>::max_depth(),
contract_account_create_fee: <Module<T>>::contract_fee(),
account_create_fee: <balances::Module<T>>::creation_fee(),
transfer_fee: <balances::Module<T>>::transfer_fee(),
call_base_fee: <Module<T>>::call_base_fee(),
create_base_fee: <Module<T>>::create_base_fee(),
}
}
}
/// Definition of the cost schedule and other parameterizations for wasm vm.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[derive(Clone, Encode, Decode)]
pub struct Schedule<Gas> {
/// Gas cost of a growing memory by single page.
pub grow_mem_cost: Gas,
/// Gas cost of a regular operation.
pub regular_op_cost: Gas,
/// Gas cost per one byte returned.
pub return_data_per_byte_cost: Gas,
/// Gas cost per one byte read from the sandbox memory.
sandbox_data_read_cost: Gas,
/// Gas cost per one byte written to the sandbox memory.
sandbox_data_write_cost: 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.
pub max_stack_height: u32,
//// What is the maximal memory pages amount is allowed to have for
/// a contract.
pub max_memory_pages: u32,
}
impl<Gas: As<u64>> Default for Schedule<Gas> {
fn default() -> Schedule<Gas> {
Schedule {
grow_mem_cost: Gas::sa(1),
regular_op_cost: Gas::sa(1),
return_data_per_byte_cost: Gas::sa(1),
sandbox_data_read_cost: Gas::sa(1),
sandbox_data_write_cost: Gas::sa(1),
max_stack_height: 64 * 1024,
max_memory_pages: 16,
}
}
}
+1
View File
@@ -142,6 +142,7 @@ impl ExtBuilder {
gas_price: self.gas_price,
max_depth: 100,
block_gas_limit: self.block_gas_limit,
current_schedule: Default::default(),
}.build_storage()
.unwrap().0,
);
@@ -219,7 +219,7 @@ mod tests {
#[test]
fn macro_define_func() {
define_func!( <E: Ext> ext_gas (_ctx, amount: u32) => {
let amount = <<<E as Ext>::T as Trait>::Gas as As<u32>>::sa(amount);
let amount = <<E::T as Trait>::Gas as As<u32>>::sa(amount);
if !amount.is_zero() {
Ok(())
} else {
@@ -269,7 +269,7 @@ mod tests {
fn macro_define_env() {
define_env!(init_env, <E: Ext>,
ext_gas( _ctx, amount: u32 ) => {
let amount = <<<E as Ext>::T as Trait>::Gas as As<u32>>::sa(amount);
let amount = <<E::T as Trait>::Gas as As<u32>>::sa(amount);
if !amount.is_zero() {
Ok(())
} else {
+9 -53
View File
@@ -20,8 +20,7 @@
use exec::CreateReceipt;
use gas::GasMeter;
use rstd::prelude::*;
use runtime_primitives::traits::As;
use Trait;
use {Trait, Schedule};
use {balances, sandbox, system};
type BalanceOf<T> = <T as balances::Trait>::Balance;
@@ -118,7 +117,7 @@ pub fn execute<'a, E: Ext>(
input_data: &[u8],
output_data: &mut Vec<u8>,
ext: &'a mut E,
config: &Config<E::T>,
schedule: &Schedule<<E::T as Trait>::Gas>,
gas_meter: &mut GasMeter<E::T>,
) -> Result<(), Error> {
let env = runtime::init_env();
@@ -126,7 +125,7 @@ pub fn execute<'a, E: Ext>(
let PreparedContract {
instrumented_code,
memory,
} = prepare_contract(code, &config, &env)?;
} = prepare_contract(code, &schedule, &env)?;
let mut imports = sandbox::EnvironmentDefinitionBuilder::new();
for (func_name, ext_func) in &env.funcs {
@@ -134,7 +133,7 @@ pub fn execute<'a, E: Ext>(
}
imports.add_memory("env", "memory", memory.clone());
let mut runtime = Runtime::new(ext, input_data, output_data, &config, memory, gas_meter);
let mut runtime = Runtime::new(ext, input_data, output_data, &schedule, memory, gas_meter);
// Instantiate the instance from the instrumented module code.
match sandbox::Instance::new(&instrumented_code, &imports, &mut runtime) {
@@ -152,49 +151,6 @@ pub fn execute<'a, E: Ext>(
}
}
// TODO: Extract it to the root of the crate
#[derive(Clone)]
pub struct Config<T: Trait> {
/// 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,
/// Gas cost per one byte read from the sandbox memory.
sandbox_data_read_cost: T::Gas,
/// Gas cost per one byte written to the sandbox memory.
sandbox_data_write_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<T: Trait> Default for Config<T> {
fn default() -> Config<T> {
Config {
grow_mem_cost: T::Gas::sa(1),
regular_op_cost: T::Gas::sa(1),
return_data_per_byte_cost: T::Gas::sa(1),
sandbox_data_read_cost: T::Gas::sa(1),
sandbox_data_write_cost: T::Gas::sa(1),
max_stack_height: 64 * 1024,
max_memory_pages: 16,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -318,7 +274,7 @@ mod tests {
&[],
&mut Vec::new(),
&mut mock_ext,
&::vm::Config::default(),
&Schedule::<u64>::default(),
&mut GasMeter::with_limit(50_000, 1),
).unwrap();
@@ -381,7 +337,7 @@ mod tests {
&[],
&mut Vec::new(),
&mut mock_ext,
&::vm::Config::default(),
&Schedule::default(),
&mut GasMeter::with_limit(50_000, 1),
).unwrap();
@@ -421,7 +377,7 @@ mod tests {
&[],
&mut Vec::new(),
&mut mock_ext,
&::vm::Config::default(),
&Schedule::default(),
&mut GasMeter::with_limit(100_000, 1)
),
Err(_)
@@ -475,7 +431,7 @@ mod tests {
&[],
&mut Vec::new(),
&mut mock_ext,
&::vm::Config::default(),
&Schedule::default(),
&mut GasMeter::with_limit(50_000, 1),
).unwrap();
@@ -568,7 +524,7 @@ mod tests {
&[],
&mut return_buf,
&mut mock_ext,
&Config::default(),
&Schedule::default(),
&mut GasMeter::with_limit(50_000, 1),
).unwrap();
+17 -18
View File
@@ -18,30 +18,30 @@
//! wasm module before execution.
use super::env_def::HostFunctionSet;
use super::{Config, Error, Ext};
use super::{Error, Ext};
use rstd::prelude::*;
use parity_wasm::elements::{self, External, MemoryType, Type};
use pwasm_utils;
use pwasm_utils::rules;
use runtime_primitives::traits::As;
use sandbox;
use Trait;
use {Trait, Schedule};
struct ContractModule<'a, T: Trait + 'a> {
struct ContractModule<'a, Gas: '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<elements::Module>,
config: &'a Config<T>,
schedule: &'a Schedule<Gas>,
}
impl<'a, T: Trait> ContractModule<'a, T> {
fn new(original_code: &[u8], config: &'a Config<T>) -> Result<ContractModule<'a, T>, Error> {
impl<'a, Gas: 'a + As<u32> + Clone> ContractModule<'a, Gas> {
fn new(original_code: &[u8], schedule: &'a Schedule<Gas>) -> Result<ContractModule<'a, Gas>, Error> {
let module =
elements::deserialize_buffer(original_code).map_err(|_| Error::Deserialization)?;
Ok(ContractModule {
module: Some(module),
config,
schedule,
})
}
@@ -65,8 +65,8 @@ impl<'a, T: Trait> ContractModule<'a, T> {
}
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_())
let gas_rules = rules::Set::new(self.schedule.regular_op_cost.clone().as_(), Default::default())
.with_grow_cost(self.schedule.grow_mem_cost.clone().as_())
.with_forbidden_floats();
let module = self
@@ -88,7 +88,7 @@ impl<'a, T: Trait> ContractModule<'a, T> {
.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)
pwasm_utils::stack_height::inject_limiter(module, self.schedule.max_stack_height)
.map_err(|_| Error::StackHeightInstrumentation)?;
self.module = Some(contract_module);
@@ -167,16 +167,16 @@ pub(super) struct PreparedContract {
/// The checks are:
///
/// - module doesn't define an internal memory instance,
/// - imported memory (if any) doesn't reserve more memory than permitted by the `config`,
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`,
/// - all imported functions from the external environment matches defined by `env` module,
///
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
pub(super) fn prepare_contract<E: Ext>(
original_code: &[u8],
config: &Config<E::T>,
schedule: &Schedule<<E::T as Trait>::Gas>,
env: &HostFunctionSet<E>,
) -> Result<PreparedContract, Error> {
let mut contract_module = ContractModule::new(original_code, config)?;
let mut contract_module = ContractModule::new(original_code, schedule)?;
contract_module.ensure_no_internal_memory()?;
contract_module.inject_gas_metering()?;
contract_module.inject_stack_height_metering()?;
@@ -189,7 +189,7 @@ pub(super) fn prepare_contract<E: Ext>(
// Requested initial number of pages should not exceed the requested maximum.
return Err(Error::Memory);
}
(_, Some(maximum)) if maximum > config.max_memory_pages => {
(_, Some(maximum)) if maximum > schedule.max_memory_pages => {
// Maximum number of pages should not exceed the configured maximum.
return Err(Error::Memory);
}
@@ -218,7 +218,6 @@ pub(super) fn prepare_contract<E: Ext>(
mod tests {
use super::*;
use std::fmt;
use tests::Test;
use vm::tests::MockExt;
use wabt;
@@ -230,9 +229,9 @@ mod tests {
fn parse_and_prepare_wat(wat: &str) -> Result<PreparedContract, Error> {
let wasm = wabt::Wat2Wasm::new().validate(false).convert(wat).unwrap();
let config = Config::<Test>::default();
let schedule = Schedule::<u64>::default();
let env = ::vm::runtime::init_env();
prepare_contract::<MockExt>(wasm.as_ref(), &config, &env)
prepare_contract::<MockExt>(wasm.as_ref(), &schedule, &env)
}
#[test]
@@ -244,7 +243,7 @@ mod tests {
#[test]
fn memory() {
// This test assumes that maximum page number is configured to a certain number.
assert_eq!(Config::<Test>::default().max_memory_pages, 16);
assert_eq!(Schedule::<u64>::default().max_memory_pages, 16);
let r = parse_and_prepare_wat(r#"(module (import "env" "memory" (memory 1 1)))"#);
assert_matches!(r, Ok(_));
+12 -12
View File
@@ -16,7 +16,7 @@
//! Environment definition of the wasm smart-contract runtime.
use super::{BalanceOf, Config, CreateReceipt, Error, Ext};
use super::{BalanceOf, Schedule, CreateReceipt, Error, Ext};
use rstd::prelude::*;
use codec::{Decode, Encode};
use gas::{GasMeter, GasMeterResult};
@@ -41,7 +41,7 @@ pub(crate) struct Runtime<'a, 'data, E: Ext + 'a> {
input_data: &'data [u8],
output_data: &'data mut Vec<u8>,
scratch_buf: Vec<u8>,
config: &'a Config<E::T>,
schedule: &'a Schedule<<E::T as Trait>::Gas>,
memory: sandbox::Memory,
gas_meter: &'a mut GasMeter<E::T>,
special_trap: Option<SpecialTrap>,
@@ -51,7 +51,7 @@ impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> {
ext: &'a mut E,
input_data: &'data [u8],
output_data: &'data mut Vec<u8>,
config: &'a Config<E::T>,
schedule: &'a Schedule<<E::T as Trait>::Gas>,
memory: sandbox::Memory,
gas_meter: &'a mut GasMeter<E::T>,
) -> Self {
@@ -60,7 +60,7 @@ impl<'a, 'data, E: Ext + 'a> Runtime<'a, 'data, E> {
input_data,
output_data,
scratch_buf: Vec::new(),
config,
schedule,
memory,
gas_meter,
special_trap: None,
@@ -117,7 +117,7 @@ fn read_sandbox_memory<E: Ext>(
ptr: u32,
len: u32,
) -> Result<Vec<u8>, sandbox::HostError> {
let price = (ctx.config.sandbox_data_read_cost)
let price = (ctx.schedule.sandbox_data_read_cost)
.checked_mul(&<GasOf<E> as As<u32>>::sa(len))
.ok_or(sandbox::HostError)?;
charge_gas(ctx.gas_meter, price)?;
@@ -169,7 +169,7 @@ define_env!(init_env, <E: Ext>,
//
// - amount: How much gas is used.
gas(ctx, amount: u32) => {
let amount = <<<E as Ext>::T as Trait>::Gas as As<u32>>::sa(amount);
let amount = <<E::T as Trait>::Gas as As<u32>>::sa(amount);
charge_gas(&mut ctx.gas_meter, amount)?;
Ok(())
@@ -257,7 +257,7 @@ define_env!(init_env, <E: Ext>,
let nested_gas_limit = if gas == 0 {
ctx.gas_meter.gas_left()
} else {
<<<E as Ext>::T as Trait>::Gas as As<u64>>::sa(gas)
<<E::T as Trait>::Gas as As<u64>>::sa(gas)
};
let ext = &mut ctx.ext;
let scratch_buf = &mut ctx.scratch_buf;
@@ -316,7 +316,7 @@ define_env!(init_env, <E: Ext>,
let nested_gas_limit = if gas == 0 {
ctx.gas_meter.gas_left()
} else {
<<<E as Ext>::T as Trait>::Gas as As<u64>>::sa(gas)
<<E::T as Trait>::Gas as As<u64>>::sa(gas)
};
let ext = &mut ctx.ext;
let create_outcome = ctx.gas_meter.with_nested(nested_gas_limit, |nested_meter| {
@@ -339,8 +339,8 @@ define_env!(init_env, <E: Ext>,
// Save a data buffer as a result of the execution, terminate the execution and return a
// successful result to the caller.
ext_return(ctx, data_ptr: u32, data_len: u32) => {
let data_len_in_gas = <<<E as Ext>::T as Trait>::Gas as As<u64>>::sa(data_len as u64);
let price = (ctx.config.return_data_per_byte_cost)
let data_len_in_gas = <<E::T as Trait>::Gas as As<u64>>::sa(data_len as u64);
let price = (ctx.schedule.return_data_per_byte_cost)
.checked_mul(&data_len_in_gas)
.ok_or(sandbox::HostError)?;
@@ -382,7 +382,7 @@ define_env!(init_env, <E: Ext>,
// Finally, perform the write.
write_sandbox_memory(
ctx.config.sandbox_data_write_cost,
ctx.schedule.sandbox_data_write_cost,
ctx.gas_meter,
&ctx.memory,
dest_ptr,
@@ -414,7 +414,7 @@ define_env!(init_env, <E: Ext>,
// Finally, perform the write.
write_sandbox_memory(
ctx.config.sandbox_data_write_cost,
ctx.schedule.sandbox_data_write_cost,
ctx.gas_meter,
&ctx.memory,
dest_ptr,