mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-15 16:11:05 +00:00
contracts: Add automated weights for wasm instructions (#7361)
* pallet_contracts: Inline benchmark helper that is only used once * Move all max_* Schedule items into a new struct * Limit the number of globals a module can declare * The current limits are too high for wasmi to even execute * Limit the amount of parameters any wasm function is allowed to have * Limit the size the BrTable's immediate value * Add instruction benchmarks * Add new benchmarks to the schedule and make use of it * Add Benchmark Results generated by the bench bot * Add proc macro that implements `Debug` for `Schedule` * Add missing imports necessary for no_std build * Make the WeightDebug macro available for no_std In this case a dummy implementation is derived in order to not blow up the code size akin to the RuntimeDebug macro. * Rework instr_memory_grow benchmark to use only the maximum amount of pages allowed * Add maximum amount of memory when benching (seal_)call/instantiate * 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 --heap-pages=4096 --output ./bin/node/runtime/src/weights --header ./HEADER --pallet pallet_contracts * Added utility benchmark that allows pretty printing of the real schedule * review: Add missing header to the proc-macro lib.rs * review: Clarify why #[allow(dead_code)] attribute is there * review: Fix pwasm-utils line * review: Fixup rand usage * review: Fix typo * review: Imported -> Exported * cargo run --release --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * contracts: Adapt to new weight structure * contracts: Fixup runtime WeightInfo * contracts: Remove unneeded fullpath of WeightInfo type * Apply suggestions from code review Co-authored-by: Andrew Jones <ascjones@gmail.com> * Fix typo in schedule.rs Co-authored-by: Andrew Jones <ascjones@gmail.com> * Fix docs in schedule.rs * Apply suggestions from code review Co-authored-by: Nikolay Volf <nikvolf@gmail.com> * Don't publish proc-macro crate until 3.0.0 is ready * Optimize imports for less repetition * Break overlong line Co-authored-by: Parity Benchmarking Bot <admin@parity.io> Co-authored-by: Andrew Jones <ascjones@gmail.com> Co-authored-by: Nikolay Volf <nikvolf@gmail.com>
This commit is contained in:
committed by
GitHub
parent
9704c204e6
commit
51c67fe881
@@ -164,6 +164,7 @@ mod tests {
|
||||
use hex_literal::hex;
|
||||
use sp_runtime::DispatchError;
|
||||
use frame_support::weights::Weight;
|
||||
use assert_matches::assert_matches;
|
||||
use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags, ExecError, ErrorOrigin};
|
||||
|
||||
const GAS_LIMIT: Gas = 10_000_000_000;
|
||||
@@ -645,14 +646,14 @@ mod tests {
|
||||
&mut GasMeter::new(GAS_LIMIT),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
&mock_ext.instantiates,
|
||||
&[InstantiateEntry {
|
||||
code_hash: [0x11; 32].into(),
|
||||
assert_matches!(
|
||||
&mock_ext.instantiates[..],
|
||||
[InstantiateEntry {
|
||||
code_hash,
|
||||
endowment: 3,
|
||||
data: vec![1, 2, 3, 4],
|
||||
gas_left: 9392302058,
|
||||
}]
|
||||
data,
|
||||
gas_left: _,
|
||||
}] if code_hash == &[0x11; 32].into() && data == &vec![1, 2, 3, 4]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1461,7 +1462,7 @@ mod tests {
|
||||
vec![0x00, 0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe5, 0x14, 0x00])
|
||||
]);
|
||||
|
||||
assert_eq!(gas_meter.gas_left(), 9834099446);
|
||||
assert!(gas_meter.gas_left() > 0);
|
||||
}
|
||||
|
||||
const CODE_DEPOSIT_EVENT_MAX_TOPICS: &str = r#"
|
||||
|
||||
@@ -24,9 +24,7 @@ use crate::{Schedule, Trait};
|
||||
|
||||
use parity_wasm::elements::{self, Internal, External, MemoryType, Type, ValueType};
|
||||
use pwasm_utils;
|
||||
use pwasm_utils::rules;
|
||||
use sp_std::prelude::*;
|
||||
use sp_runtime::traits::{SaturatedConversion};
|
||||
|
||||
/// Currently, all imported functions must be located inside this module. We might support
|
||||
/// additional modules for versioning later.
|
||||
@@ -101,6 +99,33 @@ impl<'a, T: Trait> ContractModule<'a, T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure that any `br_table` instruction adheres to its immediate value limit.
|
||||
fn ensure_br_table_size_limit(&self, limit: u32) -> Result<(), &'static str> {
|
||||
let code_section = if let Some(type_section) = self.module.code_section() {
|
||||
type_section
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
for instr in code_section.bodies().iter().flat_map(|body| body.code().elements()) {
|
||||
use parity_wasm::elements::Instruction::BrTable;
|
||||
if let BrTable(table) = instr {
|
||||
if table.table.len() > limit as usize {
|
||||
return Err("BrTable's immediate value is too big.")
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_global_variable_limit(&self, limit: u32) -> Result<(), &'static str> {
|
||||
if let Some(global_section) = self.module.global_section() {
|
||||
if global_section.entries().len() > limit as usize {
|
||||
return Err("module declares too many globals")
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensures that no floating point types are in use.
|
||||
fn ensure_no_floating_types(&self) -> Result<(), &'static str> {
|
||||
if let Some(global_section) = self.module.global_section() {
|
||||
@@ -145,15 +170,25 @@ impl<'a, T: Trait> ContractModule<'a, T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_gas_metering(self) -> Result<Self, &'static str> {
|
||||
let gas_rules =
|
||||
rules::Set::new(
|
||||
self.schedule.instruction_weights.regular.clone().saturated_into(),
|
||||
Default::default(),
|
||||
)
|
||||
.with_grow_cost(self.schedule.instruction_weights.grow_mem.clone().saturated_into())
|
||||
.with_forbidden_floats();
|
||||
/// Ensure that no function exists that has more parameters than allowed.
|
||||
fn ensure_parameter_limit(&self, limit: u32) -> Result<(), &'static str> {
|
||||
let type_section = if let Some(type_section) = self.module.type_section() {
|
||||
type_section
|
||||
} else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
for Type::Function(func) in type_section.types() {
|
||||
if func.params().len() > limit as usize {
|
||||
return Err("Use of a function type with too many parameters.");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn inject_gas_metering(self) -> Result<Self, &'static str> {
|
||||
let gas_rules = self.schedule.rules(&self.module);
|
||||
let contract_module = pwasm_utils::inject_gas_counter(
|
||||
self.module,
|
||||
&gas_rules,
|
||||
@@ -167,7 +202,8 @@ impl<'a, T: Trait> ContractModule<'a, T> {
|
||||
|
||||
fn inject_stack_height_metering(self) -> Result<Self, &'static str> {
|
||||
let contract_module =
|
||||
pwasm_utils::stack_height::inject_limiter(self.module, self.schedule.max_stack_height)
|
||||
pwasm_utils::stack_height
|
||||
::inject_limiter(self.module, self.schedule.limits.stack_height)
|
||||
.map_err(|_| "stack height instrumentation failed")?;
|
||||
Ok(ContractModule {
|
||||
module: contract_module,
|
||||
@@ -345,7 +381,7 @@ fn get_memory_limits<T: Trait>(module: Option<&MemoryType>, schedule: &Schedule<
|
||||
"Requested initial number of pages should not exceed the requested maximum",
|
||||
);
|
||||
}
|
||||
(_, Some(maximum)) if maximum > schedule.max_memory_pages => {
|
||||
(_, Some(maximum)) if maximum > schedule.limits.memory_pages => {
|
||||
return Err("Maximum number of pages should not exceed the configured maximum.");
|
||||
}
|
||||
(initial, Some(maximum)) => Ok((initial, maximum)),
|
||||
@@ -381,8 +417,11 @@ pub fn prepare_contract<C: ImportSatisfyCheck, T: Trait>(
|
||||
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_table_size_limit(schedule.limits.table_size)?;
|
||||
contract_module.ensure_global_variable_limit(schedule.limits.globals)?;
|
||||
contract_module.ensure_no_floating_types()?;
|
||||
contract_module.ensure_parameter_limit(schedule.limits.parameters)?;
|
||||
contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?;
|
||||
|
||||
// We disallow importing `gas` function here since it is treated as implementation detail.
|
||||
let disallowed_imports = [b"gas".as_ref()];
|
||||
@@ -442,7 +481,7 @@ pub mod benchmarking {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::exec::Ext;
|
||||
use crate::{exec::Ext, Limits};
|
||||
use std::fmt;
|
||||
use assert_matches::assert_matches;
|
||||
|
||||
@@ -470,7 +509,17 @@ mod tests {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let wasm = wat::parse_str($wat).unwrap();
|
||||
let schedule = Schedule::default();
|
||||
let schedule = Schedule {
|
||||
limits: Limits {
|
||||
globals: 3,
|
||||
parameters: 3,
|
||||
memory_pages: 16,
|
||||
table_size: 3,
|
||||
br_table_size: 3,
|
||||
.. Default::default()
|
||||
},
|
||||
.. Default::default()
|
||||
};
|
||||
let r = prepare_contract::<TestEnv, crate::tests::Test>(wasm.as_ref(), &schedule);
|
||||
assert_matches!(r, $($expected)*);
|
||||
}
|
||||
@@ -493,14 +542,66 @@ mod tests {
|
||||
Err("gas instrumentation failed")
|
||||
);
|
||||
|
||||
mod memories {
|
||||
mod functions {
|
||||
use super::*;
|
||||
|
||||
// Tests below assumes that maximum page number is configured to a certain number.
|
||||
#[test]
|
||||
fn assume_memory_size() {
|
||||
assert_eq!(<Schedule<crate::tests::Test>>::default().max_memory_pages, 16);
|
||||
}
|
||||
prepare_test!(param_number_valid,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
(func (param i32 i32 i32))
|
||||
)
|
||||
"#,
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(param_number_invalid,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
(func (param i32 i32 i32 i32))
|
||||
(func (param i32))
|
||||
)
|
||||
"#,
|
||||
Err("Use of a function type with too many parameters.")
|
||||
);
|
||||
}
|
||||
|
||||
mod globals {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(global_number_valid,
|
||||
r#"
|
||||
(module
|
||||
(global i64 (i64.const 0))
|
||||
(global i64 (i64.const 0))
|
||||
(global i64 (i64.const 0))
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(global_number_too_high,
|
||||
r#"
|
||||
(module
|
||||
(global i64 (i64.const 0))
|
||||
(global i64 (i64.const 0))
|
||||
(global i64 (i64.const 0))
|
||||
(global i64 (i64.const 0))
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Err("module declares too many globals")
|
||||
);
|
||||
}
|
||||
|
||||
mod memories {
|
||||
use super::*;
|
||||
|
||||
prepare_test!(memory_with_one_page,
|
||||
r#"
|
||||
@@ -561,6 +662,18 @@ mod tests {
|
||||
Err("Maximum number of pages should be always declared.")
|
||||
);
|
||||
|
||||
prepare_test!(requested_maximum_valid,
|
||||
r#"
|
||||
(module
|
||||
(import "env" "memory" (memory 1 16))
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)
|
||||
"#,
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(requested_maximum_exceeds_configured_maximum,
|
||||
r#"
|
||||
(module
|
||||
@@ -625,12 +738,6 @@ mod tests {
|
||||
mod tables {
|
||||
use super::*;
|
||||
|
||||
// Tests below assumes that maximum table size is configured to a certain number.
|
||||
#[test]
|
||||
fn assume_table_size() {
|
||||
assert_eq!(<Schedule<crate::tests::Test>>::default().max_table_size, 16384);
|
||||
}
|
||||
|
||||
prepare_test!(no_tables,
|
||||
r#"
|
||||
(module
|
||||
@@ -644,7 +751,7 @@ mod tests {
|
||||
prepare_test!(table_valid_size,
|
||||
r#"
|
||||
(module
|
||||
(table 10000 funcref)
|
||||
(table 3 funcref)
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
@@ -656,13 +763,40 @@ mod tests {
|
||||
prepare_test!(table_too_big,
|
||||
r#"
|
||||
(module
|
||||
(table 20000 funcref)
|
||||
(table 4 funcref)
|
||||
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
)"#,
|
||||
Err("table exceeds maximum size allowed")
|
||||
);
|
||||
|
||||
prepare_test!(br_table_valid_size,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
(func
|
||||
i32.const 0
|
||||
br_table 0 0 0 0
|
||||
)
|
||||
)
|
||||
"#,
|
||||
Ok(_)
|
||||
);
|
||||
|
||||
prepare_test!(br_table_too_big,
|
||||
r#"
|
||||
(module
|
||||
(func (export "call"))
|
||||
(func (export "deploy"))
|
||||
(func
|
||||
i32.const 0
|
||||
br_table 0 0 0 0 0
|
||||
)
|
||||
)"#,
|
||||
Err("BrTable's immediate value is too big.")
|
||||
);
|
||||
}
|
||||
|
||||
mod imports {
|
||||
|
||||
@@ -279,7 +279,7 @@ pub enum RuntimeToken {
|
||||
}
|
||||
|
||||
impl<T: Trait> Token<T> for RuntimeToken {
|
||||
type Metadata = HostFnWeights;
|
||||
type Metadata = HostFnWeights<T>;
|
||||
|
||||
fn calculate_amount(&self, s: &Self::Metadata) -> Gas {
|
||||
use self::RuntimeToken::*;
|
||||
@@ -340,7 +340,7 @@ impl<T: Trait> Token<T> for RuntimeToken {
|
||||
fn charge_gas<E, Tok>(ctx: &mut Runtime<E>, token: Tok) -> Result<(), sp_sandbox::HostError>
|
||||
where
|
||||
E: Ext,
|
||||
Tok: Token<E::T, Metadata=HostFnWeights>,
|
||||
Tok: Token<E::T, Metadata=HostFnWeights<E::T>>,
|
||||
{
|
||||
match ctx.gas_meter.charge(&ctx.schedule.host_fn_weights, token) {
|
||||
GasMeterResult::Proceed => Ok(()),
|
||||
@@ -1024,8 +1024,7 @@ 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 {
|
||||
if subject_len > ctx.schedule.limits.subject_len {
|
||||
return Err(sp_sandbox::HostError);
|
||||
}
|
||||
let subject_buf = read_sandbox_memory(ctx, subject_ptr, subject_len)?;
|
||||
@@ -1157,7 +1156,7 @@ define_env!(Env, <E: Ext>,
|
||||
},
|
||||
|
||||
// Deposit a contract event with the data buffer and optional list of topics. There is a limit
|
||||
// on the maximum number of topics specified by `max_event_topics`.
|
||||
// on the maximum number of topics specified by `event_topics`.
|
||||
//
|
||||
// - topics_ptr - a pointer to the buffer of topics encoded as `Vec<T::Hash>`. The value of this
|
||||
// is ignored if `topics_len` is set to 0. The topics list can't contain duplicates.
|
||||
@@ -1181,8 +1180,8 @@ define_env!(Env, <E: Ext>,
|
||||
_ => read_sandbox_memory_as(ctx, topics_ptr, topics_len)?,
|
||||
};
|
||||
|
||||
// If there are more than `max_event_topics`, then trap.
|
||||
if topics.len() > ctx.schedule.max_event_topics as usize {
|
||||
// If there are more than `event_topics`, then trap.
|
||||
if topics.len() > ctx.schedule.limits.event_topics as usize {
|
||||
return Err(sp_sandbox::HostError);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user