Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.

Commit fa42631

Browse files
agryaznovathei
andauthored
[contracts] Add per local weight for function call (#12806)
* Add per local weight for function call * ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts * Update frame/contracts/src/benchmarking/mod.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * apply suggestions from code review * ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts * Update frame/contracts/src/benchmarking/mod.rs Co-authored-by: Alexander Theißen <alex.theissen@me.com> * tune the benchmark * ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts * fix benches * ".git/.scripts/bench-bot.sh" pallet dev pallet_contracts Co-authored-by: command-bot <> Co-authored-by: Alexander Theißen <alex.theissen@me.com>
1 parent b1396f7 commit fa42631

7 files changed

Lines changed: 1383 additions & 1267 deletions

File tree

Cargo.lock

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frame/contracts/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features =
2020
] }
2121
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
2222
log = { version = "0.4", default-features = false }
23-
wasm-instrument = { version = "0.3", default-features = false }
23+
wasm-instrument = { version = "0.4", default-features = false }
2424
serde = { version = "1", optional = true, features = ["derive"] }
2525
smallvec = { version = "1", default-features = false, features = [
2626
"const_generics",

frame/contracts/src/benchmarking/code.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,14 @@ use frame_support::traits::Get;
2929
use sp_core::crypto::UncheckedFrom;
3030
use sp_runtime::traits::Hash;
3131
use sp_std::{borrow::ToOwned, prelude::*};
32-
use wasm_instrument::parity_wasm::{
33-
builder,
34-
elements::{
35-
self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module,
36-
Section, ValueType,
32+
use wasm_instrument::{
33+
gas_metering,
34+
parity_wasm::{
35+
builder,
36+
elements::{
37+
self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module,
38+
Section, ValueType,
39+
},
3740
},
3841
};
3942

@@ -541,7 +544,8 @@ where
541544
fn inject_gas_metering<T: Config>(module: Module) -> Module {
542545
let schedule = T::Schedule::get();
543546
let gas_rules = schedule.rules(&module, Determinism::Deterministic);
544-
wasm_instrument::gas_metering::inject(module, &gas_rules, "seal0").unwrap()
547+
let backend = gas_metering::host_function::Injector::new("seal0", "gas");
548+
gas_metering::inject(module, backend, &gas_rules).unwrap()
545549
}
546550

547551
fn inject_stack_metering<T: Config>(module: Module) -> Module {

frame/contracts/src/benchmarking/mod.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2429,10 +2429,28 @@ benchmarks! {
24292429
sbox.invoke();
24302430
}
24312431

2432+
// w_per_local = w_bench
2433+
instr_call_per_local {
2434+
let l in 0 .. T::Schedule::get().limits.locals;
2435+
let mut aux_body = body::plain(vec![
2436+
Instruction::End,
2437+
]);
2438+
body::inject_locals(&mut aux_body, l);
2439+
let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
2440+
aux_body: Some(aux_body),
2441+
call_body: Some(body::repeated(INSTR_BENCHMARK_BATCH_SIZE, &[
2442+
Instruction::Call(2), // call aux
2443+
])),
2444+
.. Default::default()
2445+
}));
2446+
}: {
2447+
sbox.invoke();
2448+
}
2449+
24322450
// w_local_get = w_bench - 1 * w_param
24332451
instr_local_get {
24342452
let r in 0 .. INSTR_BENCHMARK_BATCHES;
2435-
let max_locals = T::Schedule::get().limits.stack_height.unwrap_or(512);
2453+
let max_locals = T::Schedule::get().limits.locals;
24362454
let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
24372455
RandomGetLocal(0, max_locals),
24382456
Regular(Instruction::Drop),
@@ -2449,7 +2467,7 @@ benchmarks! {
24492467
// w_local_set = w_bench - 1 * w_param
24502468
instr_local_set {
24512469
let r in 0 .. INSTR_BENCHMARK_BATCHES;
2452-
let max_locals = T::Schedule::get().limits.stack_height.unwrap_or(512);
2470+
let max_locals = T::Schedule::get().limits.locals;
24532471
let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
24542472
RandomI64Repeated(1),
24552473
RandomSetLocal(0, max_locals),
@@ -2466,7 +2484,7 @@ benchmarks! {
24662484
// w_local_tee = w_bench - 2 * w_param
24672485
instr_local_tee {
24682486
let r in 0 .. INSTR_BENCHMARK_BATCHES;
2469-
let max_locals = T::Schedule::get().limits.stack_height.unwrap_or(512);
2487+
let max_locals = T::Schedule::get().limits.locals;
24702488
let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
24712489
RandomI64Repeated(1),
24722490
RandomTeeLocal(0, max_locals),

frame/contracts/src/schedule.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ pub struct Limits {
118118
/// the linear memory limit `memory_pages` applies to them.
119119
pub globals: u32,
120120

121+
/// Maximum number of locals a function can have.
122+
///
123+
/// As wasm engine initializes each of the local, we need to limit their number to confine
124+
/// execution costs.
125+
pub locals: u32,
126+
121127
/// Maximum numbers of parameters a function can have.
122128
///
123129
/// Those need to be limited to prevent a potentially exploitable interaction with
@@ -212,6 +218,7 @@ pub struct InstructionWeights<T: Config> {
212218
pub call: u32,
213219
pub call_indirect: u32,
214220
pub call_indirect_per_param: u32,
221+
pub call_per_local: u32,
215222
pub local_get: u32,
216223
pub local_set: u32,
217224
pub local_tee: u32,
@@ -522,6 +529,7 @@ impl Default for Limits {
522529
// No stack limit required because we use a runtime resident execution engine.
523530
stack_height: None,
524531
globals: 256,
532+
locals: 1024,
525533
parameters: 128,
526534
memory_pages: 16,
527535
// 4k function pointers (This is in count not bytes).
@@ -552,6 +560,7 @@ impl<T: Config> Default for InstructionWeights<T> {
552560
call: cost_instr!(instr_call, 2),
553561
call_indirect: cost_instr!(instr_call_indirect, 3),
554562
call_indirect_per_param: cost_instr!(instr_call_indirect_per_param, 1),
563+
call_per_local: cost_instr!(instr_call_per_local, 1),
555564
local_get: cost_instr!(instr_local_get, 1),
556565
local_set: cost_instr!(instr_local_set, 1),
557566
local_tee: cost_instr!(instr_local_tee, 2),
@@ -792,6 +801,10 @@ impl<'a, T: Config> gas_metering::Rules for ScheduleRules<'a, T> {
792801
// The cost for growing is therefore already included in the instruction cost.
793802
gas_metering::MemoryGrowCost::Free
794803
}
804+
805+
fn call_per_local_cost(&self) -> u32 {
806+
self.schedule.instruction_weights.call_per_local
807+
}
795808
}
796809

797810
#[cfg(test)]

frame/contracts/src/wasm/prepare.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ use codec::{Encode, MaxEncodedLen};
2929
use sp_core::crypto::UncheckedFrom;
3030
use sp_runtime::{traits::Hash, DispatchError};
3131
use sp_std::prelude::*;
32-
use wasm_instrument::parity_wasm::elements::{
33-
self, External, Internal, MemoryType, Type, ValueType,
32+
use wasm_instrument::{
33+
gas_metering,
34+
parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType},
3435
};
3536
use wasmi::StackLimits;
3637
use wasmparser::{Validator, WasmFeatures};
@@ -132,6 +133,19 @@ impl<'a, T: Config> ContractModule<'a, T> {
132133
Ok(())
133134
}
134135

136+
fn ensure_local_variable_limit(&self, limit: u32) -> Result<(), &'static str> {
137+
if let Some(code_section) = self.module.code_section() {
138+
for func_body in code_section.bodies() {
139+
let locals_count: u32 =
140+
func_body.locals().iter().map(|val_type| val_type.count()).sum();
141+
if locals_count > limit {
142+
return Err("single function declares too many locals")
143+
}
144+
}
145+
}
146+
Ok(())
147+
}
148+
135149
/// Ensures that no floating point types are in use.
136150
fn ensure_no_floating_types(&self) -> Result<(), &'static str> {
137151
if let Some(global_section) = self.module.global_section() {
@@ -197,9 +211,9 @@ impl<'a, T: Config> ContractModule<'a, T> {
197211

198212
fn inject_gas_metering(self, determinism: Determinism) -> Result<Self, &'static str> {
199213
let gas_rules = self.schedule.rules(&self.module, determinism);
200-
let contract_module =
201-
wasm_instrument::gas_metering::inject(self.module, &gas_rules, "seal0")
202-
.map_err(|_| "gas instrumentation failed")?;
214+
let backend = gas_metering::host_function::Injector::new("seal0", "gas");
215+
let contract_module = gas_metering::inject(self.module, backend, &gas_rules)
216+
.map_err(|_| "gas instrumentation failed")?;
203217
Ok(ContractModule { module: contract_module, schedule: self.schedule })
204218
}
205219

@@ -422,6 +436,7 @@ where
422436
contract_module.ensure_no_internal_memory()?;
423437
contract_module.ensure_table_size_limit(schedule.limits.table_size)?;
424438
contract_module.ensure_global_variable_limit(schedule.limits.globals)?;
439+
contract_module.ensure_local_variable_limit(schedule.limits.locals)?;
425440
contract_module.ensure_parameter_limit(schedule.limits.parameters)?;
426441
contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?;
427442

@@ -636,7 +651,8 @@ mod tests {
636651
let wasm = wat::parse_str($wat).unwrap().try_into().unwrap();
637652
let schedule = Schedule {
638653
limits: Limits {
639-
globals: 3,
654+
globals: 3,
655+
locals: 3,
640656
parameters: 3,
641657
memory_pages: 16,
642658
table_size: 3,
@@ -736,6 +752,43 @@ mod tests {
736752
);
737753
}
738754

755+
mod locals {
756+
use super::*;
757+
758+
prepare_test!(
759+
local_number_valid,
760+
r#"
761+
(module
762+
(func
763+
(local i32)
764+
(local i32)
765+
(local i32)
766+
)
767+
(func (export "call"))
768+
(func (export "deploy"))
769+
)
770+
"#,
771+
Ok(_)
772+
);
773+
774+
prepare_test!(
775+
local_number_too_high,
776+
r#"
777+
(module
778+
(func
779+
(local i32)
780+
(local i32)
781+
(local i32)
782+
(local i32)
783+
)
784+
(func (export "call"))
785+
(func (export "deploy"))
786+
)
787+
"#,
788+
Err("single function declares too many locals")
789+
);
790+
}
791+
739792
mod memories {
740793
use super::*;
741794

0 commit comments

Comments
 (0)