mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
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:
committed by
Gav Wood
parent
367c99b2bb
commit
1f0f3c8f6b
BIN
Binary file not shown.
@@ -124,6 +124,7 @@ fn staging_testnet_config_genesis() -> GenesisConfig {
|
||||
gas_price: 1 * MILLICENTS,
|
||||
max_depth: 1024,
|
||||
block_gas_limit: 10_000_000,
|
||||
current_schedule: Default::default(),
|
||||
}),
|
||||
upgrade_key: Some(UpgradeKeyConfig {
|
||||
key: endowed_accounts[0].clone(),
|
||||
@@ -241,6 +242,7 @@ pub fn testnet_genesis(
|
||||
gas_price: 1,
|
||||
max_depth: 1024,
|
||||
block_gas_limit: 10_000_000,
|
||||
current_schedule: Default::default(),
|
||||
}),
|
||||
upgrade_key: Some(UpgradeKeyConfig {
|
||||
key: upgrade_key,
|
||||
|
||||
@@ -295,9 +295,9 @@ mod tests {
|
||||
1,
|
||||
GENESIS_HASH.into(),
|
||||
if support_changes_trie {
|
||||
hex!("978a3ff733a86638da39d36a349c693b5cf562bcc8db30fec6c2b6c40f925a9b").into()
|
||||
hex!("a998cf2956b526aecc0887903df66457e640bb2debfd7976b5c7696da31cdaef").into()
|
||||
} else {
|
||||
hex!("7bbad534e3de3db3c8cda015c4e8ed8ba10dde7e3fca21f4fd4fbc686e6c1410").into()
|
||||
hex!("2caffd5fcc42934e6b758613ff0a7e624a8c5b7c67b7c405bf6985a7e3a19701").into()
|
||||
},
|
||||
if support_changes_trie {
|
||||
Some(hex!("1f8f44dcae8982350c14dee720d34b147e73279f5a2ce1f9781195a991970978").into())
|
||||
@@ -321,7 +321,7 @@ mod tests {
|
||||
construct_block(
|
||||
2,
|
||||
block1(false).1,
|
||||
hex!("7be30152ee2ee909047cffad5f0a28bf8c2b0a97c124b500aeac112f6917738e").into(),
|
||||
hex!("72b2afc379ce2161aef95ef6f86a2321867f12b046703ea0af5aed158c2a4f30").into(),
|
||||
None,
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
@@ -344,7 +344,7 @@ mod tests {
|
||||
construct_block(
|
||||
1,
|
||||
GENESIS_HASH.into(),
|
||||
hex!("325a73726dc640af41becb42938e7152e218f130219c0695aae35b6a156f93f3").into(),
|
||||
hex!("5f4461c584ce91dd6862313fd075ffc26dc702fcc1183634ee7b0c5de8b5b4d1").into(),
|
||||
None,
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
@@ -626,7 +626,7 @@ mod tests {
|
||||
let b = construct_block(
|
||||
1,
|
||||
GENESIS_HASH.into(),
|
||||
hex!("cf0fee74c87ecff646804984bbdf85832a788b3ca2a2aa33e20da61fa7182b37").into(),
|
||||
hex!("9885d4297ce0341ec07957d1de32848460565a17ef2ea400df0e2326634914ae").into(),
|
||||
None,
|
||||
vec![
|
||||
CheckedExtrinsic {
|
||||
|
||||
BIN
Binary file not shown.
@@ -132,53 +132,48 @@ Consists of dropping (in the Rust sense) of the `AccountDb`.
|
||||
This function performs the following steps:
|
||||
|
||||
1. Querying source and destination balances from an overlay (see `get_balance`),
|
||||
2. Querying fee for the case. (This hits DB unless pre-loaded)
|
||||
2. Querying `existential_deposit`. (This hits DB unless pre-loaded)
|
||||
2. Querying `existential_deposit`.
|
||||
3. Executing `ensure_account_liquid` hook.
|
||||
4. Updating source and destination balance in the overlay (see `set_balance`).
|
||||
|
||||
**Note** that the complexity of executing `ensure_account_liquid` hook should be considered separately.
|
||||
|
||||
In the course of the execution this function can perform up to 4 DB reads: 2x `get_balance`, fee and `existential_deposit`. The last two can be pre-loaded pushing the cost of loading to a higher level and making it a one time. It can also induce up to 2 DB writes via `set_balance` if flushed to the storage.
|
||||
In the course of the execution this function can perform up to 2 DB reads to `get_balance` of source and destination accounts. It can also induce up to 2 DB writes via `set_balance` if flushed to the storage.
|
||||
|
||||
Moreover, if the source balance goes below `existential_deposit` then the account will be deleted along with all its storage which requires time proportional to the number of storage entries of that account.
|
||||
|
||||
Assuming marshaled size of a balance value is of the constant size we can neglect its effect on the performance.
|
||||
|
||||
**complexity**: up to 4 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has. For the current `AccountDb` implementation computing complexity also depends on the depth of the `AccountDb` cascade. Memorywise it can be assumed to be constant.
|
||||
**complexity**: up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has. For the current `AccountDb` implementation computing complexity also depends on the depth of the `AccountDb` cascade. Memorywise it can be assumed to be constant.
|
||||
|
||||
## Call
|
||||
|
||||
This function receives input data for the contract execution. The execution consists of the following steps:
|
||||
|
||||
1. Querying `MaxDepth` and `call_base_fee`. (These hit DB unless pre-loaded)
|
||||
2. Loading code from the DB.
|
||||
3. `transfer`-ing funds between the caller and the destination account.
|
||||
4. Executing the code of the destination account.
|
||||
5. Committing overlayed changed to the underlying `AccountDb`.
|
||||
1. Loading code from the DB.
|
||||
2. `transfer`-ing funds between the caller and the destination account.
|
||||
3. Executing the code of the destination account.
|
||||
4. Committing overlayed changed to the underlying `AccountDb`.
|
||||
|
||||
**Note** that the complexity of executing the contract code should be considered separately.
|
||||
|
||||
The execution of this function will involve 2 DB reads for querying `MaxDepth` and `MaxDepth` constants. These values can be pre-loaded pushing the cost of loading to a higher level and making it a one time.
|
||||
|
||||
Loading code most probably will trigger a DB read, since the code is immutable and therefore will not get into the cache (unless a suicide removes it).
|
||||
|
||||
Also, `transfer` can make up to 4 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has.
|
||||
Also, `transfer` can make up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has.
|
||||
|
||||
Finally, all changes are `commit`-ted into the underlying overlay. The complexity of this depends on the number of changes performed by the code. Thus, the pricing of storage modification should account for that.
|
||||
|
||||
**complexity**: Up to 7 DB reads. DB read of the code is of dynamic size. There can also be up to 2 DB writes (if flushed to the storage). Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has.
|
||||
**complexity**: Up to 3 DB reads. DB read of the code is of dynamic size. There can also be up to 2 DB writes (if flushed to the storage). Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has.
|
||||
|
||||
## Create
|
||||
|
||||
This function takes the code of the constructor and input data. Creation of a contract consists of the following steps:
|
||||
|
||||
1. Querying `MaxDepth` and `create_base_fee`. (These hit DB unless pre-loaded)
|
||||
2. Calling `DetermineContractAddress` hook to determine an address for the contract,
|
||||
3. `transfer`-ing funds between self and the newly created contract.
|
||||
4. Executing the constructor code. This will yield the final code of the code.
|
||||
5. Storing the code for the newly created contract in the overlay.
|
||||
6. Committing overlayed changed to the underlying `AccountDb`.
|
||||
1. Calling `DetermineContractAddress` hook to determine an address for the contract,
|
||||
2. `transfer`-ing funds between self and the newly created contract.
|
||||
3. Executing the constructor code. This will yield the final code of the code.
|
||||
4. Storing the code for the newly created contract in the overlay.
|
||||
5. Committing overlayed changed to the underlying `AccountDb`.
|
||||
|
||||
**Note** that the complexity of executing the constructor code should be considered separately.
|
||||
|
||||
@@ -186,15 +181,13 @@ This function takes the code of the constructor and input data. Creation of a co
|
||||
|
||||
**Note** that the constructor returns code in the owned form and it's obtained via return facilities, which should have take fee for the return value.
|
||||
|
||||
The execution of this function involves 2 DB reads for querying `create_base_fee` and `MaxDepth` constants. These values can be pre-loaded pushing the cost of loading to a higher level and making it a one time.
|
||||
|
||||
Also, `transfer` can make up to 4 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has.
|
||||
Also, `transfer` can make up to 2 DB reads and up to 2 DB writes (if flushed to the storage) in the standard case. If removal of the source account takes place then it will additionally perform a DB write per one storage entry that the account has.
|
||||
|
||||
Storing the code in the overlay may induce another DB write (if flushed to the storage) with the size proportional to the size of the constructor code.
|
||||
|
||||
Finally, all changes are `commit`-ted into the underlying overlay. The complexity of this depends on the number of changes performed by the constructor code. Thus, the pricing of storage modification should account for that.
|
||||
|
||||
**complexity**: Up to 6 DB reads and induces up to 3 DB writes (if flushed to the storage), one of which is dependent on the size of the code. Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has.
|
||||
**complexity**: Up to 2 DB reads and induces up to 3 DB writes (if flushed to the storage), one of which is dependent on the size of the code. Additionally, if the source account removal takes place a DB write will be performed per one storage entry that the account has.
|
||||
|
||||
# Externalities
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(_));
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user