diff --git a/Cargo.lock b/Cargo.lock index 6f4d578227f36..1617c891bc4de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1519,7 +1519,7 @@ dependencies = [ "hex-literal", "linregress", "parity-scale-codec", - "paste", + "paste 0.1.18", "sp-api", "sp-io", "sp-runtime", @@ -1589,7 +1589,7 @@ dependencies = [ "once_cell 1.4.0", "parity-scale-codec", "parity-util-mem", - "paste", + "paste 0.1.18", "pretty_assertions", "serde", "smallvec 1.4.1", @@ -3258,7 +3258,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c50092e40e0ccd1bf2015a10333fde0502ff95b832b0895dc1ca0d7ac6c52f6" dependencies = [ - "paste", + "paste 0.1.18", ] [[package]] @@ -4364,6 +4364,7 @@ dependencies = [ "pallet-timestamp", "parity-scale-codec", "parity-wasm 0.41.0", + "paste 1.0.0", "pretty_assertions", "pwasm-utils", "serde", @@ -5324,6 +5325,12 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "paste" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ddc8e145de01d9180ac7b78b9676f95a9c2447f6a88b2c2a04702211bc5d71" + [[package]] name = "paste-impl" version = "0.1.18" @@ -8230,7 +8237,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "paste", + "paste 0.1.18", "rand 0.7.3", "serde", "serde_json", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 75439fe948bdf..2d150e22bddaa 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -699,6 +699,7 @@ impl pallet_contracts::Trait for Runtime { type MaxDepth = pallet_contracts::DefaultMaxDepth; type MaxValueSize = pallet_contracts::DefaultMaxValueSize; type WeightPrice = pallet_transaction_payment::Module; + type WeightInfo = weights::pallet_contracts::WeightInfo; } impl pallet_sudo::Trait for Runtime { @@ -929,7 +930,7 @@ construct_runtime!( FinalityTracker: pallet_finality_tracker::{Module, Call, Inherent}, Grandpa: pallet_grandpa::{Module, Call, Storage, Config, Event, ValidateUnsigned}, Treasury: pallet_treasury::{Module, Call, Storage, Config, Event}, - Contracts: pallet_contracts::{Module, Call, Config, Storage, Event}, + Contracts: pallet_contracts::{Module, Call, Config, Storage, Event}, Sudo: pallet_sudo::{Module, Call, Config, Storage, Event}, ImOnline: pallet_im_online::{Module, Call, Storage, Event, ValidateUnsigned, Config}, AuthorityDiscovery: pallet_authority_discovery::{Module, Call, Config}, diff --git a/bin/node/runtime/src/weights/mod.rs b/bin/node/runtime/src/weights/mod.rs index 19269d02611ef..c75ff83085b6e 100644 --- a/bin/node/runtime/src/weights/mod.rs +++ b/bin/node/runtime/src/weights/mod.rs @@ -18,6 +18,7 @@ pub mod frame_system; pub mod pallet_balances; pub mod pallet_collective; +pub mod pallet_contracts; pub mod pallet_democracy; pub mod pallet_elections_phragmen; pub mod pallet_identity; diff --git a/bin/node/runtime/src/weights/pallet_contracts.rs b/bin/node/runtime/src/weights/pallet_contracts.rs new file mode 100644 index 0000000000000..8cd97b4a72191 --- /dev/null +++ b/bin/node/runtime/src/weights/pallet_contracts.rs @@ -0,0 +1,294 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Weights for pallet_contracts +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0 +//! DATE: 2020-10-06, STEPS: [50], REPEAT: 20, LOW RANGE: [], HIGH RANGE: [] + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::Weight}; +use sp_std::marker::PhantomData; + +pub struct WeightInfo(PhantomData); +impl pallet_contracts::WeightInfo for WeightInfo { + fn update_schedule() -> Weight { + (33_207_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn put_code(n: u32, ) -> Weight { + (0 as Weight) + .saturating_add((144_833_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn instantiate(n: u32, ) -> Weight { + (223_974_000 as Weight) + .saturating_add((1_007_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + fn call() -> Weight { + (210_638_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn claim_surcharge() -> Weight { + (508_079_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn seal_caller(r: u32, ) -> Weight { + (143_336_000 as Weight) + .saturating_add((397_788_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_address(r: u32, ) -> Weight { + (147_296_000 as Weight) + .saturating_add((396_962_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_gas_left(r: u32, ) -> Weight { + (141_677_000 as Weight) + .saturating_add((393_308_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_balance(r: u32, ) -> Weight { + (157_556_000 as Weight) + .saturating_add((879_861_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + } + fn seal_value_transferred(r: u32, ) -> Weight { + (148_867_000 as Weight) + .saturating_add((391_678_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_minimum_balance(r: u32, ) -> Weight { + (147_252_000 as Weight) + .saturating_add((393_977_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_tombstone_deposit(r: u32, ) -> Weight { + (144_208_000 as Weight) + .saturating_add((394_625_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_rent_allowance(r: u32, ) -> Weight { + (135_320_000 as Weight) + .saturating_add((925_541_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_block_number(r: u32, ) -> Weight { + (145_849_000 as Weight) + .saturating_add((390_065_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_now(r: u32, ) -> Weight { + (146_363_000 as Weight) + .saturating_add((391_772_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_weight_to_fee(r: u32, ) -> Weight { + (129_872_000 as Weight) + .saturating_add((670_744_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + } + fn seal_gas(r: u32, ) -> Weight { + (130_985_000 as Weight) + .saturating_add((198_427_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_input(r: u32, ) -> Weight { + (138_647_000 as Weight) + .saturating_add((8_363_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_input_per_kb(n: u32, ) -> Weight { + (149_418_000 as Weight) + .saturating_add((272_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_return(r: u32, ) -> Weight { + (129_116_000 as Weight) + .saturating_add((5_745_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_return_per_kb(n: u32, ) -> Weight { + (139_601_000 as Weight) + .saturating_add((680_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_terminate(r: u32, ) -> Weight { + (138_548_000 as Weight) + .saturating_add((355_473_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight))) + } + fn seal_restore_to(r: u32, ) -> Weight { + (239_880_000 as Weight) + .saturating_add((138_305_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((4 as Weight).saturating_mul(r as Weight))) + } + fn seal_restore_to_per_delta(d: u32, ) -> Weight { + (40_572_000 as Weight) + .saturating_add((3_748_632_000 as Weight).saturating_mul(d as Weight)) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(d as Weight))) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(d as Weight))) + } + fn seal_random(r: u32, ) -> Weight { + (148_156_000 as Weight) + .saturating_add((1_036_452_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + } + fn seal_deposit_event(r: u32, ) -> Weight { + (176_039_000 as Weight) + .saturating_add((1_497_705_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { + (1_923_547_000 as Weight) + .saturating_add((783_354_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((240_600_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(t as Weight))) + } + fn seal_set_rent_allowance(r: u32, ) -> Weight { + (151_095_000 as Weight) + .saturating_add((1_104_696_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + fn seal_set_storage(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((14_975_467_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_set_storage_per_kb(n: u32, ) -> Weight { + (2_465_724_000 as Weight) + .saturating_add((203_125_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + fn seal_clear_storage(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((5_254_595_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_get_storage(r: u32, ) -> Weight { + (60_303_000 as Weight) + .saturating_add((1_135_486_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_get_storage_per_kb(n: u32, ) -> Weight { + (931_900_000 as Weight) + .saturating_add((144_572_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + } + fn seal_transfer(r: u32, ) -> Weight { + (50_722_000 as Weight) + .saturating_add((6_701_164_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_call(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((10_589_747_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight { + (11_223_388_000 as Weight) + .saturating_add((4_965_182_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((50_603_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((72_972_000 as Weight).saturating_mul(o as Weight)) + .saturating_add(T::DbWeight::get().reads(105 as Weight)) + .saturating_add(T::DbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) + .saturating_add(T::DbWeight::get().writes((101 as Weight).saturating_mul(t as Weight))) + } + fn seal_instantiate(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((22_933_938_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().reads((300 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + .saturating_add(T::DbWeight::get().writes((200 as Weight).saturating_mul(r as Weight))) + } + fn seal_instantiate_per_input_output_kb(i: u32, o: u32, ) -> Weight { + (20_986_307_000 as Weight) + .saturating_add((152_611_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((73_457_000 as Weight).saturating_mul(o as Weight)) + .saturating_add(T::DbWeight::get().reads(207 as Weight)) + .saturating_add(T::DbWeight::get().writes(202 as Weight)) + } + fn seal_hash_sha2_256(r: u32, ) -> Weight { + (145_988_000 as Weight) + .saturating_add((343_540_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { + (719_758_000 as Weight) + .saturating_add((420_306_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_keccak_256(r: u32, ) -> Weight { + (116_261_000 as Weight) + .saturating_add((360_601_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { + (583_726_000 as Weight) + .saturating_add((333_091_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_256(r: u32, ) -> Weight { + (144_609_000 as Weight) + .saturating_add((332_388_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { + (612_987_000 as Weight) + .saturating_add((150_030_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_128(r: u32, ) -> Weight { + (142_085_000 as Weight) + .saturating_add((329_426_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { + (632_517_000 as Weight) + .saturating_add((149_974_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + } +} diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index d18fed1bc8a3e..41c4e893f8ca3 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -28,7 +28,6 @@ sp-io = { version = "2.0.0", default-features = false, path = "../../primitives/ sp-std = { version = "2.0.0", default-features = false, path = "../../primitives/std" } sp-sandbox = { version = "0.8.0", default-features = false, path = "../../primitives/sandbox" } wasmi-validation = { version = "0.3.0", default-features = false } -wat = { version = "1.0", optional = true, default-features = false } [dev-dependencies] assert_matches = "1.3.0" @@ -36,6 +35,7 @@ hex-literal = "0.3.1" pallet-balances = { version = "2.0.0", path = "../balances" } pallet-timestamp = { version = "2.0.0", path = "../timestamp" } pallet-randomness-collective-flip = { version = "2.0.0", path = "../randomness-collective-flip" } +paste = "1.0" pretty_assertions = "0.6.1" wat = "1.0" @@ -58,10 +58,4 @@ std = [ ] runtime-benchmarks = [ "frame-benchmarking", - "wat", - # We are linking the wat crate which uses std and therefore brings with it the - # std panic handler. Therefore we need to disable out own panic handlers. Mind that - # we still override the std memory allocator. - "sp-io/disable_panic_handler", - "sp-io/disable_oom", ] diff --git a/frame/contracts/fixtures/benchmarks/dummy.wat b/frame/contracts/fixtures/benchmarks/dummy.wat deleted file mode 100644 index b878d26ef9185..0000000000000 --- a/frame/contracts/fixtures/benchmarks/dummy.wat +++ /dev/null @@ -1,4 +0,0 @@ -(module - (func (export "call")) - (func (export "deploy")) -) diff --git a/frame/contracts/fixtures/caller_contract.wat b/frame/contracts/fixtures/caller_contract.wat index e8ff2e379716d..408af92e18296 100644 --- a/frame/contracts/fixtures/caller_contract.wat +++ b/frame/contracts/fixtures/caller_contract.wat @@ -89,7 +89,7 @@ (call $seal_instantiate (i32.const 24) ;; Pointer to the code hash. (i32.const 32) ;; Length of the code hash. - (i64.const 187500000) ;; Just enough to pay for the instantiate + (i64.const 1) ;; Supply too little gas (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 8) ;; Pointer to input data buffer address @@ -206,7 +206,7 @@ (call $seal_call (i32.const 16) ;; Pointer to "callee" address. (i32.const 8) ;; Length of "callee" address. - (i64.const 117500000) ;; Just enough to make the call + (i64.const 1) ;; Supply too little gas (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 8) ;; Pointer to input data buffer address diff --git a/frame/contracts/fixtures/event_size.wat b/frame/contracts/fixtures/event_size.wat new file mode 100644 index 0000000000000..4bd6158d72fb9 --- /dev/null +++ b/frame/contracts/fixtures/event_size.wat @@ -0,0 +1,39 @@ +(module + (import "seal0" "seal_deposit_event" (func $seal_deposit_event (param i32 i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 16 16)) + + ;; [0, 4) size of the input buffer + (data (i32.const 0) "\04") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + (call $seal_input (i32.const 4) (i32.const 0)) + + ;; assert input size == 4 + (call $assert + (i32.eq + (i32.load (i32.const 0)) + (i32.const 4) + ) + ) + + ;; place a garbage value in storage, the size of which is specified by the call input. + (call $seal_deposit_event + (i32.const 0) ;; topics_ptr + (i32.const 0) ;; topics_len + (i32.const 0) ;; data_ptr + (i32.load (i32.const 4)) ;; data_len + ) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/src/benchmarking.rs b/frame/contracts/src/benchmarking.rs deleted file mode 100644 index 1a04db4defdb6..0000000000000 --- a/frame/contracts/src/benchmarking.rs +++ /dev/null @@ -1,271 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Benchmarks for the contracts pallet - -#![cfg(feature = "runtime-benchmarks")] - -use crate::*; -use crate::Module as Contracts; - -use frame_benchmarking::{benchmarks, account}; -use frame_system::{Module as System, RawOrigin}; -use parity_wasm::elements::FuncBody; -use sp_runtime::traits::Hash; - -macro_rules! load_module { - ($name:expr) => {{ - let code = include_bytes!(concat!("../fixtures/benchmarks/", $name, ".wat")); - compile_module::(code) - }}; -} - -fn compile_module(code: &[u8]) -> (Vec, ::Output) { - let code = sp_std::str::from_utf8(code).expect("Invalid utf8 in wat file."); - let binary = wat::parse_str(code).expect("Failed to compile wat file."); - let hash = T::Hashing::hash(&binary); - (binary, hash) -} - -fn funding() -> BalanceOf { - T::Currency::minimum_balance() * 10_000.into() -} - -fn create_funded_user(string: &'static str, n: u32) -> T::AccountId { - let user = account(string, n, 0); - T::Currency::make_free_balance_be(&user, funding::()); - user -} - -fn contract_with_call_body(body: FuncBody) -> (Vec, ::Output) { - use parity_wasm::elements::{ - Instructions, Instruction::End, - }; - let contract = parity_wasm::builder::ModuleBuilder::new() - // deploy function (idx 0) - .function() - .signature().with_params(vec![]).with_return_type(None).build() - .body().with_instructions(Instructions::new(vec![End])).build() - .build() - // call function (idx 1) - .function() - .signature().with_params(vec![]).with_return_type(None).build() - .with_body(body) - .build() - .export().field("deploy").internal().func(0).build() - .export().field("call").internal().func(1).build() - .build(); - let bytes = contract.to_bytes().unwrap(); - let hash = T::Hashing::hash(&bytes); - (bytes, hash) -} - -fn expanded_contract(target_bytes: u32) -> (Vec, ::Output) { - use parity_wasm::elements::{ - Instruction::{self, If, I32Const, Return, End}, - BlockType, Instructions, - }; - // Base size of a contract is 47 bytes and each expansion adds 6 bytes. - // We do one expansion less to account for the code section and function body - // size fields inside the binary wasm module representation which are leb128 encoded - // and therefore grow in size when the contract grows. We are not allowed to overshoot - // because of the maximum code size that is enforced by `put_code`. - let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1) as usize; - const EXPANSION: [Instruction; 4] = [ - I32Const(0), - If(BlockType::NoResult), - Return, - End, - ]; - let instructions = Instructions::new( - EXPANSION - .iter() - .cycle() - .take(EXPANSION.len() * expansions) - .cloned() - .chain(sp_std::iter::once(End)) - .collect() - ); - contract_with_call_body::(FuncBody::new(Vec::new(), instructions)) -} - -fn advance_block(num: ::BlockNumber) { - let now = System::::block_number(); - System::::set_block_number(now + num); -} - -benchmarks! { - _ { - } - - // This extrinsic is pretty much constant as it is only a simple setter. - update_schedule { - let schedule = Schedule { - version: 1, - .. Default::default() - }; - }: _(RawOrigin::Root, schedule) - - // This constructs a contract that is maximal expensive to instrument. - // It creates a maximum number of metering blocks per byte. - put_code { - let n in 0 .. Contracts::::current_schedule().max_code_size; - let caller = create_funded_user::("caller", 0); - let (binary, hash) = expanded_contract::(n); - }: _(RawOrigin::Signed(caller), binary) - - // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. - // The size of the data has no influence on the costs of this extrinsic as long as the contract - // won't call `seal_input` in its constructor to copy the data to contract memory. - // The dummy contract used here does not do this. The costs for the data copy is billed as - // part of `seal_input`. - instantiate { - let data = vec![0u8; 128]; - let endowment = Config::::subsistence_threshold_uncached(); - let caller = create_funded_user::("caller", 0); - let (binary, hash) = load_module!("dummy"); - Contracts::::put_code(RawOrigin::Signed(caller.clone()).into(), binary.to_vec()) - .unwrap(); - - }: _( - RawOrigin::Signed(caller.clone()), - endowment, - Weight::max_value(), - hash, - data - ) - verify { - assert_eq!( - funding::() - endowment, - T::Currency::free_balance(&caller), - ) - } - - // We just call a dummy contract to measure to overhead of the call extrinsic. - // As for instantiate the size of the data does not influence the costs. - call { - let data = vec![0u8; 128]; - let endowment = Config::::subsistence_threshold_uncached(); - let value = T::Currency::minimum_balance() * 100.into(); - let caller = create_funded_user::("caller", 0); - let (binary, hash) = load_module!("dummy"); - let addr = T::DetermineContractAddress::contract_address_for(&hash, &[], &caller); - Contracts::::put_code(RawOrigin::Signed(caller.clone()).into(), binary.to_vec()) - .unwrap(); - Contracts::::instantiate( - RawOrigin::Signed(caller.clone()).into(), - endowment, - Weight::max_value(), - hash, - vec![], - ).unwrap(); - }: _( - RawOrigin::Signed(caller.clone()), - T::Lookup::unlookup(addr), - value, - Weight::max_value(), - data - ) - verify { - assert_eq!( - funding::() - endowment - value, - T::Currency::free_balance(&caller), - ) - } - - // We benchmark the costs for sucessfully evicting an empty contract. - // The actual costs are depending on how many storage items the evicted contract - // does have. However, those costs are not to be payed by the sender but - // will be distributed over multiple blocks using a scheduler. Otherwise there is - // no incentive to remove large contracts when the removal is more expensive than - // the reward for removing them. - claim_surcharge { - let endowment = Config::::subsistence_threshold_uncached(); - let value = T::Currency::minimum_balance() * 100.into(); - let caller = create_funded_user::("caller", 0); - let (binary, hash) = load_module!("dummy"); - let addr = T::DetermineContractAddress::contract_address_for(&hash, &[], &caller); - Contracts::::put_code(RawOrigin::Signed(caller.clone()).into(), binary.to_vec()) - .unwrap(); - Contracts::::instantiate( - RawOrigin::Signed(caller.clone()).into(), - endowment, - Weight::max_value(), - hash, - vec![], - ).unwrap(); - - // instantiate should leave us with an alive contract - ContractInfoOf::::get(addr.clone()).unwrap().get_alive().unwrap(); - - // generate some rent - advance_block::(::SignedClaimHandicap::get() + 1.into()); - - }: _(RawOrigin::Signed(caller.clone()), addr.clone(), None) - verify { - // the claim surcharge should have evicted the contract - ContractInfoOf::::get(addr.clone()).unwrap().get_tombstone().unwrap(); - - // the caller should get the reward for being a good snitch - assert_eq!( - funding::() - endowment + ::SurchargeReward::get(), - T::Currency::free_balance(&caller), - ); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::tests::{ExtBuilder, Test}; - use frame_support::assert_ok; - - #[test] - fn update_schedule() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_update_schedule::()); - }); - } - - #[test] - fn put_code() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_put_code::()); - }); - } - - #[test] - fn instantiate() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_instantiate::()); - }); - } - - #[test] - fn call() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_call::()); - }); - } - - #[test] - fn claim_surcharge() { - ExtBuilder::default().build().execute_with(|| { - assert_ok!(test_benchmark_claim_surcharge::()); - }); - } -} diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs new file mode 100644 index 0000000000000..dc3730e95ca1f --- /dev/null +++ b/frame/contracts/src/benchmarking/code.rs @@ -0,0 +1,272 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Functions to procedurally construct contract code used for benchmarking. +//! +//! In order to be able to benchmark events that are triggered by contract execution +//! (API calls into seal, individual instructions), we need to generate contracts that +//! perform those events. Because those contracts can get very big we cannot simply define +//! them as text (.wat) as this will be too slow and consume too much memory. Therefore +//! we define this simple definition of a contract that can be passed to `create_code` that +//! compiles it down into a `WasmModule` that can be used as a contract's code. + +use crate::Trait; +use crate::Module as Contracts; + +use parity_wasm::elements::{Instruction, Instructions, FuncBody, ValueType, BlockType}; +use sp_runtime::traits::Hash; +use sp_std::{prelude::*, convert::TryFrom}; + +/// Pass to `create_code` in order to create a compiled `WasmModule`. +pub struct ModuleDefinition { + pub data_segments: Vec, + pub memory: Option, + pub imported_functions: Vec, + pub deploy_body: Option, + pub call_body: Option, +} + +impl Default for ModuleDefinition { + fn default() -> Self { + Self { + data_segments: vec![], + memory: None, + imported_functions: vec![], + deploy_body: None, + call_body: None, + } + } +} + +pub struct DataSegment { + pub offset: u32, + pub value: Vec, +} + +pub struct ImportedMemory { + pub min_pages: u32, + pub max_pages: u32, +} + +impl ImportedMemory { + pub fn max() -> Self { + let pages = max_pages::(); + Self { min_pages: pages, max_pages: pages } + } +} + +pub struct ImportedFunction { + pub name: &'static str, + pub params: Vec, + pub return_type: Option, +} + +/// A wasm module ready to be put on chain with `put_code`. +#[derive(Clone)] +pub struct WasmModule { + pub code: Vec, + pub hash: ::Output, +} + +impl From for WasmModule { + fn from(def: ModuleDefinition) -> Self { + // internal functions start at that offset. + let func_offset = u32::try_from(def.imported_functions.len()).unwrap(); + + // Every contract must export "deploy" and "call" functions + let mut contract = parity_wasm::builder::module() + // deploy function (first internal function) + .function() + .signature().with_params(vec![]).with_return_type(None).build() + .with_body(def.deploy_body.unwrap_or_else(|| + FuncBody::new(Vec::new(), Instructions::empty()) + )) + .build() + // call function (second internal function) + .function() + .signature().with_params(vec![]).with_return_type(None).build() + .with_body(def.call_body.unwrap_or_else(|| + FuncBody::new(Vec::new(), Instructions::empty()) + )) + .build() + .export().field("deploy").internal().func(func_offset).build() + .export().field("call").internal().func(func_offset + 1).build(); + + // Grant access to linear memory. + if let Some(memory) = def.memory { + contract = contract.import() + .module("env").field("memory") + .external().memory(memory.min_pages, Some(memory.max_pages)) + .build(); + } + + // Import supervisor functions. They start with idx 0. + for func in def.imported_functions { + let sig = parity_wasm::builder::signature() + .with_params(func.params) + .with_return_type(func.return_type) + .build_sig(); + let sig = contract.push_signature(sig); + contract = contract.import() + .module("seal0") + .field(func.name) + .with_external(parity_wasm::elements::External::Function(sig)) + .build(); + } + + // Initialize memory + for data in def.data_segments { + contract = contract.data() + .offset(Instruction::I32Const(data.offset as i32)) + .value(data.value) + .build() + } + + let code = contract.build().to_bytes().unwrap(); + let hash = T::Hashing::hash(&code); + Self { + code, + hash + } + } +} + +impl WasmModule { + pub fn dummy() -> Self { + ModuleDefinition::default().into() + } + + pub fn sized(target_bytes: u32) -> Self { + use parity_wasm::elements::Instruction::{If, I32Const, Return, End}; + // Base size of a contract is 47 bytes and each expansion adds 6 bytes. + // We do one expansion less to account for the code section and function body + // size fields inside the binary wasm module representation which are leb128 encoded + // and therefore grow in size when the contract grows. We are not allowed to overshoot + // because of the maximum code size that is enforced by `put_code`. + let expansions = (target_bytes.saturating_sub(47) / 6).saturating_sub(1); + const EXPANSION: [Instruction; 4] = [ + I32Const(0), + If(BlockType::NoResult), + Return, + End, + ]; + ModuleDefinition { + call_body: Some(body::repeated(expansions, &EXPANSION)), + .. Default::default() + } + .into() + } + + pub fn getter(getter_name: &'static str, repeat: u32) -> Self { + let pages = max_pages::(); + ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: getter_name, + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + // Write the output buffer size. The output size will be overwritten by the + // supervisor with the real size when calling the getter. Since this size does not + // change between calls it suffices to start with an initial value and then just + // leave as whatever value was written there. + data_segments: vec![DataSegment { + offset: 0, + value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(), + }], + call_body: Some(body::repeated(repeat, &[ + Instruction::I32Const(4), // ptr where to store output + Instruction::I32Const(0), // ptr to length + Instruction::Call(0), // call the imported function + ])), + .. Default::default() + } + .into() + } + + pub fn hasher(name: &'static str, repeat: u32, data_size: u32) -> Self { + ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: name, + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body::repeated(repeat, &[ + Instruction::I32Const(0), // input_ptr + Instruction::I32Const(data_size as i32), // input_len + Instruction::I32Const(0), // output_ptr + Instruction::Call(0), + ])), + .. Default::default() + } + .into() + } +} + +/// Mechanisms to create a function body that can be used inside a `ModuleDefinition`. +pub mod body { + use super::*; + + pub enum CountedInstruction { + // (offset, increment_by) + Counter(u32, u32), + Regular(Instruction), + } + + pub fn plain(instructions: Vec) -> FuncBody { + FuncBody::new(Vec::new(), Instructions::new(instructions)) + } + + pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody { + let instructions = Instructions::new( + instructions + .iter() + .cycle() + .take(instructions.len() * usize::try_from(repetitions).unwrap()) + .cloned() + .chain(sp_std::iter::once(Instruction::End)) + .collect() + ); + FuncBody::new(Vec::new(), instructions) + } + + pub fn counted(repetitions: u32, mut instructions: Vec) -> FuncBody { + // We need to iterate over indices because we cannot cycle over mutable references + let body = (0..instructions.len()) + .cycle() + .take(instructions.len() * usize::try_from(repetitions).unwrap()) + .map(|idx| { + match &mut instructions[idx] { + CountedInstruction::Counter(offset, increment_by) => { + let current = *offset; + *offset += *increment_by; + Instruction::I32Const(current as i32) + }, + CountedInstruction::Regular(instruction) => instruction.clone(), + } + }) + .chain(sp_std::iter::once(Instruction::End)) + .collect(); + FuncBody::new(Vec::new(), Instructions::new(body)) + } +} + +/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`. +pub fn max_pages() -> u32 { + Contracts::::current_schedule().max_memory_pages +} diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs new file mode 100644 index 0000000000000..22bcc3bc4e860 --- /dev/null +++ b/frame/contracts/src/benchmarking/mod.rs @@ -0,0 +1,1651 @@ +// This file is part of Substrate. + +// Copyright (C) 2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarks for the contracts pallet + +#![cfg(feature = "runtime-benchmarks")] + +mod code; + +use crate::*; +use crate::Module as Contracts; +use crate::exec::StorageKey; +use crate::schedule::API_BENCHMARK_BATCH_SIZE; +use self::code::{ + body, ModuleDefinition, DataSegment, ImportedMemory, ImportedFunction, WasmModule, +}; + +use frame_benchmarking::{benchmarks, account, whitelisted_caller}; +use frame_system::{Module as System, RawOrigin}; +use parity_wasm::elements::{Instruction, ValueType, BlockType}; +use sp_runtime::traits::{Hash, Bounded}; +use sp_std::{default::Default, convert::{TryInto}}; + +/// How many batches we do per API benchmark. +const API_BENCHMARK_BATCHES: u32 = 20; + +/// An instantiated and deployed contract. +struct Contract { + caller: T::AccountId, + account_id: T::AccountId, + addr: ::Source, + endowment: BalanceOf, + code_hash: ::Output, +} + +/// Describes how much balance should be transferred on instantiate from the caller. +enum Endow { + /// Endow the contract with a maximum amount of balance. This value is described by + /// `Contract::max_endowment`. + Max, + /// Endow so that the amount of balance that is transferred is big but not so big + /// to offset the rent payment. This is needed in order to test rent collection. + CollectRent, +} + +impl Endow { + /// The maximum amount of balance a caller can transfer without being brought below + /// the existential deposit. This assumes that every caller is funded with the amount + /// returned by `caller_funding`. + fn max() -> BalanceOf { + caller_funding::().saturating_sub(T::Currency::minimum_balance()) + } +} + +impl Contract { + /// Create new contract and use a default account id as instantiator. + fn new( + module: WasmModule, + data: Vec, + endowment: Endow, + ) -> Result, &'static str> { + Self::with_index(0, module, data, endowment) + } + + /// Create new contract and use an account id derived from the supplied index as instantiator. + fn with_index( + index: u32, + module: WasmModule, + data: Vec, + endowment: Endow, + ) -> Result, &'static str> { + Self::with_caller(account("instantiator", index, 0), module, data, endowment) + } + + /// Create new contract and use the supplied `caller` as instantiator. + fn with_caller( + caller: T::AccountId, + module: WasmModule, + data: Vec, + endowment: Endow, + ) -> Result, &'static str> + { + use sp_runtime::traits::{CheckedDiv, SaturatedConversion}; + let (storage_size, endowment) = match endowment { + Endow::CollectRent => { + // storage_size cannot be zero because otherwise a contract that is just above + // the subsistence threshold does not pay rent given a large enough subsistence + // threshold. But we need rent payments to occur in order to benchmark for worst cases. + let storage_size = Config::::subsistence_threshold_uncached() + .checked_div(&T::RentDepositOffset::get()) + .unwrap_or_else(Zero::zero); + + // Endowment should be large but not as large to inhibit rent payments. + let endowment = T::RentDepositOffset::get() + .saturating_mul(storage_size + T::StorageSizeOffset::get().into()) + .saturating_sub(1.into()); + + (storage_size, endowment) + }, + Endow::Max => (0.into(), Endow::max::()), + }; + T::Currency::make_free_balance_be(&caller, caller_funding::()); + let addr = T::DetermineContractAddress::contract_address_for(&module.hash, &data, &caller); + init_block_number::(); + Contracts::::put_code_raw(module.code)?; + Contracts::::instantiate( + RawOrigin::Signed(caller.clone()).into(), + endowment, + Weight::max_value(), + module.hash, + data, + )?; + + let result = Contract { + caller, + account_id: addr.clone(), + addr: T::Lookup::unlookup(addr), + endowment, + code_hash: module.hash.clone(), + }; + + let mut contract = result.alive_info()?; + contract.storage_size = storage_size.saturated_into::(); + ContractInfoOf::::insert(&result.account_id, ContractInfo::Alive(contract)); + + Ok(result) + } + + /// Store the supplied storage items into this contracts storage. + fn store(&self, items: &Vec<(StorageKey, Vec)>) -> Result<(), &'static str> { + let info = self.alive_info()?; + for item in items { + crate::storage::write_contract_storage::( + &self.account_id, + &info.trie_id, + &item.0, + Some(item.1.clone()), + ) + .map_err(|_| "Failed to write storage to restoration dest")?; + } + Ok(()) + } + + /// Get the `AliveContractInfo` of the `addr` or an error if it is no longer alive. + fn address_alive_info(addr: &T::AccountId) -> Result, &'static str> { + ContractInfoOf::::get(addr).and_then(|c| c.get_alive()) + .ok_or("Expected contract to be alive at this point.") + } + + /// Get the `AliveContractInfo` of this contract or an error if it is no longer alive. + fn alive_info(&self) -> Result, &'static str> { + Self::address_alive_info(&self.account_id) + } + + /// Return an error if this contract is no tombstone. + fn ensure_tombstone(&self) -> Result<(), &'static str> { + ContractInfoOf::::get(&self.account_id).and_then(|c| c.get_tombstone()) + .ok_or("Expected contract to be a tombstone at this point.") + .map(|_| ()) + } + + /// Get the block number when this contract will be evicted. Returns an error when + /// the rent collection won't happen because the contract has to much endowment. + fn eviction_at(&self) -> Result { + let projection = crate::rent::compute_rent_projection::(&self.account_id) + .map_err(|_| "Invalid acc for rent")?; + match projection { + RentProjection::EvictionAt(at) => Ok(at), + _ => Err("Account does not pay rent.")?, + } + } +} + +/// A `Contract` that was evicted after accumulating some storage. +/// +/// This is used to benchmark contract resurrection. +struct Tombstone { + /// The contract that was evicted. + contract: Contract, + /// The storage the contract held when it was avicted. + storage: Vec<(StorageKey, Vec)>, +} + +impl Tombstone { + /// Create and evict a new contract with the supplied storage item count and size each. + fn new(stor_num: u32, stor_size: u32) -> Result { + let contract = Contract::::new(WasmModule::dummy(), vec![], Endow::CollectRent)?; + let storage_items = create_storage::(stor_num, stor_size)?; + contract.store(&storage_items)?; + System::::set_block_number( + contract.eviction_at()? + T::SignedClaimHandicap::get() + 5.into() + ); + crate::rent::collect_rent::(&contract.account_id); + contract.ensure_tombstone()?; + + Ok(Tombstone { + contract, + storage: storage_items, + }) + } +} + +/// Generate `stor_num` storage items. Each has the size `stor_size`. +fn create_storage( + stor_num: u32, + stor_size: u32 +) -> Result)>, &'static str> { + (0..stor_num).map(|i| { + let hash = T::Hashing::hash_of(&i) + .as_ref() + .try_into() + .map_err(|_| "Hash too big for storage key")?; + Ok((hash, vec![42u8; stor_size as usize])) + }).collect::, &'static str>>() +} + +/// The funding that each account that either calls or instantiates contracts is funded with. +fn caller_funding() -> BalanceOf { + BalanceOf::::max_value() / 2.into() +} + +/// Set the block number to one. +/// +/// The default block number is zero. The benchmarking system bumps the block number +/// to one for the benchmarking closure when it is set to zero. In order to prevent this +/// undesired implicit bump (which messes with rent collection), wo do the bump ourselfs +/// in the setup closure so that both the instantiate and subsequent call are run with the +/// same block number. +fn init_block_number() { + System::::set_block_number(1.into()); +} + +benchmarks! { + _ { + } + + // This extrinsic is pretty much constant as it is only a simple setter. + update_schedule { + let schedule = Schedule { + version: 1, + .. Default::default() + }; + }: _(RawOrigin::Root, schedule) + + // This constructs a contract that is maximal expensive to instrument. + // It creates a maximum number of metering blocks per byte. + // `n`: Size of the code in kilobytes. + put_code { + let n in 0 .. Contracts::::current_schedule().max_code_size / 1024; + let caller = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, caller_funding::()); + let module = WasmModule::::sized(n * 1024); + let origin = RawOrigin::Signed(caller); + }: _(origin, module.code) + + // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. + // The size of the input data influences the runtime because it is hashed in order to determine + // the contract address. + // `n`: Size of the data passed to constructor in kilobytes. + instantiate { + let n in 0 .. code::max_pages::() * 64; + let data = vec![42u8; (n * 1024) as usize]; + let endowment = Config::::subsistence_threshold_uncached(); + let caller = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, caller_funding::()); + let WasmModule { code, hash } = WasmModule::::dummy(); + let origin = RawOrigin::Signed(caller.clone()); + let addr = T::DetermineContractAddress::contract_address_for(&hash, &data, &caller); + Contracts::::put_code_raw(code)?; + }: _(origin, endowment, Weight::max_value(), hash, data) + verify { + // endowment was removed from the caller + assert_eq!(T::Currency::free_balance(&caller), caller_funding::() - endowment); + // contract has the full endowment because no rent collection happended + assert_eq!(T::Currency::free_balance(&addr), endowment); + // instantiate should leave a alive contract + Contract::::address_alive_info(&addr)?; + } + + // We just call a dummy contract to measure to overhead of the call extrinsic. + // The size of the data has no influence on the costs of this extrinsic as long as the contract + // won't call `seal_input` in its constructor to copy the data to contract memory. + // The dummy contract used here does not do this. The costs for the data copy is billed as + // part of `seal_input`. + call { + let data = vec![42u8; 1024]; + let instance = Contract::::with_caller( + whitelisted_caller(), WasmModule::dummy(), vec![], Endow::CollectRent + )?; + let value = T::Currency::minimum_balance() * 100.into(); + let origin = RawOrigin::Signed(instance.caller.clone()); + let callee = instance.addr.clone(); + + // trigger rent collection for worst case performance of call + System::::set_block_number(instance.eviction_at()? - 5.into()); + let before = T::Currency::free_balance(&instance.account_id); + }: _(origin, callee, value, Weight::max_value(), data) + verify { + // endowment and value transfered via call should be removed from the caller + assert_eq!( + T::Currency::free_balance(&instance.caller), + caller_funding::() - instance.endowment - value, + ); + // rent should have lowered the amount of balance of the contract + assert!(T::Currency::free_balance(&instance.account_id) < before + value); + // but it should not have been evicted by the rent collection + instance.alive_info()?; + } + + // We benchmark the costs for sucessfully evicting an empty contract. + // The actual costs are depending on how many storage items the evicted contract + // does have. However, those costs are not to be payed by the sender but + // will be distributed over multiple blocks using a scheduler. Otherwise there is + // no incentive to remove large contracts when the removal is more expensive than + // the reward for removing them. + claim_surcharge { + let instance = Contract::::with_caller( + whitelisted_caller(), WasmModule::dummy(), vec![], Endow::CollectRent + )?; + let origin = RawOrigin::Signed(instance.caller.clone()); + let account_id = instance.account_id.clone(); + + // instantiate should leave us with an alive contract + instance.alive_info()?; + + // generate enough rent so that the contract is evicted + System::::set_block_number( + instance.eviction_at()? + T::SignedClaimHandicap::get() + 5.into() + ); + }: _(origin, account_id, None) + verify { + // the claim surcharge should have evicted the contract + instance.ensure_tombstone()?; + + // the caller should get the reward for being a good snitch + assert_eq!( + T::Currency::free_balance(&instance.caller), + caller_funding::() - instance.endowment + ::SurchargeReward::get(), + ); + } + + seal_caller { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_caller", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_address { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_address", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_gas_left { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_balance { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_balance", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_value_transferred { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_minimum_balance { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_tombstone_deposit { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_tombstone_deposit", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_rent_allowance { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_rent_allowance", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_block_number { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_block_number", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_now { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "seal_now", r * API_BENCHMARK_BATCH_SIZE + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_weight_to_fee { + let r in 0 .. API_BENCHMARK_BATCHES; + let pages = code::max_pages::(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_weight_to_fee", + params: vec![ValueType::I64, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![DataSegment { + offset: 0, + value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(), + }], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I64Const(500_000), + Instruction::I32Const(4), + Instruction::I32Const(0), + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_gas { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = WasmModule::::from(ModuleDefinition { + imported_functions: vec![ImportedFunction { + name: "gas", + params: vec![ValueType::I32], + return_type: None, + }], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(42), + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // We cannot call seal_input multiple times. Therefore our weight determination is not + // as precise as with other APIs. Because this function can only be called once per + // contract it cannot be used for Dos. + seal_input { + let r in 0 .. 1; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_input", + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: 0u32.to_le_bytes().to_vec(), + }, + ], + call_body: Some(body::repeated(r, &[ + Instruction::I32Const(4), // ptr where to store output + Instruction::I32Const(0), // ptr to length + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_input_per_kb { + let n in 0 .. code::max_pages::() * 64; + let pages = code::max_pages::(); + let buffer_size = pages * 64 * 1024 - 4; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_input", + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: buffer_size.to_le_bytes().to_vec(), + }, + ], + call_body: Some(body::plain(vec![ + Instruction::I32Const(4), // ptr where to store output + Instruction::I32Const(0), // ptr to length + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let data = vec![42u8; (n * 1024).min(buffer_size) as usize]; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), data) + + // The same argument as for `seal_input` is true here. + seal_return { + let r in 0 .. 1; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_return", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body::repeated(r, &[ + Instruction::I32Const(0), // flags + Instruction::I32Const(0), // data_ptr + Instruction::I32Const(0), // data_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_return_per_kb { + let n in 0 .. code::max_pages::() * 64; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_return", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // flags + Instruction::I32Const(0), // data_ptr + Instruction::I32Const((n * 1024) as i32), // data_len + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // The same argument as for `seal_input` is true here. + seal_terminate { + let r in 0 .. 1; + let beneficiary = account::("beneficiary", 0, 0); + let beneficiary_bytes = beneficiary.encode(); + let beneficiary_len = beneficiary_bytes.len(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_terminate", + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: beneficiary_bytes, + }, + ], + call_body: Some(body::repeated(r, &[ + Instruction::I32Const(0), // beneficiary_ptr + Instruction::I32Const(beneficiary_len as i32), // beneficiary_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + assert_eq!(T::Currency::total_balance(&beneficiary), 0.into()); + assert_eq!(T::Currency::total_balance(&instance.account_id), Endow::max::()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + if r > 0 { + assert_eq!(T::Currency::total_balance(&instance.account_id), 0.into()); + assert_eq!(T::Currency::total_balance(&beneficiary), Endow::max::()); + } + } + + seal_restore_to { + let r in 0 .. 1; + + // Restore just moves the trie id from origin to destination and therefore + // does not depend on the size of the destination contract. However, to not + // trigger any edge case we won't use an empty contract as destination. + let tombstone = Tombstone::::new(10, T::MaxValueSize::get())?; + + let dest = tombstone.contract.account_id.encode(); + let dest_len = dest.len(); + let code_hash = tombstone.contract.code_hash.encode(); + let code_hash_len = code_hash.len(); + let rent_allowance = BalanceOf::::max_value().encode(); + let rent_allowance_len = rent_allowance.len(); + + let dest_offset = 0; + let code_hash_offset = dest_offset + dest_len; + let rent_allowance_offset = code_hash_offset + code_hash_len; + + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_restore_to", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: dest_offset as u32, + value: dest, + }, + DataSegment { + offset: code_hash_offset as u32, + value: code_hash, + }, + DataSegment { + offset: rent_allowance_offset as u32, + value: rent_allowance, + }, + ], + call_body: Some(body::repeated(r, &[ + Instruction::I32Const(dest_offset as i32), + Instruction::I32Const(dest_len as i32), + Instruction::I32Const(code_hash_offset as i32), + Instruction::I32Const(code_hash_len as i32), + Instruction::I32Const(rent_allowance_offset as i32), + Instruction::I32Const(rent_allowance_len as i32), + Instruction::I32Const(0), // delta_ptr + Instruction::I32Const(0), // delta_count + Instruction::Call(0), + ])), + .. Default::default() + }); + + let instance = Contract::::with_caller( + account("origin", 0, 0), code, vec![], Endow::Max + )?; + instance.store(&tombstone.storage)?; + System::::set_block_number(System::::block_number() + 1.into()); + + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + if r > 0 { + tombstone.contract.alive_info()?; + } + } + + seal_restore_to_per_delta { + let d in 0 .. API_BENCHMARK_BATCHES; + let tombstone = Tombstone::::new(0, 0)?; + let delta = create_storage::(d * API_BENCHMARK_BATCH_SIZE, T::MaxValueSize::get())?; + + let dest = tombstone.contract.account_id.encode(); + let dest_len = dest.len(); + let code_hash = tombstone.contract.code_hash.encode(); + let code_hash_len = code_hash.len(); + let rent_allowance = BalanceOf::::max_value().encode(); + let rent_allowance_len = rent_allowance.len(); + let delta_keys = delta.iter().flat_map(|(key, _)| key).cloned().collect::>(); + + let dest_offset = 0; + let code_hash_offset = dest_offset + dest_len; + let rent_allowance_offset = code_hash_offset + code_hash_len; + let delta_keys_offset = rent_allowance_offset + rent_allowance_len; + + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_restore_to", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: dest_offset as u32, + value: dest, + }, + DataSegment { + offset: code_hash_offset as u32, + value: code_hash, + }, + DataSegment { + offset: rent_allowance_offset as u32, + value: rent_allowance, + }, + DataSegment { + offset: delta_keys_offset as u32, + value: delta_keys, + }, + ], + call_body: Some(body::plain(vec![ + Instruction::I32Const(dest_offset as i32), + Instruction::I32Const(dest_len as i32), + Instruction::I32Const(code_hash_offset as i32), + Instruction::I32Const(code_hash_len as i32), + Instruction::I32Const(rent_allowance_offset as i32), + Instruction::I32Const(rent_allowance_len as i32), + Instruction::I32Const(delta_keys_offset as i32), // delta_ptr + Instruction::I32Const(delta.len() as i32), // delta_count + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() + }); + + let instance = Contract::::with_caller( + account("origin", 0, 0), code, vec![], Endow::Max + )?; + instance.store(&tombstone.storage)?; + instance.store(&delta)?; + System::::set_block_number(System::::block_number() + 1.into()); + + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + tombstone.contract.alive_info()?; + } + + // We benchmark only for the maximum subject length. We assume that this is some lowish + // number (< 1 KB). Therefore we are not overcharging too much in case a smaller subject is + // used. + seal_random { + let r in 0 .. API_BENCHMARK_BATCHES; + let pages = code::max_pages::(); + let subject_len = Contracts::::current_schedule().max_subject_len; + assert!(subject_len < 1024); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_random", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: (pages * 64 * 1024 - subject_len - 4).to_le_bytes().to_vec(), + }, + ], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(4), // subject_ptr + Instruction::I32Const(subject_len as i32), // subject_len + Instruction::I32Const((subject_len + 4) as i32), // out_ptr + Instruction::I32Const(0), // out_len_ptr + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Overhead of calling the function without any topic. + // We benchmark for the worst case (largest event). + seal_deposit_event { + let r in 0 .. API_BENCHMARK_BATCHES; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_deposit_event", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(0), // topics_ptr + Instruction::I32Const(0), // topics_len + Instruction::I32Const(0), // data_ptr + Instruction::I32Const(0), // data_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Benchmark the overhead that topics generate. + // `t`: Number of topics + // `n`: Size of event payload in kb + seal_deposit_event_per_topic_and_kb { + let t in 0 .. Contracts::::current_schedule().max_event_topics; + let n in 0 .. T::MaxValueSize::get() / 1024; + let mut topics = (0..API_BENCHMARK_BATCH_SIZE) + .map(|n| (n * t..n * t + t).map(|i| T::Hashing::hash_of(&i)).collect::>().encode()) + .peekable(); + let topics_len = topics.peek().map(|i| i.len()).unwrap_or(0); + let topics = topics.flatten().collect(); + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_deposit_event", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: topics, + }, + ], + call_body: Some(body::counted(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, topics_len as u32), // topics_ptr + Regular(Instruction::I32Const(topics_len as i32)), // topics_len + Regular(Instruction::I32Const(0)), // data_ptr + Regular(Instruction::I32Const((n * 1024) as i32)), // data_len + Regular(Instruction::Call(0)), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_set_rent_allowance { + let r in 0 .. API_BENCHMARK_BATCHES; + let allowance = caller_funding::().encode(); + let allowance_len = allowance.len(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory { min_pages: 1, max_pages: 1 }), + imported_functions: vec![ImportedFunction { + name: "seal_set_rent_allowance", + params: vec![ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: allowance, + }, + ], + call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(allowance_len as i32), // value_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + // The contract is a bit more complex because I needs to use different keys in order + // to generate unique storage accesses. However, it is still dominated by the storage + // accesses. + seal_set_storage { + let r in 0 .. API_BENCHMARK_BATCHES; + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .flat_map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) + .collect::>(); + let key_len = sp_std::mem::size_of::<::Output>(); + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_set_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: keys, + }, + ], + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, key_len as u32), // key_ptr + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(0)), // value_len + Regular(Instruction::Call(0)), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_set_storage_per_kb { + let n in 0 .. T::MaxValueSize::get() / 1024; + let key = T::Hashing::hash_of(&1u32).as_ref().to_vec(); + let key_len = key.len(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_set_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key, + }, + ], + call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(0), // value_ptr + Instruction::I32Const((n * 1024) as i32), // value_len + Instruction::Call(0), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Similar to seal_set_storage. However, we store all the keys that we are about to + // delete beforehand in order to prevent any optimizations that could occur when + // deleting a non existing key. + seal_clear_storage { + let r in 0 .. API_BENCHMARK_BATCHES; + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) + .collect::>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key_len = sp_std::mem::size_of::<::Output>(); + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_clear_storage", + params: vec![ValueType::I32], + return_type: None, + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + ], + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, key_len as u32), + Regular(Instruction::Call(0)), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let trie_id = instance.alive_info()?.trie_id; + for key in keys { + crate::storage::write_contract_storage::( + &instance.account_id, + &trie_id, + key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + Some(vec![42; T::MaxValueSize::get() as usize]) + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // We make sure that all storage accesses are to unique keys. + seal_get_storage { + let r in 0 .. API_BENCHMARK_BATCHES; + let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) + .collect::>(); + let key_len = sp_std::mem::size_of::<::Output>(); + let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key_bytes_len = key_bytes.len(); + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_get_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key_bytes, + }, + ], + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, key_len as u32), // key_ptr + Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr + Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let trie_id = instance.alive_info()?.trie_id; + for key in keys { + crate::storage::write_contract_storage::( + &instance.account_id, + &trie_id, + key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + Some(vec![]) + ) + .map_err(|_| "Failed to write to storage during setup.")?; + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_get_storage_per_kb { + let n in 0 .. T::MaxValueSize::get() / 1024; + let key = T::Hashing::hash_of(&1u32).as_ref().to_vec(); + let key_len = key.len(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_get_storage", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: key.clone(), + }, + DataSegment { + offset: key_len as u32, + value: T::MaxValueSize::get().to_le_bytes().into(), + }, + ], + call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[ + // call at key_ptr + Instruction::I32Const(0), // key_ptr + Instruction::I32Const((key_len + 4) as i32), // out_ptr + Instruction::I32Const(key_len as i32), // out_len_ptr + Instruction::Call(0), + Instruction::Drop, + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let trie_id = instance.alive_info()?.trie_id; + crate::storage::write_contract_storage::( + &instance.account_id, + &trie_id, + key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 1024) as usize]) + ) + .map_err(|_| "Failed to write to storage during setup.")?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // We transfer to unique accounts. + seal_transfer { + let r in 0 .. API_BENCHMARK_BATCHES; + let accounts = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| account::("receiver", i, 0)) + .collect::>(); + let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); + let account_bytes = accounts.iter().flat_map(|x| x.encode()).collect(); + let value = Config::::subsistence_threshold_uncached(); + assert!(value > 0.into()); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_transfer", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: value_bytes, + }, + DataSegment { + offset: value_len as u32, + value: account_bytes, + }, + ], + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(value_len as u32, account_len as u32), // account_ptr + Regular(Instruction::I32Const(account_len as i32)), // account_len + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + for account in &accounts { + assert_eq!(T::Currency::total_balance(account), 0.into()); + } + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + verify { + for account in &accounts { + assert_eq!(T::Currency::total_balance(account), value); + } + } + + // We call unique accounts. + seal_call { + let r in 0 .. API_BENCHMARK_BATCHES; + let dummy_code = WasmModule::::dummy(); + let callees = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![], Endow::Max)) + .collect::, _>>()?; + let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); + let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect(); + let value: BalanceOf = 0.into(); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_call", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I64, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: value_bytes, + }, + DataSegment { + offset: value_len as u32, + value: callee_bytes, + }, + ], + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(value_len as u32, callee_len as u32), // callee_ptr + Regular(Instruction::I32Const(callee_len as i32)), // callee_len + Regular(Instruction::I64Const(0)), // gas + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::I32Const(0)), // input_data_ptr + Regular(Instruction::I32Const(0)), // input_data_len + Regular(Instruction::I32Const(u32::max_value() as i32)), // output_ptr + Regular(Instruction::I32Const(0)), // output_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + seal_call_per_transfer_input_output_kb { + let t in 0 .. 1; + let i in 0 .. code::max_pages::() * 64; + let o in 0 .. (code::max_pages::() - 1) * 64; + let callee_code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_return", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: None, + }], + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // flags + Instruction::I32Const(0), // data_ptr + Instruction::I32Const((o * 1024) as i32), // data_len + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() + }); + let callees = (0..API_BENCHMARK_BATCH_SIZE) + .map(|i| Contract::with_index(i + 1, callee_code.clone(), vec![], Endow::Max)) + .collect::, _>>()?; + let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); + let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect::>(); + let callees_len = callee_bytes.len(); + let value: BalanceOf = t.into(); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_call", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I64, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: value_bytes, + }, + DataSegment { + offset: value_len as u32, + value: callee_bytes, + }, + DataSegment { + offset: (value_len + callees_len) as u32, + value: (o * 1024).to_le_bytes().into(), + }, + ], + call_body: Some(body::counted(API_BENCHMARK_BATCH_SIZE, vec![ + Counter(value_len as u32, callee_len as u32), // callee_ptr + Regular(Instruction::I32Const(callee_len as i32)), // callee_len + Regular(Instruction::I64Const(0)), // gas + Regular(Instruction::I32Const(0)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::I32Const(0)), // input_data_ptr + Regular(Instruction::I32Const((i * 1024) as i32)), // input_data_len + Regular(Instruction::I32Const((value_len + callees_len + 4) as i32)), // output_ptr + Regular(Instruction::I32Const((value_len + callees_len) as i32)), // output_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // We assume that every instantiate sends at least the subsistence amount. + seal_instantiate { + let r in 0 .. API_BENCHMARK_BATCHES; + let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|i| { + let code = WasmModule::::from(ModuleDefinition { + call_body: Some(body::plain(vec![ + Instruction::I32Const(i as i32), + Instruction::Drop, + Instruction::End, + ])), + .. Default::default() + }); + Contracts::::put_code_raw(code.code)?; + Ok(code.hash) + }) + .collect::, &'static str>>()?; + let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0); + let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::>(); + let hashes_len = hashes_bytes.len(); + let value = Config::::subsistence_threshold_uncached(); + assert!(value > 0.into()); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + let addr_len = sp_std::mem::size_of::(); + + // offsets where to place static data in contract memory + let value_offset = 0; + let hashes_offset = value_offset + value_len; + let addr_len_offset = hashes_offset + hashes_len; + let addr_offset = addr_len_offset + addr_len; + + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_instantiate", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I64, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32 + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: value_offset as u32, + value: value_bytes, + }, + DataSegment { + offset: hashes_offset as u32, + value: hashes_bytes, + }, + DataSegment { + offset: addr_len_offset as u32, + value: addr_len.to_le_bytes().into(), + }, + ], + call_body: Some(body::counted(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr + Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len + Regular(Instruction::I64Const(0)), // gas + Regular(Instruction::I32Const(value_offset as i32)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::I32Const(0)), // input_data_ptr + Regular(Instruction::I32Const(0)), // input_data_len + Regular(Instruction::I32Const(addr_offset as i32)), // address_ptr + Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr + Regular(Instruction::I32Const(u32::max_value() as i32)), // output_ptr + Regular(Instruction::I32Const(0)), // output_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + let callee = instance.addr.clone(); + let addresses = hashes + .iter() + .map(|hash| T::DetermineContractAddress::contract_address_for( + hash, &[], &instance.account_id + )) + .collect::>(); + + for addr in &addresses { + if let Some(_) = ContractInfoOf::::get(&addr) { + return Err("Expected that contract does not exist at this point."); + } + } + }: call(origin, callee, 0.into(), Weight::max_value(), vec![]) + verify { + for addr in &addresses { + instance.alive_info()?; + } + } + + seal_instantiate_per_input_output_kb { + let i in 0 .. (code::max_pages::() - 1) * 64; + let o in 0 .. (code::max_pages::() - 1) * 64; + let callee_code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_return", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I32, + ], + return_type: None, + }], + deploy_body: Some(body::plain(vec![ + Instruction::I32Const(0), // flags + Instruction::I32Const(0), // data_ptr + Instruction::I32Const((o * 1024) as i32), // data_len + Instruction::Call(0), + Instruction::End, + ])), + .. Default::default() + }); + let hash = callee_code.hash.clone(); + let hash_bytes = callee_code.hash.encode(); + let hash_len = hash_bytes.len(); + Contracts::::put_code_raw(callee_code.code)?; + let inputs = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::>(); + let input_len = inputs.get(0).map(|x| x.len()).unwrap_or(0); + let input_bytes = inputs.iter().cloned().flatten().collect::>(); + let inputs_len = input_bytes.len(); + let value = Config::::subsistence_threshold_uncached(); + assert!(value > 0.into()); + let value_bytes = value.encode(); + let value_len = value_bytes.len(); + let addr_len = sp_std::mem::size_of::(); + + // offsets where to place static data in contract memory + let input_offset = 0; + let value_offset = inputs_len; + let hash_offset = value_offset + value_len; + let addr_len_offset = hash_offset + hash_len; + let output_len_offset = addr_len_offset + 4; + let output_offset = output_len_offset + 4; + + use body::CountedInstruction::{Counter, Regular}; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + name: "seal_instantiate", + params: vec![ + ValueType::I32, + ValueType::I32, + ValueType::I64, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32, + ValueType::I32 + ], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: input_offset as u32, + value: input_bytes, + }, + DataSegment { + offset: value_offset as u32, + value: value_bytes, + }, + DataSegment { + offset: hash_offset as u32, + value: hash_bytes, + }, + DataSegment { + offset: addr_len_offset as u32, + value: (addr_len as u32).to_le_bytes().into(), + }, + DataSegment { + offset: output_len_offset as u32, + value: (o * 1024).to_le_bytes().into(), + }, + ], + call_body: Some(body::counted(API_BENCHMARK_BATCH_SIZE, vec![ + Regular(Instruction::I32Const(hash_offset as i32)), // code_hash_ptr + Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len + Regular(Instruction::I64Const(0)), // gas + Regular(Instruction::I32Const(value_offset as i32)), // value_ptr + Regular(Instruction::I32Const(value_len as i32)), // value_len + Counter(input_offset as u32, input_len as u32), // input_data_ptr + Regular(Instruction::I32Const((i * 1024).max(input_len as u32) as i32)), // input_data_len + Regular(Instruction::I32Const((addr_len_offset + addr_len) as i32)), // address_ptr + Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr + Regular(Instruction::I32Const(output_offset as i32)), // output_ptr + Regular(Instruction::I32Const(output_len_offset as i32)), // output_len_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::I32Eqz), + Regular(Instruction::If(BlockType::NoResult)), + Regular(Instruction::Nop), + Regular(Instruction::Else), + Regular(Instruction::Unreachable), + Regular(Instruction::End), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + seal_hash_sha2_256 { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::hasher( + "seal_hash_sha2_256", r * API_BENCHMARK_BATCH_SIZE, 0, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // `n`: Input to hash in kilobytes + seal_hash_sha2_256_per_kb { + let n in 0 .. code::max_pages::() * 64; + let instance = Contract::::new(WasmModule::hasher( + "seal_hash_sha2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + seal_hash_keccak_256 { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::hasher( + "seal_hash_keccak_256", r * API_BENCHMARK_BATCH_SIZE, 0, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // `n`: Input to hash in kilobytes + seal_hash_keccak_256_per_kb { + let n in 0 .. code::max_pages::() * 64; + let instance = Contract::::new(WasmModule::hasher( + "seal_hash_keccak_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + seal_hash_blake2_256 { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::hasher( + "seal_hash_blake2_256", r * API_BENCHMARK_BATCH_SIZE, 0, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // `n`: Input to hash in kilobytes + seal_hash_blake2_256_per_kb { + let n in 0 .. code::max_pages::() * 64; + let instance = Contract::::new(WasmModule::hasher( + "seal_hash_blake2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // Only the overhead of calling the function itself with minimal arguments. + seal_hash_blake2_128 { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::hasher( + "seal_hash_blake2_128", r * API_BENCHMARK_BATCH_SIZE, 0, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) + + // `n`: Input to hash in kilobytes + seal_hash_blake2_128_per_kb { + let n in 0 .. code::max_pages::() * 64; + let instance = Contract::::new(WasmModule::hasher( + "seal_hash_blake2_128", API_BENCHMARK_BATCH_SIZE, n * 1024, + ), vec![], Endow::Max)?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0.into(), Weight::max_value(), vec![]) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::{ExtBuilder, Test}; + use frame_support::assert_ok; + use paste::paste; + + macro_rules! create_test { + ($name:ident) => { + #[test] + fn $name() { + ExtBuilder::default().build().execute_with(|| { + assert_ok!(paste!{ + []::() + }); + }); + } + } + } + + create_test!(update_schedule); + create_test!(put_code); + create_test!(instantiate); + create_test!(call); + create_test!(claim_surcharge); + create_test!(seal_caller); + create_test!(seal_address); + create_test!(seal_gas_left); + create_test!(seal_balance); + create_test!(seal_value_transferred); + create_test!(seal_minimum_balance); + create_test!(seal_tombstone_deposit); + create_test!(seal_rent_allowance); + create_test!(seal_block_number); + create_test!(seal_now); + create_test!(seal_weight_to_fee); + create_test!(seal_gas); + create_test!(seal_input); + create_test!(seal_input_per_kb); + create_test!(seal_return); + create_test!(seal_return_per_kb); + create_test!(seal_terminate); + create_test!(seal_restore_to); + create_test!(seal_restore_to_per_delta); + create_test!(seal_random); + create_test!(seal_deposit_event); + create_test!(seal_deposit_event_per_topic_and_kb); + create_test!(seal_set_rent_allowance); + create_test!(seal_set_storage); + create_test!(seal_set_storage_per_kb); + create_test!(seal_get_storage); + create_test!(seal_get_storage_per_kb); + create_test!(seal_transfer); + create_test!(seal_call); + create_test!(seal_call_per_transfer_input_output_kb); + create_test!(seal_clear_storage); + create_test!(seal_hash_sha2_256); + create_test!(seal_hash_sha2_256_per_kb); + create_test!(seal_hash_keccak_256); + create_test!(seal_hash_keccak_256_per_kb); + create_test!(seal_hash_blake2_256); + create_test!(seal_hash_blake2_256_per_kb); + create_test!(seal_hash_blake2_128); + create_test!(seal_hash_blake2_128_per_kb); +} diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index ce4e17cd1b9fa..bc99431c85e65 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -17,7 +17,7 @@ use crate::{ CodeHash, Config, ContractAddressFor, Event, RawEvent, Trait, TrieId, BalanceOf, ContractInfo, TrieIdGenerator, - gas::{Gas, GasMeter, Token}, rent, storage, Error, ContractInfoOf + gas::GasMeter, rent, storage, Error, ContractInfoOf }; use bitflags::bitflags; use sp_std::prelude::*; @@ -140,7 +140,6 @@ pub trait Ext { &mut self, to: &AccountIdOf, value: BalanceOf, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError>; /// Transfer all funds to `beneficiary` and delete the contract. @@ -153,7 +152,6 @@ pub trait Ext { fn terminate( &mut self, beneficiary: &AccountIdOf, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError>; /// Call (possibly transferring some amount of funds) into the specified account. @@ -260,26 +258,6 @@ pub trait Vm { ) -> ExecResult; } -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -pub enum ExecFeeToken { - /// Base fee charged for a call. - Call, - /// Base fee charged for a instantiate. - Instantiate, -} - -impl Token for ExecFeeToken { - type Metadata = Config; - #[inline] - fn calculate_amount(&self, metadata: &Config) -> Gas { - match *self { - ExecFeeToken::Call => metadata.schedule.call_base_cost, - ExecFeeToken::Instantiate => metadata.schedule.instantiate_base_cost, - } - } -} - pub struct ExecutionContext<'a, T: Trait + 'a, V, L> { pub caller: Option<&'a ExecutionContext<'a, T, V, L>>, pub self_account: T::AccountId, @@ -344,13 +322,6 @@ where Err(Error::::MaxCallDepthReached)? } - if gas_meter - .charge(self.config, ExecFeeToken::Call) - .is_out_of_gas() - { - Err(Error::::OutOfGas)? - } - // Assumption: `collect_rent` doesn't collide with overlay because // `collect_rent` will be done on first call and destination contract and balance // cannot be changed before the first call @@ -368,7 +339,6 @@ where self.with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| { if value > BalanceOf::::zero() { transfer( - gas_meter, TransferCause::Call, transactor_kind, &caller, @@ -401,13 +371,6 @@ where Err(Error::::MaxCallDepthReached)? } - if gas_meter - .charge(self.config, ExecFeeToken::Instantiate) - .is_out_of_gas() - { - Err(Error::::OutOfGas)? - } - let transactor_kind = self.transactor_kind(); let caller = self.self_account.clone(); let dest = T::DetermineContractAddress::contract_address_for( @@ -434,7 +397,6 @@ where // Send funds unconditionally here. If the `endowment` is below existential_deposit // then error will be returned here. transfer( - gas_meter, TransferCause::Instantiate, transactor_kind, &caller, @@ -520,31 +482,6 @@ where } } -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -pub enum TransferFeeKind { - ContractInstantiate, - Transfer, -} - -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Copy, Clone)] -pub struct TransferFeeToken { - kind: TransferFeeKind, -} - -impl Token for TransferFeeToken { - type Metadata = Config; - - #[inline] - fn calculate_amount(&self, metadata: &Config) -> Gas { - match self.kind { - TransferFeeKind::ContractInstantiate => metadata.schedule.instantiate_cost, - TransferFeeKind::Transfer => metadata.schedule.transfer_cost, - } - } -} - /// Describes possible transfer causes. enum TransferCause { Call, @@ -554,22 +491,11 @@ enum TransferCause { /// Transfer some funds from `transactor` to `dest`. /// -/// All balance changes are performed in the `overlay`. -/// -/// This function also handles charging the fee. The fee depends -/// on whether the transfer happening because of contract instantiation -/// (transferring endowment) or because of a transfer via `call`. This -/// is specified using the `cause` parameter. -/// -/// NOTE: that the fee is denominated in `BalanceOf` units, but -/// charged in `Gas` from the provided `gas_meter`. This means -/// that the actual amount charged might differ. -/// -/// NOTE: that we allow for draining all funds of the contract so it -/// can go below existential deposit, essentially giving a contract -/// the chance to give up it's life. +/// We only allow allow for draining all funds of the sender if `cause` is +/// is specified as `Terminate`. Otherwise, any transfer that would bring the sender below the +/// subsistence threshold (for contracts) or the existential deposit (for plain accounts) +/// results in an error. fn transfer<'a, T: Trait, V: Vm, L: Loader>( - gas_meter: &mut GasMeter, cause: TransferCause, origin: TransactorKind, transactor: &T::AccountId, @@ -578,27 +504,8 @@ fn transfer<'a, T: Trait, V: Vm, L: Loader>( ctx: &mut ExecutionContext<'a, T, V, L>, ) -> Result<(), DispatchError> { use self::TransferCause::*; - use self::TransferFeeKind::*; use self::TransactorKind::*; - let token = { - let kind: TransferFeeKind = match cause { - // If this function is called from `Instantiate` routine, then we always - // charge contract account creation fee. - Instantiate => ContractInstantiate, - - // Otherwise the fee is to transfer to an account. - Call | Terminate => TransferFeeKind::Transfer, - }; - TransferFeeToken { - kind, - } - }; - - if gas_meter.charge(ctx.config, token).is_out_of_gas() { - Err(Error::::OutOfGas)? - } - // Only seal_terminate is allowed to bring the sender below the subsistence // threshold or even existential deposit. let existence_requirement = match (cause, origin) { @@ -690,14 +597,12 @@ where &mut self, to: &T::AccountId, value: BalanceOf, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { transfer( - gas_meter, TransferCause::Call, TransactorKind::Contract, &self.ctx.self_account.clone(), - &to, + to, value, self.ctx, ) @@ -706,7 +611,6 @@ where fn terminate( &mut self, beneficiary: &AccountIdOf, - gas_meter: &mut GasMeter, ) -> Result<(), DispatchError> { let self_id = self.ctx.self_account.clone(); let value = T::Currency::free_balance(&self_id); @@ -718,7 +622,6 @@ where } } transfer( - gas_meter, TransferCause::Terminate, TransactorKind::Contract, &self_id, @@ -865,8 +768,8 @@ fn deposit_event( #[cfg(test)] mod tests { use super::{ - BalanceOf, Event, ExecFeeToken, ExecResult, ExecutionContext, Ext, Loader, - RawEvent, TransferFeeKind, TransferFeeToken, Vm, ReturnFlags, ExecError, ErrorOrigin + BalanceOf, Event, ExecResult, ExecutionContext, Ext, Loader, + RawEvent, Vm, ReturnFlags, ExecError, ErrorOrigin }; use crate::{ gas::GasMeter, tests::{ExtBuilder, Test, MetaEvent}, @@ -1012,58 +915,6 @@ mod tests { assert_eq!(&*test_data.borrow(), &vec![0, 1]); } - #[test] - fn base_fees() { - let origin = ALICE; - let dest = BOB; - - // This test verifies that base fee for call is taken. - ExtBuilder::default().build().execute_with(|| { - let vm = MockVm::new(); - let loader = MockLoader::empty(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - set_balance(&origin, 100); - set_balance(&dest, 0); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = super::transfer( - &mut gas_meter, - super::TransferCause::Call, - super::TransactorKind::PlainAccount, - &origin, - &dest, - 0, - &mut ctx, - ); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!(toks, TransferFeeToken { kind: TransferFeeKind::Transfer },); - }); - - // This test verifies that base fee for instantiation is taken. - ExtBuilder::default().build().execute_with(|| { - let mut loader = MockLoader::empty(); - let code = loader.insert(|_| exec_success()); - - let vm = MockVm::new(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - - set_balance(&origin, 100); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = ctx.instantiate(cfg.subsistence_threshold(), &mut gas_meter, &code, vec![]); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!(toks, ExecFeeToken::Instantiate,); - }); - } - #[test] fn transfer_works() { // This test verifies that a contract is able to transfer @@ -1080,10 +931,7 @@ mod tests { set_balance(&origin, 100); set_balance(&dest, 0); - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - super::transfer( - &mut gas_meter, super::TransferCause::Call, super::TransactorKind::PlainAccount, &origin, @@ -1130,105 +978,6 @@ mod tests { }); } - #[test] - fn transfer_fees() { - let origin = ALICE; - let dest = BOB; - - // This test sends 50 units of currency to a non-existent account. - // This should lead to creation of a new account thus - // a fee should be charged. - ExtBuilder::default().existential_deposit(15).build().execute_with(|| { - let vm = MockVm::new(); - let loader = MockLoader::empty(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - set_balance(&origin, 100); - set_balance(&dest, 0); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = super::transfer( - &mut gas_meter, - super::TransferCause::Call, - super::TransactorKind::PlainAccount, - &origin, - &dest, - 50, - &mut ctx, - ); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!( - toks, - TransferFeeToken { - kind: TransferFeeKind::Transfer, - }, - ); - }); - - // This one is similar to the previous one but transfer to an existing account. - // In this test we expect that a regular transfer fee is charged. - ExtBuilder::default().existential_deposit(15).build().execute_with(|| { - let vm = MockVm::new(); - let loader = MockLoader::empty(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - set_balance(&origin, 100); - set_balance(&dest, 15); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = super::transfer( - &mut gas_meter, - super::TransferCause::Call, - super::TransactorKind::PlainAccount, - &origin, - &dest, - 50, - &mut ctx, - ); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!( - toks, - TransferFeeToken { - kind: TransferFeeKind::Transfer, - }, - ); - }); - - // This test sends 50 units of currency as an endowment to a newly - // instantiated contract. - ExtBuilder::default().existential_deposit(15).build().execute_with(|| { - let mut loader = MockLoader::empty(); - let code = loader.insert(|_| exec_success()); - - let vm = MockVm::new(); - let cfg = Config::preload(); - let mut ctx = ExecutionContext::top_level(origin, &cfg, &vm, &loader); - - set_balance(&origin, 100); - set_balance(&dest, 15); - - let mut gas_meter = GasMeter::::new(GAS_LIMIT); - - let result = ctx.instantiate(50, &mut gas_meter, &code, vec![]); - assert_matches!(result, Ok(_)); - - let mut toks = gas_meter.tokens().iter(); - match_tokens!( - toks, - ExecFeeToken::Instantiate, - TransferFeeToken { - kind: TransferFeeKind::ContractInstantiate, - }, - ); - }); - } - #[test] fn balance_too_low() { // This test verifies that a contract can't send value if it's @@ -1245,7 +994,6 @@ mod tests { set_balance(&origin, 0); let result = super::transfer( - &mut GasMeter::::new(GAS_LIMIT), super::TransferCause::Call, super::TransactorKind::PlainAccount, &origin, @@ -1696,8 +1444,8 @@ mod tests { let mut loader = MockLoader::empty(); - let terminate_ch = loader.insert(|mut ctx| { - ctx.ext.terminate(&ALICE, &mut ctx.gas_meter).unwrap(); + let terminate_ch = loader.insert(|ctx| { + ctx.ext.terminate(&ALICE).unwrap(); exec_success() }); diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 4755573783af7..cd5cbe5d32a40 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -78,6 +78,7 @@ //! * [Balances](../pallet_balances/index.html) #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(feature = "runtime-benchmarks", recursion_limit="256")] #[macro_use] mod gas; @@ -86,6 +87,8 @@ mod exec; mod wasm; mod rent; mod benchmarking; +mod schedule; +mod weight_info; #[cfg(test)] mod tests; @@ -96,9 +99,9 @@ use crate::wasm::{WasmLoader, WasmVm}; pub use crate::gas::{Gas, GasMeter}; pub use crate::exec::{ExecResult, ExecReturnValue}; pub use crate::wasm::ReturnCode as RuntimeReturnCode; +pub use crate::weight_info::WeightInfo; +pub use crate::schedule::{Schedule, HostFnWeights, InstructionWeights}; -#[cfg(feature = "std")] -use serde::{Serialize, Deserialize}; use sp_core::crypto::UncheckedFrom; use sp_std::{prelude::*, marker::PhantomData, fmt::Debug}; use codec::{Codec, Encode, Decode}; @@ -367,12 +370,16 @@ pub trait Trait: frame_system::Trait { /// The maximum nesting level of a call/instantiate stack. type MaxDepth: Get; - /// The maximum size of a storage value in bytes. + /// The maximum size of a storage value and event payload in bytes. type MaxValueSize: Get; /// Used to answer contracts's queries regarding the current weight price. This is **not** /// used to calculate the actual fee and is only for informational purposes. type WeightPrice: Convert>; + + /// Describes the weights of the dispatchables of this module and is also used to + /// construct a default cost schedule. + type WeightInfo: WeightInfo; } /// Simple contract address determiner. @@ -445,6 +452,8 @@ decl_error! { DecodingFailed, /// Contract trapped during execution. ContractTrapped, + /// The size defined in `T::MaxValueSize` was exceeded. + ValueTooLarge, } } @@ -498,8 +507,8 @@ decl_module! { /// Updates the schedule for metering contracts. /// /// The schedule must have a greater version than the stored schedule. - #[weight = 0] - pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult { + #[weight = T::WeightInfo::update_schedule()] + pub fn update_schedule(origin, schedule: Schedule) -> DispatchResult { ensure_root(origin)?; if >::current_schedule().version >= schedule.version { Err(Error::::InvalidScheduleVersion)? @@ -513,7 +522,7 @@ decl_module! { /// Stores the given binary Wasm code into the chain's storage and returns its `codehash`. /// You can instantiate contracts only with stored code. - #[weight = Module::::calc_code_put_costs(&code)] + #[weight = T::WeightInfo::put_code(code.len() as u32 / 1024)] pub fn put_code( origin, code: Vec @@ -535,7 +544,7 @@ decl_module! { /// * If the account is a regular account, any value will be transferred. /// * If no account exists and the call value is not less than `existential_deposit`, /// a regular account will be created and any value will be transferred. - #[weight = *gas_limit] + #[weight = T::WeightInfo::call().saturating_add(*gas_limit)] pub fn call( origin, dest: ::Source, @@ -563,7 +572,7 @@ decl_module! { /// after the execution is saved as the `code` of the account. That code will be invoked /// upon any call received by this account. /// - The contract is initialized. - #[weight = *gas_limit] + #[weight = T::WeightInfo::instantiate(data.len() as u32 / 1024).saturating_add(*gas_limit)] pub fn instantiate( origin, #[compact] endowment: BalanceOf, @@ -586,7 +595,7 @@ decl_module! { /// /// If contract is not evicted as a result of this call, no actions are taken and /// the sender is not eligible for the reward. - #[weight = 0] + #[weight = T::WeightInfo::claim_surcharge()] fn claim_surcharge(origin, dest: T::AccountId, aux_sender: Option) { let origin = origin.into(); let (signed, rewarded) = match (origin, aux_sender) { @@ -659,17 +668,21 @@ impl Module { ) -> sp_std::result::Result, ContractAccessError> { rent::compute_rent_projection::(&address) } -} -impl Module { - fn calc_code_put_costs(code: &Vec) -> Gas { - >::current_schedule().put_code_per_byte_cost.saturating_mul(code.len() as Gas) + /// Put code for benchmarks which does not check or instrument the code. + #[cfg(feature = "runtime-benchmarks")] + pub fn put_code_raw(code: Vec) -> DispatchResult { + let schedule = >::current_schedule(); + let result = wasm::save_code_raw::(code, &schedule); + result.map(|_| ()).map_err(Into::into) } +} +impl Module { fn execute_wasm( origin: T::AccountId, gas_meter: &mut GasMeter, - func: impl FnOnce(&mut ExecutionContext, &mut GasMeter) -> ExecResult, + func: impl FnOnce(&mut ExecutionContext, WasmLoader>, &mut GasMeter) -> ExecResult, ) -> ExecResult { let cfg = Config::preload(); let vm = WasmVm::new(&cfg.schedule); @@ -691,7 +704,7 @@ decl_event! { /// Contract has been evicted and is now in tombstone state. /// \[contract, tombstone\] - /// + /// /// # Params /// /// - `contract`: `AccountId`: The account ID of the evicted contract. @@ -700,7 +713,7 @@ decl_event! { /// Restoration for a contract has been successful. /// \[donor, dest, code_hash, rent_allowance\] - /// + /// /// # Params /// /// - `donor`: `AccountId`: Account ID of the restoring contract @@ -725,7 +738,7 @@ decl_event! { decl_storage! { trait Store for Module as Contracts { /// Current cost schedule for contracts. - CurrentSchedule get(fn current_schedule) config(): Schedule = Schedule::default(); + CurrentSchedule get(fn current_schedule) config(): Schedule = Default::default(); /// A mapping from an original code hash to the original code, untouched by instrumentation. pub PristineCode: map hasher(identity) CodeHash => Option>; /// A mapping between an original code hash and instrumented wasm code, ready for execution. @@ -744,7 +757,7 @@ decl_storage! { /// We assume that these values can't be changed in the /// course of transaction execution. pub struct Config { - pub schedule: Schedule, + pub schedule: Schedule, pub existential_deposit: BalanceOf, pub tombstone_deposit: BalanceOf, pub max_depth: u32, @@ -781,113 +794,3 @@ impl Config { T::Currency::minimum_balance().saturating_add(T::TombstoneDeposit::get()) } } - -/// Definition of the cost schedule and other parameterizations for wasm vm. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)] -pub struct Schedule { - /// Version of the schedule. - pub version: u32, - - /// Cost of putting a byte of code into storage. - pub put_code_per_byte_cost: 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 to deposit an event; the per-byte portion. - pub event_data_per_byte_cost: Gas, - - /// Gas cost to deposit an event; the cost per topic. - pub event_per_topic_cost: Gas, - - /// Gas cost to deposit an event; the base. - pub event_base_cost: Gas, - - /// Base gas cost to call into a contract. - pub call_base_cost: Gas, - - /// Base gas cost to instantiate a contract. - pub instantiate_base_cost: Gas, - - /// Base gas cost to dispatch a runtime call. - pub dispatch_base_cost: Gas, - - /// Gas cost per one byte read from the sandbox memory. - pub sandbox_data_read_cost: Gas, - - /// Gas cost per one byte written to the sandbox memory. - pub sandbox_data_write_cost: Gas, - - /// Cost for a simple balance transfer. - pub transfer_cost: Gas, - - /// Cost for instantiating a new contract. - pub instantiate_cost: Gas, - - /// The maximum number of topics supported by an event. - pub max_event_topics: u32, - - /// Maximum allowed stack height. - /// - /// See https://wiki.parity.io/WebAssembly-StackHeight to find out - /// how the stack frame cost is calculated. - pub max_stack_height: u32, - - /// Maximum number of memory pages allowed for a contract. - pub max_memory_pages: u32, - - /// Maximum allowed size of a declared table. - pub max_table_size: u32, - - /// Whether the `seal_println` function is allowed to be used contracts. - /// MUST only be enabled for `dev` chains, NOT for production chains - pub enable_println: bool, - - /// The maximum length of a subject used for PRNG generation. - pub max_subject_len: u32, - - /// The maximum length of a contract code in bytes. This limit applies to the uninstrumented - // and pristine form of the code as supplied to `put_code`. - pub max_code_size: u32, -} - -// 500 (2 instructions per nano second on 2GHZ) * 1000x slowdown through wasmi -// This is a wild guess and should be viewed as a rough estimation. -// Proper benchmarks are needed before this value and its derivatives can be used in production. -const WASM_INSTRUCTION_COST: Gas = 500_000; - -impl Default for Schedule { - fn default() -> Schedule { - Schedule { - version: 0, - put_code_per_byte_cost: WASM_INSTRUCTION_COST, - grow_mem_cost: WASM_INSTRUCTION_COST, - regular_op_cost: WASM_INSTRUCTION_COST, - return_data_per_byte_cost: WASM_INSTRUCTION_COST, - event_data_per_byte_cost: WASM_INSTRUCTION_COST, - event_per_topic_cost: WASM_INSTRUCTION_COST, - event_base_cost: WASM_INSTRUCTION_COST, - call_base_cost: 135 * WASM_INSTRUCTION_COST, - dispatch_base_cost: 135 * WASM_INSTRUCTION_COST, - instantiate_base_cost: 175 * WASM_INSTRUCTION_COST, - sandbox_data_read_cost: WASM_INSTRUCTION_COST, - sandbox_data_write_cost: WASM_INSTRUCTION_COST, - transfer_cost: 100 * WASM_INSTRUCTION_COST, - instantiate_cost: 200 * WASM_INSTRUCTION_COST, - max_event_topics: 4, - max_stack_height: 64 * 1024, - max_memory_pages: 16, - max_table_size: 16 * 1024, - enable_println: false, - max_subject_len: 32, - max_code_size: 512 * 1024, - } - } -} diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs new file mode 100644 index 0000000000000..fb38b1b895d18 --- /dev/null +++ b/frame/contracts/src/schedule.rs @@ -0,0 +1,367 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! This module contains the cost schedule and supporting code that constructs a +//! sane default schedule from a `WeightInfo` implementation. + +use crate::{Trait, WeightInfo}; + +#[cfg(feature = "std")] +use serde::{Serialize, Deserialize}; +use frame_support::weights::Weight; +use sp_std::{marker::PhantomData, fmt}; +use codec::{Encode, Decode}; + +/// How many API calls are executed in a single batch. The reason for increasing the amount +/// of API calls in batches (per benchmark component increase) is so that the linear regression +/// has an easier time determining the contribution of that component. +pub const API_BENCHMARK_BATCH_SIZE: u32 = 100; + +/// Definition of the cost schedule and other parameterizations for wasm vm. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Clone, Encode, Decode, PartialEq, Eq)] +pub struct Schedule { + /// Version of the schedule. + pub version: u32, + + /// The weights for individual wasm instructions. + pub instruction_weights: InstructionWeights, + + /// The weights for each imported function a contract is allowed to call. + pub host_fn_weights: HostFnWeights, + + /// Whether the `seal_println` function is allowed to be used contracts. + /// MUST only be enabled for `dev` chains, NOT for production chains + pub enable_println: bool, + + /// The maximum number of topics supported by an event. + pub max_event_topics: u32, + + /// Maximum allowed stack height. + /// + /// See https://wiki.parity.io/WebAssembly-StackHeight to find out + /// how the stack frame cost is calculated. + pub max_stack_height: u32, + + /// Maximum number of memory pages allowed for a contract. + pub max_memory_pages: u32, + + /// Maximum allowed size of a declared table. + pub max_table_size: u32, + + /// The maximum length of a subject used for PRNG generation. + pub max_subject_len: u32, + + /// The maximum length of a contract code in bytes. This limit applies to the uninstrumented + /// and pristine form of the code as supplied to `put_code`. + pub max_code_size: u32, + + /// The type parameter is used in the default implementation. + pub _phantom: PhantomData, +} + +/// Describes the weight for all categories of supported wasm instructions. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Clone, Encode, Decode, PartialEq, Eq)] +pub struct InstructionWeights { + /// Weight of a growing memory by single page. + pub grow_mem: Weight, + + /// Weight of a regular operation. + pub regular: Weight, +} + +/// Describes the weight for each imported function that a contract is allowed to call. +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Clone, Encode, Decode, PartialEq, Eq)] +pub struct HostFnWeights { + /// Weight of calling `seal_caller`. + pub caller: Weight, + + /// Weight of calling `seal_address`. + pub address: Weight, + + /// Weight of calling `seal_gas_left`. + pub gas_left: Weight, + + /// Weight of calling `seal_balance`. + pub balance: Weight, + + /// Weight of calling `seal_value_transferred`. + pub value_transferred: Weight, + + /// Weight of calling `seal_minimum_balance`. + pub minimum_balance: Weight, + + /// Weight of calling `seal_tombstone_deposit`. + pub tombstone_deposit: Weight, + + /// Weight of calling `seal_rent_allowance`. + pub rent_allowance: Weight, + + /// Weight of calling `seal_block_number`. + pub block_number: Weight, + + /// Weight of calling `seal_now`. + pub now: Weight, + + /// Weight of calling `seal_weight_to_fee`. + pub weight_to_fee: Weight, + + /// Weight of calling `gas`. + pub gas: Weight, + + /// Weight of calling `seal_input`. + pub input: Weight, + + /// Weight per input byte copied to contract memory by `seal_input`. + pub input_per_byte: Weight, + + /// Weight of calling `seal_return`. + pub r#return: Weight, + + /// Weight per byte returned through `seal_return`. + pub return_per_byte: Weight, + + /// Weight of calling `seal_terminate`. + pub terminate: Weight, + + /// Weight of calling `seal_restore_to`. + pub restore_to: Weight, + + /// Weight per delta key supplied to `seal_restore_to`. + pub restore_to_per_delta: Weight, + + /// Weight of calling `seal_random`. + pub random: Weight, + + /// Weight of calling `seal_reposit_event`. + pub deposit_event: Weight, + + /// Weight per topic supplied to `seal_deposit_event`. + pub deposit_event_per_topic: Weight, + + /// Weight per byte of an event deposited through `seal_deposit_event`. + pub deposit_event_per_byte: Weight, + + /// Weight of calling `seal_set_rent_allowance`. + pub set_rent_allowance: Weight, + + /// Weight of calling `seal_set_storage`. + pub set_storage: Weight, + + /// Weight per byte of an item stored with `seal_set_storage`. + pub set_storage_per_byte: Weight, + + /// Weight of calling `seal_clear_storage`. + pub clear_storage: Weight, + + /// Weight of calling `seal_get_storage`. + pub get_storage: Weight, + + /// Weight per byte of an item received via `seal_get_storage`. + pub get_storage_per_byte: Weight, + + /// Weight of calling `seal_transfer`. + pub transfer: Weight, + + /// Weight of calling `seal_call`. + pub call: Weight, + + /// Weight surcharge that is claimed if `seal_call` does a balance transfer. + pub call_transfer_surcharge: Weight, + + /// Weight per input byte supplied to `seal_call`. + pub call_per_input_byte: Weight, + + /// Weight per output byte received through `seal_call`. + pub call_per_output_byte: Weight, + + /// Weight of calling `seal_instantiate`. + pub instantiate: Weight, + + /// Weight per input byte supplied to `seal_instantiate`. + pub instantiate_per_input_byte: Weight, + + /// Weight per output byte received through `seal_instantiate`. + pub instantiate_per_output_byte: Weight, + + /// Weight of calling `seal_hash_sha_256`. + pub hash_sha2_256: Weight, + + /// Weight per byte hashed by `seal_hash_sha_256`. + pub hash_sha2_256_per_byte: Weight, + + /// Weight of calling `seal_hash_keccak_256`. + pub hash_keccak_256: Weight, + + /// Weight per byte hashed by `seal_hash_keccak_256`. + pub hash_keccak_256_per_byte: Weight, + + /// Weight of calling `seal_hash_blake2_256`. + pub hash_blake2_256: Weight, + + /// Weight per byte hashed by `seal_hash_blake2_256`. + pub hash_blake2_256_per_byte: Weight, + + /// Weight of calling `seal_hash_blake2_128`. + pub hash_blake2_128: Weight, + + /// Weight per byte hashed by `seal_hash_blake2_128`. + pub hash_blake2_128_per_byte: Weight, +} + +/// We need to implement Debug manually because the automatic derive enforces T +/// to also implement Debug. +impl fmt::Debug for Schedule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Schedule").finish() + } +} + +/// 500 (2 instructions per nano second on 2GHZ) * 1000x slowdown through wasmi +/// This is a wild guess and should be viewed as a rough estimation. +/// Proper benchmarks are needed before this value and its derivatives can be used in production. +const WASM_INSTRUCTION_COST: Weight = 500_000; + +macro_rules! replace_token { + ($_in:tt $replacement:tt) => { $replacement }; +} + +macro_rules! call_zero { + ($name:ident, $( $arg:expr ),*) => { + T::WeightInfo::$name($( replace_token!($arg 0) ),*) + }; +} + +macro_rules! cost_args { + ($name:ident, $( $arg: expr ),+) => { + (T::WeightInfo::$name($( $arg ),+).saturating_sub(call_zero!($name, $( $arg ),+))) + } +} + +macro_rules! cost_batched_args { + ($name:ident, $( $arg: expr ),+) => { + cost_args!($name, $( $arg ),+) / Weight::from(API_BENCHMARK_BATCH_SIZE) + } +} + +macro_rules! cost_byte_args { + ($name:ident, $( $arg: expr ),+) => { + cost_args!($name, $( $arg ),+) / 1024 + } +} + +macro_rules! cost_byte_batched_args { + ($name:ident, $( $arg: expr ),+) => { + cost_batched_args!($name, $( $arg ),+) / 1024 + } +} + +macro_rules! cost { + ($name:ident) => { + cost_args!($name, 1) + } +} + +macro_rules! cost_batched { + ($name:ident) => { + cost_batched_args!($name, 1) + } +} + +macro_rules! cost_byte { + ($name:ident) => { + cost_byte_args!($name, 1) + } +} + +macro_rules! cost_byte_batched { + ($name:ident) => { + cost_byte_batched_args!($name, 1) + } +} + +impl Default for Schedule { + fn default() -> Self { + let instruction_weights = InstructionWeights { + grow_mem: WASM_INSTRUCTION_COST, + regular: WASM_INSTRUCTION_COST, + }; + + let host_fn_weights = HostFnWeights { + caller: cost_batched!(seal_caller), + address: cost_batched!(seal_address), + gas_left: cost_batched!(seal_gas_left), + balance: cost_batched!(seal_balance), + value_transferred: cost_batched!(seal_value_transferred), + minimum_balance: cost_batched!(seal_minimum_balance), + tombstone_deposit: cost_batched!(seal_tombstone_deposit), + rent_allowance: cost_batched!(seal_rent_allowance), + block_number: cost_batched!(seal_block_number), + now: cost_batched!(seal_now), + weight_to_fee: cost_batched!(seal_weight_to_fee), + gas: cost_batched!(seal_gas), + input: cost!(seal_input), + input_per_byte: cost_byte!(seal_input_per_kb), + r#return: cost!(seal_return), + return_per_byte: cost_byte!(seal_return_per_kb), + terminate: cost!(seal_terminate), + restore_to: cost!(seal_restore_to), + restore_to_per_delta: cost_batched!(seal_restore_to_per_delta), + random: cost_batched!(seal_random), + deposit_event: cost_batched!(seal_deposit_event), + deposit_event_per_topic: cost_batched_args!(seal_deposit_event_per_topic_and_kb, 1, 0), + deposit_event_per_byte: cost_byte_batched_args!(seal_deposit_event_per_topic_and_kb, 0, 1), + set_rent_allowance: cost_batched!(seal_set_rent_allowance), + set_storage: cost_batched!(seal_set_storage), + set_storage_per_byte: cost_byte_batched!(seal_set_storage_per_kb), + clear_storage: cost_batched!(seal_clear_storage), + get_storage: cost_batched!(seal_get_storage), + get_storage_per_byte: cost_byte_batched!(seal_get_storage_per_kb), + transfer: cost_batched!(seal_transfer), + call: cost_batched!(seal_call), + call_transfer_surcharge: cost_batched_args!(seal_call_per_transfer_input_output_kb, 1, 0, 0), + call_per_input_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 1, 0), + call_per_output_byte: cost_byte_batched_args!(seal_call_per_transfer_input_output_kb, 0, 0, 1), + instantiate: cost_batched!(seal_instantiate), + instantiate_per_input_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_kb, 1, 0), + instantiate_per_output_byte: cost_byte_batched_args!(seal_instantiate_per_input_output_kb, 0, 1), + hash_sha2_256: cost_batched!(seal_hash_sha2_256), + hash_sha2_256_per_byte: cost_byte_batched!(seal_hash_sha2_256_per_kb), + hash_keccak_256: cost_batched!(seal_hash_keccak_256), + hash_keccak_256_per_byte: cost_byte_batched!(seal_hash_keccak_256_per_kb), + hash_blake2_256: cost_batched!(seal_hash_blake2_256), + hash_blake2_256_per_byte: cost_byte_batched!(seal_hash_blake2_256_per_kb), + hash_blake2_128: cost_batched!(seal_hash_blake2_128), + hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb), + }; + + Self { + version: 0, + instruction_weights, + host_fn_weights, + enable_println: false, + max_event_topics: 4, + max_stack_height: 64 * 1024, + max_memory_pages: 16, + max_table_size: 16 * 1024, + max_subject_len: 32, + max_code_size: 512 * 1024, + _phantom: PhantomData, + } + } +} diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 83a680396f417..1c14e3e35f248 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -199,6 +199,7 @@ impl Trait for Test { type MaxDepth = MaxDepth; type MaxValueSize = MaxValueSize; type WeightPrice = Self; + type WeightInfo = (); } type Balances = pallet_balances::Module; @@ -261,7 +262,7 @@ impl ExtBuilder { balances: vec![], }.assimilate_storage(&mut t).unwrap(); GenesisConfig { - current_schedule: Schedule { + current_schedule: Schedule:: { enable_println: true, ..Default::default() }, @@ -290,7 +291,8 @@ where // Perform a call to a plain account. // The actual transfer fails because we can only call contracts. -// Then we check that only the base costs are returned as actual costs. +// Then we check that no gas was used because the base costs for calling are either charged +// as part of the `call` extrinsic or by `seal_call`. #[test] fn calling_plain_account_fails() { ExtBuilder::default().build().execute_with(|| { @@ -302,7 +304,7 @@ fn calling_plain_account_fails() { DispatchErrorWithPostInfo { error: Error::::NotCallable.into(), post_info: PostDispatchInfo { - actual_weight: Some(67500000), + actual_weight: Some(0), pays_fee: Default::default(), }, } @@ -460,6 +462,52 @@ fn instantiate_and_call_and_deposit_event() { }); } +#[test] +fn deposit_event_max_value_limit() { + let (wasm, code_hash) = compile_module::("event_size").unwrap(); + + ExtBuilder::default() + .existential_deposit(50) + .build() + .execute_with(|| { + // Create + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + assert_ok!(Contracts::put_code(Origin::signed(ALICE), wasm)); + assert_ok!(Contracts::instantiate( + Origin::signed(ALICE), + 30_000, + GAS_LIMIT, + code_hash.into(), + vec![], + )); + + // Check creation + let bob_contract = ContractInfoOf::::get(BOB).unwrap().get_alive().unwrap(); + assert_eq!(bob_contract.rent_allowance, >::max_value()); + + // Call contract with allowed storage value. + assert_ok!(Contracts::call( + Origin::signed(ALICE), + BOB, + 0, + GAS_LIMIT * 2, // we are copying a huge buffer, + ::MaxValueSize::get().encode(), + )); + + // Call contract with too large a storage value. + assert_err_ignore_postinfo!( + Contracts::call( + Origin::signed(ALICE), + BOB, + 0, + GAS_LIMIT, + (::MaxValueSize::get() + 1).encode(), + ), + Error::::ValueTooLarge, + ); + }); +} + #[test] fn run_out_of_gas() { let (wasm, code_hash) = compile_module::("run_out_of_gas").unwrap(); @@ -1310,7 +1358,7 @@ fn storage_max_value_limit() { BOB, 0, GAS_LIMIT * 2, // we are copying a huge buffer - Encode::encode(&self::MaxValueSize::get()), + ::MaxValueSize::get().encode(), )); // Call contract with too large a storage value. @@ -1320,9 +1368,9 @@ fn storage_max_value_limit() { BOB, 0, GAS_LIMIT, - Encode::encode(&(self::MaxValueSize::get() + 1)), + (::MaxValueSize::get() + 1).encode(), ), - Error::::ContractTrapped, + Error::::ValueTooLarge, ); }); } diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index ba7a02356d282..34b8ea7443538 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -38,9 +38,27 @@ use frame_support::StorageMap; /// This function instruments the given code and caches it in the storage. pub fn save( original_code: Vec, - schedule: &Schedule, + schedule: &Schedule, ) -> Result, &'static str> { - let prefab_module = prepare::prepare_contract::(&original_code, schedule)?; + let prefab_module = prepare::prepare_contract::(&original_code, schedule)?; + let code_hash = T::Hashing::hash(&original_code); + + >::insert(code_hash, prefab_module); + >::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( + original_code: Vec, + schedule: &Schedule, +) -> Result, &'static str> { + let prefab_module = prepare::benchmarking::prepare_contract::(&original_code, schedule)?; let code_hash = T::Hashing::hash(&original_code); >::insert(code_hash, prefab_module); @@ -56,7 +74,7 @@ pub fn save( /// re-instrumentation and update the cache in the storage. pub fn load( code_hash: &CodeHash, - schedule: &Schedule, + schedule: &Schedule, ) -> Result { let mut prefab_module = >::get(code_hash).ok_or_else(|| "code is not found")?; @@ -68,7 +86,7 @@ pub fn load( // We need to re-instrument the code with the latest schedule here. let original_code = >::get(code_hash).ok_or_else(|| "pristine code is not found")?; - prefab_module = prepare::prepare_contract::(&original_code, schedule)?; + prefab_module = prepare::prepare_contract::(&original_code, schedule)?; >::insert(&code_hash, &prefab_module); } Ok(prefab_module) diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index e74adfcf3caca..100148b18dcd4 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -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, } -impl<'a> WasmLoader<'a> { - pub fn new(schedule: &'a Schedule) -> Self { +impl<'a, T: Trait> WasmLoader<'a, T> { + pub fn new(schedule: &'a Schedule) -> Self { WasmLoader { schedule } } } -impl<'a, T: Trait> crate::exec::Loader for WasmLoader<'a> { +impl<'a, T: Trait> crate::exec::Loader for WasmLoader<'a, T> { type Executable = WasmExecutable; fn load_init(&self, code_hash: &CodeHash) -> Result { @@ -94,17 +96,17 @@ impl<'a, T: Trait> crate::exec::Loader 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, } -impl<'a> WasmVm<'a> { - pub fn new(schedule: &'a Schedule) -> Self { +impl<'a, T: Trait> WasmVm<'a, T> { + pub fn new(schedule: &'a Schedule) -> Self { WasmVm { schedule } } } -impl<'a, T: Trait> crate::exec::Vm for WasmVm<'a> { +impl<'a, T: Trait> crate::exec::Vm for WasmVm<'a, T> { type Executable = WasmExecutable; fn execute>( @@ -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, - gas_left: u64, } #[derive(Default)] @@ -247,13 +247,11 @@ mod tests { &mut self, to: &u64, value: u64, - gas_meter: &mut GasMeter, ) -> 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, + _gas_meter: &mut GasMeter, data: Vec, ) -> 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, ) -> 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, ) -> Result<(), DispatchError> { - (**self).transfer(to, value, gas_meter) + (**self).transfer(to, value) } fn terminate( &mut self, beneficiary: &u64, - gas_meter: &mut GasMeter, ) -> 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::(&wasm, &schedule).unwrap(); + prepare_contract::(&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#" diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index 97cb06fa26042..171fca6339fd3 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -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, } -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, ) -> Result { use wasmi_validation::{validate_module, PlainValidator}; @@ -148,10 +148,10 @@ impl<'a> ContractModule<'a> { fn inject_gas_metering(self) -> Result { 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(&self) -> Result, &'static str> { + /// `import_fn_banlist`: list of function names that are disallowed to be imported + fn scan_imports(&self, import_fn_banlist: &[&[u8]]) + -> Result, &'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( - original_code: &[u8], - schedule: &Schedule, -) -> Result { - 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::()? { +fn get_memory_limits(module: Option<&MemoryType>, schedule: &Schedule) + -> 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( (_, 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( } 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( + original_code: &[u8], + schedule: &Schedule, +) -> Result { + 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::(&disallowed_imports)?, + schedule + )?; contract_module = contract_module .inject_gas_metering()? @@ -392,13 +397,48 @@ pub fn prepare_contract( 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(original_code: &[u8], schedule: &Schedule) + -> Result + { + 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::(wasm.as_ref(), &schedule); + let r = prepare_contract::(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!(>::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!(>::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::(wasm.as_ref(), &schedule); + let r = prepare_contract::(wasm.as_ref(), &schedule); assert_matches!(r, Ok(_)); } } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 806c956d292a0..d966ff85d9652 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -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>, - schedule: &'a Schedule, + schedule: &'a Schedule, memory: sp_sandbox::Memory, gas_meter: &'a mut GasMeter, trap_reason: Option, @@ -128,7 +128,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { pub(crate) fn new( ext: &'a mut E, input_data: Vec, - schedule: &'a Schedule, + schedule: &'a Schedule, memory: sp_sandbox::Memory, gas_meter: &'a mut GasMeter, ) -> Self { @@ -204,136 +204,185 @@ pub(crate) fn to_execution_result( #[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 Token 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>( - gas_meter: &mut GasMeter, - metadata: &Tok::Metadata, - trap_reason: &mut Option, - token: Tok, -) -> Result<(), sp_sandbox::HostError> { - match gas_meter.charge(metadata, token) { +fn charge_gas(ctx: &mut Runtime, token: Tok) -> Result<(), sp_sandbox::HostError> +where + E: Ext, + Tok: Token, +{ + match ctx.gas_meter.charge(&ctx.schedule.host_fn_weights, token) { GasMeterResult::Proceed => Ok(()), GasMeterResult::OutOfGas => { - *trap_reason = Some(TrapReason::SupervisorError(Error::::OutOfGas.into())); + ctx.trap_reason = Some(TrapReason::SupervisorError(Error::::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( ctx: &mut Runtime, ptr: u32, len: u32, ) -> Result, 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::::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( ctx: &mut Runtime, 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::::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( @@ -345,42 +394,36 @@ fn read_sandbox_memory_as( D::decode(&mut &buf[..]).map_err(|_| store_err(ctx, Error::::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( ctx: &mut Runtime, 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::::OutOfBounds)) + ctx.memory.set(ptr, buf).map_err(|_| store_err(ctx, Error::::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( @@ -389,6 +432,7 @@ fn write_sandbox_output( out_len_ptr: u32, buf: &[u8], allow_skip: bool, + create_token: impl FnOnce(u32) -> Option, ) -> Result<(), sp_sandbox::HostError> { if allow_skip && out_ptr == u32::max_value() { return Ok(()); @@ -401,19 +445,27 @@ fn write_sandbox_output( Err(store_err(ctx, Error::::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::::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 { + 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, , // // - 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, , // - 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::::ValueTooLarge))?; } let mut key: StorageKey = [0; 32]; read_sandbox_memory_into_buf(ctx, key_ptr, &mut key)?; @@ -556,6 +603,7 @@ define_env!(Env, , // // - `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, , // // `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, , value_ptr: u32, value_len: u32 ) -> ReturnCode => { + charge_gas(ctx, RuntimeToken::Transfer)?; let callee: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, account_ptr, account_len)?; let value: BalanceOf<::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, , output_ptr: u32, output_len_ptr: u32 ) -> ReturnCode => { + charge_gas(ctx, RuntimeToken::CallBase(input_data_len))?; let callee: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, callee_ptr, callee_len)?; let value: BalanceOf<::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, , }); 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, , output_ptr: u32, output_len_ptr: u32 ) -> ReturnCode => { + charge_gas(ctx, RuntimeToken::InstantiateBase(input_data_len))?; let code_hash: CodeHash<::T> = read_sandbox_memory_as(ctx, code_hash_ptr, code_hash_len)?; let value: BalanceOf<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; @@ -776,10 +836,12 @@ define_env!(Env, , 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, , beneficiary_ptr: u32, beneficiary_len: u32 ) => { + charge_gas(ctx, RuntimeToken::Terminate)?; let beneficiary: <::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, , // // 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, , // 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, , // `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, , // 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, , // // 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, , // // 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, , // // 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, , // // 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, , // `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, , // 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, , delta_ptr: u32, delta_count: u32 ) => { + charge_gas(ctx, RuntimeToken::RestoreTo(delta_count))?; let dest: <::T as frame_system::Trait>::AccountId = read_sandbox_memory_as(ctx, dest_ptr, dest_len)?; let code_hash: CodeHash<::T> = @@ -1038,9 +1125,8 @@ define_env!(Env, , let rent_allowance: BalanceOf<::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, , // - 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::>() 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::::ValueTooLarge))?; + } + let mut topics: Vec::::T>> = match topics_len { 0 => Vec::new(), _ => read_sandbox_memory_as(ctx, topics_ptr, topics_len)?, @@ -1095,12 +1192,6 @@ define_env!(Env, , 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, , // 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<::T> = read_sandbox_memory_as(ctx, value_ptr, value_len)?; ctx.ext.set_rent_allowance(value); @@ -1128,7 +1220,10 @@ define_env!(Env, , // // 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, , // `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, , // 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, , // 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, , // 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, , // 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) }, ); diff --git a/frame/contracts/src/weight_info.rs b/frame/contracts/src/weight_info.rs new file mode 100644 index 0000000000000..3a0881ed78d9a --- /dev/null +++ b/frame/contracts/src/weight_info.rs @@ -0,0 +1,341 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! This module contains the `WeightInfo` trait and its unsafe implementation on `()`. + +use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; + +/// Should be implemented by automatically generated code of the benchmarking system for +/// every runtime that makes use of this pallet. +/// This trait is also implemented on `()`. The implemention on `()` is **unsafe** and must +/// only be used during development. Proper weights can be generated by running the +/// pallet_contracts benchmark suite for the runtime in question. +pub trait WeightInfo { + fn update_schedule() -> Weight; + fn put_code(n: u32, ) -> Weight; + fn instantiate(n: u32, ) -> Weight; + fn call() -> Weight; + fn claim_surcharge() -> Weight; + fn seal_caller(r: u32, ) -> Weight; + fn seal_address(r: u32, ) -> Weight; + fn seal_gas_left(r: u32, ) -> Weight; + fn seal_balance(r: u32, ) -> Weight; + fn seal_value_transferred(r: u32, ) -> Weight; + fn seal_minimum_balance(r: u32, ) -> Weight; + fn seal_tombstone_deposit(r: u32, ) -> Weight; + fn seal_rent_allowance(r: u32, ) -> Weight; + fn seal_block_number(r: u32, ) -> Weight; + fn seal_now(r: u32, ) -> Weight; + fn seal_weight_to_fee(r: u32, ) -> Weight; + fn seal_gas(r: u32, ) -> Weight; + fn seal_input(r: u32, ) -> Weight; + fn seal_input_per_kb(n: u32, ) -> Weight; + fn seal_return(r: u32, ) -> Weight; + fn seal_return_per_kb(n: u32, ) -> Weight; + fn seal_terminate(r: u32, ) -> Weight; + fn seal_restore_to(r: u32, ) -> Weight; + fn seal_restore_to_per_delta(d: u32, ) -> Weight; + fn seal_random(r: u32, ) -> Weight; + fn seal_deposit_event(r: u32, ) -> Weight; + fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight; + fn seal_set_rent_allowance(r: u32, ) -> Weight; + fn seal_set_storage(r: u32, ) -> Weight; + fn seal_set_storage_per_kb(n: u32, ) -> Weight; + fn seal_clear_storage(r: u32, ) -> Weight; + fn seal_get_storage(r: u32, ) -> Weight; + fn seal_get_storage_per_kb(n: u32, ) -> Weight; + fn seal_transfer(r: u32, ) -> Weight; + fn seal_call(r: u32, ) -> Weight; + fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight; + fn seal_instantiate(r: u32, ) -> Weight; + fn seal_instantiate_per_input_output_kb(i: u32, o: u32, ) -> Weight; + fn seal_hash_sha2_256(r: u32, ) -> Weight; + fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_keccak_256(r: u32, ) -> Weight; + fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_blake2_256(r: u32, ) -> Weight; + fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_blake2_128(r: u32, ) -> Weight; + fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight; +} + +/// Unsafe implementation that must only be used for development. +impl WeightInfo for () { + fn update_schedule() -> Weight { + (45000000 as Weight) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn put_code(n: u32, ) -> Weight { + (263409000 as Weight) + .saturating_add((169269000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn instantiate(n: u32, ) -> Weight { + (309311000 as Weight) + .saturating_add((1018000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(7 as Weight)) + .saturating_add(DbWeight::get().writes(4 as Weight)) + } + fn call() -> Weight { + (291000000 as Weight) + .saturating_add(DbWeight::get().reads(6 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn claim_surcharge() -> Weight { + (766000000 as Weight) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn seal_caller(r: u32, ) -> Weight { + (182241000 as Weight) + .saturating_add((697428000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_address(r: u32, ) -> Weight { + (193846000 as Weight) + .saturating_add((695989000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_gas_left(r: u32, ) -> Weight { + (166031000 as Weight) + .saturating_add((702533000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_balance(r: u32, ) -> Weight { + (251892000 as Weight) + .saturating_add((1392900000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + } + fn seal_value_transferred(r: u32, ) -> Weight { + (178472000 as Weight) + .saturating_add((694921000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_minimum_balance(r: u32, ) -> Weight { + (191301000 as Weight) + .saturating_add((697871000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_tombstone_deposit(r: u32, ) -> Weight { + (241315000 as Weight) + .saturating_add((686403000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_rent_allowance(r: u32, ) -> Weight { + (104958000 as Weight) + .saturating_add((1459573000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_block_number(r: u32, ) -> Weight { + (174140000 as Weight) + .saturating_add((698152000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_now(r: u32, ) -> Weight { + (203157000 as Weight) + .saturating_add((713595000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_weight_to_fee(r: u32, ) -> Weight { + (178413000 as Weight) + .saturating_add((1071275000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + } + fn seal_gas(r: u32, ) -> Weight { + (171395000 as Weight) + .saturating_add((371653000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_input(r: u32, ) -> Weight { + (184462000 as Weight) + .saturating_add((10538000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_input_per_kb(n: u32, ) -> Weight { + (194668000 as Weight) + .saturating_add((301000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_return(r: u32, ) -> Weight { + (175538000 as Weight) + .saturating_add((7462000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_return_per_kb(n: u32, ) -> Weight { + (189759000 as Weight) + .saturating_add((754000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_terminate(r: u32, ) -> Weight { + (184385000 as Weight) + .saturating_add((542615000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((2 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight))) + } + fn seal_restore_to(r: u32, ) -> Weight { + (380385000 as Weight) + .saturating_add((160308000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes((4 as Weight).saturating_mul(r as Weight))) + } + fn seal_restore_to_per_delta(d: u32, ) -> Weight { + (0 as Weight) + .saturating_add((4786197000 as Weight).saturating_mul(d as Weight)) + .saturating_add(DbWeight::get().reads(7 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(d as Weight))) + .saturating_add(DbWeight::get().writes(5 as Weight)) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(d as Weight))) + } + fn seal_random(r: u32, ) -> Weight { + (187944000 as Weight) + .saturating_add((1592530000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + } + fn seal_deposit_event(r: u32, ) -> Weight { + (126517000 as Weight) + .saturating_add((2346945000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { + (2953428000 as Weight) + .saturating_add((1117651000 as Weight).saturating_mul(t as Weight)) + .saturating_add((299890000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(t as Weight))) + } + fn seal_set_rent_allowance(r: u32, ) -> Weight { + (142094000 as Weight) + .saturating_add((1726665000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn seal_set_storage(r: u32, ) -> Weight { + (4091409000 as Weight) + .saturating_add((26440116000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes(1 as Weight)) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_set_storage_per_kb(n: u32, ) -> Weight { + (3683270000 as Weight) + .saturating_add((233826000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn seal_clear_storage(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((7152747000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes(1 as Weight)) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_get_storage(r: u32, ) -> Weight { + (19007000 as Weight) + .saturating_add((1774675000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_get_storage_per_kb(n: u32, ) -> Weight { + (1477332000 as Weight) + .saturating_add((176601000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + } + fn seal_transfer(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((10274385000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes(1 as Weight)) + .saturating_add(DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_call(r: u32, ) -> Weight { + (241916000 as Weight) + .saturating_add((14633108000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + } + fn seal_call_per_transfer_input_output_kb(t: u32, i: u32, o: u32, ) -> Weight { + (15664107000 as Weight) + .saturating_add((8529984000 as Weight).saturating_mul(t as Weight)) + .saturating_add((52860000 as Weight).saturating_mul(i as Weight)) + .saturating_add((81175000 as Weight).saturating_mul(o as Weight)) + .saturating_add(DbWeight::get().reads(105 as Weight)) + .saturating_add(DbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) + .saturating_add(DbWeight::get().writes((101 as Weight).saturating_mul(t as Weight))) + } + fn seal_instantiate(r: u32, ) -> Weight { + (0 as Weight) + .saturating_add((32247550000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().reads((300 as Weight).saturating_mul(r as Weight))) + .saturating_add(DbWeight::get().writes(1 as Weight)) + .saturating_add(DbWeight::get().writes((200 as Weight).saturating_mul(r as Weight))) + } + fn seal_instantiate_per_input_output_kb(i: u32, o: u32, ) -> Weight { + (34376003000 as Weight) + .saturating_add((151350000 as Weight).saturating_mul(i as Weight)) + .saturating_add((82364000 as Weight).saturating_mul(o as Weight)) + .saturating_add(DbWeight::get().reads(207 as Weight)) + .saturating_add(DbWeight::get().writes(202 as Weight)) + } + fn seal_hash_sha2_256(r: u32, ) -> Weight { + (164203000 as Weight) + .saturating_add((565206000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { + (0 as Weight) + .saturating_add((330063000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_keccak_256(r: u32, ) -> Weight { + (219038000 as Weight) + .saturating_add((567992000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { + (434654000 as Weight) + .saturating_add((271134000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_256(r: u32, ) -> Weight { + (116374000 as Weight) + .saturating_add((566612000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { + (756028000 as Weight) + .saturating_add((150363000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_128(r: u32, ) -> Weight { + (150126000 as Weight) + .saturating_add((564827000 as Weight).saturating_mul(r as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } + fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { + (1021689000 as Weight) + .saturating_add((149452000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + } +}