mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 21:01:05 +00:00
seal: Add automated weights for contract API calls (#7017)
* seal: Add capability to put uninstrumented code (for benchmarks) Benchmarks should only measure the overhead of the API calls itself. For that reason we want to run them without instrumentation. * seal: Cap the the data length for deposited events Data used in events has storage implications for archive nodes. Those need to keep the events in storage forever. For that reason we want to limit the amount of storage that can be used inside events. * seal: Fix error reporting in the case out of bound sandbox access * seal: Refactor existing benchmarks * seal: Convert benchmark file to tabs * seal: Add benchmarks for functions called by contracts * seal: Create a default schedule from benchmark generated WeightInfo * seal: Make use of WeightInfo in extrinsic weight annotations * seal: Replace the old schedule by the benchmark generated one * Review: Fix copy paste typo in schedule construction * Review: Fix stale docs * Fix whitespace errors Co-authored-by: Sergei Shulepov <sergei@parity.io> * Review: Use checked_div in order to be more defensive * Review: Rename no_charge to already_charged * Review: Whitelist caller of extrinsics * Review: Remove trailing whitespace * Review: Remove confusing "self::" syntax * Review: Add docs for the benchmark prepration submodule * Review: Move code generation functions to own module * Review: Refactor and document benchmark helper functions * Remove additional empty line * Added missing comment on caller_funding * Update frame/contracts/src/benchmarking/code.rs Co-authored-by: Sergei Shulepov <sergei@parity.io> * Fix missing sp_std::prelude import in code.rs * cargo run --release --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark --chain dev --steps 50 --repeat 20 --extrinsic * --execution=wasm --wasm-execution=compiled --output ./bin/node/runtime/src/weights --header ./HEADER --pallet pallet_contracts --heap-pages 4096 * Use weights from the benchmark machine for the substrate node * Remove prefixes from Schedule members * Data lengths in the WeightInfo Trait are specified in kilobytes * Rename ApiWeights to HostFunctionWeights Co-authored-by: Sergei Shulepov <sergei@parity.io> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
committed by
GitHub
parent
e3682fa2f4
commit
443725f0f6
@@ -38,9 +38,27 @@ use frame_support::StorageMap;
|
||||
/// This function instruments the given code and caches it in the storage.
|
||||
pub fn save<T: Trait>(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<CodeHash<T>, &'static str> {
|
||||
let prefab_module = prepare::prepare_contract::<Env>(&original_code, schedule)?;
|
||||
let prefab_module = prepare::prepare_contract::<Env, T>(&original_code, schedule)?;
|
||||
let code_hash = T::Hashing::hash(&original_code);
|
||||
|
||||
<CodeStorage<T>>::insert(code_hash, prefab_module);
|
||||
<PristineCode<T>>::insert(code_hash, original_code);
|
||||
|
||||
Ok(code_hash)
|
||||
}
|
||||
|
||||
/// Version of `save` to be used in runtime benchmarks.
|
||||
//
|
||||
/// This version neither checks nor instruments the passed in code. This is useful
|
||||
/// when code needs to be benchmarked without the injected instrumentation.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn save_raw<T: Trait>(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<CodeHash<T>, &'static str> {
|
||||
let prefab_module = prepare::benchmarking::prepare_contract::<T>(&original_code, schedule)?;
|
||||
let code_hash = T::Hashing::hash(&original_code);
|
||||
|
||||
<CodeStorage<T>>::insert(code_hash, prefab_module);
|
||||
@@ -56,7 +74,7 @@ pub fn save<T: Trait>(
|
||||
/// re-instrumentation and update the cache in the storage.
|
||||
pub fn load<T: Trait>(
|
||||
code_hash: &CodeHash<T>,
|
||||
schedule: &Schedule,
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<PrefabWasmModule, &'static str> {
|
||||
let mut prefab_module =
|
||||
<CodeStorage<T>>::get(code_hash).ok_or_else(|| "code is not found")?;
|
||||
@@ -68,7 +86,7 @@ pub fn load<T: Trait>(
|
||||
// We need to re-instrument the code with the latest schedule here.
|
||||
let original_code =
|
||||
<PristineCode<T>>::get(code_hash).ok_or_else(|| "pristine code is not found")?;
|
||||
prefab_module = prepare::prepare_contract::<Env>(&original_code, schedule)?;
|
||||
prefab_module = prepare::prepare_contract::<Env, T>(&original_code, schedule)?;
|
||||
<CodeStorage<T>>::insert(&code_hash, &prefab_module);
|
||||
}
|
||||
Ok(prefab_module)
|
||||
|
||||
@@ -36,6 +36,8 @@ use self::runtime::{to_execution_result, Runtime};
|
||||
use self::code_cache::load as load_code;
|
||||
|
||||
pub use self::code_cache::save as save_code;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use self::code_cache::save_raw as save_code_raw;
|
||||
pub use self::runtime::ReturnCode;
|
||||
|
||||
/// A prepared wasm module ready for execution.
|
||||
@@ -64,17 +66,17 @@ pub struct WasmExecutable {
|
||||
}
|
||||
|
||||
/// Loader which fetches `WasmExecutable` from the code cache.
|
||||
pub struct WasmLoader<'a> {
|
||||
schedule: &'a Schedule,
|
||||
pub struct WasmLoader<'a, T: Trait> {
|
||||
schedule: &'a Schedule<T>,
|
||||
}
|
||||
|
||||
impl<'a> WasmLoader<'a> {
|
||||
pub fn new(schedule: &'a Schedule) -> Self {
|
||||
impl<'a, T: Trait> WasmLoader<'a, T> {
|
||||
pub fn new(schedule: &'a Schedule<T>) -> Self {
|
||||
WasmLoader { schedule }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Trait> crate::exec::Loader<T> for WasmLoader<'a> {
|
||||
impl<'a, T: Trait> crate::exec::Loader<T> for WasmLoader<'a, T> {
|
||||
type Executable = WasmExecutable;
|
||||
|
||||
fn load_init(&self, code_hash: &CodeHash<T>) -> Result<WasmExecutable, &'static str> {
|
||||
@@ -94,17 +96,17 @@ impl<'a, T: Trait> crate::exec::Loader<T> for WasmLoader<'a> {
|
||||
}
|
||||
|
||||
/// Implementation of `Vm` that takes `WasmExecutable` and executes it.
|
||||
pub struct WasmVm<'a> {
|
||||
schedule: &'a Schedule,
|
||||
pub struct WasmVm<'a, T: Trait> {
|
||||
schedule: &'a Schedule<T>,
|
||||
}
|
||||
|
||||
impl<'a> WasmVm<'a> {
|
||||
pub fn new(schedule: &'a Schedule) -> Self {
|
||||
impl<'a, T: Trait> WasmVm<'a, T> {
|
||||
pub fn new(schedule: &'a Schedule<T>) -> Self {
|
||||
WasmVm { schedule }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Trait> crate::exec::Vm<T> for WasmVm<'a> {
|
||||
impl<'a, T: Trait> crate::exec::Vm<T> for WasmVm<'a, T> {
|
||||
type Executable = WasmExecutable;
|
||||
|
||||
fn execute<E: Ext<T = T>>(
|
||||
@@ -186,7 +188,6 @@ mod tests {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct TerminationEntry {
|
||||
beneficiary: u64,
|
||||
gas_left: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -194,7 +195,6 @@ mod tests {
|
||||
to: u64,
|
||||
value: u64,
|
||||
data: Vec<u8>,
|
||||
gas_left: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -247,13 +247,11 @@ mod tests {
|
||||
&mut self,
|
||||
to: &u64,
|
||||
value: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
) -> Result<(), DispatchError> {
|
||||
self.transfers.push(TransferEntry {
|
||||
to: *to,
|
||||
value,
|
||||
data: Vec::new(),
|
||||
gas_left: gas_meter.gas_left(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
@@ -261,14 +259,13 @@ mod tests {
|
||||
&mut self,
|
||||
to: &u64,
|
||||
value: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
_gas_meter: &mut GasMeter<Test>,
|
||||
data: Vec<u8>,
|
||||
) -> ExecResult {
|
||||
self.transfers.push(TransferEntry {
|
||||
to: *to,
|
||||
value,
|
||||
data: data,
|
||||
gas_left: gas_meter.gas_left(),
|
||||
});
|
||||
// Assume for now that it was just a plain transfer.
|
||||
// TODO: Add tests for different call outcomes.
|
||||
@@ -277,11 +274,9 @@ mod tests {
|
||||
fn terminate(
|
||||
&mut self,
|
||||
beneficiary: &u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
) -> Result<(), DispatchError> {
|
||||
self.terminations.push(TerminationEntry {
|
||||
beneficiary: *beneficiary,
|
||||
gas_left: gas_meter.gas_left(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
@@ -372,16 +367,14 @@ mod tests {
|
||||
&mut self,
|
||||
to: &u64,
|
||||
value: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
) -> Result<(), DispatchError> {
|
||||
(**self).transfer(to, value, gas_meter)
|
||||
(**self).transfer(to, value)
|
||||
}
|
||||
fn terminate(
|
||||
&mut self,
|
||||
beneficiary: &u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
) -> Result<(), DispatchError> {
|
||||
(**self).terminate(beneficiary, gas_meter)
|
||||
(**self).terminate(beneficiary)
|
||||
}
|
||||
fn call(
|
||||
&mut self,
|
||||
@@ -461,7 +454,7 @@ mod tests {
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let schedule = crate::Schedule::default();
|
||||
let prefab_module =
|
||||
prepare_contract::<super::runtime::Env>(&wasm, &schedule).unwrap();
|
||||
prepare_contract::<super::runtime::Env, E::T>(&wasm, &schedule).unwrap();
|
||||
|
||||
let exec = WasmExecutable {
|
||||
// Use a "call" convention.
|
||||
@@ -523,7 +516,6 @@ mod tests {
|
||||
to: 7,
|
||||
value: 153,
|
||||
data: Vec::new(),
|
||||
gas_left: 9989000000,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -587,7 +579,6 @@ mod tests {
|
||||
to: 9,
|
||||
value: 6,
|
||||
data: vec![1, 2, 3, 4],
|
||||
gas_left: 9984500000,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -658,7 +649,7 @@ mod tests {
|
||||
code_hash: [0x11; 32].into(),
|
||||
endowment: 3,
|
||||
data: vec![1, 2, 3, 4],
|
||||
gas_left: 9971500000,
|
||||
gas_left: 9392302058,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -699,7 +690,6 @@ mod tests {
|
||||
&mock_ext.terminations,
|
||||
&[TerminationEntry {
|
||||
beneficiary: 0x09,
|
||||
gas_left: 9994500000,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -763,7 +753,6 @@ mod tests {
|
||||
to: 9,
|
||||
value: 6,
|
||||
data: vec![1, 2, 3, 4],
|
||||
gas_left: 228,
|
||||
}]
|
||||
);
|
||||
}
|
||||
@@ -1470,7 +1459,7 @@ mod tests {
|
||||
vec![0x00, 0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x14, 0x00])
|
||||
]);
|
||||
|
||||
assert_eq!(gas_meter.gas_left(), 9967000000);
|
||||
assert_eq!(gas_meter.gas_left(), 9834099446);
|
||||
}
|
||||
|
||||
const CODE_DEPOSIT_EVENT_MAX_TOPICS: &str = r#"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
use crate::wasm::env_def::ImportSatisfyCheck;
|
||||
use crate::wasm::PrefabWasmModule;
|
||||
use crate::Schedule;
|
||||
use crate::{Schedule, Trait};
|
||||
|
||||
use parity_wasm::elements::{self, Internal, External, MemoryType, Type, ValueType};
|
||||
use pwasm_utils;
|
||||
@@ -36,20 +36,20 @@ pub const IMPORT_MODULE_FN: &str = "seal0";
|
||||
/// compiler toolchains might not support specifying other modules than "env" for memory imports.
|
||||
pub const IMPORT_MODULE_MEMORY: &str = "env";
|
||||
|
||||
struct ContractModule<'a> {
|
||||
struct ContractModule<'a, T: Trait> {
|
||||
/// A deserialized module. The module is valid (this is Guaranteed by `new` method).
|
||||
module: elements::Module,
|
||||
schedule: &'a Schedule,
|
||||
schedule: &'a Schedule<T>,
|
||||
}
|
||||
|
||||
impl<'a> ContractModule<'a> {
|
||||
impl<'a, T: Trait> ContractModule<'a, T> {
|
||||
/// Creates a new instance of `ContractModule`.
|
||||
///
|
||||
/// Returns `Err` if the `original_code` couldn't be decoded or
|
||||
/// if it contains an invalid module.
|
||||
fn new(
|
||||
original_code: &[u8],
|
||||
schedule: &'a Schedule,
|
||||
schedule: &'a Schedule<T>,
|
||||
) -> Result<Self, &'static str> {
|
||||
use wasmi_validation::{validate_module, PlainValidator};
|
||||
|
||||
@@ -148,10 +148,10 @@ impl<'a> ContractModule<'a> {
|
||||
fn inject_gas_metering(self) -> Result<Self, &'static str> {
|
||||
let gas_rules =
|
||||
rules::Set::new(
|
||||
self.schedule.regular_op_cost.clone().saturated_into(),
|
||||
self.schedule.instruction_weights.regular.clone().saturated_into(),
|
||||
Default::default(),
|
||||
)
|
||||
.with_grow_cost(self.schedule.grow_mem_cost.clone().saturated_into())
|
||||
.with_grow_cost(self.schedule.instruction_weights.grow_mem.clone().saturated_into())
|
||||
.with_forbidden_floats();
|
||||
|
||||
let contract_module = pwasm_utils::inject_gas_counter(
|
||||
@@ -269,7 +269,10 @@ impl<'a> ContractModule<'a> {
|
||||
/// - checks any imported function against defined host functions set, incl.
|
||||
/// their signatures.
|
||||
/// - if there is a memory import, returns it's descriptor
|
||||
fn scan_imports<C: ImportSatisfyCheck>(&self) -> Result<Option<&MemoryType>, &'static str> {
|
||||
/// `import_fn_banlist`: list of function names that are disallowed to be imported
|
||||
fn scan_imports<C: ImportSatisfyCheck>(&self, import_fn_banlist: &[&[u8]])
|
||||
-> Result<Option<&MemoryType>, &'static str>
|
||||
{
|
||||
let module = &self.module;
|
||||
|
||||
let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]);
|
||||
@@ -315,8 +318,7 @@ impl<'a> ContractModule<'a> {
|
||||
return Err("module imports `seal_println` but debug features disabled");
|
||||
}
|
||||
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
if import.field().as_bytes() == b"gas"
|
||||
if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f)
|
||||
|| !C::can_satisfy(import.field().as_bytes(), func_ty)
|
||||
{
|
||||
return Err("module imports a non-existent function");
|
||||
@@ -331,33 +333,10 @@ impl<'a> ContractModule<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads the given module given in `original_code`, performs some checks on it and
|
||||
/// does some preprocessing.
|
||||
///
|
||||
/// The checks are:
|
||||
///
|
||||
/// - provided code is a valid wasm module.
|
||||
/// - the module doesn't define an internal memory instance,
|
||||
/// - 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 fn prepare_contract<C: ImportSatisfyCheck>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule,
|
||||
) -> Result<PrefabWasmModule, &'static str> {
|
||||
let mut contract_module = ContractModule::new(original_code, schedule)?;
|
||||
contract_module.scan_exports()?;
|
||||
contract_module.ensure_no_internal_memory()?;
|
||||
contract_module.ensure_table_size_limit(schedule.max_table_size)?;
|
||||
contract_module.ensure_no_floating_types()?;
|
||||
|
||||
struct MemoryDefinition {
|
||||
initial: u32,
|
||||
maximum: u32,
|
||||
}
|
||||
|
||||
let memory_def = if let Some(memory_type) = contract_module.scan_imports::<C>()? {
|
||||
fn get_memory_limits<T: Trait>(module: Option<&MemoryType>, schedule: &Schedule<T>)
|
||||
-> Result<(u32, u32), &'static str>
|
||||
{
|
||||
if let Some(memory_type) = module {
|
||||
// Inspect the module to extract the initial and maximum page count.
|
||||
let limits = memory_type.limits();
|
||||
match (limits.initial(), limits.maximum()) {
|
||||
@@ -369,7 +348,7 @@ pub fn prepare_contract<C: ImportSatisfyCheck>(
|
||||
(_, Some(maximum)) if maximum > schedule.max_memory_pages => {
|
||||
return Err("Maximum number of pages should not exceed the configured maximum.");
|
||||
}
|
||||
(initial, Some(maximum)) => MemoryDefinition { initial, maximum },
|
||||
(initial, Some(maximum)) => Ok((initial, maximum)),
|
||||
(_, None) => {
|
||||
// Maximum number of pages should be always declared.
|
||||
// This isn't a hard requirement and can be treated as a maximum set
|
||||
@@ -380,11 +359,37 @@ pub fn prepare_contract<C: ImportSatisfyCheck>(
|
||||
} else {
|
||||
// If none memory imported then just crate an empty placeholder.
|
||||
// Any access to it will lead to out of bounds trap.
|
||||
MemoryDefinition {
|
||||
initial: 0,
|
||||
maximum: 0,
|
||||
}
|
||||
};
|
||||
Ok((0, 0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads the given module given in `original_code`, performs some checks on it and
|
||||
/// does some preprocessing.
|
||||
///
|
||||
/// The checks are:
|
||||
///
|
||||
/// - provided code is a valid wasm module.
|
||||
/// - the module doesn't define an internal memory instance,
|
||||
/// - 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 fn prepare_contract<C: ImportSatisfyCheck, T: Trait>(
|
||||
original_code: &[u8],
|
||||
schedule: &Schedule<T>,
|
||||
) -> Result<PrefabWasmModule, &'static str> {
|
||||
let mut contract_module = ContractModule::new(original_code, schedule)?;
|
||||
contract_module.scan_exports()?;
|
||||
contract_module.ensure_no_internal_memory()?;
|
||||
contract_module.ensure_table_size_limit(schedule.max_table_size)?;
|
||||
contract_module.ensure_no_floating_types()?;
|
||||
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
let disallowed_imports = [b"gas".as_ref()];
|
||||
let memory_limits = get_memory_limits(
|
||||
contract_module.scan_imports::<C>(&disallowed_imports)?,
|
||||
schedule
|
||||
)?;
|
||||
|
||||
contract_module = contract_module
|
||||
.inject_gas_metering()?
|
||||
@@ -392,13 +397,48 @@ pub fn prepare_contract<C: ImportSatisfyCheck>(
|
||||
|
||||
Ok(PrefabWasmModule {
|
||||
schedule_version: schedule.version,
|
||||
initial: memory_def.initial,
|
||||
maximum: memory_def.maximum,
|
||||
initial: memory_limits.0,
|
||||
maximum: memory_limits.1,
|
||||
_reserved: None,
|
||||
code: contract_module.into_wasm_code()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Alternate (possibly unsafe) preparation functions used only for benchmarking.
|
||||
///
|
||||
/// For benchmarking we need to construct special contracts that might not pass our
|
||||
/// sanity checks or need to skip instrumentation for correct results. We hide functions
|
||||
/// allowing this behind a feature that is only set during benchmarking to prevent usage
|
||||
/// in production code.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarking {
|
||||
use super::{
|
||||
Trait, ContractModule, PrefabWasmModule, ImportSatisfyCheck, Schedule, get_memory_limits
|
||||
};
|
||||
use parity_wasm::elements::FunctionType;
|
||||
|
||||
impl ImportSatisfyCheck for () {
|
||||
fn can_satisfy(_name: &[u8], _func_type: &FunctionType) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepare function that neither checks nor instruments the passed in code.
|
||||
pub fn prepare_contract<T: Trait>(original_code: &[u8], schedule: &Schedule<T>)
|
||||
-> Result<PrefabWasmModule, &'static str>
|
||||
{
|
||||
let contract_module = ContractModule::new(original_code, schedule)?;
|
||||
let memory_limits = get_memory_limits(contract_module.scan_imports::<()>(&[])?, schedule)?;
|
||||
Ok(PrefabWasmModule {
|
||||
schedule_version: schedule.version,
|
||||
initial: memory_limits.0,
|
||||
maximum: memory_limits.1,
|
||||
_reserved: None,
|
||||
code: contract_module.into_wasm_code()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -431,7 +471,7 @@ mod tests {
|
||||
fn $name() {
|
||||
let wasm = wat::parse_str($wat).unwrap();
|
||||
let schedule = Schedule::default();
|
||||
let r = prepare_contract::<TestEnv>(wasm.as_ref(), &schedule);
|
||||
let r = prepare_contract::<TestEnv, crate::tests::Test>(wasm.as_ref(), &schedule);
|
||||
assert_matches!(r, $($expected)*);
|
||||
}
|
||||
};
|
||||
@@ -459,7 +499,7 @@ mod tests {
|
||||
// Tests below assumes that maximum page number is configured to a certain number.
|
||||
#[test]
|
||||
fn assume_memory_size() {
|
||||
assert_eq!(Schedule::default().max_memory_pages, 16);
|
||||
assert_eq!(<Schedule<crate::tests::Test>>::default().max_memory_pages, 16);
|
||||
}
|
||||
|
||||
prepare_test!(memory_with_one_page,
|
||||
@@ -588,7 +628,7 @@ mod tests {
|
||||
// Tests below assumes that maximum table size is configured to a certain number.
|
||||
#[test]
|
||||
fn assume_table_size() {
|
||||
assert_eq!(Schedule::default().max_table_size, 16384);
|
||||
assert_eq!(<Schedule<crate::tests::Test>>::default().max_table_size, 16384);
|
||||
}
|
||||
|
||||
prepare_test!(no_tables,
|
||||
@@ -757,7 +797,7 @@ mod tests {
|
||||
).unwrap();
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.enable_println = true;
|
||||
let r = prepare_contract::<TestEnv>(wasm.as_ref(), &schedule);
|
||||
let r = prepare_contract::<TestEnv, crate::tests::Test>(wasm.as_ref(), &schedule);
|
||||
assert_matches!(r, Ok(_));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
//! Environment definition of the wasm smart-contract runtime.
|
||||
|
||||
use crate::{Schedule, Trait, CodeHash, BalanceOf, Error};
|
||||
use crate::{HostFnWeights, Schedule, Trait, CodeHash, BalanceOf, Error};
|
||||
use crate::exec::{
|
||||
Ext, ExecResult, ExecReturnValue, StorageKey, TopicOf, ReturnFlags, ExecError
|
||||
};
|
||||
@@ -28,7 +28,7 @@ use frame_system;
|
||||
use frame_support::dispatch::DispatchError;
|
||||
use sp_std::prelude::*;
|
||||
use codec::{Decode, Encode};
|
||||
use sp_runtime::traits::{Bounded, SaturatedConversion};
|
||||
use sp_runtime::traits::SaturatedConversion;
|
||||
use sp_io::hashing::{
|
||||
keccak_256,
|
||||
blake2_256,
|
||||
@@ -119,7 +119,7 @@ enum TrapReason {
|
||||
pub(crate) struct Runtime<'a, E: Ext + 'a> {
|
||||
ext: &'a mut E,
|
||||
input_data: Option<Vec<u8>>,
|
||||
schedule: &'a Schedule,
|
||||
schedule: &'a Schedule<E::T>,
|
||||
memory: sp_sandbox::Memory,
|
||||
gas_meter: &'a mut GasMeter<E::T>,
|
||||
trap_reason: Option<TrapReason>,
|
||||
@@ -128,7 +128,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> {
|
||||
pub(crate) fn new(
|
||||
ext: &'a mut E,
|
||||
input_data: Vec<u8>,
|
||||
schedule: &'a Schedule,
|
||||
schedule: &'a Schedule<E::T>,
|
||||
memory: sp_sandbox::Memory,
|
||||
gas_meter: &'a mut GasMeter<E::T>,
|
||||
) -> Self {
|
||||
@@ -204,136 +204,185 @@ pub(crate) fn to_execution_result<E: Ext>(
|
||||
#[cfg_attr(test, derive(Debug, PartialEq, Eq))]
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum RuntimeToken {
|
||||
/// Explicit call to the `gas` function. Charge the gas meter
|
||||
/// with the value provided.
|
||||
Explicit(u32),
|
||||
/// The given number of bytes is read from the sandbox memory.
|
||||
ReadMemory(u32),
|
||||
/// The given number of bytes is written to the sandbox memory.
|
||||
WriteMemory(u32),
|
||||
/// The given number of bytes is read from the sandbox memory and
|
||||
/// is returned as the return data buffer of the call.
|
||||
ReturnData(u32),
|
||||
/// (topic_count, data_bytes): A buffer of the given size is posted as an event indexed with the
|
||||
/// given number of topics.
|
||||
DepositEvent(u32, u32),
|
||||
/// Charge the gas meter with the cost of a metering block. The charged costs are
|
||||
/// the supplied cost of the block plus the overhead of the metering itself.
|
||||
MeteringBlock(u32),
|
||||
/// Weight of calling `seal_caller`.
|
||||
Caller,
|
||||
/// Weight of calling `seal_address`.
|
||||
Address,
|
||||
/// Weight of calling `seal_gas_left`.
|
||||
GasLeft,
|
||||
/// Weight of calling `seal_balance`.
|
||||
Balance,
|
||||
/// Weight of calling `seal_value_transferred`.
|
||||
ValueTransferred,
|
||||
/// Weight of calling `seal_minimum_balance`.
|
||||
MinimumBalance,
|
||||
/// Weight of calling `seal_tombstone_deposit`.
|
||||
TombstoneDeposit,
|
||||
/// Weight of calling `seal_rent_allowance`.
|
||||
RentAllowance,
|
||||
/// Weight of calling `seal_block_number`.
|
||||
BlockNumber,
|
||||
/// Weight of calling `seal_now`.
|
||||
Now,
|
||||
/// Weight of calling `seal_weight_to_fee`.
|
||||
WeightToFee,
|
||||
/// Weight of calling `seal_input` without the weight of copying the input.
|
||||
InputBase,
|
||||
/// Weight of copying the input data for the given size.
|
||||
InputCopyOut(u32),
|
||||
/// Weight of calling `seal_return` for the given output size.
|
||||
Return(u32),
|
||||
/// Weight of calling `seal_terminate`.
|
||||
Terminate,
|
||||
/// Weight of calling `seal_restore_to` per number of supplied delta entries.
|
||||
RestoreTo(u32),
|
||||
/// Weight of calling `seal_random`. It includes the weight for copying the subject.
|
||||
Random,
|
||||
/// Weight of calling `seal_reposit_event` with the given number of topics and event size.
|
||||
DepositEvent{num_topic: u32, len: u32},
|
||||
/// Weight of calling `seal_set_rent_allowance`.
|
||||
SetRentAllowance,
|
||||
/// Weight of calling `seal_set_storage` for the given storage item size.
|
||||
SetStorage(u32),
|
||||
/// Weight of calling `seal_clear_storage`.
|
||||
ClearStorage,
|
||||
/// Weight of calling `seal_get_storage` without output weight.
|
||||
GetStorageBase,
|
||||
/// Weight of an item received via `seal_get_storage` for the given size.
|
||||
GetStorageCopyOut(u32),
|
||||
/// Weight of calling `seal_transfer`.
|
||||
Transfer,
|
||||
/// Weight of calling `seal_call` for the given input size.
|
||||
CallBase(u32),
|
||||
/// Weight of the transfer performed during a call.
|
||||
CallSurchargeTransfer,
|
||||
/// Weight of output received through `seal_call` for the given size.
|
||||
CallCopyOut(u32),
|
||||
/// Weight of calling `seal_instantiate` for the given input size without output weight.
|
||||
/// This includes the transfer as an instantiate without a value will always be below
|
||||
/// the existential deposit and is disregarded as corner case.
|
||||
InstantiateBase(u32),
|
||||
/// Weight of output received through `seal_instantiate` for the given size.
|
||||
InstantiateCopyOut(u32),
|
||||
/// Weight of calling `seal_hash_sha_256` for the given input size.
|
||||
HashSha256(u32),
|
||||
/// Weight of calling `seal_hash_keccak_256` for the given input size.
|
||||
HashKeccak256(u32),
|
||||
/// Weight of calling `seal_hash_blake2_256` for the given input size.
|
||||
HashBlake256(u32),
|
||||
/// Weight of calling `seal_hash_blake2_128` for the given input size.
|
||||
HashBlake128(u32),
|
||||
}
|
||||
|
||||
impl<T: Trait> Token<T> for RuntimeToken {
|
||||
type Metadata = Schedule;
|
||||
type Metadata = HostFnWeights;
|
||||
|
||||
fn calculate_amount(&self, metadata: &Schedule) -> Gas {
|
||||
fn calculate_amount(&self, s: &Self::Metadata) -> Gas {
|
||||
use self::RuntimeToken::*;
|
||||
let value = match *self {
|
||||
Explicit(amount) => Some(amount.into()),
|
||||
ReadMemory(byte_count) => metadata
|
||||
.sandbox_data_read_cost
|
||||
.checked_mul(byte_count.into()),
|
||||
WriteMemory(byte_count) => metadata
|
||||
.sandbox_data_write_cost
|
||||
.checked_mul(byte_count.into()),
|
||||
ReturnData(byte_count) => metadata
|
||||
.return_data_per_byte_cost
|
||||
.checked_mul(byte_count.into()),
|
||||
DepositEvent(topic_count, data_byte_count) => {
|
||||
let data_cost = metadata
|
||||
.event_data_per_byte_cost
|
||||
.checked_mul(data_byte_count.into());
|
||||
|
||||
let topics_cost = metadata
|
||||
.event_per_topic_cost
|
||||
.checked_mul(topic_count.into());
|
||||
|
||||
data_cost
|
||||
.and_then(|data_cost| {
|
||||
topics_cost.and_then(|topics_cost| {
|
||||
data_cost.checked_add(topics_cost)
|
||||
})
|
||||
})
|
||||
.and_then(|data_and_topics_cost|
|
||||
data_and_topics_cost.checked_add(metadata.event_base_cost)
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
value.unwrap_or_else(|| Bounded::max_value())
|
||||
match *self {
|
||||
MeteringBlock(amount) => s.gas.saturating_add(amount.into()),
|
||||
Caller => s.caller,
|
||||
Address => s.address,
|
||||
GasLeft => s.gas_left,
|
||||
Balance => s.balance,
|
||||
ValueTransferred => s.value_transferred,
|
||||
MinimumBalance => s.minimum_balance,
|
||||
TombstoneDeposit => s.tombstone_deposit,
|
||||
RentAllowance => s.rent_allowance,
|
||||
BlockNumber => s.block_number,
|
||||
Now => s.now,
|
||||
WeightToFee => s.weight_to_fee,
|
||||
InputBase => s.input,
|
||||
InputCopyOut(len) => s.input_per_byte.saturating_mul(len.into()),
|
||||
Return(len) => s.r#return
|
||||
.saturating_add(s.return_per_byte.saturating_mul(len.into())),
|
||||
Terminate => s.terminate,
|
||||
RestoreTo(delta) => s.restore_to
|
||||
.saturating_add(s.restore_to_per_delta.saturating_mul(delta.into())),
|
||||
Random => s.random,
|
||||
DepositEvent{num_topic, len} => s.deposit_event
|
||||
.saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into()))
|
||||
.saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())),
|
||||
SetRentAllowance => s.set_rent_allowance,
|
||||
SetStorage(len) => s.set_storage
|
||||
.saturating_add(s.set_storage_per_byte.saturating_mul(len.into())),
|
||||
ClearStorage => s.clear_storage,
|
||||
GetStorageBase => s.get_storage,
|
||||
GetStorageCopyOut(len) => s.get_storage_per_byte.saturating_mul(len.into()),
|
||||
Transfer => s.transfer,
|
||||
CallBase(len) => s.call
|
||||
.saturating_add(s.call_per_input_byte.saturating_mul(len.into())),
|
||||
CallSurchargeTransfer => s.call_transfer_surcharge,
|
||||
CallCopyOut(len) => s.call_per_output_byte.saturating_mul(len.into()),
|
||||
InstantiateBase(len) => s.instantiate
|
||||
.saturating_add(s.instantiate_per_input_byte.saturating_mul(len.into())),
|
||||
InstantiateCopyOut(len) => s.instantiate_per_output_byte
|
||||
.saturating_mul(len.into()),
|
||||
HashSha256(len) => s.hash_sha2_256
|
||||
.saturating_add(s.hash_sha2_256_per_byte.saturating_mul(len.into())),
|
||||
HashKeccak256(len) => s.hash_keccak_256
|
||||
.saturating_add(s.hash_keccak_256_per_byte.saturating_mul(len.into())),
|
||||
HashBlake256(len) => s.hash_blake2_256
|
||||
.saturating_add(s.hash_blake2_256_per_byte.saturating_mul(len.into())),
|
||||
HashBlake128(len) => s.hash_blake2_128
|
||||
.saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Charge the gas meter with the specified token.
|
||||
///
|
||||
/// Returns `Err(HostError)` if there is not enough gas.
|
||||
fn charge_gas<T: Trait, Tok: Token<T>>(
|
||||
gas_meter: &mut GasMeter<T>,
|
||||
metadata: &Tok::Metadata,
|
||||
trap_reason: &mut Option<TrapReason>,
|
||||
token: Tok,
|
||||
) -> Result<(), sp_sandbox::HostError> {
|
||||
match gas_meter.charge(metadata, token) {
|
||||
fn charge_gas<E, Tok>(ctx: &mut Runtime<E>, token: Tok) -> Result<(), sp_sandbox::HostError>
|
||||
where
|
||||
E: Ext,
|
||||
Tok: Token<E::T, Metadata=HostFnWeights>,
|
||||
{
|
||||
match ctx.gas_meter.charge(&ctx.schedule.host_fn_weights, token) {
|
||||
GasMeterResult::Proceed => Ok(()),
|
||||
GasMeterResult::OutOfGas => {
|
||||
*trap_reason = Some(TrapReason::SupervisorError(Error::<T>::OutOfGas.into()));
|
||||
ctx.trap_reason = Some(TrapReason::SupervisorError(Error::<E::T>::OutOfGas.into()));
|
||||
Err(sp_sandbox::HostError)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory, consuming an appropriate amount of
|
||||
/// gas.
|
||||
/// Read designated chunk from the sandbox memory.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - calculating the gas cost resulted in overflow.
|
||||
/// - out of gas
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
fn read_sandbox_memory<E: Ext>(
|
||||
ctx: &mut Runtime<E>,
|
||||
ptr: u32,
|
||||
len: u32,
|
||||
) -> Result<Vec<u8>, sp_sandbox::HostError> {
|
||||
charge_gas(
|
||||
ctx.gas_meter,
|
||||
ctx.schedule,
|
||||
&mut ctx.trap_reason,
|
||||
RuntimeToken::ReadMemory(len),
|
||||
)?;
|
||||
|
||||
let mut buf = vec![0u8; len as usize];
|
||||
ctx.memory.get(ptr, buf.as_mut_slice())
|
||||
.map_err(|_| store_err(ctx, Error::<E::T>::OutOfBounds))?;
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory into the supplied buffer, consuming
|
||||
/// an appropriate amount of gas.
|
||||
/// Read designated chunk from the sandbox memory into the supplied buffer.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - calculating the gas cost resulted in overflow.
|
||||
/// - out of gas
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
fn read_sandbox_memory_into_buf<E: Ext>(
|
||||
ctx: &mut Runtime<E>,
|
||||
ptr: u32,
|
||||
buf: &mut [u8],
|
||||
) -> Result<(), sp_sandbox::HostError> {
|
||||
charge_gas(
|
||||
ctx.gas_meter,
|
||||
ctx.schedule,
|
||||
&mut ctx.trap_reason,
|
||||
RuntimeToken::ReadMemory(buf.len() as u32),
|
||||
)?;
|
||||
|
||||
ctx.memory.get(ptr, buf).map_err(|_| store_err(ctx, Error::<E::T>::OutOfBounds))
|
||||
}
|
||||
|
||||
/// Read designated chunk from the sandbox memory, consuming an appropriate amount of
|
||||
/// gas, and attempt to decode into the specified type.
|
||||
/// Read designated chunk from the sandbox memory and attempt to decode into the specified type.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - calculating the gas cost resulted in overflow.
|
||||
/// - out of gas
|
||||
/// - requested buffer is not within the bounds of the sandbox memory.
|
||||
/// - the buffer contents cannot be decoded as the required type.
|
||||
fn read_sandbox_memory_as<E: Ext, D: Decode>(
|
||||
@@ -345,42 +394,36 @@ fn read_sandbox_memory_as<E: Ext, D: Decode>(
|
||||
D::decode(&mut &buf[..]).map_err(|_| store_err(ctx, Error::<E::T>::DecodingFailed))
|
||||
}
|
||||
|
||||
/// Write the given buffer to the designated location in the sandbox memory, consuming
|
||||
/// an appropriate amount of gas.
|
||||
/// Write the given buffer to the designated location in the sandbox memory.
|
||||
///
|
||||
/// Returns `Err` if one of the following conditions occurs:
|
||||
///
|
||||
/// - calculating the gas cost resulted in overflow.
|
||||
/// - out of gas
|
||||
/// - designated area is not within the bounds of the sandbox memory.
|
||||
fn write_sandbox_memory<E: Ext>(
|
||||
ctx: &mut Runtime<E>,
|
||||
ptr: u32,
|
||||
buf: &[u8],
|
||||
) -> Result<(), sp_sandbox::HostError> {
|
||||
charge_gas(
|
||||
ctx.gas_meter,
|
||||
ctx.schedule,
|
||||
&mut ctx.trap_reason,
|
||||
RuntimeToken::WriteMemory(buf.len() as u32),
|
||||
)?;
|
||||
|
||||
ctx.memory.set(ptr, buf)
|
||||
.map_err(|_| store_err(ctx, Error::<E::T>::OutOfBounds))
|
||||
ctx.memory.set(ptr, buf).map_err(|_| store_err(ctx, Error::<E::T>::OutOfBounds))
|
||||
}
|
||||
|
||||
/// Write the given buffer and its length to the designated locations in sandbox memory.
|
||||
/// Write the given buffer and its length to the designated locations in sandbox memory and
|
||||
/// charge gas according to the token returned by `create_token`.
|
||||
//
|
||||
/// `out_ptr` is the location in sandbox memory where `buf` should be written to.
|
||||
/// `out_len_ptr` is an in-out location in sandbox memory. It is read to determine the
|
||||
/// lenght of the buffer located at `out_ptr`. If that buffer is large enough the actual
|
||||
/// length of the buffer located at `out_ptr`. If that buffer is large enough the actual
|
||||
/// `buf.len()` is written to this location.
|
||||
///
|
||||
/// If `out_ptr` is set to the sentinel value of `u32::max_value()` and `allow_skip` is true the
|
||||
/// If `out_ptr` is set to the sentinel value of `u32::max_value()` and `allow_skip` is true the
|
||||
/// operation is skipped and `Ok` is returned. This is supposed to help callers to make copying
|
||||
/// output optional. For example to skip copying back the output buffer of an `seal_call`
|
||||
/// when the caller is not interested in the result.
|
||||
///
|
||||
/// `create_token` can optionally instruct this function to charge the gas meter with the token
|
||||
/// it returns. `create_token` receives the variable amount of bytes that are about to be copied by
|
||||
/// this function.
|
||||
///
|
||||
/// In addition to the error conditions of `write_sandbox_memory` this functions returns
|
||||
/// `Err` if the size of the buffer located at `out_ptr` is too small to fit `buf`.
|
||||
fn write_sandbox_output<E: Ext>(
|
||||
@@ -389,6 +432,7 @@ fn write_sandbox_output<E: Ext>(
|
||||
out_len_ptr: u32,
|
||||
buf: &[u8],
|
||||
allow_skip: bool,
|
||||
create_token: impl FnOnce(u32) -> Option<RuntimeToken>,
|
||||
) -> Result<(), sp_sandbox::HostError> {
|
||||
if allow_skip && out_ptr == u32::max_value() {
|
||||
return Ok(());
|
||||
@@ -401,19 +445,27 @@ fn write_sandbox_output<E: Ext>(
|
||||
Err(store_err(ctx, Error::<E::T>::OutputBufferTooSmall))?
|
||||
}
|
||||
|
||||
charge_gas(
|
||||
ctx.gas_meter,
|
||||
ctx.schedule,
|
||||
&mut ctx.trap_reason,
|
||||
RuntimeToken::WriteMemory(buf_len.saturating_add(4)),
|
||||
)?;
|
||||
if let Some(token) = create_token(buf_len) {
|
||||
charge_gas(ctx, token)?;
|
||||
}
|
||||
|
||||
ctx.memory.set(out_ptr, buf)?;
|
||||
ctx.memory.set(out_len_ptr, &buf_len.encode())?;
|
||||
ctx.memory.set(out_ptr, buf).and_then(|_| {
|
||||
ctx.memory.set(out_len_ptr, &buf_len.encode())
|
||||
})
|
||||
.map_err(|_| store_err(ctx, Error::<E::T>::OutOfBounds))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Supply to `write_sandbox_output` to indicate that the gas meter should not be charged.
|
||||
///
|
||||
/// This is only appropriate when writing out data of constant size that does not depend on user
|
||||
/// input. In this case the costs for this copy was already charged as part of the token at
|
||||
/// the beginning of the API entry point.
|
||||
fn already_charged(_: u32) -> Option<RuntimeToken> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Stores a DispatchError returned from an Ext function into the trap_reason.
|
||||
///
|
||||
/// This allows through supervisor generated errors to the caller.
|
||||
@@ -514,12 +566,7 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// - amount: How much gas is used.
|
||||
gas(ctx, amount: u32) => {
|
||||
charge_gas(
|
||||
&mut ctx.gas_meter,
|
||||
ctx.schedule,
|
||||
&mut ctx.trap_reason,
|
||||
RuntimeToken::Explicit(amount)
|
||||
)?;
|
||||
charge_gas(ctx, RuntimeToken::MeteringBlock(amount))?;
|
||||
Ok(())
|
||||
},
|
||||
|
||||
@@ -539,9 +586,9 @@ define_env!(Env, <E: Ext>,
|
||||
// - If value length exceeds the configured maximum value length of a storage entry.
|
||||
// - Upon trying to set an empty storage entry (value length is 0).
|
||||
seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::SetStorage(value_len))?;
|
||||
if value_len > ctx.ext.max_value_size() {
|
||||
// Bail out if value length exceeds the set maximum value size.
|
||||
return Err(sp_sandbox::HostError);
|
||||
Err(store_err(ctx, Error::<E::T>::ValueTooLarge))?;
|
||||
}
|
||||
let mut key: StorageKey = [0; 32];
|
||||
read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?;
|
||||
@@ -556,6 +603,7 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// - `key_ptr`: pointer into the linear memory where the location to clear the value is placed.
|
||||
seal_clear_storage(ctx, key_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::ClearStorage)?;
|
||||
let mut key: StorageKey = [0; 32];
|
||||
read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?;
|
||||
ctx.ext.set_storage(key, None);
|
||||
@@ -575,10 +623,13 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// `ReturnCode::KeyNotFound`
|
||||
seal_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => {
|
||||
charge_gas(ctx, RuntimeToken::GetStorageBase)?;
|
||||
let mut key: StorageKey = [0; 32];
|
||||
read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?;
|
||||
if let Some(value) = ctx.ext.get_storage(&key) {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &value, false)?;
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &value, false, |len| {
|
||||
Some(RuntimeToken::GetStorageCopyOut(len))
|
||||
})?;
|
||||
Ok(ReturnCode::Success)
|
||||
} else {
|
||||
Ok(ReturnCode::KeyNotFound)
|
||||
@@ -607,12 +658,13 @@ define_env!(Env, <E: Ext>,
|
||||
value_ptr: u32,
|
||||
value_len: u32
|
||||
) -> ReturnCode => {
|
||||
charge_gas(ctx, RuntimeToken::Transfer)?;
|
||||
let callee: <<E as Ext>::T as frame_system::Trait>::AccountId =
|
||||
read_sandbox_memory_as(ctx, account_ptr, account_len)?;
|
||||
let value: BalanceOf<<E as Ext>::T> =
|
||||
read_sandbox_memory_as(ctx, value_ptr, value_len)?;
|
||||
|
||||
let result = ctx.ext.transfer(&callee, value, ctx.gas_meter);
|
||||
let result = ctx.ext.transfer(&callee, value);
|
||||
map_dispatch_result(ctx, result)
|
||||
},
|
||||
|
||||
@@ -659,11 +711,16 @@ define_env!(Env, <E: Ext>,
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32
|
||||
) -> ReturnCode => {
|
||||
charge_gas(ctx, RuntimeToken::CallBase(input_data_len))?;
|
||||
let callee: <<E as Ext>::T as frame_system::Trait>::AccountId =
|
||||
read_sandbox_memory_as(ctx, callee_ptr, callee_len)?;
|
||||
let value: BalanceOf<<E as Ext>::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?;
|
||||
let input_data = read_sandbox_memory(ctx, input_data_ptr, input_data_len)?;
|
||||
|
||||
if value > 0.into() {
|
||||
charge_gas(ctx, RuntimeToken::CallSurchargeTransfer)?;
|
||||
}
|
||||
|
||||
let nested_gas_limit = if gas == 0 {
|
||||
ctx.gas_meter.gas_left()
|
||||
} else {
|
||||
@@ -686,7 +743,9 @@ define_env!(Env, <E: Ext>,
|
||||
});
|
||||
|
||||
if let Ok(output) = &call_outcome {
|
||||
write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true)?;
|
||||
write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true, |len| {
|
||||
Some(RuntimeToken::CallCopyOut(len))
|
||||
})?;
|
||||
}
|
||||
map_exec_result(ctx, call_outcome)
|
||||
},
|
||||
@@ -748,6 +807,7 @@ define_env!(Env, <E: Ext>,
|
||||
output_ptr: u32,
|
||||
output_len_ptr: u32
|
||||
) -> ReturnCode => {
|
||||
charge_gas(ctx, RuntimeToken::InstantiateBase(input_data_len))?;
|
||||
let code_hash: CodeHash<<E as Ext>::T> =
|
||||
read_sandbox_memory_as(ctx, code_hash_ptr, code_hash_len)?;
|
||||
let value: BalanceOf<<E as Ext>::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?;
|
||||
@@ -776,10 +836,12 @@ define_env!(Env, <E: Ext>,
|
||||
if let Ok((address, output)) = &instantiate_outcome {
|
||||
if !output.flags.contains(ReturnFlags::REVERT) {
|
||||
write_sandbox_output(
|
||||
ctx, address_ptr, address_len_ptr, &address.encode(), true
|
||||
ctx, address_ptr, address_len_ptr, &address.encode(), true, already_charged,
|
||||
)?;
|
||||
}
|
||||
write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true)?;
|
||||
write_sandbox_output(ctx, output_ptr, output_len_ptr, &output.data, true, |len| {
|
||||
Some(RuntimeToken::InstantiateCopyOut(len))
|
||||
})?;
|
||||
}
|
||||
map_exec_result(ctx, instantiate_outcome.map(|(_id, retval)| retval))
|
||||
},
|
||||
@@ -803,18 +865,22 @@ define_env!(Env, <E: Ext>,
|
||||
beneficiary_ptr: u32,
|
||||
beneficiary_len: u32
|
||||
) => {
|
||||
charge_gas(ctx, RuntimeToken::Terminate)?;
|
||||
let beneficiary: <<E as Ext>::T as frame_system::Trait>::AccountId =
|
||||
read_sandbox_memory_as(ctx, beneficiary_ptr, beneficiary_len)?;
|
||||
|
||||
if let Ok(_) = ctx.ext.terminate(&beneficiary, ctx.gas_meter) {
|
||||
if let Ok(_) = ctx.ext.terminate(&beneficiary) {
|
||||
ctx.trap_reason = Some(TrapReason::Termination);
|
||||
}
|
||||
Err(sp_sandbox::HostError)
|
||||
},
|
||||
|
||||
seal_input(ctx, buf_ptr: u32, buf_len_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::InputBase)?;
|
||||
if let Some(input) = ctx.input_data.take() {
|
||||
write_sandbox_output(ctx, buf_ptr, buf_len_ptr, &input, false)
|
||||
write_sandbox_output(ctx, buf_ptr, buf_len_ptr, &input, false, |len| {
|
||||
Some(RuntimeToken::InputCopyOut(len))
|
||||
})
|
||||
} else {
|
||||
Err(sp_sandbox::HostError)
|
||||
}
|
||||
@@ -838,13 +904,7 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// Using a reserved bit triggers a trap.
|
||||
seal_return(ctx, flags: u32, data_ptr: u32, data_len: u32) => {
|
||||
charge_gas(
|
||||
ctx.gas_meter,
|
||||
ctx.schedule,
|
||||
&mut ctx.trap_reason,
|
||||
RuntimeToken::ReturnData(data_len)
|
||||
)?;
|
||||
|
||||
charge_gas(ctx, RuntimeToken::Return(data_len))?;
|
||||
ctx.trap_reason = Some(TrapReason::Return(ReturnData {
|
||||
flags,
|
||||
data: read_sandbox_memory(ctx, data_ptr, data_len)?,
|
||||
@@ -867,7 +927,10 @@ define_env!(Env, <E: Ext>,
|
||||
// extrinsic will be returned. Otherwise, if this call is initiated by another contract then the
|
||||
// address of the contract will be returned. The value is encoded as T::AccountId.
|
||||
seal_caller(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false)
|
||||
charge_gas(ctx, RuntimeToken::Caller)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.caller().encode(), false, already_charged
|
||||
)
|
||||
},
|
||||
|
||||
// Stores the address of the current contract into the supplied buffer.
|
||||
@@ -877,7 +940,10 @@ define_env!(Env, <E: Ext>,
|
||||
// `out_ptr`. This call overwrites it with the size of the value. If the available
|
||||
// space at `out_ptr` is less than the size of the value a trap is triggered.
|
||||
seal_address(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.address().encode(), false)
|
||||
charge_gas(ctx, RuntimeToken::Address)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.address().encode(), false, already_charged
|
||||
)
|
||||
},
|
||||
|
||||
// Stores the price for the specified amount of gas into the supplied buffer.
|
||||
@@ -894,8 +960,10 @@ define_env!(Env, <E: Ext>,
|
||||
// It is recommended to avoid specifying very small values for `gas` as the prices for a single
|
||||
// gas can be smaller than one.
|
||||
seal_weight_to_fee(ctx, gas: u64, out_ptr: u32, out_len_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::WeightToFee)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.get_weight_price(gas).encode(), false,
|
||||
already_charged
|
||||
)
|
||||
},
|
||||
|
||||
@@ -908,7 +976,10 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// The data is encoded as Gas.
|
||||
seal_gas_left(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false)
|
||||
charge_gas(ctx, RuntimeToken::GasLeft)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.gas_meter.gas_left().encode(), false, already_charged
|
||||
)
|
||||
},
|
||||
|
||||
// Stores the balance of the current account into the supplied buffer.
|
||||
@@ -920,7 +991,10 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// The data is encoded as T::Balance.
|
||||
seal_balance(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false)
|
||||
charge_gas(ctx, RuntimeToken::Balance)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.balance().encode(), false, already_charged
|
||||
)
|
||||
},
|
||||
|
||||
// Stores the value transferred along with this call or as endowment into the supplied buffer.
|
||||
@@ -932,8 +1006,10 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// The data is encoded as T::Balance.
|
||||
seal_value_transferred(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::ValueTransferred)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.value_transferred().encode(), false,
|
||||
already_charged
|
||||
)
|
||||
},
|
||||
|
||||
@@ -946,13 +1022,15 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// The data is encoded as T::Hash.
|
||||
seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::Random)?;
|
||||
// The length of a subject can't exceed `max_subject_len`.
|
||||
if subject_len > ctx.schedule.max_subject_len {
|
||||
return Err(sp_sandbox::HostError);
|
||||
}
|
||||
let subject_buf = read_sandbox_memory(ctx, subject_ptr, subject_len)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.random(&subject_buf).encode(), false,
|
||||
already_charged
|
||||
)
|
||||
},
|
||||
|
||||
@@ -963,14 +1041,20 @@ define_env!(Env, <E: Ext>,
|
||||
// `out_ptr`. This call overwrites it with the size of the value. If the available
|
||||
// space at `out_ptr` is less than the size of the value a trap is triggered.
|
||||
seal_now(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.now().encode(), false)
|
||||
charge_gas(ctx, RuntimeToken::Now)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.now().encode(), false, already_charged
|
||||
)
|
||||
},
|
||||
|
||||
// Stores the minimum balance (a.k.a. existential deposit) into the supplied buffer.
|
||||
//
|
||||
// The data is encoded as T::Balance.
|
||||
seal_minimum_balance(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false)
|
||||
charge_gas(ctx, RuntimeToken::MinimumBalance)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.minimum_balance().encode(), false, already_charged
|
||||
)
|
||||
},
|
||||
|
||||
// Stores the tombstone deposit into the supplied buffer.
|
||||
@@ -989,8 +1073,10 @@ define_env!(Env, <E: Ext>,
|
||||
// below the sum of existential deposit and the tombstone deposit. The sum
|
||||
// is commonly referred as subsistence threshold in code.
|
||||
seal_tombstone_deposit(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::TombstoneDeposit)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.tombstone_deposit().encode(), false,
|
||||
already_charged
|
||||
)
|
||||
},
|
||||
|
||||
@@ -1031,6 +1117,7 @@ define_env!(Env, <E: Ext>,
|
||||
delta_ptr: u32,
|
||||
delta_count: u32
|
||||
) => {
|
||||
charge_gas(ctx, RuntimeToken::RestoreTo(delta_count))?;
|
||||
let dest: <<E as Ext>::T as frame_system::Trait>::AccountId =
|
||||
read_sandbox_memory_as(ctx, dest_ptr, dest_len)?;
|
||||
let code_hash: CodeHash<<E as Ext>::T> =
|
||||
@@ -1038,9 +1125,8 @@ define_env!(Env, <E: Ext>,
|
||||
let rent_allowance: BalanceOf<<E as Ext>::T> =
|
||||
read_sandbox_memory_as(ctx, rent_allowance_ptr, rent_allowance_len)?;
|
||||
let delta = {
|
||||
// We don't use `with_capacity` here to not eagerly allocate the user specified amount
|
||||
// of memory.
|
||||
let mut delta = Vec::new();
|
||||
// We can eagerly allocate because we charged for the complete delta count already
|
||||
let mut delta = Vec::with_capacity(delta_count as usize);
|
||||
let mut key_ptr = delta_ptr;
|
||||
|
||||
for _ in 0..delta_count {
|
||||
@@ -1078,6 +1164,17 @@ define_env!(Env, <E: Ext>,
|
||||
// - data_ptr - a pointer to a raw data buffer which will saved along the event.
|
||||
// - data_len - the length of the data buffer.
|
||||
seal_deposit_event(ctx, topics_ptr: u32, topics_len: u32, data_ptr: u32, data_len: u32) => {
|
||||
let num_topic = topics_len
|
||||
.checked_div(sp_std::mem::size_of::<TopicOf<E::T>>() as u32)
|
||||
.ok_or_else(|| store_err(ctx, "Zero sized topics are not allowed"))?;
|
||||
charge_gas(ctx, RuntimeToken::DepositEvent {
|
||||
num_topic,
|
||||
len: data_len,
|
||||
})?;
|
||||
if data_len > ctx.ext.max_value_size() {
|
||||
Err(store_err(ctx, Error::<E::T>::ValueTooLarge))?;
|
||||
}
|
||||
|
||||
let mut topics: Vec::<TopicOf<<E as Ext>::T>> = match topics_len {
|
||||
0 => Vec::new(),
|
||||
_ => read_sandbox_memory_as(ctx, topics_ptr, topics_len)?,
|
||||
@@ -1095,12 +1192,6 @@ define_env!(Env, <E: Ext>,
|
||||
|
||||
let event_data = read_sandbox_memory(ctx, data_ptr, data_len)?;
|
||||
|
||||
charge_gas(
|
||||
ctx.gas_meter,
|
||||
ctx.schedule,
|
||||
&mut ctx.trap_reason,
|
||||
RuntimeToken::DepositEvent(topics.len() as u32, data_len)
|
||||
)?;
|
||||
ctx.ext.deposit_event(topics, event_data);
|
||||
|
||||
Ok(())
|
||||
@@ -1112,6 +1203,7 @@ define_env!(Env, <E: Ext>,
|
||||
// Should be decodable as a `T::Balance`. Traps otherwise.
|
||||
// - value_len: length of the value buffer.
|
||||
seal_set_rent_allowance(ctx, value_ptr: u32, value_len: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::SetRentAllowance)?;
|
||||
let value: BalanceOf<<E as Ext>::T> =
|
||||
read_sandbox_memory_as(ctx, value_ptr, value_len)?;
|
||||
ctx.ext.set_rent_allowance(value);
|
||||
@@ -1128,7 +1220,10 @@ define_env!(Env, <E: Ext>,
|
||||
//
|
||||
// The data is encoded as T::Balance.
|
||||
seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false)
|
||||
charge_gas(ctx, RuntimeToken::RentAllowance)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.rent_allowance().encode(), false, already_charged
|
||||
)
|
||||
},
|
||||
|
||||
// Prints utf8 encoded string from the data buffer.
|
||||
@@ -1149,7 +1244,10 @@ define_env!(Env, <E: Ext>,
|
||||
// `out_ptr`. This call overwrites it with the size of the value. If the available
|
||||
// space at `out_ptr` is less than the size of the value a trap is triggered.
|
||||
seal_block_number(ctx, out_ptr: u32, out_len_ptr: u32) => {
|
||||
write_sandbox_output(ctx, out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false)
|
||||
charge_gas(ctx, RuntimeToken::BlockNumber)?;
|
||||
write_sandbox_output(
|
||||
ctx, out_ptr, out_len_ptr, &ctx.ext.block_number().encode(), false, already_charged
|
||||
)
|
||||
},
|
||||
|
||||
// Computes the SHA2 256-bit hash on the given input buffer.
|
||||
@@ -1173,6 +1271,7 @@ define_env!(Env, <E: Ext>,
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
seal_hash_sha2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::HashSha256(input_len))?;
|
||||
compute_hash_on_intermediate_buffer(ctx, sha2_256, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
@@ -1197,6 +1296,7 @@ define_env!(Env, <E: Ext>,
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
seal_hash_keccak_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::HashKeccak256(input_len))?;
|
||||
compute_hash_on_intermediate_buffer(ctx, keccak_256, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
@@ -1221,6 +1321,7 @@ define_env!(Env, <E: Ext>,
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
seal_hash_blake2_256(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::HashBlake256(input_len))?;
|
||||
compute_hash_on_intermediate_buffer(ctx, blake2_256, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
|
||||
@@ -1245,6 +1346,7 @@ define_env!(Env, <E: Ext>,
|
||||
// data is placed. The function will write the result
|
||||
// directly into this buffer.
|
||||
seal_hash_blake2_128(ctx, input_ptr: u32, input_len: u32, output_ptr: u32) => {
|
||||
charge_gas(ctx, RuntimeToken::HashBlake128(input_len))?;
|
||||
compute_hash_on_intermediate_buffer(ctx, blake2_128, input_ptr, input_len, output_ptr)
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user