difftreelog
Code style update
in: master
58 files changed
pallets/contracts/CHANGELOG.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/CHANGELOG.md
@@ -0,0 +1,93 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+The semantic versioning guarantees cover the interface to the substrate runtime which
+includes this pallet as a dependency. This module will also add storage migrations whenever
+changes require it. Stability with regard to offchain tooling is explicitly excluded from
+this guarantee: For example adding a new field to an in-storage data structure will require
+changes to frontends to properly display it. However, those changes will still be regarded
+as a minor version bump.
+
+The interface provided to smart contracts will adhere to semver with one exception: Even
+major version bumps will be backwards compatible with regard to already deployed contracts.
+In other words: Upgrading this pallet will not break pre-existing contracts.
+
+## [Unreleased]
+
+### Added
+
+- Add new `instantiate` RPC that allows clients to dry-run contract instantiation.
+
+- Make storage and fields of `Schedule` private to the crate.
+[1](https://github.com/paritytech/substrate/pull/8359)
+
+- Add new version of `seal_random` which exposes additional information.
+[1](https://github.com/paritytech/substrate/pull/8329)
+
+- Add `seal_rent_params` contract callable function.
+[1](https://github.com/paritytech/substrate/pull/8231)
+
+## [v3.0.0] 2021-02-25
+
+This version constitutes the first release that brings any stability guarantees (see above).
+
+### Added
+
+- Emit an event when a contract terminates (self-destructs).
+[1](https://github.com/paritytech/substrate/pull/8014)
+
+- Charge rent for code stored on the chain in addition to the already existing
+rent that is payed for data storage.
+[1](https://github.com/paritytech/substrate/pull/7935)
+
+- Allow the runtime to configure per storage item costs in addition
+to the already existing per byte costs.
+[1](https://github.com/paritytech/substrate/pull/7819)
+
+- Contracts are now deleted lazily so that the user who removes a contract
+does not need to pay for the deletion of the contract storage.
+[1](https://github.com/paritytech/substrate/pull/7740)
+
+- Allow runtime authors to define chain extensions in order to provide custom
+functionality to contracts.
+[1](https://github.com/paritytech/substrate/pull/7548)
+[2](https://github.com/paritytech/substrate/pull/8003)
+
+- Proper weights which are fully automated by benchmarking.
+[1](https://github.com/paritytech/substrate/pull/6715)
+[2](https://github.com/paritytech/substrate/pull/7017)
+[3](https://github.com/paritytech/substrate/pull/7361)
+
+### Changes
+
+- Collect the rent for one block during instantiation.
+[1](https://github.com/paritytech/substrate/pull/7847)
+
+- Instantiation takes a `salt` argument to allow for easier instantion of the
+same code by the same sender.
+[1](https://github.com/paritytech/substrate/pull/7482)
+
+- Improve the information returned by the `contracts_call` RPC.
+[1](https://github.com/paritytech/substrate/pull/7468)
+
+- Simplify the node configuration necessary to add this module.
+[1](https://github.com/paritytech/substrate/pull/7409)
+
+### Fixed
+
+- Consider the code size of a contract in the weight that is charged for
+loading a contract from storage.
+[1](https://github.com/paritytech/substrate/pull/8086)
+
+- Fix possible overflow in storage size calculation
+[1](https://github.com/paritytech/substrate/pull/7885)
+
+- Cap the surcharge reward that can be claimed.
+[1](https://github.com/paritytech/substrate/pull/7870)
+
+- Fix a possible DoS vector where contracts could allocate too large buffers.
+[1](https://github.com/paritytech/substrate/pull/7818)
pallets/contracts/COMPLEXITY.mddiffbeforeafterbothno changes
pallets/contracts/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/Cargo.toml
@@ -0,0 +1,75 @@
+[package]
+name = "pallet-contracts"
+version = "3.0.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+license = "Apache-2.0"
+homepage = "https://substrate.dev"
+repository = "https://github.com/paritytech/substrate/"
+description = "FRAME pallet for WASM contracts"
+readme = "README.md"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] }
+log = { version = "0.4", default-features = false }
+parity-wasm = { version = "0.42", default-features = false }
+pwasm-utils = { version = "0.17", default-features = false }
+serde = { version = "1", optional = true, features = ["derive"] }
+wasmi-validation = { version = "0.4", default-features = false }
+
+# Only used in benchmarking to generate random contract code
+rand = { version = "0.8", optional = true, default-features = false }
+rand_pcg = { version = "0.3", optional = true }
+
+# Substrate Dependencies
+frame-benchmarking = { version = "3.1.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3', optional = true }
+frame-support = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+frame-system = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+pallet-contracts-primitives = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+pallet-contracts-proc-macro = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-core = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-io = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-runtime = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-sandbox = { version = "0.9.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-std = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+
+[dev-dependencies]
+assert_matches = "1"
+hex-literal = "0.3"
+paste = "1"
+pretty_assertions = "0.7"
+wat = "1"
+
+# Substrate Dependencies
+pallet-balances = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+pallet-timestamp = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+pallet-randomness-collective-flip = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+
+[features]
+default = ["std"]
+std = [
+ "serde",
+ "codec/std",
+ "sp-core/std",
+ "sp-runtime/std",
+ "sp-io/std",
+ "sp-std/std",
+ "sp-sandbox/std",
+ "frame-support/std",
+ "frame-system/std",
+ "parity-wasm/std",
+ "pwasm-utils/std",
+ "wasmi-validation/std",
+ "pallet-contracts-primitives/std",
+ "pallet-contracts-proc-macro/full",
+ "log/std",
+]
+runtime-benchmarks = [
+ "frame-benchmarking",
+ "rand",
+ "rand_pcg",
+]
+
pallets/contracts/README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/README.md
@@ -0,0 +1,75 @@
+# Contract Module
+
+The Contract module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts.
+
+- [`Call`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Call.html)
+- [`Config`](https://docs.rs/pallet-contracts/latest/pallet_contracts/trait.Config.html)
+- [`Error`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Error.html)
+- [`Event`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Event.html)
+
+## Overview
+
+This module extends accounts based on the `Currency` trait to have smart-contract functionality. It can
+be used with other modules that implement accounts based on `Currency`. These "smart-contract accounts"
+have the ability to instantiate smart-contracts and make calls to other contract and non-contract accounts.
+
+The smart-contract code is stored once in a `code_cache`, and later retrievable via its `code_hash`.
+This means that multiple smart-contracts can be instantiated from the same `code_cache`, without replicating
+the code each time.
+
+When a smart-contract is called, its associated code is retrieved via the code hash and gets executed.
+This call can alter the storage entries of the smart-contract account, instantiate new smart-contracts,
+or call other smart-contracts.
+
+Finally, when an account is reaped, its associated code and storage of the smart-contract account
+will also be deleted.
+
+### Gas
+
+Senders must specify a gas limit with every call, as all instructions invoked by the smart-contract require gas.
+Unused gas is refunded after the call, regardless of the execution outcome.
+
+If the gas limit is reached, then all calls and state changes (including balance transfers) are only
+reverted at the current call's contract level. For example, if contract A calls B and B runs out of gas mid-call,
+then all of B's calls are reverted. Assuming correct error handling by contract A, A's other calls and state
+changes still persist.
+
+One gas is equivalent to one [weight](https://substrate.dev/docs/en/knowledgebase/learn-substrate/weight)
+which is defined as one picosecond of execution time on the runtime's reference machine.
+
+### Notable Scenarios
+
+Contract call failures are not always cascading. When failures occur in a sub-call, they do not "bubble up",
+and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B
+fails, A can decide how to handle that failure, either proceeding or reverting A's changes.
+
+## Interface
+
+### Dispatchable functions
+
+Those are documented in the [reference documentation](https://docs.rs/pallet-contracts/latest/pallet_contracts/#dispatchable-functions).
+
+## Usage
+
+This module executes WebAssembly smart contracts. These can potentially be written in any language
+that compiles to web assembly. However, using a language that specifically targets this module
+will make things a lot easier. One such language is [`ink`](https://github.com/paritytech/ink)
+which is an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables
+writing WebAssembly based smart contracts in the Rust programming language.
+
+## Debugging
+
+Contracts can emit messages to the node console when run on a development chain through the
+`seal_println` API. This is exposed in ink! via
+[`ink_env::debug_println()`](https://docs.rs/ink_env/latest/ink_env/fn.debug_println.html).
+
+In order to see these messages the log level for the `runtime::contracts` target needs to be raised
+to at least the `info` level which is the default. However, those messages are easy to overlook
+because of the noise generated by block production. A good starting point for contract debugging
+could be:
+
+```bash
+cargo run --release -- --dev --tmp -lerror,runtime::contracts
+```
+
+License: Apache-2.0
pallets/contracts/common/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/common/Cargo.toml
@@ -0,0 +1,33 @@
+[package]
+name = "pallet-contracts-primitives"
+version = "3.0.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+license = "Apache-2.0"
+homepage = "https://substrate.dev"
+repository = "https://github.com/paritytech/substrate/"
+description = "A crate that hosts a common definitions that are relevant for the pallet-contracts."
+readme = "README.md"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+bitflags = "1.0"
+codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] }
+serde = { version = "1", features = ["derive"], optional = true }
+
+# Substrate Dependencies (This crate should not rely on frame)
+sp-core = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3', default-features = false }
+sp-std = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-runtime = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+
+[features]
+default = ["std"]
+std = [
+ "codec/std",
+ "sp-core/std",
+ "sp-runtime/std",
+ "sp-std/std",
+ "serde",
+]
pallets/contracts/common/README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/common/README.md
@@ -0,0 +1,3 @@
+A crate that hosts a common definitions that are relevant for the pallet-contracts.
+
+License: Apache-2.0
\ No newline at end of file
pallets/contracts/common/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/common/src/lib.rs
@@ -0,0 +1,148 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 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.
+
+//! A crate that hosts a common definitions that are relevant for the pallet-contracts.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use bitflags::bitflags;
+use codec::{Decode, Encode};
+use sp_core::Bytes;
+use sp_runtime::{DispatchError, RuntimeDebug};
+use sp_std::prelude::*;
+
+#[cfg(feature = "std")]
+use serde::{Serialize, Deserialize};
+
+/// Result type of a `bare_call` or `bare_instantiate` call.
+///
+/// It contains the execution result together with some auxiliary information.
+#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
+pub struct ContractResult<T> {
+ /// How much gas was consumed during execution.
+ pub gas_consumed: u64,
+ /// An optional debug message. This message is only non-empty when explicitly requested
+ /// by the code that calls into the contract.
+ ///
+ /// The contained bytes are valid UTF-8. This is not declared as `String` because
+ /// this type is not allowed within the runtime. A client should decode them in order
+ /// to present the message to its users.
+ ///
+ /// # Note
+ ///
+ /// The debug message is never generated during on-chain execution. It is reserved for
+ /// RPC calls.
+ pub debug_message: Bytes,
+ /// The execution result of the wasm code.
+ pub result: T,
+}
+
+/// Result type of a `bare_call` call.
+pub type ContractExecResult = ContractResult<Result<ExecReturnValue, DispatchError>>;
+
+/// Result type of a `bare_instantiate` call.
+pub type ContractInstantiateResult<AccountId, BlockNumber> =
+ ContractResult<Result<InstantiateReturnValue<AccountId, BlockNumber>, DispatchError>>;
+
+/// Result type of a `get_storage` call.
+pub type GetStorageResult = Result<Option<Vec<u8>>, ContractAccessError>;
+
+/// Result type of a `rent_projection` call.
+pub type RentProjectionResult<BlockNumber> =
+ Result<RentProjection<BlockNumber>, ContractAccessError>;
+
+/// The possible errors that can happen querying the storage of a contract.
+#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
+pub enum ContractAccessError {
+ /// The given address doesn't point to a contract.
+ DoesntExist,
+ /// The specified contract is a tombstone and thus cannot have any storage.
+ IsTombstone,
+}
+
+#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
+pub enum RentProjection<BlockNumber> {
+ /// Eviction is projected to happen at the specified block number.
+ EvictionAt(BlockNumber),
+ /// No eviction is scheduled.
+ ///
+ /// E.g. Contract accumulated enough funds to offset the rent storage costs.
+ NoEviction,
+}
+
+bitflags! {
+ /// Flags used by a contract to customize exit behaviour.
+ #[derive(Encode, Decode)]
+ #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+ #[cfg_attr(feature = "std", serde(rename_all = "camelCase", transparent))]
+ pub struct ReturnFlags: u32 {
+ /// If this bit is set all changes made by the contract execution are rolled back.
+ const REVERT = 0x0000_0001;
+ }
+}
+
+/// Output of a contract call or instantiation which ran to completion.
+#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
+pub struct ExecReturnValue {
+ /// Flags passed along by `seal_return`. Empty when `seal_return` was never called.
+ pub flags: ReturnFlags,
+ /// Buffer passed along by `seal_return`. Empty when `seal_return` was never called.
+ pub data: Bytes,
+}
+
+impl ExecReturnValue {
+ /// We understand the absense of a revert flag as success.
+ pub fn is_success(&self) -> bool {
+ !self.flags.contains(ReturnFlags::REVERT)
+ }
+}
+
+/// The result of a successful contract instantiation.
+#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
+pub struct InstantiateReturnValue<AccountId, BlockNumber> {
+ /// The output of the called constructor.
+ pub result: ExecReturnValue,
+ /// The account id of the new contract.
+ pub account_id: AccountId,
+ /// Information about when and if the new project will be evicted.
+ ///
+ /// # Note
+ ///
+ /// `None` if `bare_instantiate` was called with
+ /// `compute_projection` set to false. From the perspective of an RPC this means that
+ /// the runtime API did not request this value and this feature is therefore unsupported.
+ pub rent_projection: Option<RentProjection<BlockNumber>>,
+}
+
+/// Reference to an existing code hash or a new wasm module.
+#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)]
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))]
+pub enum Code<Hash> {
+ /// A wasm module as raw bytes.
+ Upload(Bytes),
+ /// The code hash of an on-chain wasm blob.
+ Existing(Hash),
+}
pallets/contracts/fixtures/call_return_code.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/call_return_code.wat
@@ -0,0 +1,42 @@
+;; This calls the supplied dest and transfers 100 balance during this call and copies
+;; the return code of this call to the output buffer.
+;; It also forwards its input to the callee.
+(module
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
+ (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ ;; [0, 8) 100 balance
+ (data (i32.const 0) "\64\00\00\00\00\00\00\00")
+
+ ;; [8, 12) here we store the return code of the transfer
+
+ ;; [12, 16) size of the input data
+ (data (i32.const 12) "\24")
+
+ ;; [16, inf) here we store the input data
+ ;; 32 byte dest + 4 byte forward
+
+ (func (export "deploy"))
+
+ (func (export "call")
+ (call $seal_input (i32.const 16) (i32.const 12))
+ (i32.store
+ (i32.const 8)
+ (call $seal_call
+ (i32.const 16) ;; Pointer to "callee" address.
+ (i32.const 32) ;; Length of "callee" address.
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (i32.const 0) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer.
+ (i32.const 48) ;; Pointer to input data buffer address
+ (i32.const 4) ;; Length of input data buffer
+ (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Ptr to output buffer len
+ )
+ )
+ ;; exit with success and take transfer return code to the output buffer
+ (call $seal_return (i32.const 0) (i32.const 8) (i32.const 4))
+ )
+)
pallets/contracts/fixtures/caller_contract.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/caller_contract.wat
@@ -0,0 +1,293 @@
+(module
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_balance" (func $seal_balance (param i32 i32)))
+ (import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
+ (import "seal0" "seal_instantiate" (func $seal_instantiate
+ (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
+ ))
+ (import "seal0" "seal_println" (func $seal_println (param i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ (func $current_balance (param $sp i32) (result i64)
+ (i32.store
+ (i32.sub (get_local $sp) (i32.const 16))
+ (i32.const 8)
+ )
+ (call $seal_balance
+ (i32.sub (get_local $sp) (i32.const 8))
+ (i32.sub (get_local $sp) (i32.const 16))
+ )
+ (call $assert
+ (i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 16))) (i32.const 8))
+ )
+ (i64.load (i32.sub (get_local $sp) (i32.const 8)))
+ )
+
+ (func (export "deploy"))
+
+ (func (export "call")
+ (local $sp i32)
+ (local $exit_code i32)
+ (local $balance i64)
+
+ ;; Length of the buffer
+ (i32.store (i32.const 20) (i32.const 32))
+
+ ;; Copy input to this contracts memory
+ (call $seal_input (i32.const 24) (i32.const 20))
+
+ ;; Input data is the code hash of the contract to be deployed.
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 20))
+ (i32.const 32)
+ )
+ )
+
+ ;; Read current balance into local variable.
+ (set_local $sp (i32.const 1024))
+ (set_local $balance
+ (call $current_balance (get_local $sp))
+ )
+
+ ;; Fail to deploy the contract since it returns a non-zero exit status.
+ (set_local $exit_code
+ (call $seal_instantiate
+ (i32.const 24) ;; Pointer to the code hash.
+ (i32.const 32) ;; Length of the code hash.
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (i32.const 0) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer.
+ (i32.const 9) ;; Pointer to input data buffer address
+ (i32.const 7) ;; Length of input data buffer
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy address
+ (i32.const 0) ;; Length is ignored in this case
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this case
+ (i32.const 0) ;; salt_ptr
+ (i32.const 0) ;; salt_le
+ )
+ )
+
+ ;; Check non-zero exit status.
+ (call $assert
+ (i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted
+ )
+
+ ;; Check that balance has not changed.
+ (call $assert
+ (i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
+ )
+
+ ;; Fail to deploy the contract due to insufficient gas.
+ (set_local $exit_code
+ (call $seal_instantiate
+ (i32.const 24) ;; Pointer to the code hash.
+ (i32.const 32) ;; Length of the code hash.
+ (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
+ (i32.const 8) ;; Length of input data buffer
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy address
+ (i32.const 0) ;; Length is ignored in this case
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this case
+ (i32.const 0) ;; salt_ptr
+ (i32.const 0) ;; salt_le
+
+ )
+ )
+
+ ;; Check for special trap exit status.
+ (call $assert
+ (i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
+ )
+
+ ;; Check that balance has not changed.
+ (call $assert
+ (i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
+ )
+
+ ;; Length of the output buffer
+ (i32.store
+ (i32.sub (get_local $sp) (i32.const 4))
+ (i32.const 256)
+ )
+
+ ;; Deploy the contract successfully.
+ (set_local $exit_code
+ (call $seal_instantiate
+ (i32.const 24) ;; Pointer to the code hash.
+ (i32.const 32) ;; Length of the code hash.
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (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
+ (i32.const 8) ;; Length of input data buffer
+ (i32.const 16) ;; Pointer to the address output buffer
+ (i32.sub (get_local $sp) (i32.const 4)) ;; Pointer to the address buffer length
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this case
+ (i32.const 0) ;; salt_ptr
+ (i32.const 0) ;; salt_le
+
+ )
+ )
+
+ ;; Check for success exit status.
+ (call $assert
+ (i32.eq (get_local $exit_code) (i32.const 0)) ;; ReturnCode::Success
+ )
+
+ ;; Check that address has the expected length
+ (call $assert
+ (i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 4))) (i32.const 32))
+ )
+
+ ;; Check that balance has been deducted.
+ (set_local $balance
+ (i64.sub (get_local $balance) (i64.load (i32.const 0)))
+ )
+ (call $assert
+ (i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
+ )
+
+ ;; Zero out destination buffer of output
+ (i32.store
+ (i32.sub (get_local $sp) (i32.const 4))
+ (i32.const 0)
+ )
+
+ ;; Length of the output buffer
+ (i32.store
+ (i32.sub (get_local $sp) (i32.const 8))
+ (i32.const 4)
+ )
+
+ ;; Call the new contract and expect it to return failing exit code.
+ (set_local $exit_code
+ (call $seal_call
+ (i32.const 16) ;; Pointer to "callee" address.
+ (i32.const 32) ;; Length of "callee" address.
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (i32.const 0) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer.
+ (i32.const 9) ;; Pointer to input data buffer address
+ (i32.const 7) ;; Length of input data buffer
+ (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer
+ (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len
+ )
+ )
+
+ ;; Check non-zero exit status.
+ (call $assert
+ (i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted
+ )
+
+ ;; Check that output buffer contains the expected return data.
+ (call $assert
+ (i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 8))) (i32.const 3))
+ )
+ (call $assert
+ (i32.eq
+ (i32.load (i32.sub (get_local $sp) (i32.const 4)))
+ (i32.const 0x00776655)
+ )
+ )
+
+ ;; Check that balance has not changed.
+ (call $assert
+ (i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
+ )
+
+ ;; Fail to call the contract due to insufficient gas.
+ (set_local $exit_code
+ (call $seal_call
+ (i32.const 16) ;; Pointer to "callee" address.
+ (i32.const 32) ;; Length of "callee" address.
+ (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
+ (i32.const 8) ;; Length of input data buffer
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this cas
+ )
+ )
+
+ ;; Check for special trap exit status.
+ (call $assert
+ (i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped
+ )
+
+ ;; Check that balance has not changed.
+ (call $assert
+ (i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
+ )
+
+ ;; Zero out destination buffer of output
+ (i32.store
+ (i32.sub (get_local $sp) (i32.const 4))
+ (i32.const 0)
+ )
+
+ ;; Length of the output buffer
+ (i32.store
+ (i32.sub (get_local $sp) (i32.const 8))
+ (i32.const 4)
+ )
+
+ ;; Call the contract successfully.
+ (set_local $exit_code
+ (call $seal_call
+ (i32.const 16) ;; Pointer to "callee" address.
+ (i32.const 32) ;; Length of "callee" address.
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (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
+ (i32.const 8) ;; Length of input data buffer
+ (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer
+ (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len
+ )
+ )
+
+ ;; Check for success exit status.
+ (call $assert
+ (i32.eq (get_local $exit_code) (i32.const 0)) ;; ReturnCode::Success
+ )
+
+ ;; Check that the output buffer contains the expected return data.
+ (call $assert
+ (i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 8))) (i32.const 4))
+ )
+ (call $assert
+ (i32.eq
+ (i32.load (i32.sub (get_local $sp) (i32.const 4)))
+ (i32.const 0x77665544)
+ )
+ )
+
+ ;; Check that balance has been deducted.
+ (set_local $balance
+ (i64.sub (get_local $balance) (i64.load (i32.const 0)))
+ )
+ (call $assert
+ (i64.eq (get_local $balance) (call $current_balance (get_local $sp)))
+ )
+ )
+
+ (data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls.
+ ;; Chosen to be greater than existential deposit.
+ (data (i32.const 8) "\00\01\22\33\44\55\66\77") ;; The input data to instantiations and calls.
+)
pallets/contracts/fixtures/chain_extension.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/chain_extension.wat
@@ -0,0 +1,46 @@
+;; Call chain extension by passing through input and output of this contract
+(module
+ (import "seal0" "seal_call_chain_extension"
+ (func $seal_call_chain_extension (param i32 i32 i32 i32 i32) (result i32))
+ )
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
+ (import "env" "memory" (memory 16 16))
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok (get_local 0))
+ (unreachable)
+ )
+ )
+
+ ;; [0, 4) len of input output
+ (data (i32.const 0) "\02")
+
+ ;; [4, 12) buffer for input
+
+ ;; [12, 16) len of output buffer
+ (data (i32.const 12) "\02")
+
+ ;; [16, inf) buffer for output
+
+ (func (export "deploy"))
+
+ (func (export "call")
+ (call $seal_input (i32.const 4) (i32.const 0))
+
+ ;; the chain extension passes through the input and returns it as output
+ (call $seal_call_chain_extension
+ (i32.load8_u (i32.const 4)) ;; func_id
+ (i32.const 4) ;; input_ptr
+ (i32.load (i32.const 0)) ;; input_len
+ (i32.const 16) ;; output_ptr
+ (i32.const 12) ;; output_len_ptr
+ )
+
+ ;; the chain extension passes through the func_id
+ (call $assert (i32.eq (i32.load8_u (i32.const 4))))
+
+ (call $seal_return (i32.const 0) (i32.const 16) (i32.load (i32.const 12)))
+ )
+)
pallets/contracts/fixtures/check_default_rent_allowance.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/check_default_rent_allowance.wat
@@ -0,0 +1,43 @@
+(module
+ (import "seal0" "seal_rent_allowance" (func $seal_rent_allowance (param i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ ;; [0, 8) reserved for $seal_rent_allowance output
+
+ ;; [8, 16) length of the buffer
+ (data (i32.const 8) "\08")
+
+ ;; [16, inf) zero initialized
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ (func (export "call"))
+
+ (func (export "deploy")
+ ;; fill the buffer with the rent allowance.
+ (call $seal_rent_allowance (i32.const 0) (i32.const 8))
+
+ ;; assert len == 8
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 8))
+ (i32.const 8)
+ )
+ )
+
+ ;; assert that contents of the buffer is equal to <BalanceOf<T>>::max_value().
+ (call $assert
+ (i64.eq
+ (i64.load (i32.const 0))
+ (i64.const 0xFFFFFFFFFFFFFFFF)
+ )
+ )
+ )
+)
pallets/contracts/fixtures/crypto_hashes.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/crypto_hashes.wat
@@ -0,0 +1,81 @@
+(module
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
+
+ (import "seal0" "seal_hash_sha2_256" (func $seal_hash_sha2_256 (param i32 i32 i32)))
+ (import "seal0" "seal_hash_keccak_256" (func $seal_hash_keccak_256 (param i32 i32 i32)))
+ (import "seal0" "seal_hash_blake2_256" (func $seal_hash_blake2_256 (param i32 i32 i32)))
+ (import "seal0" "seal_hash_blake2_128" (func $seal_hash_blake2_128 (param i32 i32 i32)))
+
+ (import "env" "memory" (memory 1 1))
+
+ (type $hash_fn_sig (func (param i32 i32 i32)))
+ (table 8 funcref)
+ (elem (i32.const 1)
+ $seal_hash_sha2_256
+ $seal_hash_keccak_256
+ $seal_hash_blake2_256
+ $seal_hash_blake2_128
+ )
+ (data (i32.const 1) "20202010201008") ;; Output sizes of the hashes in order in hex.
+
+ ;; Not in use by the tests besides instantiating the contract.
+ (func (export "deploy"))
+
+ ;; Called by the tests.
+ ;;
+ ;; The `call` function expects data in a certain format in the input buffer.
+ ;;
+ ;; 1. The first byte encodes an identifier for the crypto hash function
+ ;; under test. (*)
+ ;; 2. The rest encodes the input data that is directly fed into the
+ ;; crypto hash function chosen in 1.
+ ;;
+ ;; The `deploy` function then computes the chosen crypto hash function
+ ;; given the input and puts the result into the output buffer.
+ ;; After contract execution the test driver then asserts that the returned
+ ;; values are equal to the expected bytes for the input and chosen hash
+ ;; function.
+ ;;
+ ;; (*) The possible value for the crypto hash identifiers can be found below:
+ ;;
+ ;; | value | Algorithm | Bit Width |
+ ;; |-------|-----------|-----------|
+ ;; | 0 | SHA2 | 256 |
+ ;; | 1 | KECCAK | 256 |
+ ;; | 2 | BLAKE2 | 256 |
+ ;; | 3 | BLAKE2 | 128 |
+ ;; ---------------------------------
+ (func (export "call")
+ (local $chosen_hash_fn i32)
+ (local $input_len_ptr i32)
+ (local $input_ptr i32)
+ (local $input_len i32)
+ (local $output_ptr i32)
+ (local $output_len i32)
+ (local.set $input_len_ptr (i32.const 256))
+ (local.set $input_ptr (i32.const 10))
+ (i32.store (local.get $input_len_ptr) (i32.const 246))
+ (call $seal_input (local.get $input_ptr) (local.get $input_len_ptr))
+ (local.set $chosen_hash_fn (i32.load8_u (local.get $input_ptr)))
+ (if (i32.gt_u (local.get $chosen_hash_fn) (i32.const 7))
+ ;; We check that the chosen hash fn identifier is within bounds: [0,7]
+ (unreachable)
+ )
+ (local.set $input_ptr (i32.add (local.get $input_ptr) (i32.const 1)))
+ (local.set $input_len (i32.sub (i32.load (local.get $input_len_ptr)) (i32.const 1)))
+ (local.set $output_len (i32.load8_u (local.get $chosen_hash_fn)))
+ (call_indirect (type $hash_fn_sig)
+ (local.get $input_ptr)
+ (local.get $input_len)
+ (local.get $input_ptr)
+ (local.get $chosen_hash_fn) ;; Which crypto hash function to execute.
+ )
+ (call $seal_return
+ (i32.const 0)
+ (local.get $input_ptr) ;; Linear memory location of the output buffer.
+ (local.get $output_len) ;; Number of output buffer bytes.
+ )
+ (unreachable)
+ )
+)
pallets/contracts/fixtures/destroy_and_transfer.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/destroy_and_transfer.wat
@@ -0,0 +1,161 @@
+(module
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32)))
+ (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
+ (import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
+ (import "seal0" "seal_transfer" (func $seal_transfer (param i32 i32 i32 i32) (result i32)))
+ (import "seal0" "seal_instantiate" (func $seal_instantiate
+ (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
+ ))
+ (import "env" "memory" (memory 1 1))
+
+ ;; [0, 8) Endowment to send when creating contract.
+ (data (i32.const 0) "\00\00\01")
+
+ ;; [8, 16) Value to send when calling contract.
+
+ ;; [16, 48) The key to store the contract address under.
+
+ ;; [48, 80) Buffer where to store the input to the contract
+
+ ;; [88, 96) Size of the buffer
+ (data (i32.const 88) "\FF")
+
+ ;; [96, 100) Size of the input buffer
+ (data (i32.const 96) "\20")
+
+ ;; [100, 132) Buffer where to store the address of the instantiated contract
+
+ ;; [132, 134) Salt
+ (data (i32.const 132) "\47\11")
+
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ (func (export "deploy")
+ ;; Input data is the code hash of the contract to be deployed.
+ (call $seal_input (i32.const 48) (i32.const 96))
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 96))
+ (i32.const 32)
+ )
+ )
+
+ ;; Deploy the contract with the provided code hash.
+ (call $assert
+ (i32.eq
+ (call $seal_instantiate
+ (i32.const 48) ;; Pointer to the code hash.
+ (i32.const 32) ;; Length of the code hash.
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (i32.const 0) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer.
+ (i32.const 0) ;; Pointer to input data buffer address
+ (i32.const 0) ;; Length of input data buffer
+ (i32.const 100) ;; Buffer where to store address of new contract
+ (i32.const 88) ;; Pointer to the length of the buffer
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this case
+ (i32.const 132) ;; salt_ptr
+ (i32.const 2) ;; salt_len
+ )
+ (i32.const 0)
+ )
+ )
+
+ ;; Check that address has expected length
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 88))
+ (i32.const 32)
+ )
+ )
+
+ ;; Store the return address.
+ (call $seal_set_storage
+ (i32.const 16) ;; Pointer to the key
+ (i32.const 100) ;; Pointer to the value
+ (i32.const 32) ;; Length of the value
+ )
+ )
+
+ (func (export "call")
+ ;; Read address of destination contract from storage.
+ (call $assert
+ (i32.eq
+ (call $seal_get_storage
+ (i32.const 16) ;; Pointer to the key
+ (i32.const 100) ;; Pointer to the value
+ (i32.const 88) ;; Pointer to the len of the value
+ )
+ (i32.const 0)
+ )
+ )
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 88))
+ (i32.const 32)
+ )
+ )
+
+ ;; Calling the destination contract with non-empty input data should fail.
+ (call $assert
+ (i32.eq
+ (call $seal_call
+ (i32.const 100) ;; Pointer to destination address
+ (i32.const 32) ;; Length of destination address
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (i32.const 0) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer
+ (i32.const 0) ;; Pointer to input data buffer address
+ (i32.const 1) ;; Length of input data buffer
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this case
+
+ )
+ (i32.const 0x1)
+ )
+ )
+
+ ;; Call the destination contract regularly, forcing it to self-destruct.
+ (call $assert
+ (i32.eq
+ (call $seal_call
+ (i32.const 100) ;; Pointer to destination address
+ (i32.const 32) ;; Length of destination address
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (i32.const 8) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer
+ (i32.const 0) ;; Pointer to input data buffer address
+ (i32.const 0) ;; Length of input data buffer
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this case
+ )
+ (i32.const 0)
+ )
+ )
+
+ ;; Calling the destination address with non-empty input data should now work since the
+ ;; contract has been removed. Also transfer a balance to the address so we can ensure this
+ ;; does not keep the contract alive.
+ (call $assert
+ (i32.eq
+ (call $seal_transfer
+ (i32.const 100) ;; Pointer to destination address
+ (i32.const 32) ;; Length of destination address
+ (i32.const 0) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer
+ )
+ (i32.const 0)
+ )
+ )
+ )
+)
pallets/contracts/fixtures/drain.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/drain.wat
@@ -0,0 +1,49 @@
+(module
+ (import "seal0" "seal_balance" (func $seal_balance (param i32 i32)))
+ (import "seal0" "seal_transfer" (func $seal_transfer (param i32 i32 i32 i32) (result i32)))
+ (import "env" "memory" (memory 1 1))
+
+ ;; [0, 8) reserved for $seal_balance output
+
+ ;; [8, 16) length of the buffer
+ (data (i32.const 8) "\08")
+
+ ;; [16, inf) zero initialized
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ (func (export "deploy"))
+
+ (func (export "call")
+ ;; Send entire remaining balance to the 0 address.
+ (call $seal_balance (i32.const 0) (i32.const 8))
+
+ ;; Balance should be encoded as a u64.
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 8))
+ (i32.const 8)
+ )
+ )
+
+ ;; Self-destruct by sending full balance to the 0 address.
+ (call $assert
+ (i32.eq
+ (call $seal_transfer
+ (i32.const 16) ;; Pointer to destination address
+ (i32.const 32) ;; Length of destination address
+ (i32.const 0) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer
+ )
+ (i32.const 4) ;; ReturnCode::BelowSubsistenceThreshold
+ )
+ )
+ )
+)
pallets/contracts/fixtures/event_size.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/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"))
+)
pallets/contracts/fixtures/instantiate_return_code.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/instantiate_return_code.wat
@@ -0,0 +1,49 @@
+;; This instantiats a contract and transfers 100 balance during this call and copies the return code
+;; of this call to the output buffer.
+;; The first 32 byte of input is the code hash to instantiate
+;; The rest of the input is forwarded to the constructor of the callee
+(module
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_instantiate" (func $seal_instantiate
+ (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32)
+ ))
+ (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ ;; [0, 8) 10_000 balance
+ (data (i32.const 0) "\10\27\00\00\00\00\00\00")
+
+ ;; [8, 12) here we store the return code of the transfer
+
+ ;; [12, 16) size of the input buffer
+ (data (i32.const 12) "\24")
+
+ ;; [16, inf) input buffer
+ ;; 32 bye code hash + 4 byte forward
+
+ (func (export "deploy"))
+
+ (func (export "call")
+ (call $seal_input (i32.const 16) (i32.const 12))
+ (i32.store
+ (i32.const 8)
+ (call $seal_instantiate
+ (i32.const 16) ;; Pointer to the code hash.
+ (i32.const 32) ;; Length of the code hash.
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (i32.const 0) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer.
+ (i32.const 48) ;; Pointer to input data buffer address
+ (i32.const 4) ;; Length of input data buffer
+ (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy address
+ (i32.const 0) ;; Length is ignored in this case
+ (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this case
+ (i32.const 0) ;; salt_ptr
+ (i32.const 0) ;; salt_len
+ )
+ )
+ ;; exit with success and take transfer return code to the output buffer
+ (call $seal_return (i32.const 0) (i32.const 8) (i32.const 4))
+ )
+)
pallets/contracts/fixtures/ok_trap_revert.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/ok_trap_revert.wat
@@ -0,0 +1,35 @@
+(module
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ (func (export "deploy")
+ (call $ok_trap_revert)
+ )
+
+ (func (export "call")
+ (call $ok_trap_revert)
+ )
+
+ (func $ok_trap_revert
+ (i32.store (i32.const 4) (i32.const 4))
+ (call $seal_input (i32.const 0) (i32.const 4))
+ (block $IF_2
+ (block $IF_1
+ (block $IF_0
+ (br_table $IF_0 $IF_1 $IF_2
+ (i32.load8_u (i32.const 0))
+ )
+ (unreachable)
+ )
+ ;; 0 = return with success
+ return
+ )
+ ;; 1 = revert
+ (call $seal_return (i32.const 1) (i32.const 0) (i32.const 0))
+ (unreachable)
+ )
+ ;; 2 = trap
+ (unreachable)
+ )
+)
\ No newline at end of file
pallets/contracts/fixtures/restoration.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/restoration.wat
@@ -0,0 +1,75 @@
+(module
+ (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_restore_to"
+ (func $seal_restore_to
+ (param i32 i32 i32 i32 i32 i32 i32 i32)
+ )
+ )
+ (import "env" "memory" (memory 1 1))
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ (func (export "call")
+ ;; copy code hash to contract memory
+ (call $seal_input (i32.const 308) (i32.const 304))
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 304))
+ (i32.const 64)
+ )
+ )
+ (call $seal_restore_to
+ ;; Pointer and length of the encoded dest buffer.
+ (i32.const 340)
+ (i32.const 32)
+ ;; Pointer and length of the encoded code hash buffer
+ (i32.const 308)
+ (i32.const 32)
+ ;; Pointer and length of the encoded rent_allowance buffer
+ (i32.const 296)
+ (i32.const 8)
+ ;; Pointer and number of items in the delta buffer.
+ ;; This buffer specifies multiple keys for removal before restoration.
+ (i32.const 100)
+ (i32.const 1)
+ )
+ )
+ (func (export "deploy")
+ ;; Data to restore
+ (call $seal_set_storage
+ (i32.const 0)
+ (i32.const 0)
+ (i32.const 4)
+ )
+
+ ;; ACL
+ (call $seal_set_storage
+ (i32.const 100)
+ (i32.const 0)
+ (i32.const 4)
+ )
+ )
+
+ ;; Data to restore
+ (data (i32.const 0) "\28")
+
+ ;; Buffer that has ACL storage keys.
+ (data (i32.const 100) "\01")
+
+ ;; [296, 304) Rent allowance
+ (data (i32.const 296) "\32\00\00\00\00\00\00\00")
+
+ ;; [304, 308) Size of the buffer that holds code_hash + addr
+ (data (i32.const 304) "\40")
+
+ ;; [308, 340) code hash of bob (copied by seal_input)
+ ;; [340, 372) addr of bob (copied by seal_input)
+)
pallets/contracts/fixtures/return_from_start_fn.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/return_from_start_fn.wat
@@ -0,0 +1,28 @@
+(module
+ (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
+ (import "seal0" "seal_deposit_event" (func $seal_deposit_event (param i32 i32 i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ (start $start)
+ (func $start
+ (call $seal_deposit_event
+ (i32.const 0) ;; The topics buffer
+ (i32.const 0) ;; The topics buffer's length
+ (i32.const 8) ;; The data buffer
+ (i32.const 4) ;; The data buffer's length
+ )
+ (call $seal_return
+ (i32.const 0)
+ (i32.const 8)
+ (i32.const 4)
+ )
+ (unreachable)
+ )
+
+ (func (export "call")
+ (unreachable)
+ )
+ (func (export "deploy"))
+
+ (data (i32.const 8) "\01\02\03\04")
+)
pallets/contracts/fixtures/return_with_data.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/return_with_data.wat
@@ -0,0 +1,33 @@
+(module
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ ;; [0, 128) buffer where input is copied
+
+ ;; [128, 132) length of the input buffer
+ (data (i32.const 128) "\80")
+
+ ;; Deploy routine is the same as call.
+ (func (export "deploy")
+ (call $call)
+ )
+
+ ;; Call reads the first 4 bytes (LE) as the exit status and returns the rest as output data.
+ (func $call (export "call")
+ ;; Copy input into this contracts memory.
+ (call $seal_input (i32.const 0) (i32.const 128))
+
+ ;; Copy all but the first 4 bytes of the input data as the output data.
+ ;; Use the first byte as exit status
+ (call $seal_return
+ (i32.load8_u (i32.const 0)) ;; Exit status
+ (i32.const 4) ;; Pointer to the data to return.
+ (i32.sub ;; Count of bytes to copy.
+ (i32.load (i32.const 128))
+ (i32.const 4)
+ )
+ )
+ (unreachable)
+ )
+)
pallets/contracts/fixtures/run_out_of_gas.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/run_out_of_gas.wat
@@ -0,0 +1,7 @@
+(module
+ (func (export "call")
+ (loop $inf (br $inf)) ;; just run out of gas
+ (unreachable)
+ )
+ (func (export "deploy"))
+)
pallets/contracts/fixtures/self_destruct.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/self_destruct.wat
@@ -0,0 +1,83 @@
+(module
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "seal0" "seal_address" (func $seal_address (param i32 i32)))
+ (import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32)))
+ (import "seal0" "seal_terminate" (func $seal_terminate (param i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ ;; [0, 32) reserved for $seal_address output
+
+ ;; [32, 36) length of the buffer
+ (data (i32.const 32) "\20")
+
+ ;; [36, 68) Address of django
+ (data (i32.const 36)
+ "\04\04\04\04\04\04\04\04\04\04\04\04\04\04\04\04"
+ "\04\04\04\04\04\04\04\04\04\04\04\04\04\04\04\04"
+ )
+
+ ;; [68, 72) reserved for output of $seal_input
+
+ ;; [72, 76) length of the buffer
+ (data (i32.const 72) "\04")
+
+ ;; [76, inf) zero initialized
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ (func (export "deploy"))
+
+ (func (export "call")
+ ;; If the input data is not empty, then recursively call self with empty input data.
+ ;; This should trap instead of self-destructing since a contract cannot be removed live in
+ ;; the execution stack cannot be removed. If the recursive call traps, then trap here as
+ ;; well.
+ (call $seal_input (i32.const 68) (i32.const 72))
+ (if (i32.load (i32.const 72))
+ (then
+ (call $seal_address (i32.const 0) (i32.const 32))
+
+ ;; Expect address to be 8 bytes.
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 32))
+ (i32.const 32)
+ )
+ )
+
+ ;; Recursively call self with empty input data.
+ (call $assert
+ (i32.eq
+ (call $seal_call
+ (i32.const 0) ;; Pointer to own address
+ (i32.const 32) ;; Length of own address
+ (i64.const 0) ;; How much gas to devote for the execution. 0 = all.
+ (i32.const 76) ;; Pointer to the buffer with value to transfer
+ (i32.const 8) ;; Length of the buffer with value to transfer
+ (i32.const 0) ;; Pointer to input data buffer address
+ (i32.const 0) ;; Length of input data buffer
+ (i32.const 4294967295) ;; u32 max sentinel value: do not copy output
+ (i32.const 0) ;; Length is ignored in this case
+ )
+ (i32.const 0)
+ )
+ )
+ )
+ (else
+ ;; Try to terminate and give balance to django.
+ (call $seal_terminate
+ (i32.const 36) ;; Pointer to beneficiary address
+ (i32.const 32) ;; Length of beneficiary address
+ )
+ (unreachable) ;; seal_terminate never returns
+ )
+ )
+ )
+)
pallets/contracts/fixtures/self_destructing_constructor.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/self_destructing_constructor.wat
@@ -0,0 +1,23 @@
+(module
+ (import "seal0" "seal_terminate" (func $seal_terminate (param i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ (func (export "deploy")
+ ;; Self-destruct by sending full balance to the 0 address.
+ (call $seal_terminate
+ (i32.const 0) ;; Pointer to destination address
+ (i32.const 32) ;; Length of destination address
+ )
+ )
+
+ (func (export "call"))
+)
pallets/contracts/fixtures/set_empty_storage.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/set_empty_storage.wat
@@ -0,0 +1,15 @@
+;; This module stores a KV pair into the storage
+(module
+ (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
+ (import "env" "memory" (memory 16 16))
+
+ (func (export "call")
+ )
+ (func (export "deploy")
+ (call $seal_set_storage
+ (i32.const 0) ;; Pointer to storage key
+ (i32.const 0) ;; Pointer to value
+ (i32.load (i32.const 0)) ;; Size of value
+ )
+ )
+)
pallets/contracts/fixtures/set_rent.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/set_rent.wat
@@ -0,0 +1,105 @@
+(module
+ (import "seal0" "seal_transfer" (func $seal_transfer (param i32 i32 i32 i32) (result i32)))
+ (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
+ (import "seal0" "seal_clear_storage" (func $seal_clear_storage (param i32)))
+ (import "seal0" "seal_set_rent_allowance" (func $seal_set_rent_allowance (param i32 i32)))
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ ;; insert a value of 4 bytes into storage
+ (func $call_0
+ (call $seal_set_storage
+ (i32.const 1)
+ (i32.const 0)
+ (i32.const 4)
+ )
+ )
+
+ ;; remove the value inserted by call_1
+ (func $call_1
+ (call $seal_clear_storage
+ (i32.const 1)
+ )
+ )
+
+ ;; transfer 50 to CHARLIE
+ (func $call_2
+ (call $assert
+ (i32.eq
+ (call $seal_transfer (i32.const 136) (i32.const 32) (i32.const 100) (i32.const 8))
+ (i32.const 0)
+ )
+ )
+ )
+
+ ;; do nothing
+ (func $call_else)
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ ;; Dispatch the call according to input size
+ (func (export "call")
+ (local $input_size i32)
+ ;; 4 byte i32 for br_table followed by 32 byte destination for transfer
+ (i32.store (i32.const 128) (i32.const 36))
+ (call $seal_input (i32.const 132) (i32.const 128))
+ (set_local $input_size
+ (i32.load (i32.const 132))
+ )
+ (block $IF_ELSE
+ (block $IF_2
+ (block $IF_1
+ (block $IF_0
+ (br_table $IF_0 $IF_1 $IF_2 $IF_ELSE
+ (get_local $input_size)
+ )
+ (unreachable)
+ )
+ (call $call_0)
+ return
+ )
+ (call $call_1)
+ return
+ )
+ (call $call_2)
+ return
+ )
+ (call $call_else)
+ )
+
+ ;; Set into storage a 4 bytes value
+ ;; Set call set_rent_allowance with input
+ (func (export "deploy")
+ (call $seal_set_storage
+ (i32.const 0)
+ (i32.const 0)
+ (i32.const 4)
+ )
+ (i32.store (i32.const 128) (i32.const 64))
+ (call $seal_input
+ (i32.const 132)
+ (i32.const 128)
+ )
+ (call $seal_set_rent_allowance
+ (i32.const 132)
+ (i32.load (i32.const 128))
+ )
+ )
+
+ ;; Encoding of 10 in balance
+ (data (i32.const 0) "\28")
+
+ ;; encoding of 50 balance
+ (data (i32.const 100) "\32")
+
+ ;; [128, 132) size of seal input buffer
+
+ ;; [132, inf) output buffer for seal input
+)
pallets/contracts/fixtures/storage_size.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/storage_size.wat
@@ -0,0 +1,68 @@
+(module
+ (import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32)))
+ (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32)))
+ (import "seal0" "seal_input" (func $seal_input (param i32 i32)))
+ (import "env" "memory" (memory 16 16))
+
+ ;; [0, 32) storage key
+ (data (i32.const 0) "\01")
+
+ ;; [32, 36) buffer where input is copied (expected size of storage item)
+
+ ;; [36, 40) size of the input buffer
+ (data (i32.const 36) "\04")
+
+ ;; [40, 44) size of buffer for seal_get_storage set to max
+ (data (i32.const 40) "\FF\FF\FF\FF")
+
+ ;; [44, inf) seal_get_storage buffer
+
+ (func $assert (param i32)
+ (block $ok
+ (br_if $ok
+ (get_local 0)
+ )
+ (unreachable)
+ )
+ )
+
+ (func (export "call")
+ (call $seal_input (i32.const 32) (i32.const 36))
+
+ ;; assert input size == 4
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 36))
+ (i32.const 4)
+ )
+ )
+
+ ;; place a garbage value in storage, the size of which is specified by the call input.
+ (call $seal_set_storage
+ (i32.const 0) ;; Pointer to storage key
+ (i32.const 0) ;; Pointer to value
+ (i32.load (i32.const 32)) ;; Size of value
+ )
+
+ (call $assert
+ (i32.eq
+ (call $seal_get_storage
+ (i32.const 0) ;; Pointer to storage key
+ (i32.const 44) ;; buffer where to copy result
+ (i32.const 40) ;; pointer to size of buffer
+ )
+ (i32.const 0)
+ )
+ )
+
+ (call $assert
+ (i32.eq
+ (i32.load (i32.const 40))
+ (i32.load (i32.const 32))
+ )
+ )
+ )
+
+ (func (export "deploy"))
+
+)
pallets/contracts/fixtures/transfer_return_code.watdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/fixtures/transfer_return_code.wat
@@ -0,0 +1,34 @@
+;; This transfers 100 balance to the zero account and copies the return code
+;; of this transfer to the output buffer.
+(module
+ (import "seal0" "seal_transfer" (func $seal_transfer (param i32 i32 i32 i32) (result i32)))
+ (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
+ (import "env" "memory" (memory 1 1))
+
+ ;; [0, 32) zero-adress
+ (data (i32.const 0)
+ "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
+ "\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00\00"
+ )
+
+ ;; [32, 40) 100 balance
+ (data (i32.const 32) "\64\00\00\00\00\00\00\00")
+
+ ;; [40, 44) here we store the return code of the transfer
+
+ (func (export "deploy"))
+
+ (func (export "call")
+ (i32.store
+ (i32.const 40)
+ (call $seal_transfer
+ (i32.const 0) ;; ptr to destination address
+ (i32.const 32) ;; length of destination address
+ (i32.const 32) ;; ptr to value to transfer
+ (i32.const 8) ;; length of value to transfer
+ )
+ )
+ ;; exit with success and take transfer return code to the output buffer
+ (call $seal_return (i32.const 0) (i32.const 40) (i32.const 4))
+ )
+)
pallets/contracts/proc-macro/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/proc-macro/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "pallet-contracts-proc-macro"
+version = "3.0.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+license = "Apache-2.0"
+homepage = "https://substrate.dev"
+repository = "https://github.com/paritytech/substrate/"
+description = "Procedural macros used in pallet_contracts"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[lib]
+proc-macro = true
+
+[dependencies]
+proc-macro2 = "1"
+quote = "1"
+syn = "1"
+
+[dev-dependencies]
+
+[features]
+# If set the full output is generated. Do NOT set when generating for wasm runtime.
+full = []
pallets/contracts/proc-macro/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/proc-macro/src/lib.rs
@@ -0,0 +1,142 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 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.
+
+//! Proc macros used in the contracts module.
+
+#![no_std]
+
+extern crate alloc;
+
+use proc_macro2::TokenStream;
+use quote::{quote, quote_spanned};
+use syn::spanned::Spanned;
+use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, Ident};
+use alloc::string::ToString;
+
+/// This derives `Debug` for a struct where each field must be of some numeric type.
+/// It interprets each field as its represents some weight and formats it as times so that
+/// it is readable by humans.
+#[proc_macro_derive(WeightDebug)]
+pub fn derive_weight_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ derive_debug(input, format_weight)
+}
+
+/// This is basically identical to the std libs Debug derive but without adding any
+/// bounds to existing generics.
+#[proc_macro_derive(ScheduleDebug)]
+pub fn derive_schedule_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ derive_debug(input, format_default)
+}
+
+fn derive_debug(
+ input: proc_macro::TokenStream,
+ fmt: impl Fn(&Ident) -> TokenStream
+) -> proc_macro::TokenStream {
+ let input = parse_macro_input!(input as DeriveInput);
+ let name = &input.ident;
+ let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
+ let data = if let Data::Struct(data) = &input.data {
+ data
+ } else {
+ return quote_spanned! {
+ name.span() =>
+ compile_error!("WeightDebug is only supported for structs.");
+ }.into();
+ };
+
+ #[cfg(feature = "full")]
+ let fields = iterate_fields(data, fmt);
+
+ #[cfg(not(feature = "full"))]
+ let fields = {
+ drop(fmt);
+ drop(data);
+ TokenStream::new()
+ };
+
+ let tokens = quote! {
+ impl #impl_generics core::fmt::Debug for #name #ty_generics #where_clause {
+ fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ use ::sp_runtime::{FixedPointNumber, FixedU128 as Fixed};
+ let mut formatter = formatter.debug_struct(stringify!(#name));
+ #fields
+ formatter.finish()
+ }
+ }
+ };
+
+ tokens.into()
+}
+
+/// This is only used then the `full` feature is activated.
+#[cfg(feature = "full")]
+fn iterate_fields(data: &DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -> TokenStream {
+ match &data.fields {
+ Fields::Named(fields) => {
+ let recurse = fields.named
+ .iter()
+ .filter_map(|f| {
+ let name = f.ident.as_ref()?;
+ if name.to_string().starts_with('_') {
+ return None;
+ }
+ let value = fmt(name);
+ let ret = quote_spanned!{ f.span() =>
+ formatter.field(stringify!(#name), #value);
+ };
+ Some(ret)
+ });
+ quote!{
+ #( #recurse )*
+ }
+ }
+ Fields::Unnamed(fields) => quote_spanned!{
+ fields.span() =>
+ compile_error!("Unnamed fields are not supported")
+ },
+ Fields::Unit => quote!(),
+ }
+}
+
+fn format_weight(field: &Ident) -> TokenStream {
+ quote_spanned! { field.span() =>
+ &if self.#field > 1_000_000_000 {
+ format!(
+ "{:.1?} ms",
+ Fixed::saturating_from_rational(self.#field, 1_000_000_000).to_float()
+ )
+ } else if self.#field > 1_000_000 {
+ format!(
+ "{:.1?} µs",
+ Fixed::saturating_from_rational(self.#field, 1_000_000).to_float()
+ )
+ } else if self.#field > 1_000 {
+ format!(
+ "{:.1?} ns",
+ Fixed::saturating_from_rational(self.#field, 1_000).to_float()
+ )
+ } else {
+ format!("{} ps", self.#field)
+ }
+ }
+}
+
+fn format_default(field: &Ident) -> TokenStream {
+ quote_spanned! { field.span() =>
+ &self.#field
+ }
+}
pallets/contracts/rpc/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/rpc/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "pallet-contracts-rpc"
+version = "3.0.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+license = "Apache-2.0"
+homepage = "https://substrate.dev"
+repository = "https://github.com/paritytech/substrate/"
+description = "Node-specific RPC methods for interaction with contracts."
+readme = "README.md"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "2" }
+jsonrpc-core = "15"
+jsonrpc-core-client = "15"
+jsonrpc-derive = "15"
+serde = { version = "1", features = ["derive"] }
+
+# Substrate Dependencies
+pallet-contracts-primitives = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+pallet-contracts-rpc-runtime-api = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-api = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-blockchain = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-core = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-rpc = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-runtime = { version = "3.0.0", git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+
+[dev-dependencies]
+serde_json = "1"
pallets/contracts/rpc/README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/rpc/README.md
@@ -0,0 +1,3 @@
+Node-specific RPC methods for interaction with contracts.
+
+License: Apache-2.0
\ No newline at end of file
pallets/contracts/rpc/runtime-api/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/rpc/runtime-api/Cargo.toml
@@ -0,0 +1,32 @@
+[package]
+name = "pallet-contracts-rpc-runtime-api"
+version = "3.0.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2018"
+license = "Apache-2.0"
+homepage = "https://substrate.dev"
+repository = "https://github.com/paritytech/substrate/"
+description = "Runtime API definition required by Contracts RPC extensions."
+readme = "README.md"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "2", default-features = false, features = ["derive"] }
+
+# Substrate Dependencies
+pallet-contracts-primitives = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-api = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-runtime = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+sp-std = { version = "3.0.0", default-features = false, git = 'https://github.com/paritytech/substrate.git', branch = 'polkadot-v0.9.3' }
+
+[features]
+default = ["std"]
+std = [
+ "sp-api/std",
+ "codec/std",
+ "sp-std/std",
+ "sp-runtime/std",
+ "pallet-contracts-primitives/std",
+]
pallets/contracts/rpc/runtime-api/README.mddiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/rpc/runtime-api/README.md
@@ -0,0 +1,7 @@
+Runtime API definition required by Contracts RPC extensions.
+
+This API should be imported and implemented by the runtime,
+of a node that wants to use the custom RPC extension
+adding Contracts access methods.
+
+License: Apache-2.0
\ No newline at end of file
pallets/contracts/rpc/runtime-api/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/rpc/runtime-api/src/lib.rs
@@ -0,0 +1,82 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2019-2021 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.
+
+//! Runtime API definition required by Contracts RPC extensions.
+//!
+//! This API should be imported and implemented by the runtime,
+//! of a node that wants to use the custom RPC extension
+//! adding Contracts access methods.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use codec::Codec;
+use sp_std::vec::Vec;
+use pallet_contracts_primitives::{
+ ContractExecResult, GetStorageResult, RentProjectionResult, Code, ContractInstantiateResult,
+};
+
+sp_api::decl_runtime_apis! {
+ /// The API to interact with contracts without using executive.
+ pub trait ContractsApi<AccountId, Balance, BlockNumber, Hash> where
+ AccountId: Codec,
+ Balance: Codec,
+ BlockNumber: Codec,
+ Hash: Codec,
+ {
+ /// Perform a call from a specified account to a given contract.
+ ///
+ /// See [`pallet_contracts::Pallet::call`].
+ fn call(
+ origin: AccountId,
+ dest: AccountId,
+ value: Balance,
+ gas_limit: u64,
+ input_data: Vec<u8>,
+ ) -> ContractExecResult;
+
+ /// Instantiate a new contract.
+ ///
+ /// See [`pallet_contracts::Pallet::instantiate`].
+ fn instantiate(
+ origin: AccountId,
+ endowment: Balance,
+ gas_limit: u64,
+ code: Code<Hash>,
+ data: Vec<u8>,
+ salt: Vec<u8>,
+ ) -> ContractInstantiateResult<AccountId, BlockNumber>;
+
+ /// Query a given storage key in a given contract.
+ ///
+ /// Returns `Ok(Some(Vec<u8>))` if the storage value exists under the given key in the
+ /// specified account and `Ok(None)` if it doesn't. If the account specified by the address
+ /// doesn't exist, or doesn't have a contract or if the contract is a tombstone, then `Err`
+ /// is returned.
+ fn get_storage(
+ address: AccountId,
+ key: [u8; 32],
+ ) -> GetStorageResult;
+
+ /// Returns the projected time a given contract will be able to sustain paying its rent.
+ ///
+ /// The returned projection is relevant for the current block, i.e. it is as if the contract
+ /// was accessed at the current block.
+ ///
+ /// Returns `Err` if the contract is in a tombstone state or doesn't exist.
+ fn rent_projection(address: AccountId) -> RentProjectionResult<BlockNumber>;
+ }
+}
pallets/contracts/rpc/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/rpc/src/lib.rs
@@ -0,0 +1,434 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2019-2021 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.
+
+//! Node-specific RPC methods for interaction with contracts.
+
+use std::sync::Arc;
+
+use codec::Codec;
+use jsonrpc_core::{Error, ErrorCode, Result};
+use jsonrpc_derive::rpc;
+use pallet_contracts_primitives::RentProjection;
+use serde::{Deserialize, Serialize};
+use sp_api::ProvideRuntimeApi;
+use sp_blockchain::HeaderBackend;
+use sp_core::{Bytes, H256};
+use sp_rpc::number::NumberOrHex;
+use sp_runtime::{
+ generic::BlockId,
+ traits::{Block as BlockT, Header as HeaderT},
+};
+use std::convert::{TryFrom, TryInto};
+use pallet_contracts_primitives::{Code, ContractExecResult, ContractInstantiateResult};
+
+pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi;
+
+const RUNTIME_ERROR: i64 = 1;
+const CONTRACT_DOESNT_EXIST: i64 = 2;
+const CONTRACT_IS_A_TOMBSTONE: i64 = 3;
+
+pub type Weight = u64;
+
+/// A rough estimate of how much gas a decent hardware consumes per second,
+/// using native execution.
+/// This value is used to set the upper bound for maximal contract calls to
+/// prevent blocking the RPC for too long.
+///
+/// As 1 gas is equal to 1 weight we base this on the conducted benchmarks which
+/// determined runtime weights:
+/// https://github.com/paritytech/substrate/pull/5446
+const GAS_PER_SECOND: Weight = 1_000_000_000_000;
+
+/// The maximum amount of weight that the call and instantiate rpcs are allowed to consume.
+/// This puts a ceiling on the weight limit that is supplied to the rpc as an argument.
+const GAS_LIMIT: Weight = 5 * GAS_PER_SECOND;
+
+/// A private newtype for converting `ContractAccessError` into an RPC error.
+struct ContractAccessError(pallet_contracts_primitives::ContractAccessError);
+impl From<ContractAccessError> for Error {
+ fn from(e: ContractAccessError) -> Error {
+ use pallet_contracts_primitives::ContractAccessError::*;
+ match e.0 {
+ DoesntExist => Error {
+ code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST),
+ message: "The specified contract doesn't exist.".into(),
+ data: None,
+ },
+ IsTombstone => Error {
+ code: ErrorCode::ServerError(CONTRACT_IS_A_TOMBSTONE),
+ message: "The contract is a tombstone and doesn't have any storage.".into(),
+ data: None,
+ },
+ }
+ }
+}
+
+/// A struct that encodes RPC parameters required for a call to a smart-contract.
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
+pub struct CallRequest<AccountId> {
+ origin: AccountId,
+ dest: AccountId,
+ value: NumberOrHex,
+ gas_limit: NumberOrHex,
+ input_data: Bytes,
+}
+
+/// A struct that encodes RPC parameters required to instantiate a new smart-contract.
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+#[serde(deny_unknown_fields)]
+pub struct InstantiateRequest<AccountId, Hash> {
+ origin: AccountId,
+ endowment: NumberOrHex,
+ gas_limit: NumberOrHex,
+ code: Code<Hash>,
+ data: Bytes,
+ salt: Bytes,
+}
+
+/// Contracts RPC methods.
+#[rpc]
+pub trait ContractsApi<BlockHash, BlockNumber, AccountId, Balance, Hash> {
+ /// Executes a call to a contract.
+ ///
+ /// This call is performed locally without submitting any transactions. Thus executing this
+ /// won't change any state. Nonetheless, the calling state-changing contracts is still possible.
+ ///
+ /// This method is useful for calling getter-like methods on contracts.
+ #[rpc(name = "contracts_call")]
+ fn call(
+ &self,
+ call_request: CallRequest<AccountId>,
+ at: Option<BlockHash>,
+ ) -> Result<ContractExecResult>;
+
+ /// Instantiate a new contract.
+ ///
+ /// This call is performed locally without submitting any transactions. Thus the contract
+ /// is not actually created.
+ ///
+ /// This method is useful for UIs to dry-run contract instantiations.
+ #[rpc(name = "contracts_instantiate")]
+ fn instantiate(
+ &self,
+ instantiate_request: InstantiateRequest<AccountId, Hash>,
+ at: Option<BlockHash>,
+ ) -> Result<ContractInstantiateResult<AccountId, BlockNumber>>;
+
+ /// Returns the value under a specified storage `key` in a contract given by `address` param,
+ /// or `None` if it is not set.
+ #[rpc(name = "contracts_getStorage")]
+ fn get_storage(
+ &self,
+ address: AccountId,
+ key: H256,
+ at: Option<BlockHash>,
+ ) -> Result<Option<Bytes>>;
+
+ /// Returns the projected time a given contract will be able to sustain paying its rent.
+ ///
+ /// The returned projection is relevant for the given block, i.e. it is as if the contract was
+ /// accessed at the beginning of that block.
+ ///
+ /// Returns `None` if the contract is exempted from rent.
+ #[rpc(name = "contracts_rentProjection")]
+ fn rent_projection(
+ &self,
+ address: AccountId,
+ at: Option<BlockHash>,
+ ) -> Result<Option<BlockNumber>>;
+}
+
+/// An implementation of contract specific RPC methods.
+pub struct Contracts<C, B> {
+ client: Arc<C>,
+ _marker: std::marker::PhantomData<B>,
+}
+
+impl<C, B> Contracts<C, B> {
+ /// Create new `Contracts` with the given reference to the client.
+ pub fn new(client: Arc<C>) -> Self {
+ Contracts {
+ client,
+ _marker: Default::default(),
+ }
+ }
+}
+impl<C, Block, AccountId, Balance, Hash>
+ ContractsApi<
+ <Block as BlockT>::Hash,
+ <<Block as BlockT>::Header as HeaderT>::Number,
+ AccountId,
+ Balance,
+ Hash,
+ > for Contracts<C, Block>
+where
+ Block: BlockT,
+ C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,
+ C::Api: ContractsRuntimeApi<
+ Block,
+ AccountId,
+ Balance,
+ <<Block as BlockT>::Header as HeaderT>::Number,
+ Hash,
+ >,
+ AccountId: Codec,
+ Balance: Codec + TryFrom<NumberOrHex>,
+ Hash: Codec,
+{
+ fn call(
+ &self,
+ call_request: CallRequest<AccountId>,
+ at: Option<<Block as BlockT>::Hash>,
+ ) -> Result<ContractExecResult> {
+ let api = self.client.runtime_api();
+ let at = BlockId::hash(at.unwrap_or_else(||
+ // If the block hash is not supplied assume the best block.
+ self.client.info().best_hash));
+
+ let CallRequest {
+ origin,
+ dest,
+ value,
+ gas_limit,
+ input_data,
+ } = call_request;
+
+ let value: Balance = decode_hex(value, "balance")?;
+ let gas_limit: Weight = decode_hex(gas_limit, "weight")?;
+ limit_gas(gas_limit)?;
+
+ let exec_result = api
+ .call(&at, origin, dest, value, gas_limit, input_data.to_vec())
+ .map_err(runtime_error_into_rpc_err)?;
+
+ Ok(exec_result)
+ }
+
+ fn instantiate(
+ &self,
+ instantiate_request: InstantiateRequest<AccountId, Hash>,
+ at: Option<<Block as BlockT>::Hash>,
+ ) -> Result<ContractInstantiateResult<AccountId, <<Block as BlockT>::Header as HeaderT>::Number>> {
+ let api = self.client.runtime_api();
+ let at = BlockId::hash(at.unwrap_or_else(||
+ // If the block hash is not supplied assume the best block.
+ self.client.info().best_hash));
+
+ let InstantiateRequest {
+ origin,
+ endowment,
+ gas_limit,
+ code,
+ data,
+ salt,
+ } = instantiate_request;
+
+ let endowment: Balance = decode_hex(endowment, "balance")?;
+ let gas_limit: Weight = decode_hex(gas_limit, "weight")?;
+ limit_gas(gas_limit)?;
+
+ let exec_result = api
+ .instantiate(&at, origin, endowment, gas_limit, code, data.to_vec(), salt.to_vec())
+ .map_err(runtime_error_into_rpc_err)?;
+
+ Ok(exec_result)
+ }
+
+ fn get_storage(
+ &self,
+ address: AccountId,
+ key: H256,
+ at: Option<<Block as BlockT>::Hash>,
+ ) -> Result<Option<Bytes>> {
+ let api = self.client.runtime_api();
+ let at = BlockId::hash(at.unwrap_or_else(||
+ // If the block hash is not supplied assume the best block.
+ self.client.info().best_hash));
+
+ let result = api
+ .get_storage(&at, address, key.into())
+ .map_err(runtime_error_into_rpc_err)?
+ .map_err(ContractAccessError)?
+ .map(Bytes);
+
+ Ok(result)
+ }
+
+ fn rent_projection(
+ &self,
+ address: AccountId,
+ at: Option<<Block as BlockT>::Hash>,
+ ) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
+ let api = self.client.runtime_api();
+ let at = BlockId::hash(at.unwrap_or_else(||
+ // If the block hash is not supplied assume the best block.
+ self.client.info().best_hash));
+
+ let result = api
+ .rent_projection(&at, address)
+ .map_err(runtime_error_into_rpc_err)?
+ .map_err(ContractAccessError)?;
+
+ Ok(match result {
+ RentProjection::NoEviction => None,
+ RentProjection::EvictionAt(block_num) => Some(block_num),
+ })
+ }
+}
+
+/// Converts a runtime trap into an RPC error.
+fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> Error {
+ Error {
+ code: ErrorCode::ServerError(RUNTIME_ERROR),
+ message: "Runtime error".into(),
+ data: Some(format!("{:?}", err).into()),
+ }
+}
+
+fn decode_hex<H: std::fmt::Debug + Copy, T: TryFrom<H>>(from: H, name: &str) -> Result<T> {
+ from.try_into().map_err(|_| Error {
+ code: ErrorCode::InvalidParams,
+ message: format!("{:?} does not fit into the {} type", from, name),
+ data: None,
+ })
+}
+
+fn limit_gas(gas_limit: Weight) -> Result<()> {
+ if gas_limit > GAS_LIMIT {
+ Err(Error {
+ code: ErrorCode::InvalidParams,
+ message: format!(
+ "Requested gas limit is greater than maximum allowed: {} > {}",
+ gas_limit, GAS_LIMIT
+ ),
+ data: None,
+ })
+ } else {
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use sp_core::U256;
+
+ fn trim(json: &str) -> String {
+ json.chars().filter(|c| !c.is_whitespace()).collect()
+ }
+
+ #[test]
+ fn call_request_should_serialize_deserialize_properly() {
+ type Req = CallRequest<String>;
+ let req: Req = serde_json::from_str(r#"
+ {
+ "origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
+ "dest": "5DRakbLVnjVrW6niwLfHGW24EeCEvDAFGEXrtaYS5M4ynoom",
+ "value": "0x112210f4B16c1cb1",
+ "gasLimit": 1000000000000,
+ "inputData": "0x8c97db39"
+ }
+ "#).unwrap();
+ assert_eq!(req.gas_limit.into_u256(), U256::from(0xe8d4a51000u64));
+ assert_eq!(req.value.into_u256(), U256::from(1234567890987654321u128));
+ }
+
+ #[test]
+ fn instantiate_request_should_serialize_deserialize_properly() {
+ type Req = InstantiateRequest<String, String>;
+ let req: Req = serde_json::from_str(r#"
+ {
+ "origin": "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
+ "endowment": "0x88",
+ "gasLimit": 42,
+ "code": { "existing": "0x1122" },
+ "data": "0x4299",
+ "salt": "0x9988"
+ }
+ "#).unwrap();
+
+ assert_eq!(req.origin, "5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL");
+ assert_eq!(req.endowment.into_u256(), 0x88.into());
+ assert_eq!(req.gas_limit.into_u256(), 42.into());
+ assert_eq!(&*req.data, [0x42, 0x99].as_ref());
+ assert_eq!(&*req.salt, [0x99, 0x88].as_ref());
+ let code = match req.code {
+ Code::Existing(hash) => hash,
+ _ => panic!("json encoded an existing hash"),
+ };
+ assert_eq!(&code, "0x1122");
+ }
+
+ #[test]
+ fn call_result_should_serialize_deserialize_properly() {
+ fn test(expected: &str) {
+ let res: ContractExecResult = serde_json::from_str(expected).unwrap();
+ let actual = serde_json::to_string(&res).unwrap();
+ assert_eq!(actual, trim(expected).as_str());
+ }
+ test(r#"{
+ "gasConsumed": 5000,
+ "debugMessage": "0x68656c704f6b",
+ "result": {
+ "Ok": {
+ "flags": 5,
+ "data": "0x1234"
+ }
+ }
+ }"#);
+ test(r#"{
+ "gasConsumed": 3400,
+ "debugMessage": "0x68656c70457272",
+ "result": {
+ "Err": "BadOrigin"
+ }
+ }"#);
+ }
+
+ #[test]
+ fn instantiate_result_should_serialize_deserialize_properly() {
+ fn test(expected: &str) {
+ let res: ContractInstantiateResult<String, u64> = serde_json::from_str(expected).unwrap();
+ let actual = serde_json::to_string(&res).unwrap();
+ assert_eq!(actual, trim(expected).as_str());
+ }
+ test(r#"{
+ "gasConsumed": 5000,
+ "debugMessage": "0x68656c704f6b",
+ "result": {
+ "Ok": {
+ "result": {
+ "flags": 5,
+ "data": "0x1234"
+ },
+ "accountId": "5CiPP",
+ "rentProjection": null
+ }
+ }
+ }"#);
+ test(r#"{
+ "gasConsumed": 3400,
+ "debugMessage": "0x68656c70457272",
+ "result": {
+ "Err": "BadOrigin"
+ }
+ }"#);
+ }
+}
pallets/contracts/src/benchmarking/code.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/benchmarking/code.rs
@@ -0,0 +1,526 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 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::{Config, CurrentSchedule};
+use parity_wasm::elements::{
+ Instruction, Instructions, FuncBody, ValueType, BlockType, Section, CustomSection,
+};
+use pwasm_utils::stack_height::inject_limiter;
+use sp_core::crypto::UncheckedFrom;
+use sp_runtime::traits::Hash;
+use sp_sandbox::{EnvironmentDefinitionBuilder, Memory};
+use sp_std::{prelude::*, convert::TryFrom, borrow::ToOwned};
+
+/// Pass to `create_code` in order to create a compiled `WasmModule`.
+///
+/// This exists to have a more declarative way to describe a wasm module than to use
+/// parity-wasm directly. It is tailored to fit the structure of contracts that are
+/// needed for benchmarking.
+#[derive(Default)]
+pub struct ModuleDefinition {
+ /// Imported memory attached to the module. No memory is imported if `None`.
+ pub memory: Option<ImportedMemory>,
+ /// Initializers for the imported memory.
+ pub data_segments: Vec<DataSegment>,
+ /// Creates the supplied amount of i64 mutable globals initialized with random values.
+ pub num_globals: u32,
+ /// List of functions that the module should import. They start with index 0.
+ pub imported_functions: Vec<ImportedFunction>,
+ /// Function body of the exported `deploy` function. Body is empty if `None`.
+ /// Its index is `imported_functions.len()`.
+ pub deploy_body: Option<FuncBody>,
+ /// Function body of the exported `call` function. Body is empty if `None`.
+ /// Its index is `imported_functions.len() + 1`.
+ pub call_body: Option<FuncBody>,
+ /// Function body of a non-exported function with index `imported_functions.len() + 2`.
+ pub aux_body: Option<FuncBody>,
+ /// The amount of I64 arguments the aux function should have.
+ pub aux_arg_num: u32,
+ /// If set to true the stack height limiter is injected into the the module. This is
+ /// needed for instruction debugging because the cost of executing the stack height
+ /// instrumentation should be included in the costs for the individual instructions
+ /// that cause more metering code (only call).
+ pub inject_stack_metering: bool,
+ /// Create a table containing function pointers.
+ pub table: Option<TableSegment>,
+ /// Create a section named "dummy" of the specified size. This is useful in order to
+ /// benchmark the overhead of loading and storing codes of specified sizes. The dummy
+ /// section only contributes to the size of the contract but does not affect execution.
+ pub dummy_section: u32,
+}
+
+pub struct TableSegment {
+ /// How many elements should be created inside the table.
+ pub num_elements: u32,
+ /// The function index with which all table elements should be initialized.
+ pub function_index: u32,
+}
+
+pub struct DataSegment {
+ pub offset: u32,
+ pub value: Vec<u8>,
+}
+
+#[derive(Clone)]
+pub struct ImportedMemory {
+ pub min_pages: u32,
+ pub max_pages: u32,
+}
+
+impl ImportedMemory {
+ pub fn max<T: Config>() -> Self
+ where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+ {
+ let pages = max_pages::<T>();
+ Self {
+ min_pages: pages,
+ max_pages: pages,
+ }
+ }
+}
+
+pub struct ImportedFunction {
+ pub name: &'static str,
+ pub params: Vec<ValueType>,
+ pub return_type: Option<ValueType>,
+}
+
+/// A wasm module ready to be put on chain.
+#[derive(Clone)]
+pub struct WasmModule<T: Config> {
+ pub code: Vec<u8>,
+ pub hash: <T::Hashing as Hash>::Output,
+ memory: Option<ImportedMemory>,
+}
+
+impl<T: Config> From<ModuleDefinition> for WasmModule<T>
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ 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()
+ .build()
+ .with_body(
+ def.deploy_body
+ .unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
+ )
+ .build()
+ // call function (second internal function)
+ .function()
+ .signature()
+ .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();
+
+ // If specified we add an additional internal function
+ if let Some(body) = def.aux_body {
+ let mut signature = contract.function().signature();
+ for _ in 0..def.aux_arg_num {
+ signature = signature.with_param(ValueType::I64);
+ }
+ contract = signature.build().with_body(body).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_results(func.return_type.into_iter().collect())
+ .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()
+ }
+
+ // Add global variables
+ if def.num_globals > 0 {
+ use rand::{prelude::*, distributions::Standard};
+ let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);
+ for val in rng.sample_iter(Standard).take(def.num_globals as usize) {
+ contract = contract
+ .global()
+ .value_type()
+ .i64()
+ .mutable()
+ .init_expr(Instruction::I64Const(val))
+ .build()
+ }
+ }
+
+ // Add function pointer table
+ if let Some(table) = def.table {
+ contract = contract
+ .table()
+ .with_min(table.num_elements)
+ .with_max(Some(table.num_elements))
+ .with_element(0, vec![table.function_index; table.num_elements as usize])
+ .build();
+ }
+
+ // Add the dummy section
+ if def.dummy_section > 0 {
+ contract = contract.with_section(Section::Custom(CustomSection::new(
+ "dummy".to_owned(),
+ vec![42; def.dummy_section as usize],
+ )));
+ }
+
+ let mut code = contract.build();
+
+ // Inject stack height metering
+ if def.inject_stack_metering {
+ code = inject_limiter(code, <CurrentSchedule<T>>::get().limits.stack_height).unwrap();
+ }
+
+ let code = code.to_bytes().unwrap();
+ let hash = T::Hashing::hash(&code);
+ Self {
+ code,
+ hash,
+ memory: def.memory,
+ }
+ }
+}
+
+impl<T: Config> WasmModule<T>
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ /// Creates a wasm module with an empty `call` and `deploy` function and nothing else.
+ pub fn dummy() -> Self {
+ ModuleDefinition::default().into()
+ }
+
+ /// Same as `dummy` but with maximum sized linear memory and a dummy section of specified size.
+ pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
+ ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ dummy_section: dummy_bytes,
+ ..Default::default()
+ }
+ .into()
+ }
+
+ /// Creates a wasm module of `target_bytes` size. Used to benchmark the performance of
+ /// `instantiate_with_code` for different sizes of wasm modules. The generated module maximizes
+ /// instrumentation runtime by nesting blocks as deeply as possible given the byte budget.
+ pub fn sized(target_bytes: u32) -> Self {
+ use parity_wasm::elements::Instruction::{If, I32Const, Return, End};
+ // Base size of a contract is 63 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 `instantiate_with_code`.
+ let expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
+ const EXPANSION: [Instruction; 4] = [I32Const(0), If(BlockType::NoResult), Return, End];
+ ModuleDefinition {
+ call_body: Some(body::repeated(expansions, &EXPANSION)),
+ memory: Some(ImportedMemory::max::<T>()),
+ ..Default::default()
+ }
+ .into()
+ }
+
+ /// Creates a wasm module that calls the imported function named `getter_name` `repeat`
+ /// times. The imported function is expected to have the "getter signature" of
+ /// (out_ptr: u32, len_ptr: u32) -> ().
+ pub fn getter(getter_name: &'static str, repeat: u32) -> Self {
+ let pages = max_pages::<T>();
+ ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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()
+ }
+
+ /// Creates a wasm module that calls the imported hash function named `name` `repeat` times
+ /// with an input of size `data_size`. Hash functions have the signature
+ /// (input_ptr: u32, input_len: u32, output_ptr: u32) -> ()
+ pub fn hasher(name: &'static str, repeat: u32, data_size: u32) -> Self {
+ ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ imported_functions: vec![ImportedFunction {
+ 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()
+ }
+
+ /// Creates a memory instance for use in a sandbox with dimensions declared in this module
+ /// and adds it to `env`. A reference to that memory is returned so that it can be used to
+ /// access the memory contents from the supervisor.
+ pub fn add_memory<S>(&self, env: &mut EnvironmentDefinitionBuilder<S>) -> Option<Memory> {
+ let memory = if let Some(memory) = &self.memory {
+ memory
+ } else {
+ return None;
+ };
+ let memory = Memory::new(memory.min_pages, Some(memory.max_pages)).unwrap();
+ env.add_memory("env", "memory", memory.clone());
+ Some(memory)
+ }
+
+ pub fn unary_instr(instr: Instruction, repeat: u32) -> Self {
+ use body::DynInstr::{RandomI64Repeated, Regular};
+ ModuleDefinition {
+ call_body: Some(body::repeated_dyn(
+ repeat,
+ vec![
+ RandomI64Repeated(1),
+ Regular(instr),
+ Regular(Instruction::Drop),
+ ],
+ )),
+ ..Default::default()
+ }
+ .into()
+ }
+
+ pub fn binary_instr(instr: Instruction, repeat: u32) -> Self {
+ use body::DynInstr::{RandomI64Repeated, Regular};
+ ModuleDefinition {
+ call_body: Some(body::repeated_dyn(
+ repeat,
+ vec![
+ RandomI64Repeated(2),
+ Regular(instr),
+ Regular(Instruction::Drop),
+ ],
+ )),
+ ..Default::default()
+ }
+ .into()
+ }
+}
+
+/// Mechanisms to generate a function body that can be used inside a `ModuleDefinition`.
+pub mod body {
+ use super::*;
+
+ /// When generating contract code by repeating a wasm sequence, it's sometimes necessary
+ /// to change those instructions on each repetition. The variants of this enum describe
+ /// various ways in which this can happen.
+ pub enum DynInstr {
+ /// Insert the associated instruction.
+ Regular(Instruction),
+ /// Insert a I32Const with incrementing value for each insertion.
+ /// (start_at, increment_by)
+ Counter(u32, u32),
+ /// Insert a I32Const with a random value in [low, high) not divisible by two.
+ /// (low, high)
+ RandomUnaligned(u32, u32),
+ /// Insert a I32Const with a random value in [low, high).
+ /// (low, high)
+ RandomI32(i32, i32),
+ /// Insert the specified amount of I32Const with a random value.
+ RandomI32Repeated(usize),
+ /// Insert the specified amount of I64Const with a random value.
+ RandomI64Repeated(usize),
+ /// Insert a GetLocal with a random offset in [low, high).
+ /// (low, high)
+ RandomGetLocal(u32, u32),
+ /// Insert a SetLocal with a random offset in [low, high).
+ /// (low, high)
+ RandomSetLocal(u32, u32),
+ /// Insert a TeeLocal with a random offset in [low, high).
+ /// (low, high)
+ RandomTeeLocal(u32, u32),
+ /// Insert a GetGlobal with a random offset in [low, high).
+ /// (low, high)
+ RandomGetGlobal(u32, u32),
+ /// Insert a SetGlobal with a random offset in [low, high).
+ /// (low, high)
+ RandomSetGlobal(u32, u32),
+ }
+
+ pub fn plain(instructions: Vec<Instruction>) -> 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 repeated_dyn(repetitions: u32, mut instructions: Vec<DynInstr>) -> FuncBody {
+ use rand::{prelude::*, distributions::Standard};
+
+ // We do not need to be secure here.
+ let mut rng = rand_pcg::Pcg32::seed_from_u64(8446744073709551615);
+
+ // 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())
+ .flat_map(|idx| match &mut instructions[idx] {
+ DynInstr::Regular(instruction) => vec![instruction.clone()],
+ DynInstr::Counter(offset, increment_by) => {
+ let current = *offset;
+ *offset += *increment_by;
+ vec![Instruction::I32Const(current as i32)]
+ }
+ DynInstr::RandomUnaligned(low, high) => {
+ let unaligned = rng.gen_range(*low..*high) | 1;
+ vec![Instruction::I32Const(unaligned as i32)]
+ }
+ DynInstr::RandomI32(low, high) => {
+ vec![Instruction::I32Const(rng.gen_range(*low..*high))]
+ }
+ DynInstr::RandomI32Repeated(num) => (&mut rng)
+ .sample_iter(Standard)
+ .take(*num)
+ .map(|val| Instruction::I32Const(val))
+ .collect(),
+ DynInstr::RandomI64Repeated(num) => (&mut rng)
+ .sample_iter(Standard)
+ .take(*num)
+ .map(|val| Instruction::I64Const(val))
+ .collect(),
+ DynInstr::RandomGetLocal(low, high) => {
+ vec![Instruction::GetLocal(rng.gen_range(*low..*high))]
+ }
+ DynInstr::RandomSetLocal(low, high) => {
+ vec![Instruction::SetLocal(rng.gen_range(*low..*high))]
+ }
+ DynInstr::RandomTeeLocal(low, high) => {
+ vec![Instruction::TeeLocal(rng.gen_range(*low..*high))]
+ }
+ DynInstr::RandomGetGlobal(low, high) => {
+ vec![Instruction::GetGlobal(rng.gen_range(*low..*high))]
+ }
+ DynInstr::RandomSetGlobal(low, high) => {
+ vec![Instruction::SetGlobal(rng.gen_range(*low..*high))]
+ }
+ })
+ .chain(sp_std::iter::once(Instruction::End))
+ .collect();
+ FuncBody::new(Vec::new(), Instructions::new(body))
+ }
+
+ /// Replace the locals of the supplied `body` with `num` i64 locals.
+ pub fn inject_locals(body: &mut FuncBody, num: u32) {
+ use parity_wasm::elements::Local;
+ *body.locals_mut() = (0..num).map(|i| Local::new(i, ValueType::I64)).collect()
+ }
+}
+
+/// The maximum amount of pages any contract is allowed to have according to the current `Schedule`.
+pub fn max_pages<T: Config>() -> u32
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ <CurrentSchedule<T>>::get().limits.memory_pages
+}
pallets/contracts/src/benchmarking/mod.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/benchmarking/mod.rs
@@ -0,0 +1,2537 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 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;
+mod sandbox;
+
+use crate::{
+ *, Pallet as Contracts,
+ exec::StorageKey,
+ rent::Rent,
+ schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE},
+ storage::Storage,
+};
+use self::{
+ code::{
+ body::{self, DynInstr::*},
+ ModuleDefinition, DataSegment, ImportedMemory, ImportedFunction, WasmModule,
+ },
+ sandbox::Sandbox,
+};
+use codec::Encode;
+use frame_benchmarking::{benchmarks, account, whitelisted_caller, impl_benchmark_test_suite};
+use frame_system::{Pallet as System, RawOrigin};
+use parity_wasm::elements::{Instruction, ValueType, BlockType};
+use sp_runtime::traits::{Hash, Bounded, Zero};
+use sp_std::{
+ default::Default,
+ convert::{TryInto},
+ vec::Vec,
+ vec,
+};
+use pallet_contracts_primitives::RentProjection;
+use frame_support::weights::Weight;
+
+/// How many batches we do per API benchmark.
+const API_BENCHMARK_BATCHES: u32 = 20;
+
+/// How many batches we do per Instruction benchmark.
+const INSTR_BENCHMARK_BATCHES: u32 = 1;
+
+/// An instantiated and deployed contract.
+struct Contract<T: Config> {
+ caller: T::AccountId,
+ account_id: T::AccountId,
+ addr: <T::Lookup as StaticLookup>::Source,
+ endowment: BalanceOf<T>,
+ code_hash: <T::Hashing as 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<T: Config>() -> BalanceOf<T> {
+ caller_funding::<T>().saturating_sub(T::Currency::minimum_balance())
+ }
+}
+
+impl<T: Config> Contract<T>
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ /// Create new contract and use a default account id as instantiator.
+ fn new(
+ module: WasmModule<T>,
+ data: Vec<u8>,
+ endowment: Endow,
+ ) -> Result<Contract<T>, &'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<T>,
+ data: Vec<u8>,
+ endowment: Endow,
+ ) -> Result<Contract<T>, &'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<T>,
+ data: Vec<u8>,
+ endowment: Endow,
+ ) -> Result<Contract<T>, &'static str> {
+ 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 = u32::max_value() / 10;
+
+ // Endowment should be large but not as large to inhibit rent payments.
+ // Balance will only cover half the storage
+ let endowment = T::DepositPerStorageByte::get()
+ .saturating_mul(<BalanceOf<T>>::from(storage_size) / 2u32.into())
+ .saturating_add(T::DepositPerContract::get());
+
+ (storage_size, endowment)
+ }
+ Endow::Max => (0u32.into(), Endow::max::<T>()),
+ };
+ T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
+ let salt = vec![0xff];
+ let addr = Contracts::<T>::contract_address(&caller, &module.hash, &salt);
+
+ // 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), we do the bump ourselves
+ // in the setup closure so that both the instantiate and subsequent call are run with the
+ // same block number.
+ System::<T>::set_block_number(1u32.into());
+
+ Contracts::<T>::store_code_raw(module.code)?;
+ Contracts::<T>::instantiate(
+ RawOrigin::Signed(caller.clone()).into(),
+ endowment,
+ Weight::max_value(),
+ module.hash,
+ data,
+ salt,
+ )?;
+
+ 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;
+ ContractInfoOf::<T>::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<u8>)>) -> Result<(), &'static str> {
+ let info = self.alive_info()?;
+ for item in items {
+ Storage::<T>::write(
+ &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<AliveContractInfo<T>, &'static str> {
+ ContractInfoOf::<T>::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<AliveContractInfo<T>, &'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::<T>::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<T::BlockNumber, &'static str> {
+ let projection = Rent::<T, PrefabWasmModule<T>>::compute_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 contains some storage items.
+///
+/// This is used to benchmark contract destruction and resurection. Those operations'
+/// weight depend on the amount of storage accumulated.
+struct ContractWithStorage<T: Config> {
+ /// The contract that was evicted.
+ contract: Contract<T>,
+ /// The storage the contract held when it was avicted.
+ storage: Vec<(StorageKey, Vec<u8>)>,
+}
+
+impl<T: Config> ContractWithStorage<T>
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ /// Same as [`Self::with_code`] but with dummy contract code.
+ fn new(stor_num: u32, stor_size: u32) -> Result<Self, &'static str> {
+ Self::with_code(WasmModule::dummy(), stor_num, stor_size)
+ }
+
+ /// Create and evict a new contract with the supplied storage item count and size each.
+ fn with_code(code: WasmModule<T>, stor_num: u32, stor_size: u32) -> Result<Self, &'static str> {
+ let contract = Contract::<T>::new(code, vec![], Endow::CollectRent)?;
+ let storage_items = create_storage::<T>(stor_num, stor_size)?;
+ contract.store(&storage_items)?;
+ Ok(Self {
+ contract,
+ storage: storage_items,
+ })
+ }
+
+ /// Increase the system block number so that this contract is eligible for eviction.
+ fn set_block_num_for_eviction(&self) -> Result<(), &'static str> {
+ System::<T>::set_block_number(
+ self.contract.eviction_at()? + T::SignedClaimHandicap::get() + 5u32.into(),
+ );
+ Ok(())
+ }
+
+ /// Evict this contract.
+ fn evict(&mut self) -> Result<(), &'static str> {
+ self.set_block_num_for_eviction()?;
+ Rent::<T, PrefabWasmModule<T>>::try_eviction(&self.contract.account_id, Zero::zero())?;
+ self.contract.ensure_tombstone()
+ }
+}
+
+/// Generate `stor_num` storage items. Each has the size `stor_size`.
+fn create_storage<T: Config>(
+ stor_num: u32,
+ stor_size: u32,
+) -> Result<Vec<(StorageKey, Vec<u8>)>, &'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::<Result<Vec<_>, &'static str>>()
+}
+
+/// The funding that each account that either calls or instantiates contracts is funded with.
+fn caller_funding<T: Config>() -> BalanceOf<T> {
+ BalanceOf::<T>::max_value() / 2u32.into()
+}
+
+benchmarks! {
+ where_clause { where
+ T::AccountId: UncheckedFrom<T::Hash>,
+ T::AccountId: AsRef<[u8]>,
+ }
+
+ // The base weight without any actual work performed apart from the setup costs.
+ on_initialize {}: {
+ Storage::<T>::process_deletion_queue_batch(Weight::max_value())
+ }
+
+ on_initialize_per_trie_key {
+ let k in 0..1024;
+ let instance = ContractWithStorage::<T>::new(k, T::MaxValueSize::get())?;
+ Storage::<T>::queue_trie_for_deletion(&instance.contract.alive_info()?)?;
+ }: {
+ Storage::<T>::process_deletion_queue_batch(Weight::max_value())
+ }
+
+ on_initialize_per_queue_item {
+ let q in 0..1024.min(T::DeletionQueueDepth::get());
+ for i in 0 .. q {
+ let instance = Contract::<T>::with_index(i, WasmModule::dummy(), vec![], Endow::Max)?;
+ Storage::<T>::queue_trie_for_deletion(&instance.alive_info()?)?;
+ ContractInfoOf::<T>::remove(instance.account_id);
+ }
+ }: {
+ Storage::<T>::process_deletion_queue_batch(Weight::max_value())
+ }
+
+ // This benchmarks the additional weight that is charged when a contract is executed the
+ // first time after a new schedule was deployed: For every new schedule a contract needs
+ // to re-run the instrumentation once.
+ instrument {
+ let c in 0 .. T::MaxCodeSize::get() / 1024;
+ let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c * 1024);
+ Contracts::<T>::store_code_raw(code)?;
+ let mut module = PrefabWasmModule::from_storage_noinstr(hash)?;
+ let schedule = <CurrentSchedule<T>>::get();
+ }: {
+ Contracts::<T>::reinstrument_module(&mut module, &schedule)?;
+ }
+
+ // 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.
+ // The size of the salt influences the runtime because is is hashed in order to
+ // determine the contract address.
+ // `c`: Size of the code in kilobytes.
+ // `s`: Size of the salt in kilobytes.
+ //
+ // # Note
+ //
+ // We cannot let `c` grow to the maximum code size because the code is not allowed
+ // to be larger than the maximum size **after instrumentation**.
+ instantiate_with_code {
+ let c in 0 .. Perbill::from_percent(50).mul_ceil(T::MaxCodeSize::get() / 1024);
+ let s in 0 .. code::max_pages::<T>() * 64;
+ let salt = vec![42u8; (s * 1024) as usize];
+ let endowment = caller_funding::<T>() / 3u32.into();
+ let caller = whitelisted_caller();
+ T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
+ let WasmModule { code, hash, .. } = WasmModule::<T>::sized(c * 1024);
+ let origin = RawOrigin::Signed(caller.clone());
+ let addr = Contracts::<T>::contract_address(&caller, &hash, &salt);
+ }: _(origin, endowment, Weight::max_value(), code, vec![], salt)
+ verify {
+ // endowment was removed from the caller
+ assert_eq!(T::Currency::free_balance(&caller), caller_funding::<T>() - 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::<T>::address_alive_info(&addr)?;
+ }
+
+ // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate.
+ // `c`: Size of the code in kilobytes.
+ // `s`: Size of the salt in kilobytes.
+ instantiate {
+ let c in 0 .. T::MaxCodeSize::get() / 1024;
+ let s in 0 .. code::max_pages::<T>() * 64;
+ let salt = vec![42u8; (s * 1024) as usize];
+ let endowment = caller_funding::<T>() / 3u32.into();
+ let caller = whitelisted_caller();
+ T::Currency::make_free_balance_be(&caller, caller_funding::<T>());
+ let WasmModule { code, hash, .. } = WasmModule::<T>::dummy_with_bytes(c * 1024);
+ let origin = RawOrigin::Signed(caller.clone());
+ let addr = Contracts::<T>::contract_address(&caller, &hash, &salt);
+ Contracts::<T>::store_code_raw(code)?;
+ }: _(origin, endowment, Weight::max_value(), hash, vec![], salt)
+ verify {
+ // endowment was removed from the caller
+ assert_eq!(T::Currency::free_balance(&caller), caller_funding::<T>() - 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::<T>::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`.
+ // `c`: Size of the code in kilobytes.
+ call {
+ let c in 0 .. T::MaxCodeSize::get() / 1024;
+ let data = vec![42u8; 1024];
+ let instance = Contract::<T>::with_caller(
+ whitelisted_caller(), WasmModule::dummy_with_bytes(c * 1024), vec![], Endow::CollectRent
+ )?;
+ let value = T::Currency::minimum_balance() * 100u32.into();
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ let callee = instance.addr.clone();
+
+ // trigger rent collection for worst case performance of call
+ System::<T>::set_block_number(instance.eviction_at()? - 5u32.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::<T>() - 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.
+ // `c`: Size of the code of the contract that should be evicted.
+ claim_surcharge {
+ let c in 0 .. T::MaxCodeSize::get() / 1024;
+ let instance = Contract::<T>::with_caller(
+ whitelisted_caller(), WasmModule::dummy_with_bytes(c * 1024), 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::<T>::set_block_number(
+ instance.eviction_at()? + T::SignedClaimHandicap::get() + 5u32.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
+ // this is capped by the maximum amount of rent payed. So we only now that it should
+ // have increased by at most the surcharge reward.
+ assert!(
+ T::Currency::free_balance(&instance.caller) >
+ caller_funding::<T>() - instance.endowment
+ );
+ assert!(
+ T::Currency::free_balance(&instance.caller) <=
+ caller_funding::<T>() - instance.endowment + <T as Config>::SurchargeReward::get(),
+ );
+ }
+
+ seal_caller {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::new(WasmModule::getter(
+ "seal_caller", r * API_BENCHMARK_BATCH_SIZE
+ ), vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_address {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::new(WasmModule::getter(
+ "seal_address", r * API_BENCHMARK_BATCH_SIZE
+ ), vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_gas_left {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_balance {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::new(WasmModule::getter(
+ "seal_balance", r * API_BENCHMARK_BATCH_SIZE
+ ), vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_value_transferred {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_minimum_balance {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_tombstone_deposit {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_rent_allowance {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_block_number {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_now {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::new(WasmModule::getter(
+ "seal_now", r * API_BENCHMARK_BATCH_SIZE
+ ), vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_rent_params {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let instance = Contract::<T>::new(WasmModule::getter(
+ "seal_rent_params", r * API_BENCHMARK_BATCH_SIZE
+ ), vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_weight_to_fee {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let pages = code::max_pages::<T>();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_gas {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let code = WasmModule::<T>::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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+
+ }: call(origin, instance.addr, 0u32.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::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_input_per_kb {
+ let n in 0 .. code::max_pages::<T>() * 64;
+ let pages = code::max_pages::<T>();
+ let buffer_size = pages * 64 * 1024 - 4;
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::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, 0u32.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::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_return_per_kb {
+ let n in 0 .. code::max_pages::<T>() * 64;
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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::<T::AccountId>("beneficiary", 0, 0);
+ let beneficiary_bytes = beneficiary.encode();
+ let beneficiary_len = beneficiary_bytes.len();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
+ assert_eq!(T::Currency::total_balance(&instance.account_id), Endow::max::<T>());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+ verify {
+ if r > 0 {
+ assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
+ assert_eq!(T::Currency::total_balance(&beneficiary), Endow::max::<T>());
+ }
+ }
+
+ seal_terminate_per_code_kb {
+ let c in 0 .. T::MaxCodeSize::get() / 1024;
+ let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
+ let beneficiary_bytes = beneficiary.encode();
+ let beneficiary_len = beneficiary_bytes.len();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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(1, &[
+ Instruction::I32Const(0), // beneficiary_ptr
+ Instruction::I32Const(beneficiary_len as i32), // beneficiary_len
+ Instruction::Call(0),
+ ])),
+ dummy_section: c * 1024,
+ .. Default::default()
+ });
+ let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into());
+ assert_eq!(T::Currency::total_balance(&instance.account_id), Endow::max::<T>());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+ verify {
+ assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into());
+ assert_eq!(T::Currency::total_balance(&beneficiary), Endow::max::<T>());
+ }
+
+ 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 mut tombstone = ContractWithStorage::<T>::new(10, T::MaxValueSize::get())?;
+ tombstone.evict()?;
+
+ 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::<T>::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::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::with_caller(
+ account("origin", 0, 0), code, vec![], Endow::Max
+ )?;
+ instance.store(&tombstone.storage)?;
+ System::<T>::set_block_number(System::<T>::block_number() + 1u32.into());
+
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+ verify {
+ if r > 0 {
+ tombstone.contract.alive_info()?;
+ }
+ }
+
+ // `c`: Code size of caller contract
+ // `t`: Code size of tombstone contract
+ // `d`: Number of supplied delta keys
+ seal_restore_to_per_code_kb_delta {
+ let c in 0 .. T::MaxCodeSize::get() / 1024;
+ let t in 0 .. T::MaxCodeSize::get() / 1024;
+ let d in 0 .. API_BENCHMARK_BATCHES;
+ let mut tombstone = ContractWithStorage::<T>::with_code(
+ WasmModule::<T>::dummy_with_bytes(t * 1024), 0, 0
+ )?;
+ tombstone.evict()?;
+ let delta = create_storage::<T>(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::<T>::max_value().encode();
+ let rent_allowance_len = rent_allowance.len();
+ let delta_keys = delta.iter().flat_map(|(key, _)| key).cloned().collect::<Vec<_>>();
+
+ 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::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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,
+ ])),
+ dummy_section: c * 1024,
+ .. Default::default()
+ });
+
+ let instance = Contract::<T>::with_caller(
+ account("origin", 0, 0), code, vec![], Endow::Max
+ )?;
+ instance.store(&tombstone.storage)?;
+ instance.store(&delta)?;
+ System::<T>::set_block_number(System::<T>::block_number() + 1u32.into());
+
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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::<T>();
+ let subject_len = <CurrentSchedule<T>>::get().limits.subject_len;
+ assert!(subject_len < 1024);
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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 .. <CurrentSchedule<T>>::get().limits.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::<Vec<_>>().encode())
+ .peekable();
+ let topics_len = topics.peek().map(|i| i.len()).unwrap_or(0);
+ let topics = topics.flatten().collect();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::repeated_dyn(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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_set_rent_allowance {
+ let r in 0 .. API_BENCHMARK_BATCHES;
+ let allowance = caller_funding::<T>().encode();
+ let allowance_len = allowance.len();
+ let code = WasmModule::<T>::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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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::<Vec<_>>();
+ let key_len = sp_std::mem::size_of::<<T::Hashing as sp_runtime::traits::Hash>::Output>();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::repeated_dyn(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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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::<Vec<_>>();
+ let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
+ let key_len = sp_std::mem::size_of::<<T::Hashing as sp_runtime::traits::Hash>::Output>();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![
+ Counter(0, key_len as u32),
+ Regular(Instruction::Call(0)),
+ ])),
+ .. Default::default()
+ });
+ let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
+ let trie_id = instance.alive_info()?.trie_id;
+ for key in keys {
+ Storage::<T>::write(
+ &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, 0u32.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::<Vec<_>>();
+ let key_len = sp_std::mem::size_of::<<T::Hashing as sp_runtime::traits::Hash>::Output>();
+ let key_bytes = keys.iter().flatten().cloned().collect::<Vec<_>>();
+ let key_bytes_len = key_bytes.len();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::repeated_dyn(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::<T>::new(code, vec![], Endow::Max)?;
+ let trie_id = instance.alive_info()?.trie_id;
+ for key in keys {
+ Storage::<T>::write(
+ &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, 0u32.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::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let trie_id = instance.alive_info()?.trie_id;
+ Storage::<T>::write(
+ &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, 0u32.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::<T::AccountId>("receiver", i, 0))
+ .collect::<Vec<_>>();
+ 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 = Contracts::<T>::subsistence_threshold();
+ assert!(value > 0u32.into());
+ let value_bytes = value.encode();
+ let value_len = value_bytes.len();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::repeated_dyn(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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ for account in &accounts {
+ assert_eq!(T::Currency::total_balance(account), 0u32.into());
+ }
+ }: call(origin, instance.addr, 0u32.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::<T>::dummy_with_bytes(0);
+ let callees = (0..r * API_BENCHMARK_BATCH_SIZE)
+ .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![], Endow::Max))
+ .collect::<Result<Vec<_>, _>>()?;
+ 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<T> = 0u32.into();
+ let value_bytes = value.encode();
+ let value_len = value_bytes.len();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::repeated_dyn(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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.into(), Weight::max_value(), vec![])
+
+ seal_call_per_code_transfer_input_output_kb {
+ let c in 0 .. T::MaxCodeSize::get() / 1024;
+ let t in 0 .. 1;
+ let i in 0 .. code::max_pages::<T>() * 64;
+ let o in 0 .. (code::max_pages::<T>() - 1) * 64;
+ let callee_code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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,
+ ])),
+ dummy_section: c * 1024,
+ .. Default::default()
+ });
+ let callees = (0..API_BENCHMARK_BATCH_SIZE)
+ .map(|i| Contract::with_index(i + 1, callee_code.clone(), vec![], Endow::Max))
+ .collect::<Result<Vec<_>, _>>()?;
+ 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::<Vec<_>>();
+ let callees_len = callee_bytes.len();
+ let value: BalanceOf<T> = t.into();
+ let value_bytes = value.encode();
+ let value_len = value_bytes.len();
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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::repeated_dyn(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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ call_body: Some(body::plain(vec![
+ // we need to add this in order to make contracts unique
+ // so that they can be deployed from the same sender
+ Instruction::I32Const(i as i32),
+ Instruction::Drop,
+ Instruction::End,
+ ])),
+ .. Default::default()
+ });
+ Contracts::<T>::store_code_raw(code.code)?;
+ Ok(code.hash)
+ })
+ .collect::<Result<Vec<_>, &'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::<Vec<_>>();
+ let hashes_len = hashes_bytes.len();
+ let value = Endow::max::<T>() / (r * API_BENCHMARK_BATCH_SIZE + 2).into();
+ assert!(value > 0u32.into());
+ let value_bytes = value.encode();
+ let value_len = value_bytes.len();
+ let addr_len = sp_std::mem::size_of::<T::AccountId>();
+
+ // 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;
+
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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,
+ 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::repeated_dyn(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::I32Const(0)), // salt_ptr
+ Regular(Instruction::I32Const(0)), // salt_ptr_len
+ Regular(Instruction::Call(0)),
+ Regular(Instruction::Drop),
+ ])),
+ .. Default::default()
+ });
+ let instance = Contract::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ let callee = instance.addr.clone();
+ let addresses = hashes
+ .iter()
+ .map(|hash| Contracts::<T>::contract_address(
+ &instance.account_id, hash, &[],
+ ))
+ .collect::<Vec<_>>();
+
+ for addr in &addresses {
+ if let Some(_) = ContractInfoOf::<T>::get(&addr) {
+ return Err("Expected that contract does not exist at this point.");
+ }
+ }
+ }: call(origin, callee, 0u32.into(), Weight::max_value(), vec![])
+ verify {
+ for addr in &addresses {
+ ContractInfoOf::<T>::get(&addr).and_then(|c| c.get_alive())
+ .ok_or_else(|| "Contract should have been instantiated")?;
+ }
+ }
+
+ seal_instantiate_per_code_input_output_salt_kb {
+ let c in 0 .. T::MaxCodeSize::get() / 1024;
+ let i in 0 .. (code::max_pages::<T>() - 1) * 64;
+ let o in 0 .. (code::max_pages::<T>() - 1) * 64;
+ let s in 0 .. (code::max_pages::<T>() - 1) * 64;
+ let callee_code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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,
+ ])),
+ dummy_section: c * 1024,
+ .. Default::default()
+ });
+ let hash = callee_code.hash.clone();
+ let hash_bytes = callee_code.hash.encode();
+ let hash_len = hash_bytes.len();
+ Contracts::<T>::store_code_raw(callee_code.code)?;
+ let inputs = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::<Vec<_>>();
+ let input_len = inputs.get(0).map(|x| x.len()).unwrap_or(0);
+ let input_bytes = inputs.iter().cloned().flatten().collect::<Vec<_>>();
+ let inputs_len = input_bytes.len();
+ let value = Endow::max::<T>() / (API_BENCHMARK_BATCH_SIZE + 2).into();
+ assert!(value > 0u32.into());
+ let value_bytes = value.encode();
+ let value_len = value_bytes.len();
+ let addr_len = sp_std::mem::size_of::<T::AccountId>();
+
+ // 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;
+
+ let code = WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ 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,
+ 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::repeated_dyn(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
+ Counter(input_offset as u32, input_len as u32), // salt_ptr
+ Regular(Instruction::I32Const((s * 1024).max(input_len as u32) as i32)), // salt_len
+ 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::<T>::new(code, vec![], Endow::Max)?;
+ let origin = RawOrigin::Signed(instance.caller.clone());
+ }: call(origin, instance.addr, 0u32.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::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ // `n`: Input to hash in kilobytes
+ seal_hash_sha2_256_per_kb {
+ let n in 0 .. code::max_pages::<T>() * 64;
+ let instance = Contract::<T>::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, 0u32.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::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ // `n`: Input to hash in kilobytes
+ seal_hash_keccak_256_per_kb {
+ let n in 0 .. code::max_pages::<T>() * 64;
+ let instance = Contract::<T>::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, 0u32.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::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ // `n`: Input to hash in kilobytes
+ seal_hash_blake2_256_per_kb {
+ let n in 0 .. code::max_pages::<T>() * 64;
+ let instance = Contract::<T>::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, 0u32.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::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ // `n`: Input to hash in kilobytes
+ seal_hash_blake2_128_per_kb {
+ let n in 0 .. code::max_pages::<T>() * 64;
+ let instance = Contract::<T>::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, 0u32.into(), Weight::max_value(), vec![])
+
+ // We make the assumption that pushing a constant and dropping a value takes roughly
+ // the same amount of time. We follow that `t.load` and `drop` both have the weight
+ // of this benchmark / 2. We need to make this assumption because there is no way
+ // to measure them on their own using a valid wasm module. We need their individual
+ // values to derive the weight of individual instructions (by substraction) from
+ // benchmarks that include those for parameter pushing and return type dropping.
+ // We call the weight of `t.load` and `drop`: `w_param`.
+ // The weight that would result from the respective benchmark we call: `w_bench`.
+ //
+ // w_i{32,64}const = w_drop = w_bench / 2
+ instr_i64const {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_i{32,64}load = w_bench - 2 * w_param
+ instr_i64load {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomUnaligned(0, code::max_pages::<T>() * 64 * 1024 - 8),
+ Regular(Instruction::I64Load(3, 0)),
+ Regular(Instruction::Drop),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_i{32,64}store{...} = w_bench - 2 * w_param
+ instr_i64store {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomUnaligned(0, code::max_pages::<T>() * 64 * 1024 - 8),
+ RandomI64Repeated(1),
+ Regular(Instruction::I64Store(3, 0)),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_select = w_bench - 4 * w_param
+ instr_select {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI64Repeated(1),
+ RandomI64Repeated(1),
+ RandomI32(0, 2),
+ Regular(Instruction::Select),
+ Regular(Instruction::Drop),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_if = w_bench - 3 * w_param
+ instr_if {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI32(0, 2),
+ Regular(Instruction::If(BlockType::Value(ValueType::I64))),
+ RandomI64Repeated(1),
+ Regular(Instruction::Else),
+ RandomI64Repeated(1),
+ Regular(Instruction::End),
+ Regular(Instruction::Drop),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_br = w_bench - 2 * w_param
+ instr_br {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Br(1)),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_br_if = w_bench - 5 * w_param
+ // The two additional pushes + drop are only executed 50% of the time.
+ // Making it: 3 * w_param + (50% * 4 * w_param)
+ instr_br_if {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Block(BlockType::NoResult)),
+ RandomI32(0, 2),
+ Regular(Instruction::BrIf(1)),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_br_table = w_bench - 3 * w_param
+ // 1 * w_param + 0.5 * 2 * w_param + 0.25 * 4 * w_param
+ instr_br_table {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let table = Box::new(parity_wasm::elements::BrTableData {
+ table: Box::new([0, 1, 2]),
+ default: 1,
+ });
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Block(BlockType::NoResult)),
+ RandomI32(0, 4),
+ Regular(Instruction::BrTable(table)),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_br_table_per_entry = w_bench
+ instr_br_table_per_entry {
+ let e in 1 .. <CurrentSchedule<T>>::get().limits.br_table_size;
+ let entry: Vec<u32> = [0, 1].iter()
+ .cloned()
+ .cycle()
+ .take((e / 2) as usize).collect();
+ let table = Box::new(parity_wasm::elements::BrTableData {
+ table: entry.into_boxed_slice(),
+ default: 0,
+ });
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(INSTR_BENCHMARK_BATCH_SIZE, vec![
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Block(BlockType::NoResult)),
+ Regular(Instruction::Block(BlockType::NoResult)),
+ RandomI32(0, (e + 1) as i32), // Make sure the default entry is also used
+ Regular(Instruction::BrTable(table)),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ RandomI64Repeated(1),
+ Regular(Instruction::Drop),
+ Regular(Instruction::End),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_call = w_bench - 2 * w_param
+ instr_call {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ // We need to make use of the stack here in order to trigger stack height
+ // instrumentation.
+ aux_body: Some(body::plain(vec![
+ Instruction::I64Const(42),
+ Instruction::Drop,
+ Instruction::End,
+ ])),
+ call_body: Some(body::repeated(r * INSTR_BENCHMARK_BATCH_SIZE, &[
+ Instruction::Call(2), // call aux
+ ])),
+ inject_stack_metering: true,
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_call_indrect = w_bench - 3 * w_param
+ instr_call_indirect {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let num_elements = <CurrentSchedule<T>>::get().limits.table_size;
+ use self::code::TableSegment;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ // We need to make use of the stack here in order to trigger stack height
+ // instrumentation.
+ aux_body: Some(body::plain(vec![
+ Instruction::I64Const(42),
+ Instruction::Drop,
+ Instruction::End,
+ ])),
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI32(0, num_elements as i32),
+ Regular(Instruction::CallIndirect(0, 0)), // we only have one sig: 0
+ ])),
+ inject_stack_metering: true,
+ table: Some(TableSegment {
+ num_elements,
+ function_index: 2, // aux
+ }),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_instr_call_indirect_per_param = w_bench - 1 * w_param
+ // Calling a function indirectly causes it to go through a thunk function whose runtime
+ // linearly depend on the amount of parameters to this function.
+ // Please note that this is not necessary with a direct call.
+ instr_call_indirect_per_param {
+ let p in 0 .. <CurrentSchedule<T>>::get().limits.parameters;
+ let num_elements = <CurrentSchedule<T>>::get().limits.table_size;
+ use self::code::TableSegment;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ // We need to make use of the stack here in order to trigger stack height
+ // instrumentation.
+ aux_body: Some(body::plain(vec![
+ Instruction::I64Const(42),
+ Instruction::Drop,
+ Instruction::End,
+ ])),
+ aux_arg_num: p,
+ call_body: Some(body::repeated_dyn(INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI64Repeated(p as usize),
+ RandomI32(0, num_elements as i32),
+ Regular(Instruction::CallIndirect(p.min(1), 0)), // aux signature: 1 or 0
+ ])),
+ inject_stack_metering: true,
+ table: Some(TableSegment {
+ num_elements,
+ function_index: 2, // aux
+ }),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_local_get = w_bench - 1 * w_param
+ instr_local_get {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let max_locals = <CurrentSchedule<T>>::get().limits.stack_height;
+ let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomGetLocal(0, max_locals),
+ Regular(Instruction::Drop),
+ ]);
+ body::inject_locals(&mut call_body, max_locals);
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(call_body),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_local_set = w_bench - 1 * w_param
+ instr_local_set {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let max_locals = <CurrentSchedule<T>>::get().limits.stack_height;
+ let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI64Repeated(1),
+ RandomSetLocal(0, max_locals),
+ ]);
+ body::inject_locals(&mut call_body, max_locals);
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(call_body),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_local_tee = w_bench - 2 * w_param
+ instr_local_tee {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let max_locals = <CurrentSchedule<T>>::get().limits.stack_height;
+ let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI64Repeated(1),
+ RandomTeeLocal(0, max_locals),
+ Regular(Instruction::Drop),
+ ]);
+ body::inject_locals(&mut call_body, max_locals);
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(call_body),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_global_get = w_bench - 1 * w_param
+ instr_global_get {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let max_globals = <CurrentSchedule<T>>::get().limits.globals;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomGetGlobal(0, max_globals),
+ Regular(Instruction::Drop),
+ ])),
+ num_globals: max_globals,
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_global_set = w_bench - 1 * w_param
+ instr_global_set {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let max_globals = <CurrentSchedule<T>>::get().limits.globals;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI64Repeated(1),
+ RandomSetGlobal(0, max_globals),
+ ])),
+ num_globals: max_globals,
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_memory_get = w_bench - 1 * w_param
+ instr_memory_current {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory::max::<T>()),
+ call_body: Some(body::repeated(r * INSTR_BENCHMARK_BATCH_SIZE, &[
+ Instruction::CurrentMemory(0),
+ Instruction::Drop
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // w_memory_grow = w_bench - 2 * w_param
+ // We can only allow allocate as much memory as it is allowed in a a contract.
+ // Therefore the repeat count is limited by the maximum memory any contract can have.
+ // Using a contract with more memory will skew the benchmark because the runtime of grow
+ // depends on how much memory is already allocated.
+ instr_memory_grow {
+ let r in 0 .. 1;
+ let max_pages = ImportedMemory::max::<T>().max_pages;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ memory: Some(ImportedMemory {
+ min_pages: 0,
+ max_pages,
+ }),
+ call_body: Some(body::repeated(r * max_pages, &[
+ Instruction::I32Const(1),
+ Instruction::GrowMemory(0),
+ Instruction::Drop,
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ // Unary numeric instructions.
+ // All use w = w_bench - 2 * w_param.
+
+ instr_i64clz {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
+ Instruction::I64Clz,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64ctz {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
+ Instruction::I64Ctz,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64popcnt {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
+ Instruction::I64Popcnt,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64eqz {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
+ Instruction::I64Eqz,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64extendsi32 {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI32Repeated(1),
+ Regular(Instruction::I64ExtendSI32),
+ Regular(Instruction::Drop),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64extendui32 {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::from(ModuleDefinition {
+ call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![
+ RandomI32Repeated(1),
+ Regular(Instruction::I64ExtendUI32),
+ Regular(Instruction::Drop),
+ ])),
+ .. Default::default()
+ }));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i32wrapi64 {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::unary_instr(
+ Instruction::I32WrapI64,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ // Binary numeric instructions.
+ // All use w = w_bench - 3 * w_param.
+
+ instr_i64eq {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Eq,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64ne {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Ne,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64lts {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64LtS,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64ltu {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64LtU,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64gts {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64GtS,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64gtu {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64GtU,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64les {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64LeS,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64leu {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64LeU,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64ges {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64GeS,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64geu {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64GeU,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64add {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Add,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64sub {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Sub,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64mul {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Mul,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64divs {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64DivS,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64divu {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64DivU,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64rems {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64RemS,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64remu {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64RemU,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64and {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64And,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64or {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Or,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64xor {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Xor,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64shl {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Shl,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64shrs {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64ShrS,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64shru {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64ShrU,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64rotl {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Rotl,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ instr_i64rotr {
+ let r in 0 .. INSTR_BENCHMARK_BATCHES;
+ let mut sbox = Sandbox::from(&WasmModule::<T>::binary_instr(
+ Instruction::I64Rotr,
+ r * INSTR_BENCHMARK_BATCH_SIZE,
+ ));
+ }: {
+ sbox.invoke();
+ }
+
+ // This is no benchmark. It merely exist to have an easy way to pretty print the curently
+ // configured `Schedule` during benchmark development.
+ // It can be outputed using the following command:
+ // cargo run --manifest-path=bin/node/cli/Cargo.toml --release \
+ // --features runtime-benchmarks -- benchmark --dev --execution=native \
+ // -p pallet_contracts -e print_schedule --no-median-slopes --no-min-squares
+ #[extra]
+ print_schedule {
+ #[cfg(feature = "std")]
+ {
+ let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) -
+ T::WeightInfo::on_initialize_per_trie_key(0);
+ let weight_per_queue_item = T::WeightInfo::on_initialize_per_queue_item(1) -
+ T::WeightInfo::on_initialize_per_queue_item(0);
+ let weight_limit = T::DeletionWeightLimit::get();
+ let queue_depth: u64 = T::DeletionQueueDepth::get().into();
+ println!("{:#?}", Schedule::<T>::default());
+ println!("###############################################");
+ println!("Lazy deletion throughput per block (empty queue, full queue): {}, {}",
+ weight_limit / weight_per_key,
+ (weight_limit - weight_per_queue_item * queue_depth) / weight_per_key,
+ );
+ }
+ #[cfg(not(feature = "std"))]
+ return Err("Run this bench with a native runtime in order to see the schedule.");
+ }: {}
+}
+
+impl_benchmark_test_suite!(
+ Contracts,
+ crate::tests::ExtBuilder::default().build(),
+ crate::tests::Test,
+);
pallets/contracts/src/benchmarking/sandbox.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/benchmarking/sandbox.rs
@@ -0,0 +1,55 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 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.
+
+///! For instruction benchmarking we do no instantiate a full contract but merely the
+///! sandbox to execute the wasm code. This is because we do not need the full
+///! environment that provides the seal interface as imported functions.
+use super::{Config, code::WasmModule};
+use sp_core::crypto::UncheckedFrom;
+use sp_sandbox::{EnvironmentDefinitionBuilder, Instance, Memory};
+
+/// Minimal execution environment without any exported functions.
+pub struct Sandbox {
+ instance: Instance<()>,
+ _memory: Option<Memory>,
+}
+
+impl Sandbox {
+ /// Invoke the `call` function of a contract code and panic on any execution error.
+ pub fn invoke(&mut self) {
+ self.instance.invoke("call", &[], &mut ()).unwrap();
+ }
+}
+
+impl<T: Config> From<&WasmModule<T>> for Sandbox
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ /// Creates an instance from the supplied module and supplies as much memory
+ /// to the instance as the module declares as imported.
+ fn from(module: &WasmModule<T>) -> Self {
+ let mut env_builder = EnvironmentDefinitionBuilder::new();
+ let memory = module.add_memory(&mut env_builder);
+ let instance = Instance::new(&module.code, &env_builder, &mut ())
+ .expect("Failed to create benchmarking Sandbox instance");
+ Self {
+ instance,
+ _memory: memory,
+ }
+ }
+}
pallets/contracts/src/chain_extension.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/chain_extension.rs
@@ -0,0 +1,400 @@
+// 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.
+
+//! A mechanism for runtime authors to augment the functionality of contracts.
+//!
+//! The runtime is able to call into any contract and retrieve the result using
+//! [`bare_call`](crate::Pallet::bare_call). This already allows customization of runtime
+//! behaviour by user generated code (contracts). However, often it is more straightforward
+//! to allow the reverse behaviour: The contract calls into the runtime. We call the latter
+//! one a "chain extension" because it allows the chain to extend the set of functions that are
+//! callable by a contract.
+//!
+//! In order to create a chain extension the runtime author implements the [`ChainExtension`]
+//! trait and declares it in this pallet's [configuration Trait](crate::Config). All types
+//! required for this endeavour are defined or re-exported in this module. There is an
+//! implementation on `()` which can be used to signal that no chain extension is available.
+//!
+//! # Security
+//!
+//! The chain author alone is responsible for the security of the chain extension.
+//! This includes avoiding the exposure of exploitable functions and charging the
+//! appropriate amount of weight. In order to do so benchmarks must be written and the
+//! [`charge_weight`](Environment::charge_weight) function must be called **before**
+//! carrying out any action that causes the consumption of the chargeable weight.
+//! It cannot be overstated how delicate of a process the creation of a chain extension
+//! is. Check whether using [`bare_call`](crate::Pallet::bare_call) suffices for the
+//! use case at hand.
+//!
+//! # Benchmarking
+//!
+//! The builtin contract callable functions that pallet-contracts provides all have
+//! benchmarks that determine the correct weight that an invocation of these functions
+//! induces. In order to be able to charge the correct weight for the functions defined
+//! by a chain extension benchmarks must be written, too. In the near future this crate
+//! will provide the means for easier creation of those specialized benchmarks.
+//!
+//! # Example
+//!
+//! The ink! repository maintains an
+//! [end-to-end example](https://github.com/paritytech/ink/tree/master/examples/rand-extension)
+//! on how to use a chain extension in order to provide new features to ink! contracts.
+
+use crate::{
+ Error,
+ wasm::{Runtime, RuntimeToken},
+};
+use codec::Decode;
+use frame_support::weights::Weight;
+use sp_runtime::DispatchError;
+use sp_std::{marker::PhantomData, vec::Vec};
+
+pub use frame_system::Config as SysConfig;
+pub use pallet_contracts_primitives::ReturnFlags;
+pub use sp_core::crypto::UncheckedFrom;
+pub use crate::{Config, exec::Ext};
+pub use state::Init as InitState;
+
+/// Result that returns a [`DispatchError`] on error.
+pub type Result<T> = sp_std::result::Result<T, DispatchError>;
+
+/// A trait used to extend the set of contract callable functions.
+///
+/// In order to create a custom chain extension this trait must be implemented and supplied
+/// to the pallet contracts configuration trait as the associated type of the same name.
+/// Consult the [module documentation](self) for a general explanation of chain extensions.
+pub trait ChainExtension<C: Config> {
+ /// Call the chain extension logic.
+ ///
+ /// This is the only function that needs to be implemented in order to write a
+ /// chain extensions. It is called whenever a contract calls the `seal_call_chain_extension`
+ /// imported wasm function.
+ ///
+ /// # Parameters
+ /// - `func_id`: The first argument to `seal_call_chain_extension`. Usually used to
+ /// determine which function to realize.
+ /// - `env`: Access to the remaining arguments and the execution environment.
+ ///
+ /// # Return
+ ///
+ /// In case of `Err` the contract execution is immediately suspended and the passed error
+ /// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit
+ /// behaviour.
+ fn call<E>(func_id: u32, env: Environment<E, InitState>) -> Result<RetVal>
+ where
+ E: Ext<T = C>,
+ <E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>;
+
+ /// Determines whether chain extensions are enabled for this chain.
+ ///
+ /// The default implementation returns `true`. Therefore it is not necessary to overwrite
+ /// this function when implementing a chain extension. In case of `false` the deployment of
+ /// a contract that references `seal_call_chain_extension` will be denied and calling this
+ /// function will return [`NoChainExtension`](Error::NoChainExtension) without first calling
+ /// into [`call`](Self::call).
+ fn enabled() -> bool {
+ true
+ }
+}
+
+/// Implementation that indicates that no chain extension is available.
+impl<C: Config> ChainExtension<C> for () {
+ fn call<E>(_func_id: u32, mut _env: Environment<E, InitState>) -> Result<RetVal>
+ where
+ E: Ext<T = C>,
+ <E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
+ {
+ // Never called since [`Self::enabled()`] is set to `false`. Because we want to
+ // avoid panics at all costs we supply a sensible error value here instead
+ // of an `unimplemented!`.
+ Err(Error::<E::T>::NoChainExtension.into())
+ }
+
+ fn enabled() -> bool {
+ false
+ }
+}
+
+/// Determines the exit behaviour and return value of a chain extension.
+pub enum RetVal {
+ /// The chain extensions returns the supplied value to its calling contract.
+ Converging(u32),
+ /// The control does **not** return to the calling contract.
+ ///
+ /// Use this to stop the execution of the contract when the chain extension returns.
+ /// The semantic is the same as for calling `seal_return`: The control returns to
+ /// the caller of the currently executing contract yielding the supplied buffer and
+ /// flags.
+ Diverging { flags: ReturnFlags, data: Vec<u8> },
+}
+
+/// Grants the chain extension access to its parameters and execution environment.
+///
+/// It uses [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html)
+/// to enforce the correct usage of the parameters passed to the chain extension.
+pub struct Environment<'a, 'b, E: Ext, S: state::State> {
+ /// The actual data of this type.
+ inner: Inner<'a, 'b, E>,
+ /// `S` is only used in the type system but never as value.
+ phantom: PhantomData<S>,
+}
+
+/// Functions that are available in every state of this type.
+impl<'a, 'b, E: Ext, S: state::State> Environment<'a, 'b, E, S>
+where
+ <E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
+{
+ /// Charge the passed `amount` of weight from the overall limit.
+ ///
+ /// It returns `Ok` when there the remaining weight budget is larger than the passed
+ /// `weight`. It returns `Err` otherwise. In this case the chain extension should
+ /// abort the execution and pass through the error.
+ ///
+ /// # Note
+ ///
+ /// Weight is synonymous with gas in substrate.
+ pub fn charge_weight(&mut self, amount: Weight) -> Result<()> {
+ self.inner
+ .runtime
+ .charge_gas(RuntimeToken::ChainExtension(amount))
+ .map(|_| ())
+ }
+
+ /// Grants access to the execution environment of the current contract call.
+ ///
+ /// Consult the functions on the returned type before re-implementing those functions.
+ pub fn ext(&mut self) -> &mut E {
+ self.inner.runtime.ext()
+ }
+}
+
+/// Functions that are only available in the initial state of this type.
+///
+/// Those are the functions that determine how the arguments to the chain extensions
+/// should be consumed.
+impl<'a, 'b, E: Ext> Environment<'a, 'b, E, state::Init> {
+ /// Creates a new environment for consumption by a chain extension.
+ ///
+ /// It is only available to this crate because only the wasm runtime module needs to
+ /// ever create this type. Chain extensions merely consume it.
+ pub(crate) fn new(
+ runtime: &'a mut Runtime<'b, E>,
+ input_ptr: u32,
+ input_len: u32,
+ output_ptr: u32,
+ output_len_ptr: u32,
+ ) -> Self {
+ Environment {
+ inner: Inner {
+ runtime,
+ input_ptr,
+ input_len,
+ output_ptr,
+ output_len_ptr,
+ },
+ phantom: PhantomData,
+ }
+ }
+
+ /// Use all arguments as integer values.
+ pub fn only_in(self) -> Environment<'a, 'b, E, state::OnlyIn> {
+ Environment {
+ inner: self.inner,
+ phantom: PhantomData,
+ }
+ }
+
+ /// Use input arguments as integer and output arguments as pointer to a buffer.
+ pub fn prim_in_buf_out(self) -> Environment<'a, 'b, E, state::PrimInBufOut> {
+ Environment {
+ inner: self.inner,
+ phantom: PhantomData,
+ }
+ }
+
+ /// Use input and output arguments as pointers to a buffer.
+ pub fn buf_in_buf_out(self) -> Environment<'a, 'b, E, state::BufInBufOut> {
+ Environment {
+ inner: self.inner,
+ phantom: PhantomData,
+ }
+ }
+}
+
+/// Functions to use the input arguments as integers.
+impl<'a, 'b, E: Ext, S: state::PrimIn> Environment<'a, 'b, E, S> {
+ /// The `input_ptr` argument.
+ pub fn val0(&self) -> u32 {
+ self.inner.input_ptr
+ }
+
+ /// The `input_len` argument.
+ pub fn val1(&self) -> u32 {
+ self.inner.input_len
+ }
+}
+
+/// Functions to use the output arguments as integers.
+impl<'a, 'b, E: Ext, S: state::PrimOut> Environment<'a, 'b, E, S> {
+ /// The `output_ptr` argument.
+ pub fn val2(&self) -> u32 {
+ self.inner.output_ptr
+ }
+
+ /// The `output_len_ptr` argument.
+ pub fn val3(&self) -> u32 {
+ self.inner.output_len_ptr
+ }
+}
+
+/// Functions to use the input arguments as pointer to a buffer.
+impl<'a, 'b, E: Ext, S: state::BufIn> Environment<'a, 'b, E, S>
+where
+ <E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
+{
+ /// Reads `min(max_len, in_len)` from contract memory.
+ ///
+ /// This does **not** charge any weight. The caller must make sure that the an
+ /// appropriate amount of weight is charged **before** reading from contract memory.
+ /// The reason for that is that usually the costs for reading data and processing
+ /// said data cannot be separated in a benchmark. Therefore a chain extension would
+ /// charge the overall costs either using `max_len` (worst case approximation) or using
+ /// [`in_len()`](Self::in_len).
+ pub fn read(&self, max_len: u32) -> Result<Vec<u8>> {
+ self.inner
+ .runtime
+ .read_sandbox_memory(self.inner.input_ptr, self.inner.input_len.min(max_len))
+ }
+
+ /// Reads `min(buffer.len(), in_len) from contract memory.
+ ///
+ /// This takes a mutable pointer to a buffer fills it with data and shrinks it to
+ /// the size of the actual data. Apart from supporting pre-allocated buffers it is
+ /// equivalent to to [`read()`](Self::read).
+ pub fn read_into(&self, buffer: &mut &mut [u8]) -> Result<()> {
+ let len = buffer.len();
+ let sliced = {
+ let buffer = core::mem::take(buffer);
+ &mut buffer[..len.min(self.inner.input_len as usize)]
+ };
+ self.inner
+ .runtime
+ .read_sandbox_memory_into_buf(self.inner.input_ptr, sliced)?;
+ *buffer = sliced;
+ Ok(())
+ }
+
+ /// Reads `in_len` from contract memory and scale decodes it.
+ ///
+ /// This function is secure and recommended for all input types of fixed size
+ /// as long as the cost of reading the memory is included in the overall already charged
+ /// weight of the chain extension. This should usually be the case when fixed input types
+ /// are used. Non fixed size types (like everything using `Vec`) usually need to use
+ /// [`in_len()`](Self::in_len) in order to properly charge the necessary weight.
+ pub fn read_as<T: Decode>(&mut self) -> Result<T> {
+ self.inner
+ .runtime
+ .read_sandbox_memory_as(self.inner.input_ptr, self.inner.input_len)
+ }
+
+ /// The length of the input as passed in as `input_len`.
+ ///
+ /// A chain extension would use this value to calculate the dynamic part of its
+ /// weight. For example a chain extension that calculates the hash of some passed in
+ /// bytes would use `in_len` to charge the costs of hashing that amount of bytes.
+ /// This also subsumes the act of copying those bytes as a benchmarks measures both.
+ pub fn in_len(&self) -> u32 {
+ self.inner.input_len
+ }
+}
+
+/// Functions to use the output arguments as pointer to a buffer.
+impl<'a, 'b, E: Ext, S: state::BufOut> Environment<'a, 'b, E, S>
+where
+ <E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
+{
+ /// Write the supplied buffer to contract memory.
+ ///
+ /// If the contract supplied buffer is smaller than the passed `buffer` an `Err` is returned.
+ /// If `allow_skip` is set to true the contract is allowed to skip the copying of the buffer
+ /// by supplying the guard value of `u32::max_value()` as `out_ptr`. The
+ /// `weight_per_byte` is only charged when the write actually happens and is not skipped or
+ /// failed due to a too small output buffer.
+ pub fn write(
+ &mut self,
+ buffer: &[u8],
+ allow_skip: bool,
+ weight_per_byte: Option<Weight>,
+ ) -> Result<()> {
+ self.inner.runtime.write_sandbox_output(
+ self.inner.output_ptr,
+ self.inner.output_len_ptr,
+ buffer,
+ allow_skip,
+ |len| {
+ weight_per_byte.map(|w| RuntimeToken::ChainExtension(w.saturating_mul(len.into())))
+ },
+ )
+ }
+}
+
+/// The actual data of an `Environment`.
+///
+/// All data is put into this struct to easily pass it around as part of the typestate
+/// pattern. Also it creates the opportunity to box this struct in the future in case it
+/// gets too large.
+struct Inner<'a, 'b, E: Ext> {
+ /// The runtime contains all necessary functions to interact with the running contract.
+ runtime: &'a mut Runtime<'b, E>,
+ /// Verbatim argument passed to `seal_call_chain_extension`.
+ input_ptr: u32,
+ /// Verbatim argument passed to `seal_call_chain_extension`.
+ input_len: u32,
+ /// Verbatim argument passed to `seal_call_chain_extension`.
+ output_ptr: u32,
+ /// Verbatim argument passed to `seal_call_chain_extension`.
+ output_len_ptr: u32,
+}
+
+/// Private submodule with public types to prevent other modules from naming them.
+mod state {
+ pub trait State {}
+
+ pub trait PrimIn: State {}
+ pub trait PrimOut: State {}
+ pub trait BufIn: State {}
+ pub trait BufOut: State {}
+
+ /// The initial state of an [`Environment`](`super::Environment`).
+ /// See [typestate programming](https://docs.rust-embedded.org/book/static-guarantees/typestate-programming.html).
+ pub enum Init {}
+ pub enum OnlyIn {}
+ pub enum PrimInBufOut {}
+ pub enum BufInBufOut {}
+
+ impl State for Init {}
+ impl State for OnlyIn {}
+ impl State for PrimInBufOut {}
+ impl State for BufInBufOut {}
+
+ impl PrimIn for OnlyIn {}
+ impl PrimOut for OnlyIn {}
+ impl PrimIn for PrimInBufOut {}
+ impl BufOut for PrimInBufOut {}
+ impl BufIn for BufInBufOut {}
+ impl BufOut for BufInBufOut {}
+}
pallets/contracts/src/exec.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/exec.rs
@@ -0,0 +1,1836 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2018-2021 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.
+
+use crate::{
+ CodeHash, Event, Config, Pallet as Contracts, TrieId, BalanceOf, ContractInfo,
+ gas::GasMeter,
+ rent::Rent,
+ storage::{self, Storage},
+ Error, ContractInfoOf, Schedule, AliveContractInfo,
+};
+use sp_core::crypto::UncheckedFrom;
+use sp_std::{prelude::*, marker::PhantomData};
+use sp_runtime::{
+ Perbill,
+ traits::{Bounded, Zero, Convert, Saturating},
+};
+use frame_support::{
+ dispatch::{DispatchResult, DispatchError},
+ traits::{ExistenceRequirement, Currency, Time, Randomness, Get},
+ weights::Weight,
+ ensure,
+};
+use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags};
+
+pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
+pub type MomentOf<T> = <<T as Config>::Time as Time>::Moment;
+pub type SeedOf<T> = <T as frame_system::Config>::Hash;
+pub type BlockNumberOf<T> = <T as frame_system::Config>::BlockNumber;
+pub type StorageKey = [u8; 32];
+pub type ExecResult = Result<ExecReturnValue, ExecError>;
+
+/// A type that represents a topic of an event. At the moment a hash is used.
+pub type TopicOf<T> = <T as frame_system::Config>::Hash;
+
+/// Origin of the error.
+///
+/// Call or instantiate both called into other contracts and pass through errors happening
+/// in those to the caller. This enum is for the caller to distinguish whether the error
+/// happened during the execution of the callee or in the current execution context.
+#[cfg_attr(test, derive(Debug, PartialEq))]
+pub enum ErrorOrigin {
+ /// Caller error origin.
+ ///
+ /// The error happened in the current exeuction context rather than in the one
+ /// of the contract that is called into.
+ Caller,
+ /// The error happened during execution of the called contract.
+ Callee,
+}
+
+/// Error returned by contract exection.
+#[cfg_attr(test, derive(Debug, PartialEq))]
+pub struct ExecError {
+ /// The reason why the execution failed.
+ pub error: DispatchError,
+ /// Origin of the error.
+ pub origin: ErrorOrigin,
+}
+
+impl<T: Into<DispatchError>> From<T> for ExecError {
+ fn from(error: T) -> Self {
+ Self {
+ error: error.into(),
+ origin: ErrorOrigin::Caller,
+ }
+ }
+}
+
+/// Information needed for rent calculations that can be requested by a contract.
+#[derive(codec::Encode)]
+#[cfg_attr(test, derive(Debug, PartialEq))]
+pub struct RentParams<T: Config> {
+ /// The total balance of the contract. Includes the balance transferred from the caller.
+ total_balance: BalanceOf<T>,
+ /// The free balance of the contract. Includes the balance transferred from the caller.
+ free_balance: BalanceOf<T>,
+ /// See crate [`Contracts::subsistence_threshold()`].
+ subsistence_threshold: BalanceOf<T>,
+ /// See crate [`Config::DepositPerContract`].
+ deposit_per_contract: BalanceOf<T>,
+ /// See crate [`Config::DepositPerStorageByte`].
+ deposit_per_storage_byte: BalanceOf<T>,
+ /// See crate [`Config::DepositPerStorageItem`].
+ deposit_per_storage_item: BalanceOf<T>,
+ /// See crate [`Ext::rent_allowance()`].
+ rent_allowance: BalanceOf<T>,
+ /// See crate [`Config::RentFraction`].
+ rent_fraction: Perbill,
+ /// See crate [`AliveContractInfo::storage_size`].
+ storage_size: u32,
+ /// See crate [`Executable::aggregate_code_len()`].
+ code_size: u32,
+ /// See crate [`Executable::refcount()`].
+ code_refcount: u32,
+ /// Reserved for backwards compatible changes to this data structure.
+ _reserved: Option<()>,
+}
+
+impl<T> RentParams<T>
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ fn new<E: Executable<T>>(
+ account_id: &T::AccountId,
+ contract: &AliveContractInfo<T>,
+ executable: &E,
+ ) -> Self {
+ Self {
+ total_balance: T::Currency::total_balance(account_id),
+ free_balance: T::Currency::free_balance(account_id),
+ subsistence_threshold: <Contracts<T>>::subsistence_threshold(),
+ deposit_per_contract: T::DepositPerContract::get(),
+ deposit_per_storage_byte: T::DepositPerStorageByte::get(),
+ deposit_per_storage_item: T::DepositPerStorageItem::get(),
+ rent_allowance: contract.rent_allowance,
+ rent_fraction: T::RentFraction::get(),
+ storage_size: contract.storage_size,
+ code_size: executable.aggregate_code_len(),
+ code_refcount: executable.refcount(),
+ _reserved: None,
+ }
+ }
+}
+
+/// We cannot derive `Default` because `T` does not necessarily implement `Default`.
+#[cfg(test)]
+impl<T: Config> Default for RentParams<T> {
+ fn default() -> Self {
+ Self {
+ total_balance: Default::default(),
+ free_balance: Default::default(),
+ subsistence_threshold: Default::default(),
+ deposit_per_contract: Default::default(),
+ deposit_per_storage_byte: Default::default(),
+ deposit_per_storage_item: Default::default(),
+ rent_allowance: Default::default(),
+ rent_fraction: Default::default(),
+ storage_size: Default::default(),
+ code_size: Default::default(),
+ code_refcount: Default::default(),
+ _reserved: Default::default(),
+ }
+ }
+}
+
+/// An interface that provides access to the external environment in which the
+/// smart-contract is executed.
+///
+/// This interface is specialized to an account of the executing code, so all
+/// operations are implicitly performed on that account.
+///
+/// # Note
+///
+/// This trait is sealed and cannot be implemented by downstream crates.
+pub trait Ext: sealing::Sealed {
+ type T: Config;
+
+ /// Returns the storage entry of the executing account by the given `key`.
+ ///
+ /// Returns `None` if the `key` wasn't previously set by `set_storage` or
+ /// was deleted.
+ fn get_storage(&self, key: &StorageKey) -> Option<Vec<u8>>;
+
+ /// Sets the storage entry by the given key to the specified value. If `value` is `None` then
+ /// the storage entry is deleted.
+ fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult;
+
+ /// Instantiate a contract from the given code.
+ ///
+ /// Returns the original code size of the called contract.
+ /// The newly created account will be associated with `code`. `value` specifies the amount of value
+ /// transferred from this to the newly created account (also known as endowment).
+ ///
+ /// # Return Value
+ ///
+ /// Result<(AccountId, ExecReturnValue, CodeSize), (ExecError, CodeSize)>
+ fn instantiate(
+ &mut self,
+ code: CodeHash<Self::T>,
+ value: BalanceOf<Self::T>,
+ gas_meter: &mut GasMeter<Self::T>,
+ input_data: Vec<u8>,
+ salt: &[u8],
+ ) -> Result<(AccountIdOf<Self::T>, ExecReturnValue, u32), (ExecError, u32)>;
+
+ /// Transfer some amount of funds into the specified account.
+ fn transfer(&mut self, to: &AccountIdOf<Self::T>, value: BalanceOf<Self::T>) -> DispatchResult;
+
+ /// Transfer all funds to `beneficiary` and delete the contract.
+ ///
+ /// Returns the original code size of the terminated contract.
+ /// Since this function removes the self contract eagerly, if succeeded, no further actions should
+ /// be performed on this `Ext` instance.
+ ///
+ /// This function will fail if the same contract is present on the contract
+ /// call stack.
+ ///
+ /// # Return Value
+ ///
+ /// Result<CodeSize, (DispatchError, CodeSize)>
+ fn terminate(
+ &mut self,
+ beneficiary: &AccountIdOf<Self::T>,
+ ) -> Result<u32, (DispatchError, u32)>;
+
+ /// Call (possibly transferring some amount of funds) into the specified account.
+ ///
+ /// Returns the original code size of the called contract.
+ ///
+ /// # Return Value
+ ///
+ /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
+ fn call(
+ &mut self,
+ to: &AccountIdOf<Self::T>,
+ value: BalanceOf<Self::T>,
+ gas_meter: &mut GasMeter<Self::T>,
+ input_data: Vec<u8>,
+ ) -> Result<(ExecReturnValue, u32), (ExecError, u32)>;
+
+ /// Restores the given destination contract sacrificing the current one.
+ ///
+ /// Since this function removes the self contract eagerly, if succeeded, no further actions should
+ /// be performed on this `Ext` instance.
+ ///
+ /// This function will fail if the same contract is present
+ /// on the contract call stack.
+ ///
+ /// # Return Value
+ ///
+ /// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
+ fn restore_to(
+ &mut self,
+ dest: AccountIdOf<Self::T>,
+ code_hash: CodeHash<Self::T>,
+ rent_allowance: BalanceOf<Self::T>,
+ delta: Vec<StorageKey>,
+ ) -> Result<(u32, u32), (DispatchError, u32, u32)>;
+
+ /// Returns a reference to the account id of the caller.
+ fn caller(&self) -> &AccountIdOf<Self::T>;
+
+ /// Returns a reference to the account id of the current contract.
+ fn address(&self) -> &AccountIdOf<Self::T>;
+
+ /// Returns the balance of the current contract.
+ ///
+ /// The `value_transferred` is already added.
+ fn balance(&self) -> BalanceOf<Self::T>;
+
+ /// Returns the value transferred along with this call or as endowment.
+ fn value_transferred(&self) -> BalanceOf<Self::T>;
+
+ /// Returns a reference to the timestamp of the current block
+ fn now(&self) -> &MomentOf<Self::T>;
+
+ /// Returns the minimum balance that is required for creating an account.
+ fn minimum_balance(&self) -> BalanceOf<Self::T>;
+
+ /// Returns the deposit required to create a tombstone upon contract eviction.
+ fn tombstone_deposit(&self) -> BalanceOf<Self::T>;
+
+ /// Returns a random number for the current block with the given subject.
+ fn random(&self, subject: &[u8]) -> (SeedOf<Self::T>, BlockNumberOf<Self::T>);
+
+ /// Deposit an event with the given topics.
+ ///
+ /// There should not be any duplicates in `topics`.
+ fn deposit_event(&mut self, topics: Vec<TopicOf<Self::T>>, data: Vec<u8>);
+
+ /// Set rent allowance of the contract
+ fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<Self::T>);
+
+ /// Rent allowance of the contract
+ fn rent_allowance(&self) -> BalanceOf<Self::T>;
+
+ /// Returns the current block number.
+ fn block_number(&self) -> BlockNumberOf<Self::T>;
+
+ /// Returns the maximum allowed size of a storage item.
+ fn max_value_size(&self) -> u32;
+
+ /// Returns the price for the specified amount of weight.
+ fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T>;
+
+ /// Get a reference to the schedule used by the current call.
+ fn schedule(&self) -> &Schedule<Self::T>;
+
+ /// Information needed for rent calculations.
+ fn rent_params(&self) -> &RentParams<Self::T>;
+}
+
+/// Describes the different functions that can be exported by an [`Executable`].
+#[cfg_attr(test, derive(Clone, Copy, PartialEq))]
+pub enum ExportedFunction {
+ /// The constructor function which is executed on deployment of a contract.
+ Constructor,
+ /// The function which is executed when a contract is called.
+ Call,
+}
+
+/// A trait that represents something that can be executed.
+///
+/// In the on-chain environment this would be represented by a wasm module. This trait exists in
+/// order to be able to mock the wasm logic for testing.
+pub trait Executable<T: Config>: Sized {
+ /// Load the executable from storage.
+ fn from_storage(
+ code_hash: CodeHash<T>,
+ schedule: &Schedule<T>,
+ gas_meter: &mut GasMeter<T>,
+ ) -> Result<Self, DispatchError>;
+
+ /// Load the module from storage without re-instrumenting it.
+ ///
+ /// A code module is re-instrumented on-load when it was originally instrumented with
+ /// an older schedule. This skips this step for cases where the code storage is
+ /// queried for purposes other than execution.
+ fn from_storage_noinstr(code_hash: CodeHash<T>) -> Result<Self, DispatchError>;
+
+ /// Decrements the refcount by one and deletes the code if it drops to zero.
+ fn drop_from_storage(self);
+
+ /// Increment the refcount by one. Fails if the code does not exist on-chain.
+ ///
+ /// Returns the size of the original code.
+ fn add_user(code_hash: CodeHash<T>) -> Result<u32, DispatchError>;
+
+ /// Decrement the refcount by one and remove the code when it drops to zero.
+ ///
+ /// Returns the size of the original code.
+ fn remove_user(code_hash: CodeHash<T>) -> u32;
+
+ /// Execute the specified exported function and return the result.
+ ///
+ /// When the specified function is `Constructor` the executable is stored and its
+ /// refcount incremented.
+ ///
+ /// # Note
+ ///
+ /// This functions expects to be executed in a storage transaction that rolls back
+ /// all of its emitted storage changes.
+ fn execute<E: Ext<T = T>>(
+ self,
+ ext: E,
+ function: &ExportedFunction,
+ input_data: Vec<u8>,
+ gas_meter: &mut GasMeter<T>,
+ ) -> ExecResult;
+
+ /// The code hash of the executable.
+ fn code_hash(&self) -> &CodeHash<T>;
+
+ /// The storage that is occupied by the instrumented executable and its pristine source.
+ ///
+ /// The returned size is already divided by the number of users who share the code.
+ /// This is essentially `aggregate_code_len() / refcount()`.
+ ///
+ /// # Note
+ ///
+ /// This works with the current in-memory value of refcount. When calling any contract
+ /// without refetching this from storage the result can be inaccurate as it might be
+ /// working with a stale value. Usually this inaccuracy is tolerable.
+ fn occupied_storage(&self) -> u32;
+
+ /// Size of the instrumented code in bytes.
+ fn code_len(&self) -> u32;
+
+ /// Sum of instrumented and pristine code len.
+ fn aggregate_code_len(&self) -> u32;
+
+ // The number of contracts using this executable.
+ fn refcount(&self) -> u32;
+}
+
+pub struct ExecutionContext<'a, T: Config + 'a, E> {
+ caller: Option<&'a ExecutionContext<'a, T, E>>,
+ self_account: T::AccountId,
+ self_trie_id: Option<TrieId>,
+ depth: usize,
+ schedule: &'a Schedule<T>,
+ timestamp: MomentOf<T>,
+ block_number: T::BlockNumber,
+ _phantom: PhantomData<E>,
+}
+
+impl<'a, T, E> ExecutionContext<'a, T, E>
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+ E: Executable<T>,
+{
+ /// Create the top level execution context.
+ ///
+ /// The specified `origin` address will be used as `sender` for. The `origin` must be a regular
+ /// account (not a contract).
+ pub fn top_level(origin: T::AccountId, schedule: &'a Schedule<T>) -> Self {
+ ExecutionContext {
+ caller: None,
+ self_trie_id: None,
+ self_account: origin,
+ depth: 0,
+ schedule,
+ timestamp: T::Time::now(),
+ block_number: <frame_system::Pallet<T>>::block_number(),
+ _phantom: Default::default(),
+ }
+ }
+
+ fn nested<'b, 'c: 'b>(
+ &'c self,
+ dest: T::AccountId,
+ trie_id: TrieId,
+ ) -> ExecutionContext<'b, T, E> {
+ ExecutionContext {
+ caller: Some(self),
+ self_trie_id: Some(trie_id),
+ self_account: dest,
+ depth: self.depth + 1,
+ schedule: self.schedule,
+ timestamp: self.timestamp.clone(),
+ block_number: self.block_number.clone(),
+ _phantom: Default::default(),
+ }
+ }
+
+ /// Make a call to the specified address, optionally transferring some funds.
+ ///
+ /// # Return Value
+ ///
+ /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)>
+ pub fn call(
+ &mut self,
+ dest: T::AccountId,
+ value: BalanceOf<T>,
+ gas_meter: &mut GasMeter<T>,
+ input_data: Vec<u8>,
+ ) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
+ if self.depth == T::MaxDepth::get() as usize {
+ return Err((Error::<T>::MaxCallDepthReached.into(), 0));
+ }
+
+ let contract = <ContractInfoOf<T>>::get(&dest)
+ .and_then(|contract| contract.get_alive())
+ .ok_or((Error::<T>::NotCallable.into(), 0))?;
+
+ let executable = E::from_storage(contract.code_hash, &self.schedule, gas_meter)
+ .map_err(|e| (e.into(), 0))?;
+ let code_len = executable.code_len();
+
+ // This charges the rent and denies access to a contract that is in need of
+ // eviction by returning `None`. We cannot evict eagerly here because those
+ // changes would be rolled back in case this contract is called by another
+ // contract.
+ // See: https://github.com/paritytech/substrate/issues/6439#issuecomment-648754324
+ let contract = Rent::<T, E>::charge(&dest, contract, executable.occupied_storage())
+ .map_err(|e| (e.into(), code_len))?
+ .ok_or((Error::<T>::NotCallable.into(), code_len))?;
+
+ let transactor_kind = self.transactor_kind();
+ let caller = self.self_account.clone();
+
+ let result = self
+ .with_nested_context(dest.clone(), contract.trie_id.clone(), |nested| {
+ if value > BalanceOf::<T>::zero() {
+ transfer::<T>(TransferCause::Call, transactor_kind, &caller, &dest, value)?
+ }
+
+ let call_context =
+ nested.new_call_context(caller, &dest, value, &contract, &executable);
+
+ let output = executable
+ .execute(call_context, &ExportedFunction::Call, input_data, gas_meter)
+ .map_err(|e| ExecError {
+ error: e.error,
+ origin: ErrorOrigin::Callee,
+ })?;
+ Ok(output)
+ })
+ .map_err(|e| (e, code_len))?;
+ Ok((result, code_len))
+ }
+
+ pub fn instantiate(
+ &mut self,
+ endowment: BalanceOf<T>,
+ gas_meter: &mut GasMeter<T>,
+ executable: E,
+ input_data: Vec<u8>,
+ salt: &[u8],
+ ) -> Result<(T::AccountId, ExecReturnValue), ExecError> {
+ if self.depth == T::MaxDepth::get() as usize {
+ Err(Error::<T>::MaxCallDepthReached)?
+ }
+
+ let transactor_kind = self.transactor_kind();
+ let caller = self.self_account.clone();
+ let dest = Contracts::<T>::contract_address(&caller, executable.code_hash(), salt);
+
+ let output = frame_support::storage::with_transaction(|| {
+ // Generate the trie id in a new transaction to only increment the counter on success.
+ let dest_trie_id = Storage::<T>::generate_trie_id(&dest);
+
+ let output = self.with_nested_context(dest.clone(), dest_trie_id, |nested| {
+ let contract = Storage::<T>::place_contract(
+ &dest,
+ nested
+ .self_trie_id
+ .clone()
+ .expect("the nested context always has to have self_trie_id"),
+ executable.code_hash().clone(),
+ )?;
+
+ // Send funds unconditionally here. If the `endowment` is below existential_deposit
+ // then error will be returned here.
+ transfer::<T>(
+ TransferCause::Instantiate,
+ transactor_kind,
+ &caller,
+ &dest,
+ endowment,
+ )?;
+
+ // Cache the value before calling into the constructor because that
+ // consumes the value. If the constructor creates additional contracts using
+ // the same code hash we still charge the "1 block rent" as if they weren't
+ // spawned. This is OK as overcharging is always safe.
+ let occupied_storage = executable.occupied_storage();
+
+ let call_context = nested.new_call_context(
+ caller.clone(),
+ &dest,
+ endowment,
+ &contract,
+ &executable,
+ );
+
+ let output = executable
+ .execute(
+ call_context,
+ &ExportedFunction::Constructor,
+ input_data,
+ gas_meter,
+ )
+ .map_err(|e| ExecError {
+ error: e.error,
+ origin: ErrorOrigin::Callee,
+ })?;
+
+ // We need to re-fetch the contract because changes are written to storage
+ // eagerly during execution.
+ let contract = <ContractInfoOf<T>>::get(&dest)
+ .and_then(|contract| contract.get_alive())
+ .ok_or(Error::<T>::NotCallable)?;
+
+ // Collect the rent for the first block to prevent the creation of very large
+ // contracts that never intended to pay for even one block.
+ // This also makes sure that it is above the subsistence threshold
+ // in order to keep up the guarantuee that we always leave a tombstone behind
+ // with the exception of a contract that called `seal_terminate`.
+ Rent::<T, E>::charge(&dest, contract, occupied_storage)?
+ .ok_or(Error::<T>::NewContractNotFunded)?;
+
+ // Deposit an instantiation event.
+ deposit_event::<T>(vec![], Event::Instantiated(caller.clone(), dest.clone()));
+
+ Ok(output)
+ });
+
+ use frame_support::storage::TransactionOutcome::*;
+ match output {
+ Ok(_) => Commit(output),
+ Err(_) => Rollback(output),
+ }
+ })?;
+
+ Ok((dest, output))
+ }
+
+ fn new_call_context<'b>(
+ &'b mut self,
+ caller: T::AccountId,
+ dest: &T::AccountId,
+ value: BalanceOf<T>,
+ contract: &AliveContractInfo<T>,
+ executable: &E,
+ ) -> CallContext<'b, 'a, T, E> {
+ let timestamp = self.timestamp.clone();
+ let block_number = self.block_number.clone();
+ CallContext {
+ ctx: self,
+ caller,
+ value_transferred: value,
+ timestamp,
+ block_number,
+ rent_params: RentParams::new(dest, contract, executable),
+ _phantom: Default::default(),
+ }
+ }
+
+ /// Execute the given closure within a nested execution context.
+ fn with_nested_context<F>(&mut self, dest: T::AccountId, trie_id: TrieId, func: F) -> ExecResult
+ where
+ F: FnOnce(&mut ExecutionContext<T, E>) -> ExecResult,
+ {
+ use frame_support::storage::TransactionOutcome::*;
+ let mut nested = self.nested(dest, trie_id);
+ frame_support::storage::with_transaction(|| {
+ let output = func(&mut nested);
+ match output {
+ Ok(ref rv) if !rv.flags.contains(ReturnFlags::REVERT) => Commit(output),
+ _ => Rollback(output),
+ }
+ })
+ }
+
+ /// Returns whether a contract, identified by address, is currently live in the execution
+ /// stack, meaning it is in the middle of an execution.
+ fn is_live(&self, account: &T::AccountId) -> bool {
+ &self.self_account == account || self.caller.map_or(false, |caller| caller.is_live(account))
+ }
+
+ fn transactor_kind(&self) -> TransactorKind {
+ if self.depth == 0 {
+ debug_assert!(self.self_trie_id.is_none());
+ debug_assert!(self.caller.is_none());
+ debug_assert!(ContractInfoOf::<T>::get(&self.self_account).is_none());
+ TransactorKind::PlainAccount
+ } else {
+ TransactorKind::Contract
+ }
+ }
+}
+
+/// Describes whether we deal with a contract or a plain account.
+enum TransactorKind {
+ /// Transaction was initiated from a plain account. That can be either be through a
+ /// signed transaction or through RPC.
+ PlainAccount,
+ /// The call was initiated by a contract account.
+ Contract,
+}
+
+/// Describes possible transfer causes.
+enum TransferCause {
+ Call,
+ Instantiate,
+ Terminate,
+}
+
+/// Transfer some funds from `transactor` to `dest`.
+///
+/// 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<T: Config>(
+ cause: TransferCause,
+ origin: TransactorKind,
+ transactor: &T::AccountId,
+ dest: &T::AccountId,
+ value: BalanceOf<T>,
+) -> DispatchResult
+where
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ use self::TransferCause::*;
+ use self::TransactorKind::*;
+
+ // Only seal_terminate is allowed to bring the sender below the subsistence
+ // threshold or even existential deposit.
+ let existence_requirement = match (cause, origin) {
+ (Terminate, _) => ExistenceRequirement::AllowDeath,
+ (_, Contract) => {
+ ensure!(
+ T::Currency::total_balance(transactor).saturating_sub(value)
+ >= Contracts::<T>::subsistence_threshold(),
+ Error::<T>::BelowSubsistenceThreshold,
+ );
+ ExistenceRequirement::KeepAlive
+ }
+ (_, PlainAccount) => ExistenceRequirement::KeepAlive,
+ };
+
+ T::Currency::transfer(transactor, dest, value, existence_requirement)
+ .map_err(|_| Error::<T>::TransferFailed)?;
+
+ Ok(())
+}
+
+/// A context that is active within a call.
+///
+/// This context has some invariants that must be held at all times. Specifically:
+///`ctx` always points to a context of an alive contract. That implies that it has an existent
+/// `self_trie_id`.
+///
+/// Be advised that there are brief time spans where these invariants could be invalidated.
+/// For example, when a contract requests self-termination the contract is removed eagerly. That
+/// implies that the control won't be returned to the contract anymore, but there is still some code
+/// on the path of the return from that call context. Therefore, care must be taken in these
+/// situations.
+struct CallContext<'a, 'b: 'a, T: Config + 'b, E> {
+ ctx: &'a mut ExecutionContext<'b, T, E>,
+ caller: T::AccountId,
+ value_transferred: BalanceOf<T>,
+ timestamp: MomentOf<T>,
+ block_number: T::BlockNumber,
+ rent_params: RentParams<T>,
+ _phantom: PhantomData<E>,
+}
+
+impl<'a, 'b: 'a, T, E> Ext for CallContext<'a, 'b, T, E>
+where
+ T: Config + 'b,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+ E: Executable<T>,
+{
+ type T = T;
+
+ fn get_storage(&self, key: &StorageKey) -> Option<Vec<u8>> {
+ let trie_id = self.ctx.self_trie_id.as_ref().expect(
+ "`ctx.self_trie_id` points to an alive contract within the `CallContext`;\
+ it cannot be `None`;\
+ expect can't fail;\
+ qed",
+ );
+ Storage::<T>::read(trie_id, key)
+ }
+
+ fn set_storage(&mut self, key: StorageKey, value: Option<Vec<u8>>) -> DispatchResult {
+ let trie_id = self.ctx.self_trie_id.as_ref().expect(
+ "`ctx.self_trie_id` points to an alive contract within the `CallContext`;\
+ it cannot be `None`;\
+ expect can't fail;\
+ qed",
+ );
+ // write panics if the passed account is not alive.
+ // the contract must be in the alive state within the `CallContext`;\
+ // the contract cannot be absent in storage;
+ // write cannot return `None`;
+ // qed
+ Storage::<T>::write(&self.ctx.self_account, trie_id, &key, value)
+ }
+
+ fn instantiate(
+ &mut self,
+ code_hash: CodeHash<T>,
+ endowment: BalanceOf<T>,
+ gas_meter: &mut GasMeter<T>,
+ input_data: Vec<u8>,
+ salt: &[u8],
+ ) -> Result<(AccountIdOf<T>, ExecReturnValue, u32), (ExecError, u32)> {
+ let executable =
+ E::from_storage(code_hash, &self.ctx.schedule, gas_meter).map_err(|e| (e.into(), 0))?;
+ let code_len = executable.code_len();
+ self.ctx
+ .instantiate(endowment, gas_meter, executable, input_data, salt)
+ .map(|r| (r.0, r.1, code_len))
+ .map_err(|e| (e, code_len))
+ }
+
+ fn transfer(&mut self, to: &T::AccountId, value: BalanceOf<T>) -> DispatchResult {
+ transfer::<T>(
+ TransferCause::Call,
+ TransactorKind::Contract,
+ &self.ctx.self_account.clone(),
+ to,
+ value,
+ )
+ }
+
+ fn terminate(
+ &mut self,
+ beneficiary: &AccountIdOf<Self::T>,
+ ) -> Result<u32, (DispatchError, u32)> {
+ let self_id = self.ctx.self_account.clone();
+ let value = T::Currency::free_balance(&self_id);
+ if let Some(caller_ctx) = self.ctx.caller {
+ if caller_ctx.is_live(&self_id) {
+ return Err((Error::<T>::ReentranceDenied.into(), 0));
+ }
+ }
+ transfer::<T>(
+ TransferCause::Terminate,
+ TransactorKind::Contract,
+ &self_id,
+ beneficiary,
+ value,
+ )
+ .map_err(|e| (e, 0))?;
+ if let Some(ContractInfo::Alive(info)) = ContractInfoOf::<T>::take(&self_id) {
+ Storage::<T>::queue_trie_for_deletion(&info).map_err(|e| (e, 0))?;
+ let code_len = E::remove_user(info.code_hash);
+ Contracts::<T>::deposit_event(Event::Terminated(self_id, beneficiary.clone()));
+ Ok(code_len)
+ } else {
+ panic!(
+ "this function is only invoked by in the context of a contract;\
+ this contract is therefore alive;\
+ qed"
+ );
+ }
+ }
+
+ fn call(
+ &mut self,
+ to: &T::AccountId,
+ value: BalanceOf<T>,
+ gas_meter: &mut GasMeter<T>,
+ input_data: Vec<u8>,
+ ) -> Result<(ExecReturnValue, u32), (ExecError, u32)> {
+ self.ctx.call(to.clone(), value, gas_meter, input_data)
+ }
+
+ fn restore_to(
+ &mut self,
+ dest: AccountIdOf<Self::T>,
+ code_hash: CodeHash<Self::T>,
+ rent_allowance: BalanceOf<Self::T>,
+ delta: Vec<StorageKey>,
+ ) -> Result<(u32, u32), (DispatchError, u32, u32)> {
+ if let Some(caller_ctx) = self.ctx.caller {
+ if caller_ctx.is_live(&self.ctx.self_account) {
+ return Err((Error::<T>::ReentranceDenied.into(), 0, 0));
+ }
+ }
+
+ let result = Rent::<T, E>::restore_to(
+ self.ctx.self_account.clone(),
+ dest.clone(),
+ code_hash.clone(),
+ rent_allowance,
+ delta,
+ );
+ if let Ok(_) = result {
+ deposit_event::<Self::T>(
+ vec![],
+ Event::Restored(
+ self.ctx.self_account.clone(),
+ dest,
+ code_hash,
+ rent_allowance,
+ ),
+ );
+ }
+ result
+ }
+
+ fn address(&self) -> &T::AccountId {
+ &self.ctx.self_account
+ }
+
+ fn caller(&self) -> &T::AccountId {
+ &self.caller
+ }
+
+ fn balance(&self) -> BalanceOf<T> {
+ T::Currency::free_balance(&self.ctx.self_account)
+ }
+
+ fn value_transferred(&self) -> BalanceOf<T> {
+ self.value_transferred
+ }
+
+ fn random(&self, subject: &[u8]) -> (SeedOf<T>, BlockNumberOf<T>) {
+ T::Randomness::random(subject)
+ }
+
+ fn now(&self) -> &MomentOf<T> {
+ &self.timestamp
+ }
+
+ fn minimum_balance(&self) -> BalanceOf<T> {
+ T::Currency::minimum_balance()
+ }
+
+ fn tombstone_deposit(&self) -> BalanceOf<T> {
+ T::TombstoneDeposit::get()
+ }
+
+ fn deposit_event(&mut self, topics: Vec<T::Hash>, data: Vec<u8>) {
+ deposit_event::<Self::T>(
+ topics,
+ Event::ContractEmitted(self.ctx.self_account.clone(), data),
+ );
+ }
+
+ fn set_rent_allowance(&mut self, rent_allowance: BalanceOf<T>) {
+ if let Err(storage::ContractAbsentError) =
+ Storage::<T>::set_rent_allowance(&self.ctx.self_account, rent_allowance)
+ {
+ panic!(
+ "`self_account` points to an alive contract within the `CallContext`;
+ set_rent_allowance cannot return `Err`; qed"
+ );
+ }
+ }
+
+ fn rent_allowance(&self) -> BalanceOf<T> {
+ Storage::<T>::rent_allowance(&self.ctx.self_account)
+ .unwrap_or_else(|_| <BalanceOf<T>>::max_value()) // Must never be triggered actually
+ }
+
+ fn block_number(&self) -> T::BlockNumber {
+ self.block_number
+ }
+
+ fn max_value_size(&self) -> u32 {
+ T::MaxValueSize::get()
+ }
+
+ fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
+ T::WeightPrice::convert(weight)
+ }
+
+ fn schedule(&self) -> &Schedule<Self::T> {
+ &self.ctx.schedule
+ }
+
+ fn rent_params(&self) -> &RentParams<Self::T> {
+ &self.rent_params
+ }
+}
+
+fn deposit_event<T: Config>(topics: Vec<T::Hash>, event: Event<T>) {
+ <frame_system::Pallet<T>>::deposit_event_indexed(
+ &*topics,
+ <T as Config>::Event::from(event).into(),
+ )
+}
+
+mod sealing {
+ use super::*;
+
+ pub trait Sealed {}
+
+ impl<'a, 'b: 'a, T: Config, E> Sealed for CallContext<'a, 'b, T, E> {}
+
+ #[cfg(test)]
+ impl Sealed for crate::wasm::MockExt {}
+
+ #[cfg(test)]
+ impl Sealed for &mut crate::wasm::MockExt {}
+}
+
+/// These tests exercise the executive layer.
+///
+/// In these tests the VM/loader are mocked. Instead of dealing with wasm bytecode they use simple closures.
+/// This allows you to tackle executive logic more thoroughly without writing a
+/// wasm VM code.
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::{
+ gas::GasMeter,
+ tests::{ExtBuilder, Test, Event as MetaEvent},
+ storage::{Storage, ContractAbsentError},
+ tests::{
+ ALICE, BOB, CHARLIE,
+ test_utils::{place_contract, set_balance, get_balance},
+ },
+ exec::ExportedFunction::*,
+ Error, Weight, CurrentSchedule,
+ };
+ use sp_core::Bytes;
+ use frame_support::assert_noop;
+ use sp_runtime::DispatchError;
+ use assert_matches::assert_matches;
+ use std::{cell::RefCell, collections::HashMap, rc::Rc};
+ use pretty_assertions::{assert_eq, assert_ne};
+
+ type MockContext<'a> = ExecutionContext<'a, Test, MockExecutable>;
+
+ const GAS_LIMIT: Weight = 10_000_000_000;
+
+ thread_local! {
+ static LOADER: RefCell<MockLoader> = RefCell::new(MockLoader::default());
+ }
+
+ fn events() -> Vec<Event<Test>> {
+ <frame_system::Pallet<Test>>::events()
+ .into_iter()
+ .filter_map(|meta| match meta.event {
+ MetaEvent::pallet_contracts(contract_event) => Some(contract_event),
+ _ => None,
+ })
+ .collect()
+ }
+
+ struct MockCtx<'a> {
+ ext: &'a mut dyn Ext<T = Test>,
+ input_data: Vec<u8>,
+ gas_meter: &'a mut GasMeter<Test>,
+ }
+
+ #[derive(Clone)]
+ struct MockExecutable {
+ func: Rc<dyn Fn(MockCtx, &Self) -> ExecResult + 'static>,
+ func_type: ExportedFunction,
+ code_hash: CodeHash<Test>,
+ refcount: u64,
+ }
+
+ #[derive(Default)]
+ struct MockLoader {
+ map: HashMap<CodeHash<Test>, MockExecutable>,
+ counter: u64,
+ }
+
+ impl MockLoader {
+ fn insert(
+ func_type: ExportedFunction,
+ f: impl Fn(MockCtx, &MockExecutable) -> ExecResult + 'static,
+ ) -> CodeHash<Test> {
+ LOADER.with(|loader| {
+ let mut loader = loader.borrow_mut();
+ // Generate code hashes as monotonically increasing values.
+ let hash = <Test as frame_system::Config>::Hash::from_low_u64_be(loader.counter);
+ loader.counter += 1;
+ loader.map.insert(
+ hash,
+ MockExecutable {
+ func: Rc::new(f),
+ func_type,
+ code_hash: hash.clone(),
+ refcount: 1,
+ },
+ );
+ hash
+ })
+ }
+
+ fn increment_refcount(code_hash: CodeHash<Test>) {
+ LOADER.with(|loader| {
+ let mut loader = loader.borrow_mut();
+ loader
+ .map
+ .entry(code_hash)
+ .and_modify(|executable| executable.refcount += 1)
+ .or_insert_with(|| panic!("code_hash does not exist"));
+ });
+ }
+
+ fn decrement_refcount(code_hash: CodeHash<Test>) {
+ use std::collections::hash_map::Entry::Occupied;
+ LOADER.with(|loader| {
+ let mut loader = loader.borrow_mut();
+ let mut entry = match loader.map.entry(code_hash) {
+ Occupied(e) => e,
+ _ => panic!("code_hash does not exist"),
+ };
+ let refcount = &mut entry.get_mut().refcount;
+ *refcount -= 1;
+ if *refcount == 0 {
+ entry.remove();
+ }
+ });
+ }
+
+ fn refcount(code_hash: &CodeHash<Test>) -> u32 {
+ LOADER.with(|loader| {
+ loader
+ .borrow()
+ .map
+ .get(code_hash)
+ .expect("code_hash does not exist")
+ .refcount()
+ })
+ }
+ }
+
+ impl Executable<Test> for MockExecutable {
+ fn from_storage(
+ code_hash: CodeHash<Test>,
+ _schedule: &Schedule<Test>,
+ _gas_meter: &mut GasMeter<Test>,
+ ) -> Result<Self, DispatchError> {
+ Self::from_storage_noinstr(code_hash)
+ }
+
+ fn from_storage_noinstr(code_hash: CodeHash<Test>) -> Result<Self, DispatchError> {
+ LOADER.with(|loader| {
+ loader
+ .borrow_mut()
+ .map
+ .get(&code_hash)
+ .cloned()
+ .ok_or(Error::<Test>::CodeNotFound.into())
+ })
+ }
+
+ fn drop_from_storage(self) {
+ MockLoader::decrement_refcount(self.code_hash);
+ }
+
+ fn add_user(code_hash: CodeHash<Test>) -> Result<u32, DispatchError> {
+ MockLoader::increment_refcount(code_hash);
+ Ok(0)
+ }
+
+ fn remove_user(code_hash: CodeHash<Test>) -> u32 {
+ MockLoader::decrement_refcount(code_hash);
+ 0
+ }
+
+ fn execute<E: Ext<T = Test>>(
+ self,
+ mut ext: E,
+ function: &ExportedFunction,
+ input_data: Vec<u8>,
+ gas_meter: &mut GasMeter<Test>,
+ ) -> ExecResult {
+ if let &Constructor = function {
+ MockLoader::increment_refcount(self.code_hash);
+ }
+ if function == &self.func_type {
+ (self.func)(
+ MockCtx {
+ ext: &mut ext,
+ input_data,
+ gas_meter,
+ },
+ &self,
+ )
+ } else {
+ exec_success()
+ }
+ }
+
+ fn code_hash(&self) -> &CodeHash<Test> {
+ &self.code_hash
+ }
+
+ fn occupied_storage(&self) -> u32 {
+ 0
+ }
+
+ fn code_len(&self) -> u32 {
+ 0
+ }
+
+ fn aggregate_code_len(&self) -> u32 {
+ 0
+ }
+
+ fn refcount(&self) -> u32 {
+ self.refcount as u32
+ }
+ }
+
+ fn exec_success() -> ExecResult {
+ Ok(ExecReturnValue {
+ flags: ReturnFlags::empty(),
+ data: Bytes(Vec::new()),
+ })
+ }
+
+ #[test]
+ fn it_works() {
+ thread_local! {
+ static TEST_DATA: RefCell<Vec<usize>> = RefCell::new(vec![0]);
+ }
+
+ let value = Default::default();
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ let exec_ch = MockLoader::insert(Call, |_ctx, _executable| {
+ TEST_DATA.with(|data| data.borrow_mut().push(1));
+ exec_success()
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ place_contract(&BOB, exec_ch);
+
+ assert_matches!(ctx.call(BOB, value, &mut gas_meter, vec![]), Ok(_));
+ });
+
+ TEST_DATA.with(|data| assert_eq!(*data.borrow(), vec![0, 1]));
+ }
+
+ #[test]
+ fn transfer_works() {
+ // This test verifies that a contract is able to transfer
+ // some funds to another account.
+ let origin = ALICE;
+ let dest = BOB;
+
+ ExtBuilder::default().build().execute_with(|| {
+ set_balance(&origin, 100);
+ set_balance(&dest, 0);
+
+ super::transfer::<Test>(
+ super::TransferCause::Call,
+ super::TransactorKind::PlainAccount,
+ &origin,
+ &dest,
+ 55,
+ )
+ .unwrap();
+
+ assert_eq!(get_balance(&origin), 45);
+ assert_eq!(get_balance(&dest), 55);
+ });
+ }
+
+ #[test]
+ fn changes_are_reverted_on_failing_call() {
+ // This test verifies that changes are reverted on a call which fails (or equally, returns
+ // a non-zero status code).
+ let origin = ALICE;
+ let dest = BOB;
+
+ let return_ch = MockLoader::insert(Call, |_, _| {
+ Ok(ExecReturnValue {
+ flags: ReturnFlags::REVERT,
+ data: Bytes(Vec::new()),
+ })
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(origin.clone(), &schedule);
+ place_contract(&BOB, return_ch);
+ set_balance(&origin, 100);
+ let balance = get_balance(&dest);
+
+ let output = ctx
+ .call(
+ dest.clone(),
+ 55,
+ &mut GasMeter::<Test>::new(GAS_LIMIT),
+ vec![],
+ )
+ .unwrap();
+
+ assert!(!output.0.is_success());
+ assert_eq!(get_balance(&origin), 100);
+
+ // the rent is still charged
+ assert!(get_balance(&dest) < balance);
+ });
+ }
+
+ #[test]
+ fn balance_too_low() {
+ // This test verifies that a contract can't send value if it's
+ // balance is too low.
+ let origin = ALICE;
+ let dest = BOB;
+
+ ExtBuilder::default().build().execute_with(|| {
+ set_balance(&origin, 0);
+
+ let result = super::transfer::<Test>(
+ super::TransferCause::Call,
+ super::TransactorKind::PlainAccount,
+ &origin,
+ &dest,
+ 100,
+ );
+
+ assert_eq!(result, Err(Error::<Test>::TransferFailed.into()));
+ assert_eq!(get_balance(&origin), 0);
+ assert_eq!(get_balance(&dest), 0);
+ });
+ }
+
+ #[test]
+ fn output_is_returned_on_success() {
+ // Verifies that if a contract returns data with a successful exit status, this data
+ // is returned from the execution context.
+ let origin = ALICE;
+ let dest = BOB;
+ let return_ch = MockLoader::insert(Call, |_, _| {
+ Ok(ExecReturnValue {
+ flags: ReturnFlags::empty(),
+ data: Bytes(vec![1, 2, 3, 4]),
+ })
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(origin, &schedule);
+ place_contract(&BOB, return_ch);
+
+ let result = ctx.call(dest, 0, &mut GasMeter::<Test>::new(GAS_LIMIT), vec![]);
+
+ let output = result.unwrap();
+ assert!(output.0.is_success());
+ assert_eq!(output.0.data, Bytes(vec![1, 2, 3, 4]));
+ });
+ }
+
+ #[test]
+ fn output_is_returned_on_failure() {
+ // Verifies that if a contract returns data with a failing exit status, this data
+ // is returned from the execution context.
+ let origin = ALICE;
+ let dest = BOB;
+ let return_ch = MockLoader::insert(Call, |_, _| {
+ Ok(ExecReturnValue {
+ flags: ReturnFlags::REVERT,
+ data: Bytes(vec![1, 2, 3, 4]),
+ })
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(origin, &schedule);
+ place_contract(&BOB, return_ch);
+
+ let result = ctx.call(dest, 0, &mut GasMeter::<Test>::new(GAS_LIMIT), vec![]);
+
+ let output = result.unwrap();
+ assert!(!output.0.is_success());
+ assert_eq!(output.0.data, Bytes(vec![1, 2, 3, 4]));
+ });
+ }
+
+ #[test]
+ fn input_data_to_call() {
+ let input_data_ch = MockLoader::insert(Call, |ctx, _| {
+ assert_eq!(ctx.input_data, &[1, 2, 3, 4]);
+ exec_success()
+ });
+
+ // This one tests passing the input data into a contract via call.
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ place_contract(&BOB, input_data_ch);
+
+ let result = ctx.call(
+ BOB,
+ 0,
+ &mut GasMeter::<Test>::new(GAS_LIMIT),
+ vec![1, 2, 3, 4],
+ );
+ assert_matches!(result, Ok(_));
+ });
+ }
+
+ #[test]
+ fn input_data_to_instantiate() {
+ let input_data_ch = MockLoader::insert(Constructor, |ctx, _| {
+ assert_eq!(ctx.input_data, &[1, 2, 3, 4]);
+ exec_success()
+ });
+
+ // This one tests passing the input data into a contract via instantiate.
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let subsistence = Contracts::<Test>::subsistence_threshold();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ let executable =
+ MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap();
+
+ set_balance(&ALICE, subsistence * 10);
+
+ let result = ctx.instantiate(
+ subsistence * 3,
+ &mut gas_meter,
+ executable,
+ vec![1, 2, 3, 4],
+ &[],
+ );
+ assert_matches!(result, Ok(_));
+ });
+ }
+
+ #[test]
+ fn max_depth() {
+ // This test verifies that when we reach the maximal depth creation of an
+ // yet another context fails.
+ thread_local! {
+ static REACHED_BOTTOM: RefCell<bool> = RefCell::new(false);
+ }
+ let value = Default::default();
+ let recurse_ch = MockLoader::insert(Call, |ctx, _| {
+ // Try to call into yourself.
+ let r = ctx.ext.call(&BOB, 0, ctx.gas_meter, vec![]);
+
+ REACHED_BOTTOM.with(|reached_bottom| {
+ let mut reached_bottom = reached_bottom.borrow_mut();
+ if !*reached_bottom {
+ // We are first time here, it means we just reached bottom.
+ // Verify that we've got proper error and set `reached_bottom`.
+ assert_eq!(r, Err((Error::<Test>::MaxCallDepthReached.into(), 0)));
+ *reached_bottom = true;
+ } else {
+ // We just unwinding stack here.
+ assert_matches!(r, Ok(_));
+ }
+ });
+
+ exec_success()
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ set_balance(&BOB, 1);
+ place_contract(&BOB, recurse_ch);
+
+ let result = ctx.call(BOB, value, &mut GasMeter::<Test>::new(GAS_LIMIT), vec![]);
+
+ assert_matches!(result, Ok(_));
+ });
+ }
+
+ #[test]
+ fn caller_returns_proper_values() {
+ let origin = ALICE;
+ let dest = BOB;
+
+ thread_local! {
+ static WITNESSED_CALLER_BOB: RefCell<Option<AccountIdOf<Test>>> = RefCell::new(None);
+ static WITNESSED_CALLER_CHARLIE: RefCell<Option<AccountIdOf<Test>>> = RefCell::new(None);
+ }
+
+ let bob_ch = MockLoader::insert(Call, |ctx, _| {
+ // Record the caller for bob.
+ WITNESSED_CALLER_BOB
+ .with(|caller| *caller.borrow_mut() = Some(ctx.ext.caller().clone()));
+
+ // Call into CHARLIE contract.
+ assert_matches!(ctx.ext.call(&CHARLIE, 0, ctx.gas_meter, vec![]), Ok(_));
+ exec_success()
+ });
+ let charlie_ch = MockLoader::insert(Call, |ctx, _| {
+ // Record the caller for charlie.
+ WITNESSED_CALLER_CHARLIE
+ .with(|caller| *caller.borrow_mut() = Some(ctx.ext.caller().clone()));
+ exec_success()
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(origin.clone(), &schedule);
+ place_contract(&dest, bob_ch);
+ place_contract(&CHARLIE, charlie_ch);
+
+ let result = ctx.call(
+ dest.clone(),
+ 0,
+ &mut GasMeter::<Test>::new(GAS_LIMIT),
+ vec![],
+ );
+
+ assert_matches!(result, Ok(_));
+ });
+
+ WITNESSED_CALLER_BOB.with(|caller| assert_eq!(*caller.borrow(), Some(origin)));
+ WITNESSED_CALLER_CHARLIE.with(|caller| assert_eq!(*caller.borrow(), Some(dest)));
+ }
+
+ #[test]
+ fn address_returns_proper_values() {
+ let bob_ch = MockLoader::insert(Call, |ctx, _| {
+ // Verify that address matches BOB.
+ assert_eq!(*ctx.ext.address(), BOB);
+
+ // Call into charlie contract.
+ assert_matches!(ctx.ext.call(&CHARLIE, 0, ctx.gas_meter, vec![]), Ok(_));
+ exec_success()
+ });
+ let charlie_ch = MockLoader::insert(Call, |ctx, _| {
+ assert_eq!(*ctx.ext.address(), CHARLIE);
+ exec_success()
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ place_contract(&BOB, bob_ch);
+ place_contract(&CHARLIE, charlie_ch);
+
+ let result = ctx.call(BOB, 0, &mut GasMeter::<Test>::new(GAS_LIMIT), vec![]);
+
+ assert_matches!(result, Ok(_));
+ });
+ }
+
+ #[test]
+ fn refuse_instantiate_with_value_below_existential_deposit() {
+ let dummy_ch = MockLoader::insert(Constructor, |_, _| exec_success());
+
+ ExtBuilder::default()
+ .existential_deposit(15)
+ .build()
+ .execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ let executable =
+ MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
+
+ assert_matches!(
+ ctx.instantiate(
+ 0, // <- zero endowment
+ &mut gas_meter,
+ executable,
+ vec![],
+ &[],
+ ),
+ Err(_)
+ );
+ });
+ }
+
+ #[test]
+ fn instantiation_work_with_success_output() {
+ let dummy_ch = MockLoader::insert(Constructor, |_, _| {
+ Ok(ExecReturnValue {
+ flags: ReturnFlags::empty(),
+ data: Bytes(vec![80, 65, 83, 83]),
+ })
+ });
+
+ ExtBuilder::default()
+ .existential_deposit(15)
+ .build()
+ .execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ let executable =
+ MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
+ set_balance(&ALICE, 1000);
+
+ let instantiated_contract_address = assert_matches!(
+ ctx.instantiate(
+ 100,
+ &mut gas_meter,
+ executable,
+ vec![],
+ &[],
+ ),
+ Ok((address, ref output)) if output.data == Bytes(vec![80, 65, 83, 83]) => address
+ );
+
+ // Check that the newly created account has the expected code hash and
+ // there are instantiation event.
+ assert_eq!(
+ Storage::<Test>::code_hash(&instantiated_contract_address).unwrap(),
+ dummy_ch
+ );
+ assert_eq!(
+ &events(),
+ &[Event::Instantiated(ALICE, instantiated_contract_address)]
+ );
+ });
+ }
+
+ #[test]
+ fn instantiation_fails_with_failing_output() {
+ let dummy_ch = MockLoader::insert(Constructor, |_, _| {
+ Ok(ExecReturnValue {
+ flags: ReturnFlags::REVERT,
+ data: Bytes(vec![70, 65, 73, 76]),
+ })
+ });
+
+ ExtBuilder::default()
+ .existential_deposit(15)
+ .build()
+ .execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ let executable =
+ MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap();
+ set_balance(&ALICE, 1000);
+
+ let instantiated_contract_address = assert_matches!(
+ ctx.instantiate(
+ 100,
+ &mut gas_meter,
+ executable,
+ vec![],
+ &[],
+ ),
+ Ok((address, ref output)) if output.data == Bytes(vec![70, 65, 73, 76]) => address
+ );
+
+ // Check that the account has not been created.
+ assert_noop!(
+ Storage::<Test>::code_hash(&instantiated_contract_address),
+ ContractAbsentError,
+ );
+ assert!(events().is_empty());
+ });
+ }
+
+ #[test]
+ fn instantiation_from_contract() {
+ let dummy_ch = MockLoader::insert(Call, |_, _| exec_success());
+ let instantiated_contract_address = Rc::new(RefCell::new(None::<AccountIdOf<Test>>));
+ let instantiator_ch = MockLoader::insert(Call, {
+ let dummy_ch = dummy_ch.clone();
+ let instantiated_contract_address = Rc::clone(&instantiated_contract_address);
+ move |ctx, _| {
+ // Instantiate a contract and save it's address in `instantiated_contract_address`.
+ let (address, output, _) = ctx
+ .ext
+ .instantiate(
+ dummy_ch,
+ Contracts::<Test>::subsistence_threshold() * 3,
+ ctx.gas_meter,
+ vec![],
+ &[48, 49, 50],
+ )
+ .unwrap();
+
+ *instantiated_contract_address.borrow_mut() = address.into();
+ Ok(output)
+ }
+ });
+
+ ExtBuilder::default()
+ .existential_deposit(15)
+ .build()
+ .execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ set_balance(&ALICE, Contracts::<Test>::subsistence_threshold() * 100);
+ place_contract(&BOB, instantiator_ch);
+
+ assert_matches!(
+ ctx.call(BOB, 20, &mut GasMeter::<Test>::new(GAS_LIMIT), vec![]),
+ Ok(_)
+ );
+
+ let instantiated_contract_address = instantiated_contract_address
+ .borrow()
+ .as_ref()
+ .unwrap()
+ .clone();
+
+ // Check that the newly created account has the expected code hash and
+ // there are instantiation event.
+ assert_eq!(
+ Storage::<Test>::code_hash(&instantiated_contract_address).unwrap(),
+ dummy_ch
+ );
+ assert_eq!(
+ &events(),
+ &[Event::Instantiated(BOB, instantiated_contract_address)]
+ );
+ });
+ }
+
+ #[test]
+ fn instantiation_traps() {
+ let dummy_ch = MockLoader::insert(Constructor, |_, _| Err("It's a trap!".into()));
+ let instantiator_ch = MockLoader::insert(Call, {
+ let dummy_ch = dummy_ch.clone();
+ move |ctx, _| {
+ // Instantiate a contract and save it's address in `instantiated_contract_address`.
+ assert_matches!(
+ ctx.ext
+ .instantiate(dummy_ch, 15u64, ctx.gas_meter, vec![], &[],),
+ Err((
+ ExecError {
+ error: DispatchError::Other("It's a trap!"),
+ origin: ErrorOrigin::Callee,
+ },
+ 0
+ ))
+ );
+
+ exec_success()
+ }
+ });
+
+ ExtBuilder::default()
+ .existential_deposit(15)
+ .build()
+ .execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ set_balance(&ALICE, 1000);
+ set_balance(&BOB, 100);
+ place_contract(&BOB, instantiator_ch);
+
+ assert_matches!(
+ ctx.call(BOB, 20, &mut GasMeter::<Test>::new(GAS_LIMIT), vec![]),
+ Ok(_)
+ );
+
+ // The contract wasn't instantiated so we don't expect to see an instantiation
+ // event here.
+ assert_eq!(&events(), &[]);
+ });
+ }
+
+ #[test]
+ fn termination_from_instantiate_fails() {
+ let terminate_ch = MockLoader::insert(Constructor, |ctx, _| {
+ ctx.ext.terminate(&ALICE).unwrap();
+ exec_success()
+ });
+
+ ExtBuilder::default()
+ .existential_deposit(15)
+ .build()
+ .execute_with(|| {
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ let executable =
+ MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap();
+ set_balance(&ALICE, 1000);
+
+ assert_eq!(
+ ctx.instantiate(100, &mut gas_meter, executable, vec![], &[],),
+ Err(Error::<Test>::NotCallable.into())
+ );
+
+ assert_eq!(&events(), &[]);
+ });
+ }
+
+ #[test]
+ fn rent_allowance() {
+ let rent_allowance_ch = MockLoader::insert(Constructor, |ctx, _| {
+ let subsistence = Contracts::<Test>::subsistence_threshold();
+ let allowance = subsistence * 3;
+ assert_eq!(ctx.ext.rent_allowance(), <BalanceOf<Test>>::max_value());
+ ctx.ext.set_rent_allowance(allowance);
+ assert_eq!(ctx.ext.rent_allowance(), allowance);
+ exec_success()
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let subsistence = Contracts::<Test>::subsistence_threshold();
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ let executable =
+ MockExecutable::from_storage(rent_allowance_ch, &schedule, &mut gas_meter).unwrap();
+ set_balance(&ALICE, subsistence * 10);
+
+ let result = ctx.instantiate(subsistence * 5, &mut gas_meter, executable, vec![], &[]);
+ assert_matches!(result, Ok(_));
+ });
+ }
+
+ #[test]
+ fn rent_params_works() {
+ let code_hash = MockLoader::insert(Call, |ctx, executable| {
+ let address = ctx.ext.address();
+ let contract = <ContractInfoOf<Test>>::get(address)
+ .and_then(|c| c.get_alive())
+ .unwrap();
+ assert_eq!(
+ ctx.ext.rent_params(),
+ &RentParams::new(address, &contract, executable)
+ );
+ exec_success()
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let subsistence = Contracts::<Test>::subsistence_threshold();
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ set_balance(&ALICE, subsistence * 10);
+ place_contract(&BOB, code_hash);
+ ctx.call(BOB, 0, &mut gas_meter, vec![]).unwrap();
+ });
+ }
+
+ #[test]
+ fn rent_params_snapshotted() {
+ let code_hash = MockLoader::insert(Call, |ctx, executable| {
+ let subsistence = Contracts::<Test>::subsistence_threshold();
+ let address = ctx.ext.address();
+ let contract = <ContractInfoOf<Test>>::get(address)
+ .and_then(|c| c.get_alive())
+ .unwrap();
+ let rent_params = RentParams::new(address, &contract, executable);
+
+ // Changing the allowance during the call: rent params stay unchanged.
+ let allowance = 42;
+ assert_ne!(allowance, rent_params.rent_allowance);
+ ctx.ext.set_rent_allowance(allowance);
+ assert_eq!(ctx.ext.rent_params(), &rent_params);
+
+ // Creating another instance from the same code_hash increases the refcount.
+ // This is also not reflected in the rent params.
+ assert_eq!(MockLoader::refcount(&executable.code_hash), 1);
+ ctx.ext
+ .instantiate(
+ executable.code_hash,
+ subsistence * 25,
+ &mut GasMeter::<Test>::new(GAS_LIMIT),
+ vec![],
+ &[],
+ )
+ .unwrap();
+ assert_eq!(MockLoader::refcount(&executable.code_hash), 2);
+ assert_eq!(ctx.ext.rent_params(), &rent_params);
+
+ exec_success()
+ });
+
+ ExtBuilder::default().build().execute_with(|| {
+ let subsistence = Contracts::<Test>::subsistence_threshold();
+ let schedule = <CurrentSchedule<Test>>::get();
+ let mut ctx = MockContext::top_level(ALICE, &schedule);
+ let mut gas_meter = GasMeter::<Test>::new(GAS_LIMIT);
+ set_balance(&ALICE, subsistence * 100);
+ place_contract(&BOB, code_hash);
+ ctx.call(BOB, subsistence * 50, &mut gas_meter, vec![])
+ .unwrap();
+ });
+ }
+}
pallets/contracts/src/gas.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/gas.rs
@@ -0,0 +1,364 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2018-2021 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.
+
+use crate::{Config, Error, exec::ExecError};
+use sp_std::marker::PhantomData;
+use sp_runtime::traits::Zero;
+use frame_support::{
+ dispatch::{
+ DispatchResultWithPostInfo, PostDispatchInfo, DispatchErrorWithPostInfo, DispatchError,
+ },
+ weights::Weight,
+};
+use sp_core::crypto::UncheckedFrom;
+
+#[cfg(test)]
+use std::{any::Any, fmt::Debug};
+
+#[derive(Debug, PartialEq, Eq)]
+pub struct ChargedAmount(Weight);
+
+impl ChargedAmount {
+ pub fn amount(&self) -> Weight {
+ self.0
+ }
+}
+
+#[cfg(not(test))]
+pub trait TestAuxiliaries {}
+#[cfg(not(test))]
+impl<T> TestAuxiliaries for T {}
+
+#[cfg(test)]
+pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
+#[cfg(test)]
+impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
+
+/// This trait represents a token that can be used for charging `GasMeter`.
+/// There is no other way of charging it.
+///
+/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added
+/// for consistency). If inlined there should be no observable difference compared
+/// to a hand-written code.
+pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
+ /// Metadata type, which the token can require for calculating the amount
+ /// of gas to charge. Can be a some configuration type or
+ /// just the `()`.
+ type Metadata;
+
+ /// Calculate amount of gas that should be taken by this token.
+ ///
+ /// This function should be really lightweight and must not fail. It is not
+ /// expected that implementors will query the storage or do any kinds of heavy operations.
+ ///
+ /// That said, implementors of this function still can run into overflows
+ /// while calculating the amount. In this case it is ok to use saturating operations
+ /// since on overflow they will return `max_value` which should consume all gas.
+ fn calculate_amount(&self, metadata: &Self::Metadata) -> Weight;
+}
+
+/// A wrapper around a type-erased trait object of what used to be a `Token`.
+#[cfg(test)]
+pub struct ErasedToken {
+ pub description: String,
+ pub token: Box<dyn Any>,
+}
+
+pub struct GasMeter<T: Config> {
+ gas_limit: Weight,
+ /// Amount of gas left from initial gas limit. Can reach zero.
+ gas_left: Weight,
+ _phantom: PhantomData<T>,
+ #[cfg(test)]
+ tokens: Vec<ErasedToken>,
+}
+
+impl<T: Config> GasMeter<T>
+where
+ T::AccountId: UncheckedFrom<<T as frame_system::Config>::Hash> + AsRef<[u8]>,
+{
+ pub fn new(gas_limit: Weight) -> Self {
+ GasMeter {
+ gas_limit,
+ gas_left: gas_limit,
+ _phantom: PhantomData,
+ #[cfg(test)]
+ tokens: Vec::new(),
+ }
+ }
+
+ /// Account for used gas.
+ ///
+ /// Amount is calculated by the given `token`.
+ ///
+ /// Returns `OutOfGas` if there is not enough gas or addition of the specified
+ /// amount of gas has lead to overflow. On success returns `Proceed`.
+ ///
+ /// NOTE that amount is always consumed, i.e. if there is not enough gas
+ /// then the counter will be set to zero.
+ #[inline]
+ pub fn charge<Tok: Token<T>>(
+ &mut self,
+ metadata: &Tok::Metadata,
+ token: Tok,
+ ) -> Result<ChargedAmount, DispatchError> {
+ #[cfg(test)]
+ {
+ // Unconditionally add the token to the storage.
+ let erased_tok = ErasedToken {
+ description: format!("{:?}", token),
+ token: Box::new(token),
+ };
+ self.tokens.push(erased_tok);
+ }
+
+ let amount = token.calculate_amount(metadata);
+ let new_value = self.gas_left.checked_sub(amount);
+
+ // We always consume the gas even if there is not enough gas.
+ self.gas_left = new_value.unwrap_or_else(Zero::zero);
+
+ match new_value {
+ Some(_) => Ok(ChargedAmount(amount)),
+ None => Err(Error::<T>::OutOfGas.into()),
+ }
+ }
+
+ /// Adjust a previously charged amount down to its actual amount.
+ ///
+ /// This is when a maximum a priori amount was charged and then should be partially
+ /// refunded to match the actual amount.
+ pub fn adjust_gas<Tok: Token<T>>(
+ &mut self,
+ charged_amount: ChargedAmount,
+ metadata: &Tok::Metadata,
+ token: Tok,
+ ) {
+ let adjustment = charged_amount
+ .0
+ .saturating_sub(token.calculate_amount(metadata));
+ self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
+ }
+
+ /// Refund previously charged gas back to the gas meter.
+ ///
+ /// This can be used if a gas worst case estimation must be charged before
+ /// performing a certain action. This way the difference can be refundend when
+ /// the worst case did not happen.
+ pub fn refund(&mut self, amount: ChargedAmount) {
+ self.gas_left = self.gas_left.saturating_add(amount.0).min(self.gas_limit)
+ }
+
+ /// Allocate some amount of gas and perform some work with
+ /// a newly created nested gas meter.
+ ///
+ /// Invokes `f` with either the gas meter that has `amount` gas left or
+ /// with `None`, if this gas meter has not enough gas to allocate given `amount`.
+ ///
+ /// All unused gas in the nested gas meter is returned to this gas meter.
+ pub fn with_nested<R, F: FnOnce(Option<&mut GasMeter<T>>) -> R>(
+ &mut self,
+ amount: Weight,
+ f: F,
+ ) -> R {
+ // NOTE that it is ok to allocate all available gas since it still ensured
+ // by `charge` that it doesn't reach zero.
+ if self.gas_left < amount {
+ f(None)
+ } else {
+ self.gas_left = self.gas_left - amount;
+ let mut nested = GasMeter::new(amount);
+
+ let r = f(Some(&mut nested));
+
+ self.gas_left = self.gas_left + nested.gas_left;
+
+ r
+ }
+ }
+
+ /// Returns how much gas was used.
+ pub fn gas_spent(&self) -> Weight {
+ self.gas_limit - self.gas_left
+ }
+
+ /// Returns how much gas left from the initial budget.
+ pub fn gas_left(&self) -> Weight {
+ self.gas_left
+ }
+
+ /// Turn this GasMeter into a DispatchResult that contains the actually used gas.
+ pub fn into_dispatch_result<R, E>(
+ self,
+ result: Result<R, E>,
+ base_weight: Weight,
+ ) -> DispatchResultWithPostInfo
+ where
+ E: Into<ExecError>,
+ {
+ let post_info = PostDispatchInfo {
+ actual_weight: Some(self.gas_spent().saturating_add(base_weight)),
+ pays_fee: Default::default(),
+ };
+
+ result
+ .map(|_| post_info)
+ .map_err(|e| DispatchErrorWithPostInfo {
+ post_info,
+ error: e.into().error,
+ })
+ }
+
+ #[cfg(test)]
+ pub fn tokens(&self) -> &[ErasedToken] {
+ &self.tokens
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{GasMeter, Token};
+ use crate::tests::Test;
+
+ /// A simple utility macro that helps to match against a
+ /// list of tokens.
+ macro_rules! match_tokens {
+ ($tokens_iter:ident,) => {
+ };
+ ($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
+ {
+ let next = ($tokens_iter).next().unwrap();
+ let pattern = $x;
+
+ // Note that we don't specify the type name directly in this macro,
+ // we only have some expression $x of some type. At the same time, we
+ // have an iterator of Box<dyn Any> and to downcast we need to specify
+ // the type which we want downcast to.
+ //
+ // So what we do is we assign `_pattern_typed_next_ref` to a variable which has
+ // the required type.
+ //
+ // Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes
+ // rustc infer the type `T` (in `downcast_ref<T: Any>`) to be the same as in $x.
+
+ let mut _pattern_typed_next_ref = &pattern;
+ _pattern_typed_next_ref = match next.token.downcast_ref() {
+ Some(p) => {
+ assert_eq!(p, &pattern);
+ p
+ }
+ None => {
+ panic!("expected type {} got {}", stringify!($x), next.description);
+ }
+ };
+ }
+
+ match_tokens!($tokens_iter, $($rest)*);
+ };
+ }
+
+ /// A trivial token that charges the specified number of gas units.
+ #[derive(Copy, Clone, PartialEq, Eq, Debug)]
+ struct SimpleToken(u64);
+ impl Token<Test> for SimpleToken {
+ type Metadata = ();
+ fn calculate_amount(&self, _metadata: &()) -> u64 {
+ self.0
+ }
+ }
+
+ struct MultiplierTokenMetadata {
+ multiplier: u64,
+ }
+ /// A simple token that charges for the given amount multiplied to
+ /// a multiplier taken from a given metadata.
+ #[derive(Copy, Clone, PartialEq, Eq, Debug)]
+ struct MultiplierToken(u64);
+
+ impl Token<Test> for MultiplierToken {
+ type Metadata = MultiplierTokenMetadata;
+ fn calculate_amount(&self, metadata: &MultiplierTokenMetadata) -> u64 {
+ // Probably you want to use saturating mul in production code.
+ self.0 * metadata.multiplier
+ }
+ }
+
+ #[test]
+ fn it_works() {
+ let gas_meter = GasMeter::<Test>::new(50000);
+ assert_eq!(gas_meter.gas_left(), 50000);
+ }
+
+ #[test]
+ fn simple() {
+ let mut gas_meter = GasMeter::<Test>::new(50000);
+
+ let result = gas_meter.charge(
+ &MultiplierTokenMetadata { multiplier: 3 },
+ MultiplierToken(10),
+ );
+ assert!(!result.is_err());
+
+ assert_eq!(gas_meter.gas_left(), 49_970);
+ }
+
+ #[test]
+ fn tracing() {
+ let mut gas_meter = GasMeter::<Test>::new(50000);
+ assert!(!gas_meter.charge(&(), SimpleToken(1)).is_err());
+ assert!(!gas_meter
+ .charge(
+ &MultiplierTokenMetadata { multiplier: 3 },
+ MultiplierToken(10)
+ )
+ .is_err());
+
+ let mut tokens = gas_meter.tokens()[0..2].iter();
+ match_tokens!(tokens, SimpleToken(1), MultiplierToken(10),);
+ }
+
+ // This test makes sure that nothing can be executed if there is no gas.
+ #[test]
+ fn refuse_to_execute_anything_if_zero() {
+ let mut gas_meter = GasMeter::<Test>::new(0);
+ assert!(gas_meter.charge(&(), SimpleToken(1)).is_err());
+ }
+
+ // Make sure that if the gas meter is charged by exceeding amount then not only an error
+ // returned for that charge, but also for all consequent charges.
+ //
+ // This is not strictly necessary, because the execution should be interrupted immediately
+ // if the gas meter runs out of gas. However, this is just a nice property to have.
+ #[test]
+ fn overcharge_is_unrecoverable() {
+ let mut gas_meter = GasMeter::<Test>::new(200);
+
+ // The first charge is should lead to OOG.
+ assert!(gas_meter.charge(&(), SimpleToken(300)).is_err());
+
+ // The gas meter is emptied at this moment, so this should also fail.
+ assert!(gas_meter.charge(&(), SimpleToken(1)).is_err());
+ }
+
+ // Charging the exact amount that the user paid for should be
+ // possible.
+ #[test]
+ fn charge_exact_amount() {
+ let mut gas_meter = GasMeter::<Test>::new(25);
+ assert!(!gas_meter.charge(&(), SimpleToken(25)).is_err());
+ }
+}
pallets/contracts/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/lib.rs
@@ -0,0 +1,831 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2018-2021 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.
+
+//! # Contract Pallet
+//!
+//! The Contract module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts.
+//!
+//! - [`Config`]
+//! - [`Call`]
+//!
+//! ## Overview
+//!
+//! This module extends accounts based on the [`Currency`] trait to have smart-contract functionality. It can
+//! be used with other modules that implement accounts based on [`Currency`]. These "smart-contract accounts"
+//! have the ability to instantiate smart-contracts and make calls to other contract and non-contract accounts.
+//!
+//! The smart-contract code is stored once in a code cache, and later retrievable via its hash.
+//! This means that multiple smart-contracts can be instantiated from the same hash, without replicating
+//! the code each time.
+//!
+//! When a smart-contract is called, its associated code is retrieved via the code hash and gets executed.
+//! This call can alter the storage entries of the smart-contract account, instantiate new smart-contracts,
+//! or call other smart-contracts.
+//!
+//! Finally, when an account is reaped, its associated code and storage of the smart-contract account
+//! will also be deleted.
+//!
+//! ### Gas
+//!
+//! Senders must specify a gas limit with every call, as all instructions invoked by the smart-contract require gas.
+//! Unused gas is refunded after the call, regardless of the execution outcome.
+//!
+//! If the gas limit is reached, then all calls and state changes (including balance transfers) are only
+//! reverted at the current call's contract level. For example, if contract A calls B and B runs out of gas mid-call,
+//! then all of B's calls are reverted. Assuming correct error handling by contract A, A's other calls and state
+//! changes still persist.
+//!
+//! ### Notable Scenarios
+//!
+//! Contract call failures are not always cascading. When failures occur in a sub-call, they do not "bubble up",
+//! and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B
+//! fails, A can decide how to handle that failure, either proceeding or reverting A's changes.
+//!
+//! ## Interface
+//!
+//! ### Dispatchable functions
+//!
+//! * [`Pallet::update_schedule`] -
+//! ([Root Origin](https://substrate.dev/docs/en/knowledgebase/runtime/origin) Only) -
+//! Set a new [`Schedule`].
+//! * [`Pallet::instantiate_with_code`] - Deploys a new contract from the supplied wasm binary,
+//! optionally transferring
+//! some balance. This instantiates a new smart contract account with the supplied code and
+//! calls its constructor to initialize the contract.
+//! * [`Pallet::instantiate`] - The same as `instantiate_with_code` but instead of uploading new
+//! code an existing `code_hash` is supplied.
+//! * [`Pallet::call`] - Makes a call to an account, optionally transferring some balance.
+//! * [`Pallet::claim_surcharge`] - Evict a contract that cannot pay rent anymore.
+//!
+//! ## Usage
+//!
+//! The Contract module is a work in progress. The following examples show how this Contract module
+//! can be used to instantiate and call contracts.
+//!
+//! * [`ink`](https://github.com/paritytech/ink) is
+//! an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables writing
+//! WebAssembly based smart contracts in the Rust programming language. This is a work in progress.
+//!
+//! ## Related Modules
+//!
+//! * [Balances](../pallet_balances/index.html)
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "512")]
+
+#[macro_use]
+mod gas;
+mod benchmarking;
+mod exec;
+mod migration;
+mod rent;
+mod schedule;
+mod storage;
+mod wasm;
+
+pub mod chain_extension;
+pub mod weights;
+
+#[cfg(test)]
+mod tests;
+
+pub use crate::{pallet::*, schedule::Schedule};
+use crate::{
+ gas::GasMeter,
+ exec::{ExecutionContext, Executable},
+ rent::Rent,
+ storage::{Storage, DeletedContract, ContractInfo, AliveContractInfo, TombstoneContractInfo},
+ weights::WeightInfo,
+ wasm::PrefabWasmModule,
+};
+use sp_core::{Bytes, crypto::UncheckedFrom};
+use sp_std::prelude::*;
+use sp_runtime::{
+ traits::{Hash, StaticLookup, Convert, Saturating, Zero},
+ Perbill,
+};
+use frame_support::{
+ traits::{OnUnbalanced, Currency, Get, Time, Randomness},
+ weights::{Weight, PostDispatchInfo, WithPostDispatchInfo},
+};
+use frame_system::Pallet as System;
+use pallet_contracts_primitives::{
+ RentProjectionResult, GetStorageResult, ContractAccessError, ContractExecResult,
+ ContractInstantiateResult, Code, InstantiateReturnValue,
+};
+
+type CodeHash<T> = <T as frame_system::Config>::Hash;
+type TrieId = Vec<u8>;
+type BalanceOf<T> =
+ <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
+type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
+ <T as frame_system::Config>::AccountId,
+>>::NegativeImbalance;
+
+#[frame_support::pallet]
+pub mod pallet {
+ use frame_support::pallet_prelude::*;
+ use frame_system::pallet_prelude::*;
+ use super::*;
+
+ #[pallet::config]
+ pub trait Config: frame_system::Config {
+ /// The time implementation used to supply timestamps to conntracts through `seal_now`.
+ type Time: Time;
+
+ /// The generator used to supply randomness to contracts through `seal_random`.
+ type Randomness: Randomness<Self::Hash, Self::BlockNumber>;
+
+ /// The currency in which fees are paid and contract balances are held.
+ type Currency: Currency<Self::AccountId>;
+
+ /// The overarching event type.
+ type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
+
+ /// Handler for rent payments.
+ type RentPayment: OnUnbalanced<NegativeImbalanceOf<Self>>;
+
+ /// Number of block delay an extrinsic claim surcharge has.
+ ///
+ /// When claim surcharge is called by an extrinsic the rent is checked
+ /// for current_block - delay
+ #[pallet::constant]
+ type SignedClaimHandicap: Get<Self::BlockNumber>;
+
+ /// The minimum amount required to generate a tombstone.
+ #[pallet::constant]
+ type TombstoneDeposit: Get<BalanceOf<Self>>;
+
+ /// The balance every contract needs to deposit to stay alive indefinitely.
+ ///
+ /// This is different from the [`Self::TombstoneDeposit`] because this only needs to be
+ /// deposited while the contract is alive. Costs for additional storage are added to
+ /// this base cost.
+ ///
+ /// This is a simple way to ensure that contracts with empty storage eventually get deleted by
+ /// making them pay rent. This creates an incentive to remove them early in order to save rent.
+ #[pallet::constant]
+ type DepositPerContract: Get<BalanceOf<Self>>;
+
+ /// The balance a contract needs to deposit per storage byte to stay alive indefinitely.
+ ///
+ /// Let's suppose the deposit is 1,000 BU (balance units)/byte and the rent is 1 BU/byte/day,
+ /// then a contract with 1,000,000 BU that uses 1,000 bytes of storage would pay no rent.
+ /// But if the balance reduced to 500,000 BU and the storage stayed the same at 1,000,
+ /// then it would pay 500 BU/day.
+ #[pallet::constant]
+ type DepositPerStorageByte: Get<BalanceOf<Self>>;
+
+ /// The balance a contract needs to deposit per storage item to stay alive indefinitely.
+ ///
+ /// It works the same as [`Self::DepositPerStorageByte`] but for storage items.
+ #[pallet::constant]
+ type DepositPerStorageItem: Get<BalanceOf<Self>>;
+
+ /// The fraction of the deposit that should be used as rent per block.
+ ///
+ /// When a contract hasn't enough balance deposited to stay alive indefinitely it needs
+ /// to pay per block for the storage it consumes that is not covered by the deposit.
+ /// This determines how high this rent payment is per block as a fraction of the deposit.
+ #[pallet::constant]
+ type RentFraction: Get<Perbill>;
+
+ /// Reward that is received by the party whose touch has led
+ /// to removal of a contract.
+ #[pallet::constant]
+ type SurchargeReward: Get<BalanceOf<Self>>;
+
+ /// The maximum nesting level of a call/instantiate stack.
+ #[pallet::constant]
+ type MaxDepth: Get<u32>;
+
+ /// The maximum size of a storage value and event payload in bytes.
+ #[pallet::constant]
+ type MaxValueSize: Get<u32>;
+
+ /// Used to answer contracts' queries regarding the current weight price. This is **not**
+ /// used to calculate the actual fee and is only for informational purposes.
+ type WeightPrice: Convert<Weight, BalanceOf<Self>>;
+
+ /// Describes the weights of the dispatchables of this module and is also used to
+ /// construct a default cost schedule.
+ type WeightInfo: WeightInfo;
+
+ /// Type that allows the runtime authors to add new host functions for a contract to call.
+ type ChainExtension: chain_extension::ChainExtension<Self>;
+
+ /// The maximum number of tries that can be queued for deletion.
+ #[pallet::constant]
+ type DeletionQueueDepth: Get<u32>;
+
+ /// The maximum amount of weight that can be consumed per block for lazy trie removal.
+ #[pallet::constant]
+ type DeletionWeightLimit: Get<Weight>;
+
+ /// The maximum length of a contract code in bytes. This limit applies to the instrumented
+ /// version of the code. Therefore `instantiate_with_code` can fail even when supplying
+ /// a wasm binary below this maximum size.
+ #[pallet::constant]
+ type MaxCodeSize: Get<u32>;
+ }
+
+ #[pallet::pallet]
+ pub struct Pallet<T>(PhantomData<T>);
+
+ #[pallet::hooks]
+ impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T>
+ where
+ T::AccountId: UncheckedFrom<T::Hash>,
+ T::AccountId: AsRef<[u8]>,
+ {
+ fn on_initialize(_block: T::BlockNumber) -> Weight {
+ // We do not want to go above the block limit and rather avoid lazy deletion
+ // in that case. This should only happen on runtime upgrades.
+ let weight_limit = T::BlockWeights::get()
+ .max_block
+ .saturating_sub(System::<T>::block_weight().total())
+ .min(T::DeletionWeightLimit::get());
+ Storage::<T>::process_deletion_queue_batch(weight_limit)
+ .saturating_add(T::WeightInfo::on_initialize())
+ }
+
+ fn on_runtime_upgrade() -> Weight {
+ migration::migrate::<T>()
+ }
+ }
+
+ #[pallet::call]
+ impl<T: Config> Pallet<T>
+ where
+ T::AccountId: UncheckedFrom<T::Hash>,
+ T::AccountId: AsRef<[u8]>,
+ {
+ /// Updates the schedule for metering contracts.
+ ///
+ /// The schedule's version cannot be less than the version of the stored schedule.
+ /// If a schedule does not change the instruction weights the version does not
+ /// need to be increased. Therefore we allow storing a schedule that has the same
+ /// version as the stored one.
+ #[pallet::weight(T::WeightInfo::update_schedule())]
+ pub fn update_schedule(
+ origin: OriginFor<T>,
+ schedule: Schedule<T>,
+ ) -> DispatchResultWithPostInfo {
+ ensure_root(origin)?;
+ if <CurrentSchedule<T>>::get().version > schedule.version {
+ Err(Error::<T>::InvalidScheduleVersion)?
+ }
+ Self::deposit_event(Event::ScheduleUpdated(schedule.version));
+ CurrentSchedule::put(schedule);
+ Ok(().into())
+ }
+
+ /// Makes a call to an account, optionally transferring some balance.
+ ///
+ /// * If the account is a smart-contract account, the associated code will be
+ /// executed and any value will be transferred.
+ /// * 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.
+ #[pallet::weight(T::WeightInfo::call(T::MaxCodeSize::get() / 1024).saturating_add(*gas_limit))]
+ pub fn call(
+ origin: OriginFor<T>,
+ dest: <T::Lookup as StaticLookup>::Source,
+ #[pallet::compact] value: BalanceOf<T>,
+ #[pallet::compact] gas_limit: Weight,
+ data: Vec<u8>,
+ ) -> DispatchResultWithPostInfo {
+ let origin = ensure_signed(origin)?;
+ let dest = T::Lookup::lookup(dest)?;
+ let mut gas_meter = GasMeter::new(gas_limit);
+ let schedule = <CurrentSchedule<T>>::get();
+ let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
+ let (result, code_len) = match ctx.call(dest, value, &mut gas_meter, data) {
+ Ok((output, len)) => (Ok(output), len),
+ Err((err, len)) => (Err(err), len),
+ };
+ gas_meter.into_dispatch_result(result, T::WeightInfo::call(code_len / 1024))
+ }
+
+ /// Instantiates a new contract from the supplied `code` optionally transferring
+ /// some balance.
+ ///
+ /// This is the only function that can deploy new code to the chain.
+ ///
+ /// # Parameters
+ ///
+ /// * `endowment`: The balance to transfer from the `origin` to the newly created contract.
+ /// * `gas_limit`: The gas limit enforced when executing the constructor.
+ /// * `code`: The contract code to deploy in raw bytes.
+ /// * `data`: The input data to pass to the contract constructor.
+ /// * `salt`: Used for the address derivation. See [`Pallet::contract_address`].
+ ///
+ /// Instantiation is executed as follows:
+ ///
+ /// - The supplied `code` is instrumented, deployed, and a `code_hash` is created for that code.
+ /// - If the `code_hash` already exists on the chain the underlying `code` will be shared.
+ /// - The destination address is computed based on the sender, code_hash and the salt.
+ /// - The smart-contract account is created at the computed address.
+ /// - The `endowment` is transferred to the new account.
+ /// - The `deploy` function is executed in the context of the newly-created account.
+ #[pallet::weight(
+ T::WeightInfo::instantiate_with_code(
+ code.len() as u32 / 1024,
+ salt.len() as u32 / 1024,
+ )
+ .saturating_add(*gas_limit)
+ )]
+ pub fn instantiate_with_code(
+ origin: OriginFor<T>,
+ #[pallet::compact] endowment: BalanceOf<T>,
+ #[pallet::compact] gas_limit: Weight,
+ code: Vec<u8>,
+ data: Vec<u8>,
+ salt: Vec<u8>,
+ ) -> DispatchResultWithPostInfo {
+ let origin = ensure_signed(origin)?;
+ let code_len = code.len() as u32;
+ ensure!(code_len <= T::MaxCodeSize::get(), Error::<T>::CodeTooLarge);
+ let mut gas_meter = GasMeter::new(gas_limit);
+ let schedule = <CurrentSchedule<T>>::get();
+ let executable = PrefabWasmModule::from_code(code, &schedule)?;
+ let code_len = executable.code_len();
+ ensure!(code_len <= T::MaxCodeSize::get(), Error::<T>::CodeTooLarge);
+ let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
+ let result = ctx
+ .instantiate(endowment, &mut gas_meter, executable, data, &salt)
+ .map(|(_address, output)| output);
+ gas_meter.into_dispatch_result(
+ result,
+ T::WeightInfo::instantiate_with_code(code_len / 1024, salt.len() as u32 / 1024),
+ )
+ }
+
+ /// Instantiates a contract from a previously deployed wasm binary.
+ ///
+ /// This function is identical to [`Self::instantiate_with_code`] but without the
+ /// code deployment step. Instead, the `code_hash` of an on-chain deployed wasm binary
+ /// must be supplied.
+ #[pallet::weight(
+ T::WeightInfo::instantiate(T::MaxCodeSize::get() / 1024, salt.len() as u32 / 1024)
+ .saturating_add(*gas_limit)
+ )]
+ pub fn instantiate(
+ origin: OriginFor<T>,
+ #[pallet::compact] endowment: BalanceOf<T>,
+ #[pallet::compact] gas_limit: Weight,
+ code_hash: CodeHash<T>,
+ data: Vec<u8>,
+ salt: Vec<u8>,
+ ) -> DispatchResultWithPostInfo {
+ let origin = ensure_signed(origin)?;
+ let mut gas_meter = GasMeter::new(gas_limit);
+ let schedule = <CurrentSchedule<T>>::get();
+ let executable = PrefabWasmModule::from_storage(code_hash, &schedule, &mut gas_meter)?;
+ let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
+ let code_len = executable.code_len();
+ let result = ctx
+ .instantiate(endowment, &mut gas_meter, executable, data, &salt)
+ .map(|(_address, output)| output);
+ gas_meter.into_dispatch_result(
+ result,
+ T::WeightInfo::instantiate(code_len / 1024, salt.len() as u32 / 1024),
+ )
+ }
+
+ /// Allows block producers to claim a small reward for evicting a contract. If a block
+ /// producer fails to do so, a regular users will be allowed to claim the reward.
+ ///
+ /// In case of a successful eviction no fees are charged from the sender. However, the
+ /// reward is capped by the total amount of rent that was payed by the contract while
+ /// it was alive.
+ ///
+ /// If contract is not evicted as a result of this call, [`Error::ContractNotEvictable`]
+ /// is returned and the sender is not eligible for the reward.
+ #[pallet::weight(T::WeightInfo::claim_surcharge(T::MaxCodeSize::get() / 1024))]
+ pub fn claim_surcharge(
+ origin: OriginFor<T>,
+ dest: T::AccountId,
+ aux_sender: Option<T::AccountId>,
+ ) -> DispatchResultWithPostInfo {
+ let origin = origin.into();
+ let (signed, rewarded) = match (origin, aux_sender) {
+ (Ok(frame_system::RawOrigin::Signed(account)), None) => (true, account),
+ (Ok(frame_system::RawOrigin::None), Some(aux_sender)) => (false, aux_sender),
+ _ => Err(Error::<T>::InvalidSurchargeClaim)?,
+ };
+
+ // Add some advantage for block producers (who send unsigned extrinsics) by
+ // adding a handicap: for signed extrinsics we use a slightly older block number
+ // for the eviction check. This can be viewed as if we pushed regular users back in past.
+ let handicap = if signed {
+ T::SignedClaimHandicap::get()
+ } else {
+ Zero::zero()
+ };
+
+ // If poking the contract has lead to eviction of the contract, give out the rewards.
+ match Rent::<T, PrefabWasmModule<T>>::try_eviction(&dest, handicap)? {
+ (Some(rent_payed), code_len) => T::Currency::deposit_into_existing(
+ &rewarded,
+ T::SurchargeReward::get().min(rent_payed),
+ )
+ .map(|_| PostDispatchInfo {
+ actual_weight: Some(T::WeightInfo::claim_surcharge(code_len / 1024)),
+ pays_fee: Pays::No,
+ })
+ .map_err(Into::into),
+ (None, code_len) => Err(Error::<T>::ContractNotEvictable
+ .with_weight(T::WeightInfo::claim_surcharge(code_len / 1024))),
+ }
+ }
+ }
+
+ #[pallet::event]
+ #[pallet::generate_deposit(pub(super) fn deposit_event)]
+ #[pallet::metadata(T::AccountId = "AccountId", T::Hash = "Hash", BalanceOf<T> = "Balance")]
+ pub enum Event<T: Config> {
+ /// Contract deployed by address at the specified address. \[deployer, contract\]
+ Instantiated(T::AccountId, T::AccountId),
+
+ /// Contract has been evicted and is now in tombstone state. \[contract\]
+ Evicted(T::AccountId),
+
+ /// Contract has been terminated without leaving a tombstone.
+ /// \[contract, beneficiary\]
+ ///
+ /// # Params
+ ///
+ /// - `contract`: The contract that was terminated.
+ /// - `beneficiary`: The account that received the contracts remaining balance.
+ ///
+ /// # Note
+ ///
+ /// The only way for a contract to be removed without a tombstone and emitting
+ /// this event is by calling `seal_terminate`.
+ Terminated(T::AccountId, T::AccountId),
+
+ /// Restoration of a contract has been successful.
+ /// \[restorer, dest, code_hash, rent_allowance\]
+ ///
+ /// # Params
+ ///
+ /// - `restorer`: Account ID of the restoring contract.
+ /// - `dest`: Account ID of the restored contract.
+ /// - `code_hash`: Code hash of the restored contract.
+ /// - `rent_allowance`: Rent allowance of the restored contract.
+ Restored(T::AccountId, T::AccountId, T::Hash, BalanceOf<T>),
+
+ /// Code with the specified hash has been stored. \[code_hash\]
+ CodeStored(T::Hash),
+
+ /// Triggered when the current schedule is updated.
+ /// \[version\]
+ ///
+ /// # Params
+ ///
+ /// - `version`: The version of the newly set schedule.
+ ScheduleUpdated(u32),
+
+ /// A custom event emitted by the contract.
+ /// \[contract, data\]
+ ///
+ /// # Params
+ ///
+ /// - `contract`: The contract that emitted the event.
+ /// - `data`: Data supplied by the contract. Metadata generated during contract
+ /// compilation is needed to decode it.
+ ContractEmitted(T::AccountId, Vec<u8>),
+
+ /// A code with the specified hash was removed.
+ /// \[code_hash\]
+ ///
+ /// This happens when the last contract that uses this code hash was removed or evicted.
+ CodeRemoved(T::Hash),
+ }
+
+ #[pallet::error]
+ pub enum Error<T> {
+ /// A new schedule must have a greater version than the current one.
+ InvalidScheduleVersion,
+ /// An origin must be signed or inherent and auxiliary sender only provided on inherent.
+ InvalidSurchargeClaim,
+ /// Cannot restore from nonexisting or tombstone contract.
+ InvalidSourceContract,
+ /// Cannot restore to nonexisting or alive contract.
+ InvalidDestinationContract,
+ /// Tombstones don't match.
+ InvalidTombstone,
+ /// An origin TrieId written in the current block.
+ InvalidContractOrigin,
+ /// The executed contract exhausted its gas limit.
+ OutOfGas,
+ /// The output buffer supplied to a contract API call was too small.
+ OutputBufferTooSmall,
+ /// Performing the requested transfer would have brought the contract below
+ /// the subsistence threshold. No transfer is allowed to do this in order to allow
+ /// for a tombstone to be created. Use `seal_terminate` to remove a contract without
+ /// leaving a tombstone behind.
+ BelowSubsistenceThreshold,
+ /// The newly created contract is below the subsistence threshold after executing
+ /// its contructor. No contracts are allowed to exist below that threshold.
+ NewContractNotFunded,
+ /// Performing the requested transfer failed for a reason originating in the
+ /// chosen currency implementation of the runtime. Most probably the balance is
+ /// too low or locks are placed on it.
+ TransferFailed,
+ /// Performing a call was denied because the calling depth reached the limit
+ /// of what is specified in the schedule.
+ MaxCallDepthReached,
+ /// The contract that was called is either no contract at all (a plain account)
+ /// or is a tombstone.
+ NotCallable,
+ /// The code supplied to `instantiate_with_code` exceeds the limit specified in the
+ /// current schedule.
+ CodeTooLarge,
+ /// No code could be found at the supplied code hash.
+ CodeNotFound,
+ /// A buffer outside of sandbox memory was passed to a contract API function.
+ OutOfBounds,
+ /// Input passed to a contract API function failed to decode as expected type.
+ DecodingFailed,
+ /// Contract trapped during execution.
+ ContractTrapped,
+ /// The size defined in `T::MaxValueSize` was exceeded.
+ ValueTooLarge,
+ /// The action performed is not allowed while the contract performing it is already
+ /// on the call stack. Those actions are contract self destruction and restoration
+ /// of a tombstone.
+ ReentranceDenied,
+ /// `seal_input` was called twice from the same contract execution context.
+ InputAlreadyRead,
+ /// The subject passed to `seal_random` exceeds the limit.
+ RandomSubjectTooLong,
+ /// The amount of topics passed to `seal_deposit_events` exceeds the limit.
+ TooManyTopics,
+ /// The topics passed to `seal_deposit_events` contains at least one duplicate.
+ DuplicateTopics,
+ /// The chain does not provide a chain extension. Calling the chain extension results
+ /// in this error. Note that this usually shouldn't happen as deploying such contracts
+ /// is rejected.
+ NoChainExtension,
+ /// Removal of a contract failed because the deletion queue is full.
+ ///
+ /// This can happen when either calling [`Pallet::claim_surcharge`] or `seal_terminate`.
+ /// The queue is filled by deleting contracts and emptied by a fixed amount each block.
+ /// Trying again during another block is the only way to resolve this issue.
+ DeletionQueueFull,
+ /// A contract could not be evicted because it has enough balance to pay rent.
+ ///
+ /// This can be returned from [`Pallet::claim_surcharge`] because the target
+ /// contract has enough balance to pay for its rent.
+ ContractNotEvictable,
+ /// A storage modification exhausted the 32bit type that holds the storage size.
+ ///
+ /// This can either happen when the accumulated storage in bytes is too large or
+ /// when number of storage items is too large.
+ StorageExhausted,
+ /// A contract with the same AccountId already exists.
+ DuplicateContract,
+ }
+
+ /// Current cost schedule for contracts.
+ #[pallet::storage]
+ pub(crate) type CurrentSchedule<T: Config> = StorageValue<_, Schedule<T>, ValueQuery>;
+
+ /// A mapping from an original code hash to the original code, untouched by instrumentation.
+ #[pallet::storage]
+ pub(crate) type PristineCode<T: Config> = StorageMap<_, Identity, CodeHash<T>, Vec<u8>>;
+
+ /// A mapping between an original code hash and instrumented wasm code, ready for execution.
+ #[pallet::storage]
+ pub(crate) type CodeStorage<T: Config> =
+ StorageMap<_, Identity, CodeHash<T>, PrefabWasmModule<T>>;
+
+ /// The subtrie counter.
+ #[pallet::storage]
+ pub(crate) type AccountCounter<T: Config> = StorageValue<_, u64, ValueQuery>;
+
+ /// The code associated with a given account.
+ ///
+ /// TWOX-NOTE: SAFE since `AccountId` is a secure hash.
+ #[pallet::storage]
+ pub(crate) type ContractInfoOf<T: Config> =
+ StorageMap<_, Twox64Concat, T::AccountId, ContractInfo<T>>;
+
+ /// Evicted contracts that await child trie deletion.
+ ///
+ /// Child trie deletion is a heavy operation depending on the amount of storage items
+ /// stored in said trie. Therefore this operation is performed lazily in `on_initialize`.
+ #[pallet::storage]
+ pub(crate) type DeletionQueue<T: Config> = StorageValue<_, Vec<DeletedContract>, ValueQuery>;
+
+ #[pallet::genesis_config]
+ pub struct GenesisConfig<T: Config> {
+ #[doc = "Current cost schedule for contracts."]
+ pub current_schedule: Schedule<T>,
+ }
+
+ #[cfg(feature = "std")]
+ impl<T: Config> Default for GenesisConfig<T> {
+ fn default() -> Self {
+ Self {
+ current_schedule: Default::default(),
+ }
+ }
+ }
+
+ #[pallet::genesis_build]
+ impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
+ fn build(&self) {
+ <CurrentSchedule<T>>::put(&self.current_schedule);
+ }
+ }
+}
+
+impl<T: Config> Pallet<T>
+where
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ /// Perform a call to a specified contract.
+ ///
+ /// This function is similar to [`Self::call`], but doesn't perform any address lookups
+ /// and better suitable for calling directly from Rust.
+ ///
+ /// It returns the execution result and the amount of used weight.
+ pub fn bare_call(
+ origin: T::AccountId,
+ dest: T::AccountId,
+ value: BalanceOf<T>,
+ gas_limit: Weight,
+ input_data: Vec<u8>,
+ ) -> ContractExecResult {
+ let mut gas_meter = GasMeter::new(gas_limit);
+ let schedule = <CurrentSchedule<T>>::get();
+ let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
+ let result = ctx.call(dest, value, &mut gas_meter, input_data);
+ let gas_consumed = gas_meter.gas_spent();
+ ContractExecResult {
+ result: result.map(|r| r.0).map_err(|r| r.0.error),
+ gas_consumed,
+ debug_message: Bytes(Vec::new()).to_vec(),
+ }
+ }
+
+ /// Instantiate a new contract.
+ ///
+ /// This function is similar to [`Self::instantiate`], but doesn't perform any address lookups
+ /// and better suitable for calling directly from Rust.
+ ///
+ /// It returns the execution result, account id and the amount of used weight.
+ ///
+ /// If `compute_projection` is set to `true` the result also contains the rent projection.
+ /// This is optional because some non trivial and stateful work is performed to compute
+ /// the projection. See [`Self::rent_projection`].
+ pub fn bare_instantiate(
+ origin: T::AccountId,
+ endowment: BalanceOf<T>,
+ gas_limit: Weight,
+ code: Code<CodeHash<T>>,
+ data: Vec<u8>,
+ salt: Vec<u8>,
+ compute_projection: bool,
+ ) -> ContractInstantiateResult<T::AccountId, T::BlockNumber> {
+ let mut gas_meter = GasMeter::new(gas_limit);
+ let schedule = <CurrentSchedule<T>>::get();
+ let mut ctx = ExecutionContext::<T, PrefabWasmModule<T>>::top_level(origin, &schedule);
+ let executable = match code {
+ Code::Upload(Bytes(binary)) => PrefabWasmModule::from_code(binary, &schedule),
+ Code::Existing(hash) => PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter),
+ };
+ let executable = match executable {
+ Ok(executable) => executable,
+ Err(error) => {
+ return ContractInstantiateResult {
+ result: Err(error.into()),
+ gas_consumed: gas_meter.gas_spent(),
+ debug_message: Bytes(Vec::new()).to_vec(),
+ }
+ }
+ };
+ let result = ctx
+ .instantiate(endowment, &mut gas_meter, executable, data, &salt)
+ .and_then(|(account_id, result)| {
+ let rent_projection = if compute_projection {
+ Some(
+ Rent::<T, PrefabWasmModule<T>>::compute_projection(&account_id)
+ .map_err(|_| <Error<T>>::NewContractNotFunded)?,
+ )
+ } else {
+ None
+ };
+
+ Ok(InstantiateReturnValue {
+ result,
+ account_id,
+ rent_projection,
+ })
+ });
+ ContractInstantiateResult {
+ result: result.map_err(|e| e.error),
+ gas_consumed: gas_meter.gas_spent(),
+ debug_message: Bytes(Vec::new()).to_vec(),
+ }
+ }
+
+ /// Query storage of a specified contract under a specified key.
+ pub fn get_storage(address: T::AccountId, key: [u8; 32]) -> GetStorageResult {
+ let contract_info = ContractInfoOf::<T>::get(&address)
+ .ok_or(ContractAccessError::DoesntExist)?
+ .get_alive()
+ .ok_or(ContractAccessError::IsTombstone)?;
+
+ let maybe_value = Storage::<T>::read(&contract_info.trie_id, &key);
+ Ok(maybe_value)
+ }
+
+ /// Query how many blocks the contract stays alive given that the amount endowment
+ /// and consumed storage does not change.
+ pub fn rent_projection(address: T::AccountId) -> RentProjectionResult<T::BlockNumber> {
+ Rent::<T, PrefabWasmModule<T>>::compute_projection(&address)
+ }
+
+ /// Determine the address of a contract,
+ ///
+ /// This is the address generation function used by contract instantiation. Its result
+ /// is only dependend on its inputs. It can therefore be used to reliably predict the
+ /// address of a contract. This is akin to the formular of eth's CREATE2 opcode. There
+ /// is no CREATE equivalent because CREATE2 is strictly more powerful.
+ ///
+ /// Formula: `hash(deploying_address ++ code_hash ++ salt)`
+ pub fn contract_address(
+ deploying_address: &T::AccountId,
+ code_hash: &CodeHash<T>,
+ salt: &[u8],
+ ) -> T::AccountId {
+ let buf: Vec<_> = deploying_address
+ .as_ref()
+ .iter()
+ .chain(code_hash.as_ref())
+ .chain(salt)
+ .cloned()
+ .collect();
+ UncheckedFrom::unchecked_from(T::Hashing::hash(&buf))
+ }
+
+ /// Subsistence threshold is the extension of the minimum balance (aka existential deposit)
+ /// by the tombstone deposit, required for leaving a tombstone.
+ ///
+ /// Rent or any contract initiated balance transfer mechanism cannot make the balance lower
+ /// than the subsistence threshold in order to guarantee that a tombstone is created.
+ ///
+ /// The only way to completely kill a contract without a tombstone is calling `seal_terminate`.
+ pub fn subsistence_threshold() -> BalanceOf<T> {
+ T::Currency::minimum_balance().saturating_add(T::TombstoneDeposit::get())
+ }
+
+ /// The in-memory size in bytes of the data structure associated with each contract.
+ ///
+ /// The data structure is also put into storage for each contract. The in-storage size
+ /// is never larger than the in-memory representation and usually smaller due to compact
+ /// encoding and lack of padding.
+ ///
+ /// # Note
+ ///
+ /// This returns the in-memory size because the in-storage size (SCALE encoded) cannot
+ /// be efficiently determined. Treat this as an upper bound of the in-storage size.
+ pub fn contract_info_size() -> u32 {
+ sp_std::mem::size_of::<ContractInfo<T>>() as u32
+ }
+
+ /// Store code for benchmarks which does not check nor instrument the code.
+ #[cfg(feature = "runtime-benchmarks")]
+ fn store_code_raw(code: Vec<u8>) -> frame_support::dispatch::DispatchResult {
+ let schedule = <CurrentSchedule<T>>::get();
+ PrefabWasmModule::store_code_unchecked(code, &schedule)?;
+ Ok(())
+ }
+
+ /// This exists so that benchmarks can determine the weight of running an instrumentation.
+ #[cfg(feature = "runtime-benchmarks")]
+ fn reinstrument_module(
+ module: &mut PrefabWasmModule<T>,
+ schedule: &Schedule<T>,
+ ) -> frame_support::dispatch::DispatchResult {
+ self::wasm::reinstrument(module, schedule)
+ }
+}
pallets/contracts/src/migration.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/migration.rs
@@ -0,0 +1,41 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2018-2021 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.
+
+use crate::{Config, Weight, CurrentSchedule, Pallet, Schedule};
+use frame_support::traits::{GetPalletVersion, PalletVersion, Get};
+
+pub fn migrate<T: Config>() -> Weight {
+ let mut weight: Weight = 0;
+
+ match <Pallet<T>>::storage_version() {
+ // Replace the schedule with the new default and increment its version.
+ Some(version) if version == PalletVersion::new(3, 0, 0) => {
+ weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1));
+ let _ = <CurrentSchedule<T>>::translate::<u32, _>(|version| {
+ version.map(|version| Schedule {
+ version: version.saturating_add(1),
+ // Default limits were not decreased. Therefore it is OK to overwrite
+ // the schedule with the new defaults.
+ ..Default::default()
+ })
+ });
+ }
+ _ => (),
+ }
+
+ weight
+}
pallets/contracts/src/rent.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/rent.rs
@@ -0,0 +1,553 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2018-2021 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.
+
+//! A module responsible for computing the right amount of weight and charging it.
+
+use crate::{
+ AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Pallet, Event,
+ TombstoneContractInfo, Config, CodeHash, Error, storage::Storage, wasm::PrefabWasmModule,
+ exec::Executable,
+};
+use sp_std::prelude::*;
+use sp_io::hashing::blake2_256;
+use sp_core::crypto::UncheckedFrom;
+use frame_support::{
+ storage::child,
+ traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReasons},
+};
+use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult};
+use sp_runtime::{
+ DispatchError,
+ traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},
+};
+
+/// The amount to charge.
+///
+/// This amount respects the contract's rent allowance and the subsistence deposit.
+/// Because of that, charging the amount cannot remove the contract.
+struct OutstandingAmount<T: Config> {
+ amount: BalanceOf<T>,
+}
+
+impl<T: Config> OutstandingAmount<T> {
+ /// Create the new outstanding amount.
+ ///
+ /// The amount should be always withdrawable and it should not kill the account.
+ fn new(amount: BalanceOf<T>) -> Self {
+ Self { amount }
+ }
+
+ /// Returns the amount this instance wraps.
+ fn peek(&self) -> BalanceOf<T> {
+ self.amount
+ }
+
+ /// Withdraws the outstanding amount from the given account.
+ fn withdraw(self, account: &T::AccountId) {
+ if let Ok(imbalance) = T::Currency::withdraw(
+ account,
+ self.amount,
+ WithdrawReasons::FEE,
+ ExistenceRequirement::KeepAlive,
+ ) {
+ // This should never fail. However, let's err on the safe side.
+ T::RentPayment::on_unbalanced(imbalance);
+ }
+ }
+}
+
+enum Verdict<T: Config> {
+ /// The contract is exempted from paying rent.
+ ///
+ /// For example, it already paid its rent in the current block, or it has enough deposit for not
+ /// paying rent at all.
+ Exempt,
+ /// The contract cannot afford payment within its rent budget so it gets evicted. However,
+ /// because its balance is greater than the subsistence threshold it leaves a tombstone.
+ Evict {
+ amount: Option<OutstandingAmount<T>>,
+ },
+ /// Everything is OK, we just only take some charge.
+ Charge { amount: OutstandingAmount<T> },
+}
+
+pub struct Rent<T, E>(sp_std::marker::PhantomData<(T, E)>);
+
+impl<T, E> Rent<T, E>
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+ E: Executable<T>,
+{
+ /// Returns a fee charged per block from the contract.
+ ///
+ /// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds
+ /// then the fee can drop to zero.
+ fn compute_fee_per_block(
+ free_balance: &BalanceOf<T>,
+ contract: &AliveContractInfo<T>,
+ code_size_share: u32,
+ ) -> BalanceOf<T> {
+ let uncovered_by_balance = T::DepositPerStorageByte::get()
+ .saturating_mul(contract.storage_size.saturating_add(code_size_share).into())
+ .saturating_add(
+ T::DepositPerStorageItem::get().saturating_mul(contract.pair_count.into()),
+ )
+ .saturating_add(T::DepositPerContract::get())
+ .saturating_sub(*free_balance);
+ T::RentFraction::get().mul_ceil(uncovered_by_balance)
+ }
+
+ /// Returns amount of funds available to consume by rent mechanism.
+ ///
+ /// Rent mechanism cannot consume more than `rent_allowance` set by the contract and it cannot make
+ /// the balance lower than [`subsistence_threshold`].
+ ///
+ /// In case the toal_balance is below the subsistence threshold, this function returns `None`.
+ fn rent_budget(
+ total_balance: &BalanceOf<T>,
+ free_balance: &BalanceOf<T>,
+ contract: &AliveContractInfo<T>,
+ ) -> Option<BalanceOf<T>> {
+ let subsistence_threshold = Pallet::<T>::subsistence_threshold();
+ // Reserved balance contributes towards the subsistence threshold to stay consistent
+ // with the existential deposit where the reserved balance is also counted.
+ if *total_balance < subsistence_threshold {
+ return None;
+ }
+
+ // However, reserved balance cannot be charged so we need to use the free balance
+ // to calculate the actual budget (which can be 0).
+ let rent_allowed_to_charge = free_balance.saturating_sub(subsistence_threshold);
+ Some(<BalanceOf<T>>::min(
+ contract.rent_allowance,
+ rent_allowed_to_charge,
+ ))
+ }
+
+ /// Consider the case for rent payment of the given account and returns a `Verdict`.
+ ///
+ /// Use `handicap` in case you want to change the reference block number. (To get more details see
+ /// `try_eviction` ).
+ fn consider_case(
+ account: &T::AccountId,
+ current_block_number: T::BlockNumber,
+ handicap: T::BlockNumber,
+ contract: &AliveContractInfo<T>,
+ code_size: u32,
+ ) -> Verdict<T> {
+ // How much block has passed since the last deduction for the contract.
+ let blocks_passed = {
+ // Calculate an effective block number, i.e. after adjusting for handicap.
+ let effective_block_number = current_block_number.saturating_sub(handicap);
+ effective_block_number.saturating_sub(contract.deduct_block)
+ };
+ if blocks_passed.is_zero() {
+ // Rent has already been paid
+ return Verdict::Exempt;
+ }
+
+ let total_balance = T::Currency::total_balance(account);
+ let free_balance = T::Currency::free_balance(account);
+
+ // An amount of funds to charge per block for storage taken up by the contract.
+ let fee_per_block = Self::compute_fee_per_block(&free_balance, contract, code_size);
+ if fee_per_block.is_zero() {
+ // The rent deposit offset reduced the fee to 0. This means that the contract
+ // gets the rent for free.
+ return Verdict::Exempt;
+ }
+
+ let rent_budget = match Self::rent_budget(&total_balance, &free_balance, contract) {
+ Some(rent_budget) => rent_budget,
+ None => {
+ // All functions that allow a contract to transfer balance enforce
+ // that the contract always stays above the subsistence threshold.
+ // We want the rent system to always leave a tombstone to prevent the
+ // accidental loss of a contract. Ony `seal_terminate` can remove a
+ // contract without a tombstone. Therefore this case should be never
+ // hit.
+ log::error!(
+ target: "runtime::contracts",
+ "Tombstoned a contract that is below the subsistence threshold: {:?}",
+ account,
+ );
+ 0u32.into()
+ }
+ };
+
+ let dues = fee_per_block
+ .checked_mul(&blocks_passed.saturated_into::<u32>().into())
+ .unwrap_or_else(|| <BalanceOf<T>>::max_value());
+ let insufficient_rent = rent_budget < dues;
+
+ // If the rent payment cannot be withdrawn due to locks on the account balance, then evict the
+ // account.
+ //
+ // NOTE: This seems problematic because it provides a way to tombstone an account while
+ // avoiding the last rent payment. In effect, someone could retroactively set rent_allowance
+ // for their contract to 0.
+ let dues_limited = dues.min(rent_budget);
+ let can_withdraw_rent = T::Currency::ensure_can_withdraw(
+ account,
+ dues_limited,
+ WithdrawReasons::FEE,
+ free_balance.saturating_sub(dues_limited),
+ )
+ .is_ok();
+
+ if insufficient_rent || !can_withdraw_rent {
+ // The contract cannot afford the rent payment and has a balance above the subsistence
+ // threshold, so it leaves a tombstone.
+ let amount = if can_withdraw_rent {
+ Some(OutstandingAmount::new(dues_limited))
+ } else {
+ None
+ };
+ return Verdict::Evict { amount };
+ }
+
+ return Verdict::Charge {
+ // We choose to use `dues_limited` here instead of `dues` just to err on the safer side.
+ amount: OutstandingAmount::new(dues_limited),
+ };
+ }
+
+ /// Enacts the given verdict and returns the updated `ContractInfo`.
+ ///
+ /// `alive_contract_info` should be from the same address as `account`.
+ ///
+ /// # Note
+ ///
+ /// if `evictable_code` is `None` an `Evict` verdict will not be enacted. This is for
+ /// when calling this function during a `call` where access to the soon to be evicted
+ /// contract should be denied but storage should be left unmodified.
+ fn enact_verdict(
+ account: &T::AccountId,
+ alive_contract_info: AliveContractInfo<T>,
+ current_block_number: T::BlockNumber,
+ verdict: Verdict<T>,
+ evictable_code: Option<PrefabWasmModule<T>>,
+ ) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
+ match (verdict, evictable_code) {
+ (Verdict::Evict { amount }, Some(code)) => {
+ // We need to remove the trie first because it is the only operation
+ // that can fail and this function is called without a storage
+ // transaction when called through `claim_surcharge`.
+ Storage::<T>::queue_trie_for_deletion(&alive_contract_info)?;
+
+ if let Some(amount) = amount {
+ amount.withdraw(account);
+ }
+
+ // Note: this operation is heavy.
+ let child_storage_root = child::root(&alive_contract_info.child_trie_info());
+
+ let tombstone = <TombstoneContractInfo<T>>::new(
+ &child_storage_root[..],
+ alive_contract_info.code_hash,
+ );
+ let tombstone_info = ContractInfo::Tombstone(tombstone);
+ <ContractInfoOf<T>>::insert(account, &tombstone_info);
+ code.drop_from_storage();
+ <Pallet<T>>::deposit_event(Event::Evicted(account.clone()));
+ Ok(None)
+ }
+ (Verdict::Evict { amount: _ }, None) => Ok(None),
+ (Verdict::Exempt, _) => {
+ let contract = ContractInfo::Alive(AliveContractInfo::<T> {
+ deduct_block: current_block_number,
+ ..alive_contract_info
+ });
+ <ContractInfoOf<T>>::insert(account, &contract);
+ Ok(Some(
+ contract
+ .get_alive()
+ .expect("We just constructed it as alive. qed"),
+ ))
+ }
+ (Verdict::Charge { amount }, _) => {
+ let contract = ContractInfo::Alive(AliveContractInfo::<T> {
+ rent_allowance: alive_contract_info.rent_allowance - amount.peek(),
+ deduct_block: current_block_number,
+ rent_payed: alive_contract_info.rent_payed.saturating_add(amount.peek()),
+ ..alive_contract_info
+ });
+ <ContractInfoOf<T>>::insert(account, &contract);
+ amount.withdraw(account);
+ Ok(Some(
+ contract
+ .get_alive()
+ .expect("We just constructed it as alive. qed"),
+ ))
+ }
+ }
+ }
+
+ /// Make account paying the rent for the current block number
+ ///
+ /// This functions does **not** evict the contract. It returns `None` in case the
+ /// contract is in need of eviction. [`try_eviction`] must
+ /// be called to perform the eviction.
+ pub fn charge(
+ account: &T::AccountId,
+ contract: AliveContractInfo<T>,
+ code_size: u32,
+ ) -> Result<Option<AliveContractInfo<T>>, DispatchError> {
+ let current_block_number = <frame_system::Pallet<T>>::block_number();
+ let verdict = Self::consider_case(
+ account,
+ current_block_number,
+ Zero::zero(),
+ &contract,
+ code_size,
+ );
+ Self::enact_verdict(account, contract, current_block_number, verdict, None)
+ }
+
+ /// Process a report that a contract under the given address should be evicted.
+ ///
+ /// Enact the eviction right away if the contract should be evicted and return the amount
+ /// of rent that the contract payed over its lifetime.
+ /// Otherwise, **do nothing** and return None.
+ ///
+ /// The `handicap` parameter gives a way to check the rent to a moment in the past instead
+ /// of current block. E.g. if the contract is going to be evicted at the current block,
+ /// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain snitchers
+ /// relative to others.
+ ///
+ /// NOTE this function performs eviction eagerly. All changes are read and written directly to
+ /// storage.
+ pub fn try_eviction(
+ account: &T::AccountId,
+ handicap: T::BlockNumber,
+ ) -> Result<(Option<BalanceOf<T>>, u32), DispatchError> {
+ let contract = <ContractInfoOf<T>>::get(account);
+ let contract = match contract {
+ None | Some(ContractInfo::Tombstone(_)) => return Ok((None, 0)),
+ Some(ContractInfo::Alive(contract)) => contract,
+ };
+ let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?;
+ let code_len = module.code_len();
+ let current_block_number = <frame_system::Pallet<T>>::block_number();
+ let verdict = Self::consider_case(
+ account,
+ current_block_number,
+ handicap,
+ &contract,
+ module.occupied_storage(),
+ );
+
+ // Enact the verdict only if the contract gets removed.
+ match verdict {
+ Verdict::Evict { ref amount } => {
+ // The outstanding `amount` is withdrawn inside `enact_verdict`.
+ let rent_payed = amount
+ .as_ref()
+ .map(|a| a.peek())
+ .unwrap_or_else(|| <BalanceOf<T>>::zero())
+ .saturating_add(contract.rent_payed);
+ Self::enact_verdict(
+ account,
+ contract,
+ current_block_number,
+ verdict,
+ Some(module),
+ )?;
+ Ok((Some(rent_payed), code_len))
+ }
+ _ => Ok((None, code_len)),
+ }
+ }
+
+ /// Returns the projected time a given contract will be able to sustain paying its rent. The
+ /// returned projection is relevant for the current block, i.e. it is as if the contract was
+ /// accessed at the beginning of the current block. Returns `None` in case if the contract was
+ /// evicted before or as a result of the rent collection.
+ ///
+ /// The returned value is only an estimation. It doesn't take into account any top ups, changing the
+ /// rent allowance, or any problems coming from withdrawing the dues.
+ ///
+ /// NOTE that this is not a side-effect free function! It will actually collect rent and then
+ /// compute the projection. This function is only used for implementation of an RPC method through
+ /// `RuntimeApi` meaning that the changes will be discarded anyway.
+ pub fn compute_projection(account: &T::AccountId) -> RentProjectionResult<T::BlockNumber> {
+ use ContractAccessError::IsTombstone;
+
+ let contract_info = <ContractInfoOf<T>>::get(account);
+ let alive_contract_info = match contract_info {
+ None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone),
+ Some(ContractInfo::Alive(contract)) => contract,
+ };
+ let module = <PrefabWasmModule<T>>::from_storage_noinstr(alive_contract_info.code_hash)
+ .map_err(|_| IsTombstone)?;
+ let code_size = module.occupied_storage();
+ let current_block_number = <frame_system::Pallet<T>>::block_number();
+ let verdict = Self::consider_case(
+ account,
+ current_block_number,
+ Zero::zero(),
+ &alive_contract_info,
+ code_size,
+ );
+
+ // We skip the eviction in case one is in order.
+ // Evictions should only be performed by [`try_eviction`].
+ let new_contract_info = Self::enact_verdict(
+ account,
+ alive_contract_info,
+ current_block_number,
+ verdict,
+ None,
+ );
+
+ // Check what happened after enaction of the verdict.
+ let alive_contract_info = new_contract_info
+ .map_err(|_| IsTombstone)?
+ .ok_or_else(|| IsTombstone)?;
+
+ // Compute how much would the fee per block be with the *updated* balance.
+ let total_balance = T::Currency::total_balance(account);
+ let free_balance = T::Currency::free_balance(account);
+ let fee_per_block =
+ Self::compute_fee_per_block(&free_balance, &alive_contract_info, code_size);
+ if fee_per_block.is_zero() {
+ return Ok(RentProjection::NoEviction);
+ }
+
+ // Then compute how much the contract will sustain under these circumstances.
+ let rent_budget = Self::rent_budget(&total_balance, &free_balance, &alive_contract_info)
+ .expect(
+ "the contract exists and in the alive state;
+ the updated balance must be greater than subsistence deposit;
+ this function doesn't return `None`;
+ qed
+ ",
+ );
+ let blocks_left = match rent_budget.checked_div(&fee_per_block) {
+ Some(blocks_left) => blocks_left,
+ None => {
+ // `fee_per_block` is not zero here, so `checked_div` can return `None` if
+ // there is an overflow. This cannot happen with integers though. Return
+ // `NoEviction` here just in case.
+ return Ok(RentProjection::NoEviction);
+ }
+ };
+
+ let blocks_left = blocks_left.saturated_into::<u32>().into();
+ Ok(RentProjection::EvictionAt(
+ current_block_number + blocks_left,
+ ))
+ }
+
+ /// Restores the destination account using the origin as prototype.
+ ///
+ /// The restoration will be performed iff:
+ /// - the supplied code_hash does still exist on-chain
+ /// - origin exists and is alive,
+ /// - the origin's storage is not written in the current block
+ /// - the restored account has tombstone
+ /// - the tombstone matches the hash of the origin storage root, and code hash.
+ ///
+ /// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to
+ /// the restored account. The restored account will inherit the last write block and its last
+ /// deduct block will be set to the current block.
+ ///
+ /// # Return Value
+ ///
+ /// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>
+ pub fn restore_to(
+ origin: T::AccountId,
+ dest: T::AccountId,
+ code_hash: CodeHash<T>,
+ rent_allowance: BalanceOf<T>,
+ delta: Vec<crate::exec::StorageKey>,
+ ) -> Result<(u32, u32), (DispatchError, u32, u32)> {
+ let mut origin_contract = <ContractInfoOf<T>>::get(&origin)
+ .and_then(|c| c.get_alive())
+ .ok_or((Error::<T>::InvalidSourceContract.into(), 0, 0))?;
+
+ let child_trie_info = origin_contract.child_trie_info();
+
+ let current_block = <frame_system::Pallet<T>>::block_number();
+
+ if origin_contract.last_write == Some(current_block) {
+ return Err((Error::<T>::InvalidContractOrigin.into(), 0, 0));
+ }
+
+ let dest_tombstone = <ContractInfoOf<T>>::get(&dest)
+ .and_then(|c| c.get_tombstone())
+ .ok_or((Error::<T>::InvalidDestinationContract.into(), 0, 0))?;
+
+ let last_write = if !delta.is_empty() {
+ Some(current_block)
+ } else {
+ origin_contract.last_write
+ };
+
+ // Fails if the code hash does not exist on chain
+ let caller_code_len = E::add_user(code_hash).map_err(|e| (e, 0, 0))?;
+
+ // We are allowed to eagerly modify storage even though the function can
+ // fail later due to tombstones not matching. This is because the restoration
+ // is always called from a contract and therefore in a storage transaction.
+ // The failure of this function will lead to this transaction's rollback.
+ let bytes_taken: u32 = delta
+ .iter()
+ .filter_map(|key| {
+ let key = blake2_256(key);
+ child::get_raw(&child_trie_info, &key).map(|value| {
+ child::kill(&child_trie_info, &key);
+ value.len() as u32
+ })
+ })
+ .sum();
+
+ let tombstone = <TombstoneContractInfo<T>>::new(
+ // This operation is cheap enough because last_write (delta not included)
+ // is not this block as it has been checked earlier.
+ &child::root(&child_trie_info)[..],
+ code_hash,
+ );
+
+ if tombstone != dest_tombstone {
+ return Err((Error::<T>::InvalidTombstone.into(), caller_code_len, 0));
+ }
+
+ origin_contract.storage_size -= bytes_taken;
+
+ <ContractInfoOf<T>>::remove(&origin);
+ let tombstone_code_len = E::remove_user(origin_contract.code_hash);
+ <ContractInfoOf<T>>::insert(
+ &dest,
+ ContractInfo::Alive(AliveContractInfo::<T> {
+ code_hash,
+ rent_allowance,
+ rent_payed: <BalanceOf<T>>::zero(),
+ deduct_block: current_block,
+ last_write,
+ ..origin_contract
+ }),
+ );
+
+ let origin_free_balance = T::Currency::free_balance(&origin);
+ T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());
+ T::Currency::deposit_creating(&dest, origin_free_balance);
+
+ Ok((caller_code_len, tombstone_code_len))
+ }
+}
pallets/contracts/src/schedule.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/schedule.rs
@@ -0,0 +1,806 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020-2021 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.
+
+//! This module contains the cost schedule and supporting code that constructs a
+//! sane default schedule from a `WeightInfo` implementation.
+
+use crate::{Config, weights::WeightInfo};
+
+#[cfg(feature = "std")]
+use serde::{Serialize, Deserialize};
+use pallet_contracts_proc_macro::{ScheduleDebug, WeightDebug};
+use frame_support::weights::Weight;
+use sp_std::{marker::PhantomData, vec::Vec};
+use codec::{Encode, Decode};
+use parity_wasm::elements;
+use pwasm_utils::rules;
+use sp_runtime::RuntimeDebug;
+
+/// 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;
+
+/// How many instructions are executed in a single batch. The reasoning is the same
+/// as for `API_BENCHMARK_BATCH_SIZE`.
+pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 1_000;
+
+/// Definition of the cost schedule and other parameterizations for the wasm vm.
+///
+/// Its fields are private to the crate in order to allow addition of new contract
+/// callable functions without bumping to a new major version. A genesis config should
+/// rely on public functions of this type.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "std", serde(bound(serialize = "", deserialize = "")))]
+#[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug)]
+pub struct Schedule<T: Config> {
+ /// Version of the schedule.
+ ///
+ /// # Note
+ ///
+ /// Must be incremented whenever the [`self.instruction_weights`] are changed. The
+ /// reason is that changes to instruction weights require a re-instrumentation
+ /// of all contracts which are triggered by a version comparison on call.
+ /// Changes to other parts of the schedule should not increment the version in
+ /// order to avoid unnecessary re-instrumentations.
+ pub(crate) version: u32,
+
+ /// Whether the `seal_println` function is allowed to be used contracts.
+ /// MUST only be enabled for `dev` chains, NOT for production chains
+ pub(crate) enable_println: bool,
+
+ /// Describes the upper limits on various metrics.
+ pub(crate) limits: Limits,
+
+ /// The weights for individual wasm instructions.
+ pub(crate) instruction_weights: InstructionWeights<T>,
+
+ /// The weights for each imported function a contract is allowed to call.
+ pub(crate) host_fn_weights: HostFnWeights<T>,
+}
+
+/// Describes the upper limits on various metrics.
+///
+/// # Note
+///
+/// The values in this struct should only ever be increased for a deployed chain. The reason
+/// is that decreasing those values will break existing contracts which are above the new limits.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug)]
+pub struct Limits {
+ /// The maximum number of topics supported by an event.
+ pub event_topics: u32,
+
+ /// Maximum allowed stack height in number of elements.
+ ///
+ /// See <https://wiki.parity.io/WebAssembly-StackHeight> to find out
+ /// how the stack frame cost is calculated. Each element can be of one of the
+ /// wasm value types. This means the maximum size per element is 64bit.
+ pub stack_height: u32,
+
+ /// Maximum number of globals a module is allowed to declare.
+ ///
+ /// Globals are not limited through the `stack_height` as locals are. Neither does
+ /// the linear memory limit `memory_pages` applies to them.
+ pub globals: u32,
+
+ /// Maximum numbers of parameters a function can have.
+ ///
+ /// Those need to be limited to prevent a potentially exploitable interaction with
+ /// the stack height instrumentation: The costs of executing the stack height
+ /// instrumentation for an indirectly called function scales linearly with the amount
+ /// of parameters of this function. Because the stack height instrumentation itself is
+ /// is not weight metered its costs must be static (via this limit) and included in
+ /// the costs of the instructions that cause them (call, call_indirect).
+ pub parameters: u32,
+
+ /// Maximum number of memory pages allowed for a contract.
+ pub memory_pages: u32,
+
+ /// Maximum number of elements allowed in a table.
+ ///
+ /// Currently, the only type of element that is allowed in a table is funcref.
+ pub table_size: u32,
+
+ /// Maximum number of elements that can appear as immediate value to the br_table instruction.
+ pub br_table_size: u32,
+
+ /// The maximum length of a subject in bytes used for PRNG generation.
+ pub subject_len: u32,
+}
+
+impl Limits {
+ /// The maximum memory size in bytes that a contract can occupy.
+ pub fn max_memory_size(&self) -> u32 {
+ self.memory_pages * 64 * 1024
+ }
+}
+
+/// Describes the weight for all categories of supported wasm instructions.
+///
+/// There there is one field for each wasm instruction that describes the weight to
+/// execute one instruction of that name. There are a few execptions:
+///
+/// 1. If there is a i64 and a i32 variant of an instruction we use the weight
+/// of the former for both.
+/// 2. The following instructions are free of charge because they merely structure the
+/// wasm module and cannot be spammed without making the module invalid (and rejected):
+/// End, Unreachable, Return, Else
+/// 3. The following instructions cannot be benchmarked because they are removed by any
+/// real world execution engine as a preprocessing step and therefore don't yield a
+/// meaningful benchmark result. However, in contrast to the instructions mentioned
+/// in 2. they can be spammed. We price them with the same weight as the "default"
+/// instruction (i64.const): Block, Loop, Nop
+/// 4. We price both i64.const and drop as InstructionWeights.i64const / 2. The reason
+/// for that is that we cannot benchmark either of them on its own but we need their
+/// individual values to derive (by subtraction) the weight of all other instructions
+/// that use them as supporting instructions. Supporting means mainly pushing arguments
+/// and dropping return values in order to maintain a valid module.
+#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+#[derive(Clone, Encode, Decode, PartialEq, Eq, WeightDebug)]
+pub struct InstructionWeights<T: Config> {
+ pub i64const: u32,
+ pub i64load: u32,
+ pub i64store: u32,
+ pub select: u32,
+ pub r#if: u32,
+ pub br: u32,
+ pub br_if: u32,
+ pub br_table: u32,
+ pub br_table_per_entry: u32,
+ pub call: u32,
+ pub call_indirect: u32,
+ pub call_indirect_per_param: u32,
+ pub local_get: u32,
+ pub local_set: u32,
+ pub local_tee: u32,
+ pub global_get: u32,
+ pub global_set: u32,
+ pub memory_current: u32,
+ pub memory_grow: u32,
+ pub i64clz: u32,
+ pub i64ctz: u32,
+ pub i64popcnt: u32,
+ pub i64eqz: u32,
+ pub i64extendsi32: u32,
+ pub i64extendui32: u32,
+ pub i32wrapi64: u32,
+ pub i64eq: u32,
+ pub i64ne: u32,
+ pub i64lts: u32,
+ pub i64ltu: u32,
+ pub i64gts: u32,
+ pub i64gtu: u32,
+ pub i64les: u32,
+ pub i64leu: u32,
+ pub i64ges: u32,
+ pub i64geu: u32,
+ pub i64add: u32,
+ pub i64sub: u32,
+ pub i64mul: u32,
+ pub i64divs: u32,
+ pub i64divu: u32,
+ pub i64rems: u32,
+ pub i64remu: u32,
+ pub i64and: u32,
+ pub i64or: u32,
+ pub i64xor: u32,
+ pub i64shl: u32,
+ pub i64shrs: u32,
+ pub i64shru: u32,
+ pub i64rotl: u32,
+ pub i64rotr: u32,
+ /// The type parameter is used in the default implementation.
+ #[codec(skip)]
+ pub _phantom: PhantomData<T>,
+}
+
+/// 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, WeightDebug)]
+pub struct HostFnWeights<T: Config> {
+ /// 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 per byte of the terminated contract.
+ pub terminate_per_code_byte: Weight,
+
+ /// Weight of calling `seal_restore_to`.
+ pub restore_to: Weight,
+
+ /// Weight per byte of the restoring contract.
+ pub restore_to_per_caller_code_byte: Weight,
+
+ /// Weight per byte of the restored contract.
+ pub restore_to_per_tombstone_code_byte: 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 per byte of the called contract.
+ pub call_per_code_byte: 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 byte of the instantiated contract.
+ pub instantiate_per_code_byte: 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 per salt byte supplied to `seal_instantiate`.
+ pub instantiate_per_salt_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,
+
+ /// Weight of calling `seal_rent_params`.
+ pub rent_params: Weight,
+
+ /// The type parameter is used in the default implementation.
+ #[codec(skip)]
+ pub _phantom: PhantomData<T>,
+}
+
+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_instr_no_params_with_batch_size {
+ ($name:ident, $batch_size:expr) => {
+ (cost_args!($name, 1) / Weight::from($batch_size)) as u32
+ };
+}
+
+macro_rules! cost_instr_with_batch_size {
+ ($name:ident, $num_params:expr, $batch_size:expr) => {
+ cost_instr_no_params_with_batch_size!($name, $batch_size).saturating_sub(
+ (cost_instr_no_params_with_batch_size!(instr_i64const, $batch_size) / 2)
+ .saturating_mul($num_params),
+ )
+ };
+}
+
+macro_rules! cost_instr {
+ ($name:ident, $num_params:expr) => {
+ cost_instr_with_batch_size!($name, $num_params, INSTR_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<T: Config> Default for Schedule<T> {
+ fn default() -> Self {
+ Self {
+ version: 0,
+ enable_println: false,
+ limits: Default::default(),
+ instruction_weights: Default::default(),
+ host_fn_weights: Default::default(),
+ }
+ }
+}
+
+impl Default for Limits {
+ fn default() -> Self {
+ Self {
+ event_topics: 4,
+ // 512 * sizeof(i64) will give us a 4k stack.
+ stack_height: 512,
+ globals: 256,
+ parameters: 128,
+ memory_pages: 16,
+ // 4k function pointers (This is in count not bytes).
+ table_size: 4096,
+ br_table_size: 256,
+ subject_len: 32,
+ }
+ }
+}
+
+impl<T: Config> Default for InstructionWeights<T> {
+ fn default() -> Self {
+ let max_pages = Limits::default().memory_pages;
+ Self {
+ i64const: cost_instr!(instr_i64const, 1),
+ i64load: cost_instr!(instr_i64load, 2),
+ i64store: cost_instr!(instr_i64store, 2),
+ select: cost_instr!(instr_select, 4),
+ r#if: cost_instr!(instr_if, 3),
+ br: cost_instr!(instr_br, 2),
+ br_if: cost_instr!(instr_br_if, 5),
+ br_table: cost_instr!(instr_br_table, 3),
+ br_table_per_entry: cost_instr!(instr_br_table_per_entry, 0),
+ call: cost_instr!(instr_call, 2),
+ call_indirect: cost_instr!(instr_call_indirect, 3),
+ call_indirect_per_param: cost_instr!(instr_call_indirect_per_param, 1),
+ local_get: cost_instr!(instr_local_get, 1),
+ local_set: cost_instr!(instr_local_set, 1),
+ local_tee: cost_instr!(instr_local_tee, 2),
+ global_get: cost_instr!(instr_global_get, 1),
+ global_set: cost_instr!(instr_global_set, 1),
+ memory_current: cost_instr!(instr_memory_current, 1),
+ memory_grow: cost_instr_with_batch_size!(instr_memory_grow, 1, max_pages),
+ i64clz: cost_instr!(instr_i64clz, 2),
+ i64ctz: cost_instr!(instr_i64ctz, 2),
+ i64popcnt: cost_instr!(instr_i64popcnt, 2),
+ i64eqz: cost_instr!(instr_i64eqz, 2),
+ i64extendsi32: cost_instr!(instr_i64extendsi32, 2),
+ i64extendui32: cost_instr!(instr_i64extendui32, 2),
+ i32wrapi64: cost_instr!(instr_i32wrapi64, 2),
+ i64eq: cost_instr!(instr_i64eq, 3),
+ i64ne: cost_instr!(instr_i64ne, 3),
+ i64lts: cost_instr!(instr_i64lts, 3),
+ i64ltu: cost_instr!(instr_i64ltu, 3),
+ i64gts: cost_instr!(instr_i64gts, 3),
+ i64gtu: cost_instr!(instr_i64gtu, 3),
+ i64les: cost_instr!(instr_i64les, 3),
+ i64leu: cost_instr!(instr_i64leu, 3),
+ i64ges: cost_instr!(instr_i64ges, 3),
+ i64geu: cost_instr!(instr_i64geu, 3),
+ i64add: cost_instr!(instr_i64add, 3),
+ i64sub: cost_instr!(instr_i64sub, 3),
+ i64mul: cost_instr!(instr_i64mul, 3),
+ i64divs: cost_instr!(instr_i64divs, 3),
+ i64divu: cost_instr!(instr_i64divu, 3),
+ i64rems: cost_instr!(instr_i64rems, 3),
+ i64remu: cost_instr!(instr_i64remu, 3),
+ i64and: cost_instr!(instr_i64and, 3),
+ i64or: cost_instr!(instr_i64or, 3),
+ i64xor: cost_instr!(instr_i64xor, 3),
+ i64shl: cost_instr!(instr_i64shl, 3),
+ i64shrs: cost_instr!(instr_i64shrs, 3),
+ i64shru: cost_instr!(instr_i64shru, 3),
+ i64rotl: cost_instr!(instr_i64rotl, 3),
+ i64rotr: cost_instr!(instr_i64rotr, 3),
+ _phantom: PhantomData,
+ }
+ }
+}
+
+impl<T: Config> Default for HostFnWeights<T> {
+ fn default() -> Self {
+ Self {
+ 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),
+ terminate_per_code_byte: cost_byte!(seal_terminate_per_code_kb),
+ restore_to: cost!(seal_restore_to),
+ restore_to_per_caller_code_byte: cost_byte_args!(
+ seal_restore_to_per_code_kb_delta,
+ 1,
+ 0,
+ 0
+ ),
+ restore_to_per_tombstone_code_byte: cost_byte_args!(
+ seal_restore_to_per_code_kb_delta,
+ 0,
+ 1,
+ 0
+ ),
+ restore_to_per_delta: cost_batched_args!(seal_restore_to_per_code_kb_delta, 0, 0, 1),
+ 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_per_code_byte: cost_byte_batched_args!(
+ seal_call_per_code_transfer_input_output_kb,
+ 1,
+ 0,
+ 0,
+ 0
+ ),
+ call_transfer_surcharge: cost_batched_args!(
+ seal_call_per_code_transfer_input_output_kb,
+ 0,
+ 1,
+ 0,
+ 0
+ ),
+ call_per_input_byte: cost_byte_batched_args!(
+ seal_call_per_code_transfer_input_output_kb,
+ 0,
+ 0,
+ 1,
+ 0
+ ),
+ call_per_output_byte: cost_byte_batched_args!(
+ seal_call_per_code_transfer_input_output_kb,
+ 0,
+ 0,
+ 0,
+ 1
+ ),
+ instantiate: cost_batched!(seal_instantiate),
+ instantiate_per_code_byte: cost_byte_batched_args!(
+ seal_instantiate_per_code_input_output_salt_kb,
+ 1,
+ 0,
+ 0,
+ 0
+ ),
+ instantiate_per_input_byte: cost_byte_batched_args!(
+ seal_instantiate_per_code_input_output_salt_kb,
+ 0,
+ 1,
+ 0,
+ 0
+ ),
+ instantiate_per_output_byte: cost_byte_batched_args!(
+ seal_instantiate_per_code_input_output_salt_kb,
+ 0,
+ 0,
+ 1,
+ 0
+ ),
+ instantiate_per_salt_byte: cost_byte_batched_args!(
+ seal_instantiate_per_code_input_output_salt_kb,
+ 0,
+ 0,
+ 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),
+ rent_params: cost_batched!(seal_rent_params),
+ _phantom: PhantomData,
+ }
+ }
+}
+
+struct ScheduleRules<'a, T: Config> {
+ schedule: &'a Schedule<T>,
+ params: Vec<u32>,
+}
+
+impl<T: Config> Schedule<T> {
+ /// Allow contracts to call `seal_println` in order to print messages to the console.
+ ///
+ /// This should only ever be activated in development chains. The printed messages
+ /// can be observed on the console by setting the environment variable
+ /// `RUST_LOG=runtime=debug` when running the node.
+ ///
+ /// # Note
+ ///
+ /// Is set to `false` by default.
+ pub fn enable_println(mut self, enable: bool) -> Self {
+ self.enable_println = enable;
+ self
+ }
+
+ pub(crate) fn rules(&self, module: &elements::Module) -> impl rules::Rules + '_ {
+ ScheduleRules {
+ schedule: &self,
+ params: module
+ .type_section()
+ .iter()
+ .flat_map(|section| section.types())
+ .map(|func| {
+ let elements::Type::Function(func) = func;
+ func.params().len() as u32
+ })
+ .collect(),
+ }
+ }
+}
+
+impl<'a, T: Config> rules::Rules for ScheduleRules<'a, T> {
+ fn instruction_cost(&self, instruction: &elements::Instruction) -> Option<u32> {
+ use parity_wasm::elements::Instruction::*;
+ let w = &self.schedule.instruction_weights;
+ let max_params = self.schedule.limits.parameters;
+
+ let weight = match *instruction {
+ End | Unreachable | Return | Else => 0,
+ I32Const(_) | I64Const(_) | Block(_) | Loop(_) | Nop | Drop => w.i64const,
+ I32Load(_, _)
+ | I32Load8S(_, _)
+ | I32Load8U(_, _)
+ | I32Load16S(_, _)
+ | I32Load16U(_, _)
+ | I64Load(_, _)
+ | I64Load8S(_, _)
+ | I64Load8U(_, _)
+ | I64Load16S(_, _)
+ | I64Load16U(_, _)
+ | I64Load32S(_, _)
+ | I64Load32U(_, _) => w.i64load,
+ I32Store(_, _)
+ | I32Store8(_, _)
+ | I32Store16(_, _)
+ | I64Store(_, _)
+ | I64Store8(_, _)
+ | I64Store16(_, _)
+ | I64Store32(_, _) => w.i64store,
+ Select => w.select,
+ If(_) => w.r#if,
+ Br(_) => w.br,
+ BrIf(_) => w.br_if,
+ Call(_) => w.call,
+ GetLocal(_) => w.local_get,
+ SetLocal(_) => w.local_set,
+ TeeLocal(_) => w.local_tee,
+ GetGlobal(_) => w.global_get,
+ SetGlobal(_) => w.global_set,
+ CurrentMemory(_) => w.memory_current,
+ GrowMemory(_) => w.memory_grow,
+ CallIndirect(idx, _) => *self.params.get(idx as usize).unwrap_or(&max_params),
+ BrTable(ref data) => w
+ .br_table
+ .saturating_add(w.br_table_per_entry.saturating_mul(data.table.len() as u32)),
+ I32Clz | I64Clz => w.i64clz,
+ I32Ctz | I64Ctz => w.i64ctz,
+ I32Popcnt | I64Popcnt => w.i64popcnt,
+ I32Eqz | I64Eqz => w.i64eqz,
+ I64ExtendSI32 => w.i64extendsi32,
+ I64ExtendUI32 => w.i64extendui32,
+ I32WrapI64 => w.i32wrapi64,
+ I32Eq | I64Eq => w.i64eq,
+ I32Ne | I64Ne => w.i64ne,
+ I32LtS | I64LtS => w.i64lts,
+ I32LtU | I64LtU => w.i64ltu,
+ I32GtS | I64GtS => w.i64gts,
+ I32GtU | I64GtU => w.i64gtu,
+ I32LeS | I64LeS => w.i64les,
+ I32LeU | I64LeU => w.i64leu,
+ I32GeS | I64GeS => w.i64ges,
+ I32GeU | I64GeU => w.i64geu,
+ I32Add | I64Add => w.i64add,
+ I32Sub | I64Sub => w.i64sub,
+ I32Mul | I64Mul => w.i64mul,
+ I32DivS | I64DivS => w.i64divs,
+ I32DivU | I64DivU => w.i64divu,
+ I32RemS | I64RemS => w.i64rems,
+ I32RemU | I64RemU => w.i64remu,
+ I32And | I64And => w.i64and,
+ I32Or | I64Or => w.i64or,
+ I32Xor | I64Xor => w.i64xor,
+ I32Shl | I64Shl => w.i64shl,
+ I32ShrS | I64ShrS => w.i64shrs,
+ I32ShrU | I64ShrU => w.i64shru,
+ I32Rotl | I64Rotl => w.i64rotl,
+ I32Rotr | I64Rotr => w.i64rotr,
+
+ // Returning None makes the gas instrumentation fail which we intend for
+ // unsupported or unknown instructions.
+ _ => return None,
+ };
+ Some(weight)
+ }
+
+ fn memory_grow_cost(&self) -> Option<rules::MemoryGrowCost> {
+ // We benchmarked the memory.grow instruction with the maximum allowed pages.
+ // The cost for growing is therefore already included in the instruction cost.
+ None
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::tests::Test;
+ use super::*;
+
+ #[test]
+ fn print_test_schedule() {
+ let schedule = Schedule::<Test>::default();
+ println!("{:#?}", schedule);
+ }
+}
pallets/contracts/src/storage.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/storage.rs
@@ -0,0 +1,440 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2019-2021 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.
+
+//! This module contains routines for accessing and altering a contract related state.
+
+use crate::{
+ exec::{AccountIdOf, StorageKey},
+ BalanceOf, CodeHash, ContractInfoOf, Config, TrieId, AccountCounter, DeletionQueue, Error,
+ weights::WeightInfo,
+};
+use codec::{Codec, Encode, Decode};
+use sp_std::prelude::*;
+use sp_std::{marker::PhantomData, fmt::Debug};
+use sp_io::hashing::blake2_256;
+use sp_runtime::{
+ RuntimeDebug,
+ traits::{Bounded, Saturating, Zero, Hash, Member, MaybeSerializeDeserialize},
+};
+use sp_core::crypto::UncheckedFrom;
+use frame_support::{
+ dispatch::{DispatchError, DispatchResult},
+ storage::child::{self, KillChildStorageResult, ChildInfo},
+ traits::Get,
+ weights::Weight,
+};
+
+pub type AliveContractInfo<T> =
+ RawAliveContractInfo<CodeHash<T>, BalanceOf<T>, <T as frame_system::Config>::BlockNumber>;
+pub type TombstoneContractInfo<T> = RawTombstoneContractInfo<
+ <T as frame_system::Config>::Hash,
+ <T as frame_system::Config>::Hashing,
+>;
+
+/// Information for managing an account and its sub trie abstraction.
+/// This is the required info to cache for an account
+#[derive(Encode, Decode, RuntimeDebug)]
+pub enum ContractInfo<T: Config> {
+ Alive(AliveContractInfo<T>),
+ Tombstone(TombstoneContractInfo<T>),
+}
+
+impl<T: Config> ContractInfo<T> {
+ /// If contract is alive then return some alive info
+ pub fn get_alive(self) -> Option<AliveContractInfo<T>> {
+ if let ContractInfo::Alive(alive) = self {
+ Some(alive)
+ } else {
+ None
+ }
+ }
+ /// If contract is alive then return some reference to alive info
+ pub fn as_alive(&self) -> Option<&AliveContractInfo<T>> {
+ if let ContractInfo::Alive(ref alive) = self {
+ Some(alive)
+ } else {
+ None
+ }
+ }
+
+ /// If contract is tombstone then return some tombstone info
+ pub fn get_tombstone(self) -> Option<TombstoneContractInfo<T>> {
+ if let ContractInfo::Tombstone(tombstone) = self {
+ Some(tombstone)
+ } else {
+ None
+ }
+ }
+}
+
+/// Information for managing an account and its sub trie abstraction.
+/// This is the required info to cache for an account.
+#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug)]
+pub struct RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
+ /// Unique ID for the subtree encoded as a bytes vector.
+ pub trie_id: TrieId,
+ /// The total number of bytes used by this contract.
+ ///
+ /// It is a sum of each key-value pair stored by this contract.
+ pub storage_size: u32,
+ /// The total number of key-value pairs in storage of this contract.
+ pub pair_count: u32,
+ /// The code associated with a given account.
+ pub code_hash: CodeHash,
+ /// Pay rent at most up to this value.
+ pub rent_allowance: Balance,
+ /// The amount of rent that was payed by the contract over its whole lifetime.
+ ///
+ /// A restored contract starts with a value of zero just like a new contract.
+ pub rent_payed: Balance,
+ /// Last block rent has been payed.
+ pub deduct_block: BlockNumber,
+ /// Last block child storage has been written.
+ pub last_write: Option<BlockNumber>,
+ /// This field is reserved for future evolution of format.
+ pub _reserved: Option<()>,
+}
+
+impl<CodeHash, Balance, BlockNumber> RawAliveContractInfo<CodeHash, Balance, BlockNumber> {
+ /// Associated child trie unique id is built from the hash part of the trie id.
+ pub fn child_trie_info(&self) -> ChildInfo {
+ child_trie_info(&self.trie_id[..])
+ }
+}
+
+/// Associated child trie unique id is built from the hash part of the trie id.
+fn child_trie_info(trie_id: &[u8]) -> ChildInfo {
+ ChildInfo::new_default(trie_id)
+}
+
+#[derive(Encode, Decode, PartialEq, Eq, RuntimeDebug)]
+pub struct RawTombstoneContractInfo<H, Hasher>(H, PhantomData<Hasher>);
+
+impl<H, Hasher> RawTombstoneContractInfo<H, Hasher>
+where
+ H: Member
+ + MaybeSerializeDeserialize
+ + Debug
+ + AsRef<[u8]>
+ + AsMut<[u8]>
+ + Copy
+ + Default
+ + sp_std::hash::Hash
+ + Codec,
+ Hasher: Hash<Output = H>,
+{
+ pub fn new(storage_root: &[u8], code_hash: H) -> Self {
+ let mut buf = Vec::new();
+ storage_root.using_encoded(|encoded| buf.extend_from_slice(encoded));
+ buf.extend_from_slice(code_hash.as_ref());
+ RawTombstoneContractInfo(<Hasher as Hash>::hash(&buf[..]), PhantomData)
+ }
+}
+
+impl<T: Config> From<AliveContractInfo<T>> for ContractInfo<T> {
+ fn from(alive_info: AliveContractInfo<T>) -> Self {
+ Self::Alive(alive_info)
+ }
+}
+
+/// An error that means that the account requested either doesn't exist or represents a tombstone
+/// account.
+#[cfg_attr(test, derive(PartialEq, Eq, Debug))]
+pub struct ContractAbsentError;
+
+#[derive(Encode, Decode)]
+pub struct DeletedContract {
+ pair_count: u32,
+ trie_id: TrieId,
+}
+
+pub struct Storage<T>(PhantomData<T>);
+
+impl<T> Storage<T>
+where
+ T: Config,
+ T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,
+{
+ /// Reads a storage kv pair of a contract.
+ ///
+ /// The read is performed from the `trie_id` only. The `address` is not necessary. If the contract
+ /// doesn't store under the given `key` `None` is returned.
+ pub fn read(trie_id: &TrieId, key: &StorageKey) -> Option<Vec<u8>> {
+ child::get_raw(&child_trie_info(&trie_id), &blake2_256(key))
+ }
+
+ /// Update a storage entry into a contract's kv storage.
+ ///
+ /// If the `opt_new_value` is `None` then the kv pair is removed.
+ ///
+ /// This function also updates the bookkeeping info such as: number of total non-empty pairs a
+ /// contract owns, the last block the storage was written to, etc. That's why, in contrast to
+ /// `read`, this function also requires the `account` ID.
+ ///
+ /// If the contract specified by the id `account` doesn't exist `Err` is returned.`
+ ///
+ /// # Panics
+ ///
+ /// Panics iff the `account` specified is not alive and in storage.
+ pub fn write(
+ account: &AccountIdOf<T>,
+ trie_id: &TrieId,
+ key: &StorageKey,
+ opt_new_value: Option<Vec<u8>>,
+ ) -> DispatchResult {
+ let mut new_info = match <ContractInfoOf<T>>::get(account) {
+ Some(ContractInfo::Alive(alive)) => alive,
+ None | Some(ContractInfo::Tombstone(_)) => panic!("Contract not found"),
+ };
+
+ let hashed_key = blake2_256(key);
+ let child_trie_info = &child_trie_info(&trie_id);
+
+ let opt_prev_len = child::len(&child_trie_info, &hashed_key);
+
+ // Update the total number of KV pairs and the number of empty pairs.
+ match (&opt_prev_len, &opt_new_value) {
+ (Some(_), None) => {
+ new_info.pair_count = new_info
+ .pair_count
+ .checked_sub(1)
+ .ok_or_else(|| Error::<T>::StorageExhausted)?;
+ }
+ (None, Some(_)) => {
+ new_info.pair_count = new_info
+ .pair_count
+ .checked_add(1)
+ .ok_or_else(|| Error::<T>::StorageExhausted)?;
+ }
+ (Some(_), Some(_)) => {}
+ (None, None) => {}
+ }
+
+ // Update the total storage size.
+ let prev_value_len = opt_prev_len.unwrap_or(0);
+ let new_value_len = opt_new_value
+ .as_ref()
+ .map(|new_value| new_value.len() as u32)
+ .unwrap_or(0);
+ new_info.storage_size = new_info
+ .storage_size
+ .checked_sub(prev_value_len)
+ .and_then(|val| val.checked_add(new_value_len))
+ .ok_or_else(|| Error::<T>::StorageExhausted)?;
+
+ new_info.last_write = Some(<frame_system::Pallet<T>>::block_number());
+ <ContractInfoOf<T>>::insert(&account, ContractInfo::Alive(new_info));
+
+ // Finally, perform the change on the storage.
+ match opt_new_value {
+ Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, &new_value[..]),
+ None => child::kill(&child_trie_info, &hashed_key),
+ }
+
+ Ok(())
+ }
+
+ /// Returns the rent allowance set for the contract give by the account id.
+ pub fn rent_allowance(account: &AccountIdOf<T>) -> Result<BalanceOf<T>, ContractAbsentError> {
+ <ContractInfoOf<T>>::get(account)
+ .and_then(|i| i.as_alive().map(|i| i.rent_allowance))
+ .ok_or(ContractAbsentError)
+ }
+
+ /// Set the rent allowance for the contract given by the account id.
+ ///
+ /// Returns `Err` if the contract doesn't exist or is a tombstone.
+ pub fn set_rent_allowance(
+ account: &AccountIdOf<T>,
+ rent_allowance: BalanceOf<T>,
+ ) -> Result<(), ContractAbsentError> {
+ <ContractInfoOf<T>>::mutate(account, |maybe_contract_info| match maybe_contract_info {
+ Some(ContractInfo::Alive(ref mut alive_info)) => {
+ alive_info.rent_allowance = rent_allowance;
+ Ok(())
+ }
+ _ => Err(ContractAbsentError),
+ })
+ }
+
+ /// Creates a new contract descriptor in the storage with the given code hash at the given address.
+ ///
+ /// Returns `Err` if there is already a contract (or a tombstone) exists at the given address.
+ pub fn place_contract(
+ account: &AccountIdOf<T>,
+ trie_id: TrieId,
+ ch: CodeHash<T>,
+ ) -> Result<AliveContractInfo<T>, DispatchError> {
+ <ContractInfoOf<T>>::try_mutate(account, |existing| {
+ if existing.is_some() {
+ return Err(Error::<T>::DuplicateContract.into());
+ }
+
+ let contract = AliveContractInfo::<T> {
+ code_hash: ch,
+ storage_size: 0,
+ trie_id,
+ deduct_block:
+ // We want to charge rent for the first block in advance. Therefore we
+ // treat the contract as if it was created in the last block and then
+ // charge rent for it during instantiation.
+ <frame_system::Pallet<T>>::block_number().saturating_sub(1u32.into()),
+ rent_allowance: <BalanceOf<T>>::max_value(),
+ rent_payed: <BalanceOf<T>>::zero(),
+ pair_count: 0,
+ last_write: None,
+ _reserved: None,
+ };
+
+ *existing = Some(contract.clone().into());
+
+ Ok(contract)
+ })
+ }
+
+ /// Push a contract's trie to the deletion queue for lazy removal.
+ ///
+ /// You must make sure that the contract is also removed or converted into a tombstone
+ /// when queuing the trie for deletion.
+ pub fn queue_trie_for_deletion(contract: &AliveContractInfo<T>) -> DispatchResult {
+ if <DeletionQueue<T>>::decode_len().unwrap_or(0) >= T::DeletionQueueDepth::get() as usize {
+ Err(Error::<T>::DeletionQueueFull.into())
+ } else {
+ <DeletionQueue<T>>::append(DeletedContract {
+ pair_count: contract.pair_count,
+ trie_id: contract.trie_id.clone(),
+ });
+ Ok(())
+ }
+ }
+
+ /// Calculates the weight that is necessary to remove one key from the trie and how many
+ /// of those keys can be deleted from the deletion queue given the supplied queue length
+ /// and weight limit.
+ pub fn deletion_budget(queue_len: usize, weight_limit: Weight) -> (u64, u32) {
+ let base_weight = T::WeightInfo::on_initialize();
+ let weight_per_queue_item = T::WeightInfo::on_initialize_per_queue_item(1)
+ - T::WeightInfo::on_initialize_per_queue_item(0);
+ let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1)
+ - T::WeightInfo::on_initialize_per_trie_key(0);
+ let decoding_weight = weight_per_queue_item.saturating_mul(queue_len as Weight);
+
+ // `weight_per_key` being zero makes no sense and would constitute a failure to
+ // benchmark properly. We opt for not removing any keys at all in this case.
+ let key_budget = weight_limit
+ .saturating_sub(base_weight)
+ .saturating_sub(decoding_weight)
+ .checked_div(weight_per_key)
+ .unwrap_or(0) as u32;
+
+ (weight_per_key, key_budget)
+ }
+
+ /// Delete as many items from the deletion queue possible within the supplied weight limit.
+ ///
+ /// It returns the amount of weight used for that task or `None` when no weight was used
+ /// apart from the base weight.
+ pub fn process_deletion_queue_batch(weight_limit: Weight) -> Weight {
+ let queue_len = <DeletionQueue<T>>::decode_len().unwrap_or(0);
+ if queue_len == 0 {
+ return weight_limit;
+ }
+
+ let (weight_per_key, mut remaining_key_budget) =
+ Self::deletion_budget(queue_len, weight_limit);
+
+ // We want to check whether we have enough weight to decode the queue before
+ // proceeding. Too little weight for decoding might happen during runtime upgrades
+ // which consume the whole block before the other `on_initialize` blocks are called.
+ if remaining_key_budget == 0 {
+ return weight_limit;
+ }
+
+ let mut queue = <DeletionQueue<T>>::get();
+
+ while !queue.is_empty() && remaining_key_budget > 0 {
+ // Cannot panic due to loop condition
+ let trie = &mut queue[0];
+ let pair_count = trie.pair_count;
+ let outcome =
+ child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget));
+ if pair_count > remaining_key_budget {
+ // Cannot underflow because of the if condition
+ trie.pair_count -= remaining_key_budget;
+ } else {
+ // We do not care to preserve order. The contract is deleted already and
+ // noone waits for the trie to be deleted.
+ let removed = queue.swap_remove(0);
+ match outcome {
+ // This should not happen as our budget was large enough to remove all keys.
+ KillChildStorageResult::SomeRemaining(_) => {
+ log::error!(
+ target: "runtime::contracts",
+ "After deletion keys are remaining in this child trie: {:?}",
+ removed.trie_id,
+ );
+ }
+ KillChildStorageResult::AllRemoved(_) => (),
+ }
+ }
+ remaining_key_budget =
+ remaining_key_budget.saturating_sub(remaining_key_budget.min(pair_count));
+ }
+
+ <DeletionQueue<T>>::put(queue);
+ weight_limit.saturating_sub(weight_per_key.saturating_mul(remaining_key_budget as Weight))
+ }
+
+ /// This generator uses inner counter for account id and applies the hash over `AccountId +
+ /// accountid_counter`.
+ pub fn generate_trie_id(account_id: &AccountIdOf<T>) -> TrieId {
+ // Note that skipping a value due to error is not an issue here.
+ // We only need uniqueness, not sequence.
+ let new_seed = <AccountCounter<T>>::mutate(|v| {
+ *v = v.wrapping_add(1);
+ *v
+ });
+
+ let buf: Vec<_> = account_id
+ .as_ref()
+ .iter()
+ .chain(&new_seed.to_le_bytes())
+ .cloned()
+ .collect();
+ T::Hashing::hash(&buf).as_ref().into()
+ }
+
+ /// Returns the code hash of the contract specified by `account` ID.
+ #[cfg(test)]
+ pub fn code_hash(account: &AccountIdOf<T>) -> Result<CodeHash<T>, ContractAbsentError> {
+ <ContractInfoOf<T>>::get(account)
+ .and_then(|i| i.as_alive().map(|i| i.code_hash))
+ .ok_or(ContractAbsentError)
+ }
+
+ /// Fill up the queue in order to exercise the limits during testing.
+ #[cfg(test)]
+ pub fn fill_queue_with_dummies() {
+ let queue: Vec<_> = (0..T::DeletionQueueDepth::get())
+ .map(|_| DeletedContract {
+ pair_count: 0,
+ trie_id: vec![],
+ })
+ .collect();
+ <DeletionQueue<T>>::put(queue);
+ }
+}
pallets/contracts/src/tests.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/tests.rs
@@ -0,0 +1,2971 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2018-2021 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.
+
+use crate::{
+ BalanceOf, ContractInfo, ContractInfoOf, Pallet, Config, Schedule, Error,
+ storage::Storage,
+ chain_extension::{
+ Result as ExtensionResult, Environment, ChainExtension, Ext, SysConfig, RetVal,
+ UncheckedFrom, InitState, ReturnFlags,
+ },
+ exec::{AccountIdOf, Executable},
+ wasm::PrefabWasmModule,
+ weights::WeightInfo,
+ wasm::ReturnCode as RuntimeReturnCode,
+ storage::RawAliveContractInfo,
+};
+use assert_matches::assert_matches;
+use codec::Encode;
+use sp_core::Bytes;
+use sp_runtime::{
+ traits::{BlakeTwo256, Hash, IdentityLookup, Convert},
+ testing::{Header, H256},
+ AccountId32, Perbill,
+};
+use sp_io::hashing::blake2_256;
+use frame_support::{
+ assert_ok, assert_err, assert_err_ignore_postinfo, parameter_types, assert_storage_noop,
+ traits::{Currency, ReservableCurrency, OnInitialize, GenesisBuild},
+ weights::{Weight, PostDispatchInfo, DispatchClass, constants::WEIGHT_PER_SECOND},
+ dispatch::DispatchErrorWithPostInfo,
+ storage::child,
+};
+use frame_system::{self as system, EventRecord, Phase};
+use pretty_assertions::assert_eq;
+
+use crate as pallet_contracts;
+
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
+type Block = frame_system::mocking::MockBlock<Test>;
+
+frame_support::construct_runtime!(
+ pub enum Test where
+ Block = Block,
+ NodeBlock = Block,
+ UncheckedExtrinsic = UncheckedExtrinsic,
+ {
+ System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
+ Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
+ Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
+ Randomness: pallet_randomness_collective_flip::{Pallet, Call, Storage},
+ Contracts: pallet_contracts::{Pallet, Call, Config<T>, Storage, Event<T>},
+ }
+);
+
+#[macro_use]
+pub mod test_utils {
+ use super::{Test, Balances};
+ use crate::{
+ ContractInfoOf, CodeHash,
+ storage::Storage,
+ exec::{StorageKey, AccountIdOf},
+ Pallet as Contracts,
+ };
+ use frame_support::traits::Currency;
+
+ pub fn set_storage(addr: &AccountIdOf<Test>, key: &StorageKey, value: Option<Vec<u8>>) {
+ let contract_info = <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ Storage::<Test>::write(addr, &contract_info.trie_id, key, value).unwrap();
+ }
+ pub fn get_storage(addr: &AccountIdOf<Test>, key: &StorageKey) -> Option<Vec<u8>> {
+ let contract_info = <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ Storage::<Test>::read(&contract_info.trie_id, key)
+ }
+ pub fn place_contract(address: &AccountIdOf<Test>, code_hash: CodeHash<Test>) {
+ let trie_id = Storage::<Test>::generate_trie_id(address);
+ set_balance(address, Contracts::<Test>::subsistence_threshold() * 10);
+ Storage::<Test>::place_contract(&address, trie_id, code_hash).unwrap();
+ }
+ pub fn set_balance(who: &AccountIdOf<Test>, amount: u64) {
+ let imbalance = Balances::deposit_creating(who, amount);
+ drop(imbalance);
+ }
+ pub fn get_balance(who: &AccountIdOf<Test>) -> u64 {
+ Balances::free_balance(who)
+ }
+ macro_rules! assert_return_code {
+ ( $x:expr , $y:expr $(,)? ) => {{
+ use sp_std::convert::TryInto;
+ assert_eq!(
+ u32::from_le_bytes($x.data[..].try_into().unwrap()),
+ $y as u32
+ );
+ }};
+ }
+ macro_rules! assert_refcount {
+ ( $code_hash:expr , $should:expr $(,)? ) => {{
+ let is = crate::CodeStorage::<Test>::get($code_hash)
+ .map(|m| m.refcount())
+ .unwrap_or(0);
+ assert_eq!(is, $should);
+ }};
+ }
+}
+
+thread_local! {
+ static TEST_EXTENSION: sp_std::cell::RefCell<TestExtension> = Default::default();
+}
+
+pub struct TestExtension {
+ enabled: bool,
+ last_seen_buffer: Vec<u8>,
+ last_seen_inputs: (u32, u32, u32, u32),
+}
+
+impl TestExtension {
+ fn disable() {
+ TEST_EXTENSION.with(|e| e.borrow_mut().enabled = false)
+ }
+
+ fn last_seen_buffer() -> Vec<u8> {
+ TEST_EXTENSION.with(|e| e.borrow().last_seen_buffer.clone())
+ }
+
+ fn last_seen_inputs() -> (u32, u32, u32, u32) {
+ TEST_EXTENSION.with(|e| e.borrow().last_seen_inputs.clone())
+ }
+}
+
+impl Default for TestExtension {
+ fn default() -> Self {
+ Self {
+ enabled: true,
+ last_seen_buffer: vec![],
+ last_seen_inputs: (0, 0, 0, 0),
+ }
+ }
+}
+
+impl ChainExtension<Test> for TestExtension {
+ fn call<E>(func_id: u32, env: Environment<E, InitState>) -> ExtensionResult<RetVal>
+ where
+ E: Ext<T = Test>,
+ <E::T as SysConfig>::AccountId: UncheckedFrom<<E::T as SysConfig>::Hash> + AsRef<[u8]>,
+ {
+ match func_id {
+ 0 => {
+ let mut env = env.buf_in_buf_out();
+ let input = env.read(2)?;
+ env.write(&input, false, None)?;
+ TEST_EXTENSION.with(|e| e.borrow_mut().last_seen_buffer = input);
+ Ok(RetVal::Converging(func_id))
+ }
+ 1 => {
+ let env = env.only_in();
+ TEST_EXTENSION.with(|e| {
+ e.borrow_mut().last_seen_inputs =
+ (env.val0(), env.val1(), env.val2(), env.val3())
+ });
+ Ok(RetVal::Converging(func_id))
+ }
+ 2 => {
+ let mut env = env.buf_in_buf_out();
+ let weight = env.read(2)?[1].into();
+ env.charge_weight(weight)?;
+ Ok(RetVal::Converging(func_id))
+ }
+ 3 => Ok(RetVal::Diverging {
+ flags: ReturnFlags::REVERT,
+ data: vec![42, 99],
+ }),
+ _ => {
+ panic!(
+ "Passed unknown func_id to test chain extension: {}",
+ func_id
+ );
+ }
+ }
+ }
+
+ fn enabled() -> bool {
+ TEST_EXTENSION.with(|e| e.borrow().enabled)
+ }
+}
+
+parameter_types! {
+ pub const BlockHashCount: u64 = 250;
+ pub BlockWeights: frame_system::limits::BlockWeights =
+ frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND);
+ pub static ExistentialDeposit: u64 = 0;
+}
+impl frame_system::Config for Test {
+ type BaseCallFilter = ();
+ type BlockWeights = BlockWeights;
+ type BlockLength = ();
+ type DbWeight = ();
+ type Origin = Origin;
+ type Index = u64;
+ type BlockNumber = u64;
+ type Hash = H256;
+ type Call = Call;
+ type Hashing = BlakeTwo256;
+ type AccountId = AccountId32;
+ type Lookup = IdentityLookup<Self::AccountId>;
+ type Header = Header;
+ type Event = Event;
+ type BlockHashCount = BlockHashCount;
+ type Version = ();
+ type PalletInfo = PalletInfo;
+ type AccountData = pallet_balances::AccountData<u64>;
+ type OnNewAccount = ();
+ type OnKilledAccount = ();
+ type SystemWeightInfo = ();
+ type SS58Prefix = ();
+ type OnSetCode = ();
+}
+impl pallet_balances::Config for Test {
+ type MaxLocks = ();
+ type Balance = u64;
+ type Event = Event;
+ type DustRemoval = ();
+ type ExistentialDeposit = ExistentialDeposit;
+ type AccountStore = System;
+ type WeightInfo = ();
+}
+parameter_types! {
+ pub const MinimumPeriod: u64 = 1;
+}
+impl pallet_timestamp::Config for Test {
+ type Moment = u64;
+ type OnTimestampSet = ();
+ type MinimumPeriod = MinimumPeriod;
+ type WeightInfo = ();
+}
+parameter_types! {
+ pub const SignedClaimHandicap: u64 = 2;
+ pub const TombstoneDeposit: u64 = 16;
+ pub const DepositPerContract: u64 = 8 * DepositPerStorageByte::get();
+ pub const DepositPerStorageByte: u64 = 10_000;
+ pub const DepositPerStorageItem: u64 = 10_000;
+ pub RentFraction: Perbill = Perbill::from_rational(4u32, 10_000u32);
+ pub const SurchargeReward: u64 = 500_000;
+ pub const MaxDepth: u32 = 100;
+ pub const MaxValueSize: u32 = 16_384;
+ pub const DeletionQueueDepth: u32 = 1024;
+ pub const DeletionWeightLimit: Weight = 500_000_000_000;
+ pub const MaxCodeSize: u32 = 2 * 1024;
+}
+
+parameter_types! {
+ pub const TransactionByteFee: u64 = 0;
+}
+
+impl Convert<Weight, BalanceOf<Self>> for Test {
+ fn convert(w: Weight) -> BalanceOf<Self> {
+ w
+ }
+}
+
+impl Config for Test {
+ type Time = Timestamp;
+ type Randomness = Randomness;
+ type Currency = Balances;
+ type Event = Event;
+ type RentPayment = ();
+ type SignedClaimHandicap = SignedClaimHandicap;
+ type TombstoneDeposit = TombstoneDeposit;
+ type DepositPerContract = DepositPerContract;
+ type DepositPerStorageByte = DepositPerStorageByte;
+ type DepositPerStorageItem = DepositPerStorageItem;
+ type RentFraction = RentFraction;
+ type SurchargeReward = SurchargeReward;
+ type MaxDepth = MaxDepth;
+ type MaxValueSize = MaxValueSize;
+ type WeightPrice = Self;
+ type WeightInfo = ();
+ type ChainExtension = TestExtension;
+ type DeletionQueueDepth = DeletionQueueDepth;
+ type DeletionWeightLimit = DeletionWeightLimit;
+ type MaxCodeSize = MaxCodeSize;
+}
+
+pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]);
+pub const BOB: AccountId32 = AccountId32::new([2u8; 32]);
+pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]);
+pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]);
+
+const GAS_LIMIT: Weight = 10_000_000_000;
+
+pub struct ExtBuilder {
+ existential_deposit: u64,
+}
+impl Default for ExtBuilder {
+ fn default() -> Self {
+ Self {
+ existential_deposit: 1,
+ }
+ }
+}
+impl ExtBuilder {
+ pub fn existential_deposit(mut self, existential_deposit: u64) -> Self {
+ self.existential_deposit = existential_deposit;
+ self
+ }
+ pub fn set_associated_consts(&self) {
+ EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
+ }
+ pub fn build(self) -> sp_io::TestExternalities {
+ self.set_associated_consts();
+ let mut t = frame_system::GenesisConfig::default()
+ .build_storage::<Test>()
+ .unwrap();
+ pallet_balances::GenesisConfig::<Test> { balances: vec![] }
+ .assimilate_storage(&mut t)
+ .unwrap();
+ pallet_contracts::GenesisConfig {
+ current_schedule: Schedule::<Test> {
+ enable_println: true,
+ ..Default::default()
+ },
+ }
+ .assimilate_storage(&mut t)
+ .unwrap();
+ let mut ext = sp_io::TestExternalities::new(t);
+ ext.execute_with(|| System::set_block_number(1));
+ ext
+ }
+}
+
+/// Load a given wasm module represented by a .wat file and returns a wasm binary contents along
+/// with it's hash.
+///
+/// The fixture files are located under the `fixtures/` directory.
+fn compile_module<T>(fixture_name: &str) -> wat::Result<(Vec<u8>, <T::Hashing as Hash>::Output)>
+where
+ T: frame_system::Config,
+{
+ let fixture_path = ["fixtures/", fixture_name, ".wat"].concat();
+ let wasm_binary = wat::parse_file(fixture_path)?;
+ let code_hash = T::Hashing::hash(&wasm_binary);
+ Ok((wasm_binary, code_hash))
+}
+
+// Perform a call to a plain account.
+// The actual transfer fails because we can only call contracts.
+// Then we check that at least the base costs where charged (no runtime gas costs.)
+#[test]
+fn calling_plain_account_fails() {
+ ExtBuilder::default().build().execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 100_000_000);
+ let base_cost = <<Test as Config>::WeightInfo as WeightInfo>::call(0);
+
+ assert_eq!(
+ Contracts::call(Origin::signed(ALICE), BOB, 0, GAS_LIMIT, Vec::new()),
+ Err(DispatchErrorWithPostInfo {
+ error: Error::<Test>::NotCallable.into(),
+ post_info: PostDispatchInfo {
+ actual_weight: Some(base_cost),
+ pays_fee: Default::default(),
+ },
+ })
+ );
+ });
+}
+
+#[test]
+fn account_removal_does_not_remove_storage() {
+ use self::test_utils::{set_storage, get_storage};
+
+ ExtBuilder::default()
+ .existential_deposit(100)
+ .build()
+ .execute_with(|| {
+ let trie_id1 = Storage::<Test>::generate_trie_id(&ALICE);
+ let trie_id2 = Storage::<Test>::generate_trie_id(&BOB);
+ let key1 = &[1; 32];
+ let key2 = &[2; 32];
+
+ // Set up two accounts with free balance above the existential threshold.
+ {
+ let alice_contract_info = ContractInfo::Alive(RawAliveContractInfo {
+ trie_id: trie_id1.clone(),
+ storage_size: 0,
+ pair_count: 0,
+ deduct_block: System::block_number(),
+ code_hash: H256::repeat_byte(1),
+ rent_allowance: 40,
+ rent_payed: 0,
+ last_write: None,
+ _reserved: None,
+ });
+ let _ = Balances::deposit_creating(&ALICE, 110);
+ ContractInfoOf::<Test>::insert(ALICE, &alice_contract_info);
+ set_storage(&ALICE, &key1, Some(b"1".to_vec()));
+ set_storage(&ALICE, &key2, Some(b"2".to_vec()));
+
+ let bob_contract_info = ContractInfo::Alive(RawAliveContractInfo {
+ trie_id: trie_id2.clone(),
+ storage_size: 0,
+ pair_count: 0,
+ deduct_block: System::block_number(),
+ code_hash: H256::repeat_byte(2),
+ rent_allowance: 40,
+ rent_payed: 0,
+ last_write: None,
+ _reserved: None,
+ });
+ let _ = Balances::deposit_creating(&BOB, 110);
+ ContractInfoOf::<Test>::insert(BOB, &bob_contract_info);
+ set_storage(&BOB, &key1, Some(b"3".to_vec()));
+ set_storage(&BOB, &key2, Some(b"4".to_vec()));
+ }
+
+ // Transfer funds from ALICE account of such amount that after this transfer
+ // the balance of the ALICE account will be below the existential threshold.
+ //
+ // This does not remove the contract storage as we are not notified about a
+ // account removal. This cannot happen in reality because a contract can only
+ // remove itself by `seal_terminate`. There is no external event that can remove
+ // the account appart from that.
+ assert_ok!(Balances::transfer(Origin::signed(ALICE), BOB, 20));
+
+ // Verify that no entries are removed.
+ {
+ assert_eq!(get_storage(&ALICE, key1), Some(b"1".to_vec()));
+ assert_eq!(get_storage(&ALICE, key2), Some(b"2".to_vec()));
+
+ assert_eq!(get_storage(&BOB, key1), Some(b"3".to_vec()));
+ assert_eq!(get_storage(&BOB, key2), Some(b"4".to_vec()));
+ }
+ });
+}
+
+#[test]
+fn instantiate_and_call_and_deposit_event() {
+ let (wasm, code_hash) = compile_module::<Test>("return_from_start_fn").unwrap();
+
+ ExtBuilder::default()
+ .existential_deposit(100)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+
+ // Check at the end to get hash on error easily
+ let creation = Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ );
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ assert_eq!(
+ System::events(),
+ vec![
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(frame_system::Event::NewAccount(ALICE.clone())),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Endowed(
+ ALICE, 1_000_000
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(frame_system::Event::NewAccount(addr.clone())),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Endowed(
+ addr.clone(),
+ subsistence * 100
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Transfer(
+ ALICE,
+ addr.clone(),
+ subsistence * 100
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::CodeStored(code_hash.into())),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::ContractEmitted(
+ addr.clone(),
+ vec![1, 2, 3, 4]
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::Instantiated(
+ ALICE,
+ addr.clone()
+ )),
+ topics: vec![],
+ },
+ ]
+ );
+
+ assert_ok!(creation);
+ assert!(ContractInfoOf::<Test>::contains_key(&addr));
+ });
+}
+
+#[test]
+fn deposit_event_max_value_limit() {
+ let (wasm, code_hash) = compile_module::<Test>("event_size").unwrap();
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Call contract with allowed storage value.
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT * 2, // we are copying a huge buffer,
+ <Test as Config>::MaxValueSize::get().encode(),
+ ));
+
+ // Call contract with too large a storage value.
+ assert_err_ignore_postinfo!(
+ Contracts::call(
+ Origin::signed(ALICE),
+ addr,
+ 0,
+ GAS_LIMIT,
+ (<Test as Config>::MaxValueSize::get() + 1).encode(),
+ ),
+ Error::<Test>::ValueTooLarge,
+ );
+ });
+}
+
+#[test]
+fn run_out_of_gas() {
+ let (wasm, code_hash) = compile_module::<Test>("run_out_of_gas").unwrap();
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100 * subsistence,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Call the contract with a fixed gas limit. It must run out of gas because it just
+ // loops forever.
+ assert_err_ignore_postinfo!(
+ Contracts::call(
+ Origin::signed(ALICE),
+ addr, // newly created account
+ 0,
+ 67_500_000,
+ vec![],
+ ),
+ Error::<Test>::OutOfGas,
+ );
+ });
+}
+
+/// Input data for each call in set_rent code
+mod call {
+ use super::{AccountIdOf, Test};
+ pub fn set_storage_4_byte() -> Vec<u8> {
+ 0u32.to_le_bytes().to_vec()
+ }
+ pub fn remove_storage_4_byte() -> Vec<u8> {
+ 1u32.to_le_bytes().to_vec()
+ }
+ #[allow(dead_code)]
+ pub fn transfer(to: &AccountIdOf<Test>) -> Vec<u8> {
+ 2u32.to_le_bytes()
+ .iter()
+ .chain(AsRef::<[u8]>::as_ref(to))
+ .cloned()
+ .collect()
+ }
+ pub fn null() -> Vec<u8> {
+ 3u32.to_le_bytes().to_vec()
+ }
+}
+
+#[test]
+fn storage_size() {
+ let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
+
+ // Storage size
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ wasm,
+ // rent_allowance
+ <Test as pallet_balances::Config>::Balance::from(10_000u32).encode(),
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ let bob_contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert_eq!(bob_contract.storage_size, 4);
+ assert_eq!(bob_contract.pair_count, 1,);
+
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::set_storage_4_byte()
+ ));
+ let bob_contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert_eq!(bob_contract.storage_size, 4 + 4);
+ assert_eq!(bob_contract.pair_count, 2,);
+
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::remove_storage_4_byte()
+ ));
+ let bob_contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert_eq!(bob_contract.storage_size, 4);
+ assert_eq!(bob_contract.pair_count, 1,);
+ });
+}
+
+#[test]
+fn empty_kv_pairs() {
+ let (wasm, code_hash) = compile_module::<Test>("set_empty_storage").unwrap();
+
+ ExtBuilder::default().build().execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ let bob_contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+
+ assert_eq!(bob_contract.storage_size, 0,);
+ assert_eq!(bob_contract.pair_count, 1,);
+ });
+}
+
+fn initialize_block(number: u64) {
+ System::initialize(
+ &number,
+ &[0u8; 32].into(),
+ &Default::default(),
+ Default::default(),
+ );
+}
+
+#[test]
+fn deduct_blocks() {
+ let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
+ let endowment: BalanceOf<Test> = 100_000;
+ let allowance: BalanceOf<Test> = 70_000;
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ endowment,
+ GAS_LIMIT,
+ wasm,
+ allowance.encode(),
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ let contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ let code_len: BalanceOf<Test> =
+ PrefabWasmModule::<Test>::from_storage_noinstr(contract.code_hash)
+ .unwrap()
+ .occupied_storage()
+ .into();
+
+ // The instantiation deducted the rent for one block immediately
+ let rent0 = <Test as Config>::RentFraction::get()
+ // (base_deposit(8) + bytes in storage(4) + size of code) * byte_price
+ // + 1 storage item (10_000) - free_balance
+ .mul_ceil((8 + 4 + code_len) * 10_000 + 10_000 - endowment)
+ // blocks to rent
+ * 1;
+ assert!(rent0 > 0);
+ assert_eq!(contract.rent_allowance, allowance - rent0);
+ assert_eq!(contract.deduct_block, 1);
+ assert_eq!(Balances::free_balance(&addr), endowment - rent0);
+
+ // Advance 4 blocks
+ initialize_block(5);
+
+ // Trigger rent through call
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::null()
+ ));
+
+ // Check result
+ let rent = <Test as Config>::RentFraction::get()
+ .mul_ceil((8 + 4 + code_len) * 10_000 + 10_000 - (endowment - rent0))
+ * 4;
+ let contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert_eq!(contract.rent_allowance, allowance - rent0 - rent);
+ assert_eq!(contract.deduct_block, 5);
+ assert_eq!(Balances::free_balance(&addr), endowment - rent0 - rent);
+
+ // Advance 2 blocks more
+ initialize_block(7);
+
+ // Trigger rent through call
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::null()
+ ));
+
+ // Check result
+ let rent_2 = <Test as Config>::RentFraction::get()
+ .mul_ceil((8 + 4 + code_len) * 10_000 + 10_000 - (endowment - rent0 - rent))
+ * 2;
+ let contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert_eq!(contract.rent_allowance, allowance - rent0 - rent - rent_2);
+ assert_eq!(contract.deduct_block, 7);
+ assert_eq!(
+ Balances::free_balance(&addr),
+ endowment - rent0 - rent - rent_2
+ );
+
+ // Second call on same block should have no effect on rent
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::null()
+ ));
+ let contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert_eq!(contract.rent_allowance, allowance - rent0 - rent - rent_2);
+ assert_eq!(contract.deduct_block, 7);
+ assert_eq!(
+ Balances::free_balance(&addr),
+ endowment - rent0 - rent - rent_2
+ )
+ });
+}
+
+#[test]
+fn inherent_claim_surcharge_contract_removals() {
+ removals(|addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok());
+}
+
+#[test]
+fn signed_claim_surcharge_contract_removals() {
+ removals(|addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok());
+}
+
+#[test]
+fn claim_surcharge_malus() {
+ // Test surcharge malus for inherent
+ claim_surcharge(
+ 8,
+ |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(),
+ true,
+ );
+ claim_surcharge(
+ 7,
+ |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(),
+ true,
+ );
+ claim_surcharge(
+ 6,
+ |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(),
+ true,
+ );
+ claim_surcharge(
+ 5,
+ |addr| Contracts::claim_surcharge(Origin::none(), addr, Some(ALICE)).is_ok(),
+ false,
+ );
+
+ // Test surcharge malus for signed
+ claim_surcharge(
+ 8,
+ |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(),
+ true,
+ );
+ claim_surcharge(
+ 7,
+ |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(),
+ false,
+ );
+ claim_surcharge(
+ 6,
+ |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(),
+ false,
+ );
+ claim_surcharge(
+ 5,
+ |addr| Contracts::claim_surcharge(Origin::signed(ALICE), addr, None).is_ok(),
+ false,
+ );
+}
+
+/// Claim surcharge with the given trigger_call at the given blocks.
+/// If `removes` is true then assert that the contract is a tombstone.
+fn claim_surcharge(blocks: u64, trigger_call: impl Fn(AccountIdOf<Test>) -> bool, removes: bool) {
+ let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ wasm,
+ <Test as pallet_balances::Config>::Balance::from(30_000u32).encode(), // rent allowance
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Advance blocks
+ initialize_block(blocks);
+
+ // Trigger rent through call
+ assert_eq!(trigger_call(addr.clone()), removes);
+
+ if removes {
+ assert!(ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_tombstone()
+ .is_some());
+ } else {
+ assert!(ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .is_some());
+ }
+ });
+}
+
+/// Test for all kind of removals for the given trigger:
+/// * if balance is reached and balance > subsistence threshold
+/// * if allowance is exceeded
+/// * if balance is reached and balance < subsistence threshold
+/// * this case cannot be triggered by a contract: we check whether a tombstone is left
+fn removals(trigger_call: impl Fn(AccountIdOf<Test>) -> bool) {
+ let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
+
+ // Balance reached and superior to subsistence threshold
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 70_000,
+ GAS_LIMIT,
+ wasm.clone(),
+ <Test as pallet_balances::Config>::Balance::from(100_000u32).encode(), // rent allowance
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ let allowance = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap()
+ .rent_allowance;
+ let balance = Balances::free_balance(&addr);
+
+ let subsistence_threshold = Pallet::<Test>::subsistence_threshold();
+
+ // Trigger rent must have no effect
+ assert!(!trigger_call(addr.clone()));
+ assert_eq!(
+ ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap()
+ .rent_allowance,
+ allowance,
+ );
+ assert_eq!(Balances::free_balance(&addr), balance);
+
+ // Advance blocks
+ initialize_block(27);
+
+ // Trigger rent through call (should remove the contract)
+ assert!(trigger_call(addr.clone()));
+ assert!(ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_tombstone()
+ .is_some());
+ assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
+
+ // Advance blocks
+ initialize_block(30);
+
+ // Trigger rent must have no effect
+ assert!(!trigger_call(addr.clone()));
+ assert!(ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_tombstone()
+ .is_some());
+ assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
+ });
+
+ // Allowance exceeded
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ wasm.clone(),
+ <Test as pallet_balances::Config>::Balance::from(70_000u32).encode(), // rent allowance
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ let allowance = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap()
+ .rent_allowance;
+ let balance = Balances::free_balance(&addr);
+
+ // Trigger rent must have no effect
+ assert!(!trigger_call(addr.clone()));
+ assert_eq!(
+ ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap()
+ .rent_allowance,
+ allowance,
+ );
+ assert_eq!(Balances::free_balance(&addr), balance);
+
+ // Advance blocks
+ initialize_block(27);
+
+ // Trigger rent through call
+ assert!(trigger_call(addr.clone()));
+ assert!(ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_tombstone()
+ .is_some());
+ // Balance should be initial balance - initial rent_allowance
+ assert_eq!(Balances::free_balance(&addr), 30_000);
+
+ // Advance blocks
+ initialize_block(20);
+
+ // Trigger rent must have no effect
+ assert!(!trigger_call(addr.clone()));
+ assert!(ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_tombstone()
+ .is_some());
+ assert_eq!(Balances::free_balance(&addr), 30_000);
+ });
+
+ // Balance reached and inferior to subsistence threshold
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let subsistence_threshold = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, subsistence_threshold * 1000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence_threshold * 100,
+ GAS_LIMIT,
+ wasm,
+ (subsistence_threshold * 100).encode(), // rent allowance
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ let allowance = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap()
+ .rent_allowance;
+ let balance = Balances::free_balance(&addr);
+
+ // Trigger rent must have no effect
+ assert!(!trigger_call(addr.clone()));
+ assert_eq!(
+ ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap()
+ .rent_allowance,
+ allowance,
+ );
+ assert_eq!(Balances::free_balance(&addr), balance,);
+
+ // Make contract have exactly the subsistence threshold
+ Balances::make_free_balance_be(&addr, subsistence_threshold);
+ assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
+
+ // Advance blocks (should remove as balance is exactly subsistence)
+ initialize_block(10);
+
+ // Trigger rent through call
+ assert!(trigger_call(addr.clone()));
+ assert_matches!(
+ ContractInfoOf::<Test>::get(&addr),
+ Some(ContractInfo::Tombstone(_))
+ );
+ assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
+
+ // Advance blocks
+ initialize_block(20);
+
+ // Trigger rent must have no effect
+ assert!(!trigger_call(addr.clone()));
+ assert_matches!(
+ ContractInfoOf::<Test>::get(&addr),
+ Some(ContractInfo::Tombstone(_))
+ );
+ assert_eq!(Balances::free_balance(&addr), subsistence_threshold);
+ });
+}
+
+#[test]
+fn call_removed_contract() {
+ let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
+
+ // Balance reached and superior to subsistence threshold
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ wasm,
+ // rent allowance
+ <Test as pallet_balances::Config>::Balance::from(10_000u32).encode(),
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Calling contract should succeed.
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::null()
+ ));
+
+ // Advance blocks
+ initialize_block(27);
+
+ // Calling contract should deny access because rent cannot be paid.
+ assert_err_ignore_postinfo!(
+ Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::null()
+ ),
+ Error::<Test>::NotCallable
+ );
+ // No event is generated because the contract is not actually removed.
+ assert_eq!(System::events(), vec![]);
+
+ // Subsequent contract calls should also fail.
+ assert_err_ignore_postinfo!(
+ Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::null()
+ ),
+ Error::<Test>::NotCallable
+ );
+
+ // A snitch can now remove the contract
+ assert_ok!(Contracts::claim_surcharge(
+ Origin::none(),
+ addr.clone(),
+ Some(ALICE)
+ ));
+ assert!(ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_tombstone()
+ .is_some());
+ })
+}
+
+#[test]
+fn default_rent_allowance_on_instantiate() {
+ let (wasm, code_hash) = compile_module::<Test>("check_default_rent_allowance").unwrap();
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ let contract = ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ let code_len: BalanceOf<Test> =
+ PrefabWasmModule::<Test>::from_storage_noinstr(contract.code_hash)
+ .unwrap()
+ .occupied_storage()
+ .into();
+
+ // The instantiation deducted the rent for one block immediately
+ let first_rent = <Test as Config>::RentFraction::get()
+ // (base_deposit(8) + code_len) * byte_price - free_balance
+ .mul_ceil((8 + code_len) * 10_000 - 30_000)
+ // blocks to rent
+ * 1;
+ assert_eq!(
+ contract.rent_allowance,
+ <BalanceOf<Test>>::max_value() - first_rent
+ );
+
+ // Advance blocks
+ initialize_block(5);
+
+ // Trigger rent through call
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ call::null()
+ ));
+
+ // Check contract is still alive
+ let contract = ContractInfoOf::<Test>::get(&addr).unwrap().get_alive();
+ assert!(contract.is_some())
+ });
+}
+
+#[test]
+fn restorations_dirty_storage_and_different_storage() {
+ restoration(true, true, false);
+}
+
+#[test]
+fn restorations_dirty_storage() {
+ restoration(false, true, false);
+}
+
+#[test]
+fn restoration_different_storage() {
+ restoration(true, false, false);
+}
+
+#[test]
+fn restoration_code_evicted() {
+ restoration(false, false, true);
+}
+
+#[test]
+fn restoration_success() {
+ restoration(false, false, false);
+}
+
+fn restoration(
+ test_different_storage: bool,
+ test_restore_to_with_dirty_storage: bool,
+ test_code_evicted: bool,
+) {
+ let (set_rent_wasm, set_rent_code_hash) = compile_module::<Test>("set_rent").unwrap();
+ let (restoration_wasm, restoration_code_hash) = compile_module::<Test>("restoration").unwrap();
+ let allowance: <Test as pallet_balances::Config>::Balance = 10_000;
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+
+ // Create an account with address `BOB` with code `CODE_SET_RENT`.
+ // The input parameter sets the rent allowance to 0.
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ set_rent_wasm.clone(),
+ allowance.encode(),
+ vec![],
+ ));
+ let addr_bob = Contracts::contract_address(&ALICE, &set_rent_code_hash, &[]);
+
+ let mut events = vec![
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(frame_system::Event::NewAccount(ALICE)),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Endowed(
+ ALICE, 1_000_000,
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(frame_system::Event::NewAccount(addr_bob.clone())),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Endowed(
+ addr_bob.clone(),
+ 30_000,
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Transfer(
+ ALICE,
+ addr_bob.clone(),
+ 30_000,
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::CodeStored(
+ set_rent_code_hash.into(),
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::Instantiated(
+ ALICE,
+ addr_bob.clone(),
+ )),
+ topics: vec![],
+ },
+ ];
+
+ // Create another contract from the same code in order to increment the codes
+ // refcounter so that it stays on chain.
+ if !test_code_evicted {
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 20_000,
+ GAS_LIMIT,
+ set_rent_wasm,
+ allowance.encode(),
+ vec![1],
+ ));
+ assert_refcount!(set_rent_code_hash, 2);
+ let addr_dummy = Contracts::contract_address(&ALICE, &set_rent_code_hash, &[1]);
+ events.extend(
+ [
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(frame_system::Event::NewAccount(
+ addr_dummy.clone(),
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Endowed(
+ addr_dummy.clone(),
+ 20_000,
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Transfer(
+ ALICE,
+ addr_dummy.clone(),
+ 20_000,
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::Instantiated(
+ ALICE,
+ addr_dummy.clone(),
+ )),
+ topics: vec![],
+ },
+ ]
+ .iter()
+ .cloned(),
+ );
+ }
+
+ assert_eq!(System::events(), events);
+
+ // Check if `BOB` was created successfully and that the rent allowance is below what
+ // we specified as the first rent was already collected.
+ let bob_contract = ContractInfoOf::<Test>::get(&addr_bob)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert!(bob_contract.rent_allowance < allowance);
+
+ if test_different_storage {
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr_bob.clone(),
+ 0,
+ GAS_LIMIT,
+ call::set_storage_4_byte()
+ ));
+ }
+
+ // Advance blocks in order to make the contract run out of money for rent.
+ initialize_block(27);
+
+ // Call `BOB`, which makes it pay rent. Since the rent allowance is set to 20_000
+ // we expect that it is no longer callable but keeps existing until someone
+ // calls `claim_surcharge`.
+ assert_err_ignore_postinfo!(
+ Contracts::call(
+ Origin::signed(ALICE),
+ addr_bob.clone(),
+ 0,
+ GAS_LIMIT,
+ call::null()
+ ),
+ Error::<Test>::NotCallable
+ );
+ assert!(System::events().is_empty());
+ assert!(ContractInfoOf::<Test>::get(&addr_bob)
+ .unwrap()
+ .get_alive()
+ .is_some());
+ assert_ok!(Contracts::claim_surcharge(
+ Origin::none(),
+ addr_bob.clone(),
+ Some(ALICE)
+ ));
+ assert!(ContractInfoOf::<Test>::get(&addr_bob)
+ .unwrap()
+ .get_tombstone()
+ .is_some());
+ if test_code_evicted {
+ assert_refcount!(set_rent_code_hash, 0);
+ } else {
+ assert_refcount!(set_rent_code_hash, 1);
+ }
+
+ // Create another account with the address `DJANGO` with `CODE_RESTORATION`.
+ //
+ // Note that we can't use `ALICE` for creating `DJANGO` so we create yet another
+ // account `CHARLIE` and create `DJANGO` with it.
+ let _ = Balances::deposit_creating(&CHARLIE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(CHARLIE),
+ 30_000,
+ GAS_LIMIT,
+ restoration_wasm,
+ vec![],
+ vec![],
+ ));
+ let addr_django = Contracts::contract_address(&CHARLIE, &restoration_code_hash, &[]);
+
+ // Before performing a call to `DJANGO` save its original trie id.
+ let django_trie_id = ContractInfoOf::<Test>::get(&addr_django)
+ .unwrap()
+ .get_alive()
+ .unwrap()
+ .trie_id;
+
+ // The trie is regarded as 'dirty' when it was written to in the current block.
+ if !test_restore_to_with_dirty_storage {
+ // Advance 1 block.
+ initialize_block(28);
+ }
+
+ // Perform a call to `DJANGO`. This should either perform restoration successfully or
+ // fail depending on the test parameters.
+ let perform_the_restoration = || {
+ Contracts::call(
+ Origin::signed(ALICE),
+ addr_django.clone(),
+ 0,
+ GAS_LIMIT,
+ set_rent_code_hash
+ .as_ref()
+ .iter()
+ .chain(AsRef::<[u8]>::as_ref(&addr_bob))
+ .cloned()
+ .collect(),
+ )
+ };
+
+ // The key that is used in the restorer contract but is not in the target contract.
+ // Is supplied as delta to the restoration. We need it to check whether the key
+ // is properly removed on success but still there on failure.
+ let delta_key = {
+ let mut key = [0u8; 32];
+ key[0] = 1;
+ key
+ };
+
+ if test_different_storage || test_restore_to_with_dirty_storage || test_code_evicted {
+ // Parametrization of the test imply restoration failure. Check that `DJANGO` aka
+ // restoration contract is still in place and also that `BOB` doesn't exist.
+ let result = perform_the_restoration();
+ assert!(ContractInfoOf::<Test>::get(&addr_bob)
+ .unwrap()
+ .get_tombstone()
+ .is_some());
+ let django_contract = ContractInfoOf::<Test>::get(&addr_django)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert_eq!(django_contract.storage_size, 8);
+ assert_eq!(django_contract.trie_id, django_trie_id);
+ assert_eq!(django_contract.deduct_block, System::block_number());
+ assert_eq!(
+ Storage::<Test>::read(&django_trie_id, &delta_key),
+ Some(vec![40, 0, 0, 0]),
+ );
+ match (
+ test_different_storage,
+ test_restore_to_with_dirty_storage,
+ test_code_evicted,
+ ) {
+ (true, false, false) => {
+ assert_err_ignore_postinfo!(result, Error::<Test>::InvalidTombstone,);
+ assert_eq!(System::events(), vec![]);
+ }
+ (_, true, false) => {
+ assert_err_ignore_postinfo!(result, Error::<Test>::InvalidContractOrigin,);
+ assert_eq!(
+ System::events(),
+ vec![
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::Evicted(addr_bob)),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(frame_system::Event::NewAccount(
+ CHARLIE
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Endowed(
+ CHARLIE, 1_000_000
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(frame_system::Event::NewAccount(
+ addr_django.clone()
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Endowed(
+ addr_django.clone(),
+ 30_000
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(
+ pallet_balances::Event::Transfer(
+ CHARLIE,
+ addr_django.clone(),
+ 30_000
+ )
+ ),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::CodeStored(
+ restoration_code_hash
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::Instantiated(
+ CHARLIE,
+ addr_django.clone()
+ )),
+ topics: vec![],
+ },
+ ]
+ );
+ }
+ (false, false, true) => {
+ assert_err_ignore_postinfo!(result, Error::<Test>::CodeNotFound,);
+ assert_refcount!(set_rent_code_hash, 0);
+ assert_eq!(System::events(), vec![]);
+ }
+ _ => unreachable!(),
+ }
+ } else {
+ assert_ok!(perform_the_restoration());
+ assert_refcount!(set_rent_code_hash, 2);
+
+ // Here we expect that the restoration is succeeded. Check that the restoration
+ // contract `DJANGO` ceased to exist and that `BOB` returned back.
+ let bob_contract = ContractInfoOf::<Test>::get(&addr_bob)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ assert_eq!(bob_contract.rent_allowance, 50);
+ assert_eq!(bob_contract.storage_size, 4);
+ assert_eq!(bob_contract.trie_id, django_trie_id);
+ assert_eq!(bob_contract.deduct_block, System::block_number());
+ assert!(ContractInfoOf::<Test>::get(&addr_django).is_none());
+ assert_matches!(Storage::<Test>::read(&django_trie_id, &delta_key), None);
+ assert_eq!(
+ System::events(),
+ vec![
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::CodeRemoved(
+ restoration_code_hash
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(system::Event::KilledAccount(
+ addr_django.clone()
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::Restored(
+ addr_django,
+ addr_bob,
+ bob_contract.code_hash,
+ 50
+ )),
+ topics: vec![],
+ },
+ ]
+ );
+ }
+ });
+}
+
+#[test]
+fn storage_max_value_limit() {
+ let (wasm, code_hash) = compile_module::<Test>("storage_size").unwrap();
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ ContractInfoOf::<Test>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+
+ // Call contract with allowed storage value.
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT * 2, // we are copying a huge buffer
+ <Test as Config>::MaxValueSize::get().encode(),
+ ));
+
+ // Call contract with too large a storage value.
+ assert_err_ignore_postinfo!(
+ Contracts::call(
+ Origin::signed(ALICE),
+ addr,
+ 0,
+ GAS_LIMIT,
+ (<Test as Config>::MaxValueSize::get() + 1).encode(),
+ ),
+ Error::<Test>::ValueTooLarge,
+ );
+ });
+}
+
+#[test]
+fn deploy_and_call_other_contract() {
+ let (callee_wasm, callee_code_hash) = compile_module::<Test>("return_with_data").unwrap();
+ let (caller_wasm, caller_code_hash) = compile_module::<Test>("caller_contract").unwrap();
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ caller_wasm,
+ vec![],
+ vec![],
+ ));
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ callee_wasm,
+ 0u32.to_le_bytes().encode(),
+ vec![42],
+ ));
+
+ // Call BOB contract, which attempts to instantiate and call the callee contract and
+ // makes various assertions on the results from those calls.
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ Contracts::contract_address(&ALICE, &caller_code_hash, &[]),
+ 0,
+ GAS_LIMIT,
+ callee_code_hash.as_ref().to_vec(),
+ ));
+ });
+}
+
+#[test]
+fn cannot_self_destruct_through_draning() {
+ let (wasm, code_hash) = compile_module::<Test>("drain").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+
+ // Instantiate the BOB contract.
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Check that the BOB contract has been instantiated.
+ assert_matches!(
+ ContractInfoOf::<Test>::get(&addr),
+ Some(ContractInfo::Alive(_))
+ );
+
+ // Call BOB which makes it send all funds to the zero address
+ // The contract code asserts that the correct error value is returned.
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr,
+ 0,
+ GAS_LIMIT,
+ vec![],
+ ));
+ });
+}
+
+#[test]
+fn cannot_self_destruct_while_live() {
+ let (wasm, code_hash) = compile_module::<Test>("self_destruct").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+
+ // Instantiate the BOB contract.
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Check that the BOB contract has been instantiated.
+ assert_matches!(
+ ContractInfoOf::<Test>::get(&addr),
+ Some(ContractInfo::Alive(_))
+ );
+
+ // Call BOB with input data, forcing it make a recursive call to itself to
+ // self-destruct, resulting in a trap.
+ assert_err_ignore_postinfo!(
+ Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![0],),
+ Error::<Test>::ContractTrapped,
+ );
+
+ // Check that BOB is still alive.
+ assert_matches!(
+ ContractInfoOf::<Test>::get(&addr),
+ Some(ContractInfo::Alive(_))
+ );
+ });
+}
+
+#[test]
+fn self_destruct_works() {
+ let (wasm, code_hash) = compile_module::<Test>("self_destruct").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ let _ = Balances::deposit_creating(&DJANGO, 1_000_000);
+
+ // Instantiate the BOB contract.
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Check that the BOB contract has been instantiated.
+ assert_matches!(
+ ContractInfoOf::<Test>::get(&addr),
+ Some(ContractInfo::Alive(_))
+ );
+
+ // Drop all previous events
+ initialize_block(2);
+
+ // Call BOB without input data which triggers termination.
+ assert_matches!(
+ Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![],),
+ Ok(_)
+ );
+
+ pretty_assertions::assert_eq!(
+ System::events(),
+ vec![
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::frame_system(frame_system::Event::KilledAccount(
+ addr.clone()
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_balances(pallet_balances::Event::Transfer(
+ addr.clone(),
+ DJANGO,
+ 93_086
+ )),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::CodeRemoved(code_hash)),
+ topics: vec![],
+ },
+ EventRecord {
+ phase: Phase::Initialization,
+ event: Event::pallet_contracts(crate::Event::Terminated(
+ addr.clone(),
+ DJANGO
+ )),
+ topics: vec![],
+ },
+ ]
+ );
+
+ // Check that account is gone
+ assert!(ContractInfoOf::<Test>::get(&addr).is_none());
+
+ // check that the beneficiary (django) got remaining balance
+ // some rent was deducted before termination
+ assert_eq!(Balances::free_balance(DJANGO), 1_093_086);
+ });
+}
+
+// This tests that one contract cannot prevent another from self-destructing by sending it
+// additional funds after it has been drained.
+#[test]
+fn destroy_contract_and_transfer_funds() {
+ let (callee_wasm, callee_code_hash) = compile_module::<Test>("self_destruct").unwrap();
+ let (caller_wasm, caller_code_hash) = compile_module::<Test>("destroy_and_transfer").unwrap();
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 200_000,
+ GAS_LIMIT,
+ callee_wasm,
+ vec![],
+ vec![42]
+ ));
+
+ // This deploys the BOB contract, which in turn deploys the CHARLIE contract during
+ // construction.
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 200_000,
+ GAS_LIMIT,
+ caller_wasm,
+ callee_code_hash.as_ref().to_vec(),
+ vec![],
+ ));
+ let addr_bob = Contracts::contract_address(&ALICE, &caller_code_hash, &[]);
+ let addr_charlie =
+ Contracts::contract_address(&addr_bob, &callee_code_hash, &[0x47, 0x11]);
+
+ // Check that the CHARLIE contract has been instantiated.
+ assert_matches!(
+ ContractInfoOf::<Test>::get(&addr_charlie),
+ Some(ContractInfo::Alive(_))
+ );
+
+ // Call BOB, which calls CHARLIE, forcing CHARLIE to self-destruct.
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr_bob,
+ 0,
+ GAS_LIMIT,
+ addr_charlie.encode(),
+ ));
+
+ // Check that CHARLIE has moved on to the great beyond (ie. died).
+ assert!(ContractInfoOf::<Test>::get(&addr_charlie).is_none());
+ });
+}
+
+#[test]
+fn cannot_self_destruct_in_constructor() {
+ let (wasm, _) = compile_module::<Test>("self_destructing_constructor").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+
+ // Fail to instantiate the BOB because the contructor calls seal_terminate.
+ assert_err_ignore_postinfo!(
+ Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ),
+ Error::<Test>::NotCallable,
+ );
+ });
+}
+
+#[test]
+fn crypto_hashes() {
+ let (wasm, code_hash) = compile_module::<Test>("crypto_hashes").unwrap();
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+
+ // Instantiate the CRYPTO_HASHES contract.
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 100_000,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ // Perform the call.
+ let input = b"_DEAD_BEEF";
+ use sp_io::hashing::*;
+ // Wraps a hash function into a more dynamic form usable for testing.
+ macro_rules! dyn_hash_fn {
+ ($name:ident) => {
+ Box::new(|input| $name(input).as_ref().to_vec().into_boxed_slice())
+ };
+ }
+ // All hash functions and their associated output byte lengths.
+ let test_cases: &[(Box<dyn Fn(&[u8]) -> Box<[u8]>>, usize)] = &[
+ (dyn_hash_fn!(sha2_256), 32),
+ (dyn_hash_fn!(keccak_256), 32),
+ (dyn_hash_fn!(blake2_256), 32),
+ (dyn_hash_fn!(blake2_128), 16),
+ ];
+ // Test the given hash functions for the input: "_DEAD_BEEF"
+ for (n, (hash_fn, expected_size)) in test_cases.iter().enumerate() {
+ // We offset data in the contract tables by 1.
+ let mut params = vec![(n + 1) as u8];
+ params.extend_from_slice(input);
+ let result = <Pallet<Test>>::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, params)
+ .result
+ .unwrap();
+ assert!(result.is_success());
+ let expected = hash_fn(input.as_ref());
+ assert_eq!(&result.data[..*expected_size], &*expected);
+ }
+ })
+}
+
+#[test]
+fn transfer_return_code() {
+ let (wasm, code_hash) = compile_module::<Test>("transfer_return_code").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ wasm,
+ vec![],
+ vec![],
+ ),);
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Contract has only the minimal balance so any transfer will return BelowSubsistence.
+ Balances::make_free_balance_be(&addr, subsistence);
+ let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![])
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
+
+ // Contract has enough total balance in order to not go below the subsistence
+ // threshold when transfering 100 balance but this balance is reserved so
+ // the transfer still fails but with another return code.
+ Balances::make_free_balance_be(&addr, subsistence + 100);
+ Balances::reserve(&addr, subsistence + 100).unwrap();
+ let result = Contracts::bare_call(ALICE, addr, 0, GAS_LIMIT, vec![])
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::TransferFailed);
+ });
+}
+
+#[test]
+fn call_return_code() {
+ let (caller_code, caller_hash) = compile_module::<Test>("call_return_code").unwrap();
+ let (callee_code, callee_hash) = compile_module::<Test>("ok_trap_revert").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+ let _ = Balances::deposit_creating(&CHARLIE, 1000 * subsistence);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ caller_code,
+ vec![0],
+ vec![],
+ ),);
+ let addr_bob = Contracts::contract_address(&ALICE, &caller_hash, &[]);
+ Balances::make_free_balance_be(&addr_bob, subsistence);
+
+ // Contract calls into Django which is no valid contract
+ let result = Contracts::bare_call(
+ ALICE,
+ addr_bob.clone(),
+ 0,
+ GAS_LIMIT,
+ AsRef::<[u8]>::as_ref(&DJANGO).to_vec(),
+ )
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::NotCallable);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(CHARLIE),
+ subsistence * 100,
+ GAS_LIMIT,
+ callee_code,
+ vec![0],
+ vec![],
+ ),);
+ let addr_django = Contracts::contract_address(&CHARLIE, &callee_hash, &[]);
+ Balances::make_free_balance_be(&addr_django, subsistence);
+
+ // Contract has only the minimal balance so any transfer will return BelowSubsistence.
+ let result = Contracts::bare_call(
+ ALICE,
+ addr_bob.clone(),
+ 0,
+ GAS_LIMIT,
+ AsRef::<[u8]>::as_ref(&addr_django)
+ .iter()
+ .chain(&0u32.to_le_bytes())
+ .cloned()
+ .collect(),
+ )
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
+
+ // Contract has enough total balance in order to not go below the subsistence
+ // threshold when transfering 100 balance but this balance is reserved so
+ // the transfer still fails but with another return code.
+ Balances::make_free_balance_be(&addr_bob, subsistence + 100);
+ Balances::reserve(&addr_bob, subsistence + 100).unwrap();
+ let result = Contracts::bare_call(
+ ALICE,
+ addr_bob.clone(),
+ 0,
+ GAS_LIMIT,
+ AsRef::<[u8]>::as_ref(&addr_django)
+ .iter()
+ .chain(&0u32.to_le_bytes())
+ .cloned()
+ .collect(),
+ )
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::TransferFailed);
+
+ // Contract has enough balance but callee reverts because "1" is passed.
+ Balances::make_free_balance_be(&addr_bob, subsistence + 1000);
+ let result = Contracts::bare_call(
+ ALICE,
+ addr_bob.clone(),
+ 0,
+ GAS_LIMIT,
+ AsRef::<[u8]>::as_ref(&addr_django)
+ .iter()
+ .chain(&1u32.to_le_bytes())
+ .cloned()
+ .collect(),
+ )
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::CalleeReverted);
+
+ // Contract has enough balance but callee traps because "2" is passed.
+ let result = Contracts::bare_call(
+ ALICE,
+ addr_bob,
+ 0,
+ GAS_LIMIT,
+ AsRef::<[u8]>::as_ref(&addr_django)
+ .iter()
+ .chain(&2u32.to_le_bytes())
+ .cloned()
+ .collect(),
+ )
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);
+ });
+}
+
+#[test]
+fn instantiate_return_code() {
+ let (caller_code, caller_hash) = compile_module::<Test>("instantiate_return_code").unwrap();
+ let (callee_code, callee_hash) = compile_module::<Test>("ok_trap_revert").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+ let _ = Balances::deposit_creating(&CHARLIE, 1000 * subsistence);
+ let callee_hash = callee_hash.as_ref().to_vec();
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ callee_code,
+ vec![],
+ vec![],
+ ),);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ caller_code,
+ vec![],
+ vec![],
+ ),);
+ let addr = Contracts::contract_address(&ALICE, &caller_hash, &[]);
+
+ // Contract has only the minimal balance so any transfer will return BelowSubsistence.
+ Balances::make_free_balance_be(&addr, subsistence);
+ let result =
+ Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, callee_hash.clone())
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::BelowSubsistenceThreshold);
+
+ // Contract has enough total balance in order to not go below the subsistence
+ // threshold when transfering the balance but this balance is reserved so
+ // the transfer still fails but with another return code.
+ Balances::make_free_balance_be(&addr, subsistence + 10_000);
+ Balances::reserve(&addr, subsistence + 10_000).unwrap();
+ let result =
+ Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, callee_hash.clone())
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::TransferFailed);
+
+ // Contract has enough balance but the passed code hash is invalid
+ Balances::make_free_balance_be(&addr, subsistence + 10_000);
+ let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![0; 33])
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::CodeNotFound);
+
+ // Contract has enough balance but callee reverts because "1" is passed.
+ let result = Contracts::bare_call(
+ ALICE,
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ callee_hash
+ .iter()
+ .chain(&1u32.to_le_bytes())
+ .cloned()
+ .collect(),
+ )
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::CalleeReverted);
+
+ // Contract has enough balance but callee traps because "2" is passed.
+ let result = Contracts::bare_call(
+ ALICE,
+ addr,
+ 0,
+ GAS_LIMIT,
+ callee_hash
+ .iter()
+ .chain(&2u32.to_le_bytes())
+ .cloned()
+ .collect(),
+ )
+ .result
+ .unwrap();
+ assert_return_code!(result, RuntimeReturnCode::CalleeTrapped);
+ });
+}
+
+#[test]
+fn disabled_chain_extension_wont_deploy() {
+ let (code, _hash) = compile_module::<Test>("chain_extension").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+ TestExtension::disable();
+ assert_err_ignore_postinfo!(
+ Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 3 * subsistence,
+ GAS_LIMIT,
+ code,
+ vec![],
+ vec![],
+ ),
+ "module uses chain extensions but chain extensions are disabled",
+ );
+ });
+}
+
+#[test]
+fn disabled_chain_extension_errors_on_call() {
+ let (code, hash) = compile_module::<Test>("chain_extension").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ code,
+ vec![],
+ vec![],
+ ),);
+ let addr = Contracts::contract_address(&ALICE, &hash, &[]);
+ TestExtension::disable();
+ assert_err_ignore_postinfo!(
+ Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![],),
+ Error::<Test>::NoChainExtension,
+ );
+ });
+}
+
+#[test]
+fn chain_extension_works() {
+ let (code, hash) = compile_module::<Test>("chain_extension").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ code,
+ vec![],
+ vec![],
+ ),);
+ let addr = Contracts::contract_address(&ALICE, &hash, &[]);
+
+ // The contract takes a up to 2 byte buffer where the first byte passed is used as
+ // as func_id to the chain extension which behaves differently based on the
+ // func_id.
+
+ // 0 = read input buffer and pass it through as output
+ let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![0, 99]);
+ let gas_consumed = result.gas_consumed;
+ assert_eq!(TestExtension::last_seen_buffer(), vec![0, 99]);
+ assert_eq!(result.result.unwrap().data, Bytes(vec![0, 99]));
+
+ // 1 = treat inputs as integer primitives and store the supplied integers
+ Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![1])
+ .result
+ .unwrap();
+ // those values passed in the fixture
+ assert_eq!(TestExtension::last_seen_inputs(), (4, 1, 16, 12));
+
+ // 2 = charge some extra weight (amount supplied in second byte)
+ let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![2, 42]);
+ assert_ok!(result.result);
+ assert_eq!(result.gas_consumed, gas_consumed + 42);
+
+ // 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer
+ let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, vec![3])
+ .result
+ .unwrap();
+ assert_eq!(result.flags, ReturnFlags::REVERT);
+ assert_eq!(result.data, Bytes(vec![42, 99]));
+ });
+}
+
+#[test]
+fn lazy_removal_works() {
+ let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ code,
+ vec![],
+ vec![],
+ ),);
+
+ let addr = Contracts::contract_address(&ALICE, &hash, &[]);
+ let info = <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ let trie = &info.child_trie_info();
+
+ // Put value into the contracts child trie
+ child::put(trie, &[99], &42);
+
+ // Terminate the contract
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ vec![],
+ ));
+
+ // Contract info should be gone
+ assert!(!<ContractInfoOf::<Test>>::contains_key(&addr));
+
+ // But value should be still there as the lazy removal did not run, yet.
+ assert_matches!(child::get(trie, &[99]), Some(42));
+
+ // Run the lazy removal
+ Contracts::on_initialize(Weight::max_value());
+
+ // Value should be gone now
+ assert_matches!(child::get::<i32>(trie, &[99]), None);
+ });
+}
+
+#[test]
+fn lazy_removal_partial_remove_works() {
+ let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
+
+ // We create a contract with some extra keys above the weight limit
+ let extra_keys = 7u32;
+ let weight_limit = 5_000_000_000;
+ let (_, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
+ let vals: Vec<_> = (0..max_keys + extra_keys)
+ .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode()))
+ .collect();
+
+ let mut ext = ExtBuilder::default().existential_deposit(50).build();
+
+ let trie = ext.execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ code,
+ vec![],
+ vec![],
+ ),);
+
+ let addr = Contracts::contract_address(&ALICE, &hash, &[]);
+ let info = <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ let trie = &info.child_trie_info();
+
+ // Put value into the contracts child trie
+ for val in &vals {
+ Storage::<Test>::write(&addr, &info.trie_id, &val.0, Some(val.2.clone())).unwrap();
+ }
+
+ // Terminate the contract
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ vec![],
+ ));
+
+ // Contract info should be gone
+ assert!(!<ContractInfoOf::<Test>>::contains_key(&addr));
+
+ // But value should be still there as the lazy removal did not run, yet.
+ for val in &vals {
+ assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), Some(val.1));
+ }
+
+ trie.clone()
+ });
+
+ // The lazy removal limit only applies to the backend but not to the overlay.
+ // This commits all keys from the overlay to the backend.
+ ext.commit_all().unwrap();
+
+ ext.execute_with(|| {
+ // Run the lazy removal
+ let weight_used = Storage::<Test>::process_deletion_queue_batch(weight_limit);
+
+ // Weight should be exhausted because we could not even delete all keys
+ assert_eq!(weight_used, weight_limit);
+
+ let mut num_deleted = 0u32;
+ let mut num_remaining = 0u32;
+
+ for val in &vals {
+ match child::get::<u32>(&trie, &blake2_256(&val.0)) {
+ None => num_deleted += 1,
+ Some(x) if x == val.1 => num_remaining += 1,
+ Some(_) => panic!("Unexpected value in contract storage"),
+ }
+ }
+
+ // All but one key is removed
+ assert_eq!(num_deleted + num_remaining, vals.len() as u32);
+ assert_eq!(num_deleted, max_keys);
+ assert_eq!(num_remaining, extra_keys);
+ });
+}
+
+#[test]
+fn lazy_removal_does_no_run_on_full_block() {
+ let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ code,
+ vec![],
+ vec![],
+ ),);
+
+ let addr = Contracts::contract_address(&ALICE, &hash, &[]);
+ let info = <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ let trie = &info.child_trie_info();
+ let max_keys = 30;
+
+ // Create some storage items for the contract.
+ let vals: Vec<_> = (0..max_keys)
+ .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode()))
+ .collect();
+
+ // Put value into the contracts child trie
+ for val in &vals {
+ Storage::<Test>::write(&addr, &info.trie_id, &val.0, Some(val.2.clone())).unwrap();
+ }
+
+ // Terminate the contract
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ vec![],
+ ));
+
+ // Contract info should be gone
+ assert!(!<ContractInfoOf::<Test>>::contains_key(&addr));
+
+ // But value should be still there as the lazy removal did not run, yet.
+ for val in &vals {
+ assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), Some(val.1));
+ }
+
+ // Fill up the block which should prevent the lazy storage removal from running.
+ System::register_extra_weight_unchecked(
+ <Test as system::Config>::BlockWeights::get().max_block,
+ DispatchClass::Mandatory,
+ );
+
+ // Run the lazy removal without any limit so that all keys would be removed if there
+ // had been some weight left in the block.
+ let weight_used = Contracts::on_initialize(Weight::max_value());
+ let base = <<Test as Config>::WeightInfo as WeightInfo>::on_initialize();
+ assert_eq!(weight_used, base);
+
+ // All the keys are still in place
+ for val in &vals {
+ assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), Some(val.1));
+ }
+
+ // Run the lazy removal directly which disregards the block limits
+ Storage::<Test>::process_deletion_queue_batch(Weight::max_value());
+
+ // Now the keys should be gone
+ for val in &vals {
+ assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), None);
+ }
+ });
+}
+
+#[test]
+fn lazy_removal_does_not_use_all_weight() {
+ let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ code,
+ vec![],
+ vec![],
+ ),);
+
+ let addr = Contracts::contract_address(&ALICE, &hash, &[]);
+ let info = <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ let trie = &info.child_trie_info();
+ let weight_limit = 5_000_000_000;
+ let (weight_per_key, max_keys) = Storage::<Test>::deletion_budget(1, weight_limit);
+
+ // We create a contract with one less storage item than we can remove within the limit
+ let vals: Vec<_> = (0..max_keys - 1)
+ .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode()))
+ .collect();
+
+ // Put value into the contracts child trie
+ for val in &vals {
+ Storage::<Test>::write(&addr, &info.trie_id, &val.0, Some(val.2.clone())).unwrap();
+ }
+
+ // Terminate the contract
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr.clone(),
+ 0,
+ GAS_LIMIT,
+ vec![],
+ ));
+
+ // Contract info should be gone
+ assert!(!<ContractInfoOf::<Test>>::contains_key(&addr));
+
+ // But value should be still there as the lazy removal did not run, yet.
+ for val in &vals {
+ assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), Some(val.1));
+ }
+
+ // Run the lazy removal
+ let weight_used = Storage::<Test>::process_deletion_queue_batch(weight_limit);
+
+ // We have one less key in our trie than our weight limit suffices for
+ assert_eq!(weight_used, weight_limit - weight_per_key);
+
+ // All the keys are removed
+ for val in vals {
+ assert_eq!(child::get::<u32>(trie, &blake2_256(&val.0)), None);
+ }
+ });
+}
+
+#[test]
+fn deletion_queue_full() {
+ let (code, hash) = compile_module::<Test>("self_destruct").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let _ = Balances::deposit_creating(&ALICE, 1000 * subsistence);
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ code,
+ vec![],
+ vec![],
+ ),);
+
+ let addr = Contracts::contract_address(&ALICE, &hash, &[]);
+
+ // fill the deletion queue up until its limit
+ Storage::<Test>::fill_queue_with_dummies();
+
+ // Terminate the contract should fail
+ assert_err_ignore_postinfo!(
+ Contracts::call(Origin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, vec![],),
+ Error::<Test>::DeletionQueueFull,
+ );
+
+ // Contract should be alive because removal failed
+ <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+
+ // make the contract ripe for eviction
+ initialize_block(5);
+
+ // eviction should fail for the same reason as termination
+ assert_err!(
+ Contracts::claim_surcharge(Origin::none(), addr.clone(), Some(ALICE)),
+ Error::<Test>::DeletionQueueFull,
+ );
+
+ // Contract should be alive because removal failed
+ <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ });
+}
+
+#[test]
+fn not_deployed_if_endowment_too_low_for_first_rent() {
+ let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
+
+ // The instantiation deducted the rent for one block immediately
+ let first_rent = <Test as Config>::RentFraction::get()
+ // base_deposit + deploy_set_storage (4 bytes in 1 item) - free_balance
+ .mul_ceil(80_000u32 + 40_000 + 10_000 - 30_000)
+ // blocks to rent
+ * 1;
+
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ // Create
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_storage_noop!(assert_err_ignore_postinfo!(
+ Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ wasm,
+ (BalanceOf::<Test>::from(first_rent) - BalanceOf::<Test>::from(1u32)).encode(), // rent allowance
+ vec![],
+ ),
+ Error::<Test>::NewContractNotFunded,
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ assert_matches!(ContractInfoOf::<Test>::get(&addr), None);
+ });
+}
+
+#[test]
+fn surcharge_reward_is_capped() {
+ let (wasm, code_hash) = compile_module::<Test>("set_rent").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ 30_000,
+ GAS_LIMIT,
+ wasm,
+ <BalanceOf<Test>>::from(10_000u32).encode(), // rent allowance
+ vec![],
+ ));
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+ let contract = <ContractInfoOf<Test>>::get(&addr)
+ .unwrap()
+ .get_alive()
+ .unwrap();
+ let balance = Balances::free_balance(&ALICE);
+ let reward = <Test as Config>::SurchargeReward::get();
+
+ // some rent should have payed due to instantiation
+ assert_ne!(contract.rent_payed, 0);
+
+ // the reward should be parameterized sufficiently high to make this test useful
+ assert!(reward > contract.rent_payed);
+
+ // make contract eligible for eviction
+ initialize_block(40);
+
+ // this should have removed the contract
+ assert_ok!(Contracts::claim_surcharge(
+ Origin::none(),
+ addr.clone(),
+ Some(ALICE)
+ ));
+
+ // this reward does not take into account the last rent payment collected during eviction
+ let capped_reward = reward.min(contract.rent_payed);
+
+ // this is smaller than the actual reward because it does not take into account the
+ // rent collected during eviction
+ assert!(Balances::free_balance(&ALICE) > balance + capped_reward);
+
+ // the full reward is not payed out because of the cap introduced by rent_payed
+ assert!(Balances::free_balance(&ALICE) < balance + reward);
+ });
+}
+
+#[test]
+fn refcounter() {
+ let (wasm, code_hash) = compile_module::<Test>("self_destruct").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+
+ // Create two contracts with the same code and check that they do in fact share it.
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ wasm.clone(),
+ vec![],
+ vec![0],
+ ));
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ wasm.clone(),
+ vec![],
+ vec![1],
+ ));
+ assert_refcount!(code_hash, 2);
+
+ // Sharing should also work with the usual instantiate call
+ assert_ok!(Contracts::instantiate(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ code_hash,
+ vec![],
+ vec![2],
+ ));
+ assert_refcount!(code_hash, 3);
+
+ // addresses of all three existing contracts
+ let addr0 = Contracts::contract_address(&ALICE, &code_hash, &[0]);
+ let addr1 = Contracts::contract_address(&ALICE, &code_hash, &[1]);
+ let addr2 = Contracts::contract_address(&ALICE, &code_hash, &[2]);
+
+ // Terminating one contract should decrement the refcount
+ assert_ok!(Contracts::call(
+ Origin::signed(ALICE),
+ addr0,
+ 0,
+ GAS_LIMIT,
+ vec![],
+ ));
+ assert_refcount!(code_hash, 2);
+
+ // make remaining contracts eligible for eviction
+ initialize_block(40);
+
+ // remove one of them
+ assert_ok!(Contracts::claim_surcharge(
+ Origin::none(),
+ addr1,
+ Some(ALICE)
+ ));
+ assert_refcount!(code_hash, 1);
+
+ // Pristine code should still be there
+ crate::PristineCode::<Test>::get(code_hash).unwrap();
+
+ // remove the last contract
+ assert_ok!(Contracts::claim_surcharge(
+ Origin::none(),
+ addr2,
+ Some(ALICE)
+ ));
+ assert_refcount!(code_hash, 0);
+
+ // all code should be gone
+ assert_matches!(crate::PristineCode::<Test>::get(code_hash), None);
+ assert_matches!(crate::CodeStorage::<Test>::get(code_hash), None);
+ });
+}
+
+#[test]
+fn reinstrument_does_charge() {
+ let (wasm, code_hash) = compile_module::<Test>("return_with_data").unwrap();
+ ExtBuilder::default()
+ .existential_deposit(50)
+ .build()
+ .execute_with(|| {
+ let _ = Balances::deposit_creating(&ALICE, 1_000_000);
+ let subsistence = Pallet::<Test>::subsistence_threshold();
+ let zero = 0u32.to_le_bytes().encode();
+ let code_len = wasm.len() as u32;
+
+ assert_ok!(Contracts::instantiate_with_code(
+ Origin::signed(ALICE),
+ subsistence * 100,
+ GAS_LIMIT,
+ wasm,
+ zero.clone(),
+ vec![],
+ ));
+
+ let addr = Contracts::contract_address(&ALICE, &code_hash, &[]);
+
+ // Call the contract two times without reinstrument
+
+ let result0 = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, zero.clone());
+ assert!(result0.result.unwrap().is_success());
+
+ let result1 = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, zero.clone());
+ assert!(result1.result.unwrap().is_success());
+
+ // They should match because both where called with the same schedule.
+ assert_eq!(result0.gas_consumed, result1.gas_consumed);
+
+ // Update the schedule version but keep the rest the same
+ crate::CurrentSchedule::mutate(|old: &mut Schedule<Test>| {
+ old.version += 1;
+ });
+
+ // This call should trigger reinstrumentation
+ let result2 = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, zero.clone());
+ assert!(result2.result.unwrap().is_success());
+ assert!(result2.gas_consumed > result1.gas_consumed);
+ assert_eq!(
+ result2.gas_consumed,
+ result1.gas_consumed + <Test as Config>::WeightInfo::instrument(code_len / 1024),
+ );
+ });
+}
pallets/contracts/src/weights.rsdiffbeforeafterboth--- /dev/null
+++ b/pallets/contracts/src/weights.rs
@@ -0,0 +1,1417 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2021 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.
+
+//! Autogenerated weights for pallet_contracts
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 3.0.0
+//! DATE: 2021-02-18, STEPS: `[50, ]`, REPEAT: 20, LOW RANGE: [], HIGH RANGE: []
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128
+
+// Executed Command:
+// target/release/substrate
+// benchmark
+// --chain=dev
+// --steps=50
+// --repeat=20
+// --pallet=pallet_contracts
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --output=./frame/contracts/src/weights.rs
+// --template=./.maintain/frame-weight-template.hbs
+
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+
+use frame_support::{
+ traits::Get,
+ weights::{Weight, constants::RocksDbWeight},
+};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for pallet_contracts.
+pub trait WeightInfo {
+ fn on_initialize() -> Weight;
+ fn on_initialize_per_trie_key(k: u32) -> Weight;
+ fn on_initialize_per_queue_item(q: u32) -> Weight;
+ fn instrument(c: u32) -> Weight;
+ fn update_schedule() -> Weight;
+ fn instantiate_with_code(c: u32, s: u32) -> Weight;
+ fn instantiate(c: u32, s: u32) -> Weight;
+ fn call(c: u32) -> Weight;
+ fn claim_surcharge(c: u32) -> 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_rent_params(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_terminate_per_code_kb(c: u32) -> Weight;
+ fn seal_restore_to(r: u32) -> Weight;
+ fn seal_restore_to_per_code_kb_delta(c: u32, t: u32, 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_code_transfer_input_output_kb(c: u32, t: u32, i: u32, o: u32) -> Weight;
+ fn seal_instantiate(r: u32) -> Weight;
+ fn seal_instantiate_per_code_input_output_salt_kb(c: u32, i: u32, o: u32, s: 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;
+ fn instr_i64const(r: u32) -> Weight;
+ fn instr_i64load(r: u32) -> Weight;
+ fn instr_i64store(r: u32) -> Weight;
+ fn instr_select(r: u32) -> Weight;
+ fn instr_if(r: u32) -> Weight;
+ fn instr_br(r: u32) -> Weight;
+ fn instr_br_if(r: u32) -> Weight;
+ fn instr_br_table(r: u32) -> Weight;
+ fn instr_br_table_per_entry(e: u32) -> Weight;
+ fn instr_call(r: u32) -> Weight;
+ fn instr_call_indirect(r: u32) -> Weight;
+ fn instr_call_indirect_per_param(p: u32) -> Weight;
+ fn instr_local_get(r: u32) -> Weight;
+ fn instr_local_set(r: u32) -> Weight;
+ fn instr_local_tee(r: u32) -> Weight;
+ fn instr_global_get(r: u32) -> Weight;
+ fn instr_global_set(r: u32) -> Weight;
+ fn instr_memory_current(r: u32) -> Weight;
+ fn instr_memory_grow(r: u32) -> Weight;
+ fn instr_i64clz(r: u32) -> Weight;
+ fn instr_i64ctz(r: u32) -> Weight;
+ fn instr_i64popcnt(r: u32) -> Weight;
+ fn instr_i64eqz(r: u32) -> Weight;
+ fn instr_i64extendsi32(r: u32) -> Weight;
+ fn instr_i64extendui32(r: u32) -> Weight;
+ fn instr_i32wrapi64(r: u32) -> Weight;
+ fn instr_i64eq(r: u32) -> Weight;
+ fn instr_i64ne(r: u32) -> Weight;
+ fn instr_i64lts(r: u32) -> Weight;
+ fn instr_i64ltu(r: u32) -> Weight;
+ fn instr_i64gts(r: u32) -> Weight;
+ fn instr_i64gtu(r: u32) -> Weight;
+ fn instr_i64les(r: u32) -> Weight;
+ fn instr_i64leu(r: u32) -> Weight;
+ fn instr_i64ges(r: u32) -> Weight;
+ fn instr_i64geu(r: u32) -> Weight;
+ fn instr_i64add(r: u32) -> Weight;
+ fn instr_i64sub(r: u32) -> Weight;
+ fn instr_i64mul(r: u32) -> Weight;
+ fn instr_i64divs(r: u32) -> Weight;
+ fn instr_i64divu(r: u32) -> Weight;
+ fn instr_i64rems(r: u32) -> Weight;
+ fn instr_i64remu(r: u32) -> Weight;
+ fn instr_i64and(r: u32) -> Weight;
+ fn instr_i64or(r: u32) -> Weight;
+ fn instr_i64xor(r: u32) -> Weight;
+ fn instr_i64shl(r: u32) -> Weight;
+ fn instr_i64shrs(r: u32) -> Weight;
+ fn instr_i64shru(r: u32) -> Weight;
+ fn instr_i64rotl(r: u32) -> Weight;
+ fn instr_i64rotr(r: u32) -> Weight;
+}
+
+/// Weights for pallet_contracts using the Substrate node and recommended hardware.
+pub struct SubstrateWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
+ fn on_initialize() -> Weight {
+ (3_850_000 as Weight).saturating_add(T::DbWeight::get().reads(1 as Weight))
+ }
+ fn on_initialize_per_trie_key(k: u32) -> Weight {
+ (52_925_000 as Weight)
+ // Standard Error: 5_000
+ .saturating_add((2_297_000 as Weight).saturating_mul(k as Weight))
+ .saturating_add(T::DbWeight::get().reads(1 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
+ .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight)))
+ }
+ fn on_initialize_per_queue_item(q: u32) -> Weight {
+ (434_698_000 as Weight)
+ // Standard Error: 210_000
+ .saturating_add((166_559_000 as Weight).saturating_mul(q as Weight))
+ .saturating_add(T::DbWeight::get().reads(1 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
+ }
+ fn instrument(c: u32) -> Weight {
+ (29_918_000 as Weight)
+ // Standard Error: 185_000
+ .saturating_add((123_774_000 as Weight).saturating_mul(c as Weight))
+ .saturating_add(T::DbWeight::get().reads(1 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
+ }
+ fn update_schedule() -> Weight {
+ (29_795_000 as Weight)
+ .saturating_add(T::DbWeight::get().reads(1 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
+ }
+ fn instantiate_with_code(c: u32, s: u32) -> Weight {
+ (225_834_000 as Weight)
+ // Standard Error: 144_000
+ .saturating_add((165_632_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 9_000
+ .saturating_add((2_563_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(T::DbWeight::get().reads(6 as Weight))
+ .saturating_add(T::DbWeight::get().writes(5 as Weight))
+ }
+ fn instantiate(c: u32, s: u32) -> Weight {
+ (190_482_000 as Weight)
+ // Standard Error: 12_000
+ .saturating_add((8_724_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 1_000
+ .saturating_add((2_512_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(T::DbWeight::get().reads(6 as Weight))
+ .saturating_add(T::DbWeight::get().writes(4 as Weight))
+ }
+ fn call(c: u32) -> Weight {
+ (195_414_000 as Weight)
+ // Standard Error: 2_000
+ .saturating_add((3_920_000 as Weight).saturating_mul(c as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ .saturating_add(T::DbWeight::get().writes(2 as Weight))
+ }
+ fn claim_surcharge(c: u32) -> Weight {
+ (336_867_000 as Weight)
+ // Standard Error: 10_000
+ .saturating_add((5_262_000 as Weight).saturating_mul(c as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ .saturating_add(T::DbWeight::get().writes(4 as Weight))
+ }
+ fn seal_caller(r: u32) -> Weight {
+ (143_935_000 as Weight)
+ // Standard Error: 128_000
+ .saturating_add((266_876_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_address(r: u32) -> Weight {
+ (150_342_000 as Weight)
+ // Standard Error: 127_000
+ .saturating_add((266_051_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_gas_left(r: u32) -> Weight {
+ (144_833_000 as Weight)
+ // Standard Error: 124_000
+ .saturating_add((259_279_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_balance(r: u32) -> Weight {
+ (152_032_000 as Weight)
+ // Standard Error: 218_000
+ .saturating_add((573_038_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_831_000 as Weight)
+ // Standard Error: 147_000
+ .saturating_add((260_718_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_minimum_balance(r: u32) -> Weight {
+ (142_925_000 as Weight)
+ // Standard Error: 130_000
+ .saturating_add((260_426_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_tombstone_deposit(r: u32) -> Weight {
+ (143_151_000 as Weight)
+ // Standard Error: 119_000
+ .saturating_add((260_964_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_rent_allowance(r: u32) -> Weight {
+ (155_126_000 as Weight)
+ // Standard Error: 225_000
+ .saturating_add((599_056_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_block_number(r: u32) -> Weight {
+ (144_566_000 as Weight)
+ // Standard Error: 110_000
+ .saturating_add((257_620_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_now(r: u32) -> Weight {
+ (147_274_000 as Weight)
+ // Standard Error: 115_000
+ .saturating_add((258_627_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_rent_params(r: u32) -> Weight {
+ (168_575_000 as Weight)
+ // Standard Error: 394_000
+ .saturating_add((397_754_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_weight_to_fee(r: u32) -> Weight {
+ (148_102_000 as Weight)
+ // Standard Error: 201_000
+ .saturating_add((537_088_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(6 as Weight))
+ }
+ fn seal_gas(r: u32) -> Weight {
+ (125_122_000 as Weight)
+ // Standard Error: 89_000
+ .saturating_add((122_350_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_input(r: u32) -> Weight {
+ (137_334_000 as Weight)
+ // Standard Error: 99_000
+ .saturating_add((7_359_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_input_per_kb(n: u32) -> Weight {
+ (145_094_000 as Weight)
+ // Standard Error: 0
+ .saturating_add((283_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_return(r: u32) -> Weight {
+ (127_544_000 as Weight)
+ // Standard Error: 138_000
+ .saturating_add((4_640_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_return_per_kb(n: u32) -> Weight {
+ (137_517_000 as Weight)
+ // Standard Error: 0
+ .saturating_add((783_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_terminate(r: u32) -> Weight {
+ (138_292_000 as Weight)
+ // Standard Error: 689_000
+ .saturating_add((111_698_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(r as Weight)))
+ .saturating_add(T::DbWeight::get().writes((5 as Weight).saturating_mul(r as Weight)))
+ }
+ fn seal_terminate_per_code_kb(c: u32) -> Weight {
+ (263_507_000 as Weight)
+ // Standard Error: 12_000
+ .saturating_add((8_409_000 as Weight).saturating_mul(c as Weight))
+ .saturating_add(T::DbWeight::get().reads(7 as Weight))
+ .saturating_add(T::DbWeight::get().writes(5 as Weight))
+ }
+ fn seal_restore_to(r: u32) -> Weight {
+ (232_291_000 as Weight)
+ // Standard Error: 301_000
+ .saturating_add((136_379_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(r as Weight)))
+ .saturating_add(T::DbWeight::get().writes((6 as Weight).saturating_mul(r as Weight)))
+ }
+ fn seal_restore_to_per_code_kb_delta(c: u32, t: u32, d: u32) -> Weight {
+ (0 as Weight)
+ // Standard Error: 162_000
+ .saturating_add((8_619_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 162_000
+ .saturating_add((4_877_000 as Weight).saturating_mul(t as Weight))
+ // Standard Error: 1_433_000
+ .saturating_add((3_762_810_000 as Weight).saturating_mul(d as Weight))
+ .saturating_add(T::DbWeight::get().reads(8 as Weight))
+ .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(d as Weight)))
+ .saturating_add(T::DbWeight::get().writes(7 as Weight))
+ .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(d as Weight)))
+ }
+ fn seal_random(r: u32) -> Weight {
+ (153_634_000 as Weight)
+ // Standard Error: 267_000
+ .saturating_add((650_160_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(6 as Weight))
+ }
+ fn seal_deposit_event(r: u32) -> Weight {
+ (137_080_000 as Weight)
+ // Standard Error: 1_009_000
+ .saturating_add((949_228_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32) -> Weight {
+ (1_259_129_000 as Weight)
+ // Standard Error: 2_542_000
+ .saturating_add((609_859_000 as Weight).saturating_mul(t as Weight))
+ // Standard Error: 501_000
+ .saturating_add((249_496_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 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 {
+ (170_417_000 as Weight)
+ // Standard Error: 434_000
+ .saturating_add((721_511_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ .saturating_add(T::DbWeight::get().writes(1 as Weight))
+ }
+ fn seal_set_storage(r: u32) -> Weight {
+ (1_870_542_000 as Weight)
+ // Standard Error: 26_871_000
+ .saturating_add((18_312_239_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_set_storage_per_kb(n: u32) -> Weight {
+ (1_763_732_000 as Weight)
+ // Standard Error: 258_000
+ .saturating_add((74_848_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(6 as Weight))
+ .saturating_add(T::DbWeight::get().writes(2 as Weight))
+ }
+ fn seal_clear_storage(r: u32) -> Weight {
+ (0 as Weight)
+ // Standard Error: 2_745_000
+ .saturating_add((2_316_433_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_get_storage(r: u32) -> Weight {
+ (87_218_000 as Weight)
+ // Standard Error: 745_000
+ .saturating_add((948_121_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_get_storage_per_kb(n: u32) -> Weight {
+ (719_050_000 as Weight)
+ // Standard Error: 266_000
+ .saturating_add((154_812_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(6 as Weight))
+ }
+ fn seal_transfer(r: u32) -> Weight {
+ (19_439_000 as Weight)
+ // Standard Error: 2_468_000
+ .saturating_add((5_674_822_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)
+ // Standard Error: 7_465_000
+ .saturating_add((11_066_530_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(6 as Weight))
+ .saturating_add(T::DbWeight::get().reads((200 as Weight).saturating_mul(r as Weight)))
+ }
+ fn seal_call_per_code_transfer_input_output_kb(c: u32, t: u32, i: u32, o: u32) -> Weight {
+ (9_916_288_000 as Weight)
+ // Standard Error: 552_000
+ .saturating_add((397_842_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 229_902_000
+ .saturating_add((5_243_673_000 as Weight).saturating_mul(t as Weight))
+ // Standard Error: 72_000
+ .saturating_add((59_737_000 as Weight).saturating_mul(i as Weight))
+ // Standard Error: 77_000
+ .saturating_add((82_259_000 as Weight).saturating_mul(o as Weight))
+ .saturating_add(T::DbWeight::get().reads(206 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)
+ // Standard Error: 32_016_000
+ .saturating_add((22_206_489_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((300 as Weight).saturating_mul(r as Weight)))
+ }
+ fn seal_instantiate_per_code_input_output_salt_kb(c: u32, i: u32, o: u32, s: u32) -> Weight {
+ (9_991_947_000 as Weight)
+ // Standard Error: 637_000
+ .saturating_add((881_981_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 90_000
+ .saturating_add((63_638_000 as Weight).saturating_mul(i as Weight))
+ // Standard Error: 90_000
+ .saturating_add((87_288_000 as Weight).saturating_mul(o as Weight))
+ // Standard Error: 90_000
+ .saturating_add((311_808_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(T::DbWeight::get().reads(207 as Weight))
+ .saturating_add(T::DbWeight::get().writes(203 as Weight))
+ }
+ fn seal_hash_sha2_256(r: u32) -> Weight {
+ (132_452_000 as Weight)
+ // Standard Error: 227_000
+ .saturating_add((239_671_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_sha2_256_per_kb(n: u32) -> Weight {
+ (756_802_000 as Weight)
+ // Standard Error: 48_000
+ .saturating_add((429_454_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_keccak_256(r: u32) -> Weight {
+ (139_440_000 as Weight)
+ // Standard Error: 128_000
+ .saturating_add((249_514_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_keccak_256_per_kb(n: u32) -> Weight {
+ (658_595_000 as Weight)
+ // Standard Error: 35_000
+ .saturating_add((343_814_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_blake2_256(r: u32) -> Weight {
+ (138_124_000 as Weight)
+ // Standard Error: 140_000
+ .saturating_add((223_189_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_blake2_256_per_kb(n: u32) -> Weight {
+ (689_667_000 as Weight)
+ // Standard Error: 41_000
+ .saturating_add((160_006_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_blake2_128(r: u32) -> Weight {
+ (140_225_000 as Weight)
+ // Standard Error: 156_000
+ .saturating_add((223_696_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_blake2_128_per_kb(n: u32) -> Weight {
+ (693_756_000 as Weight)
+ // Standard Error: 40_000
+ .saturating_add((159_996_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(T::DbWeight::get().reads(5 as Weight))
+ }
+ fn instr_i64const(r: u32) -> Weight {
+ (24_250_000 as Weight)
+ // Standard Error: 14_000
+ .saturating_add((3_134_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64load(r: u32) -> Weight {
+ (26_509_000 as Weight)
+ // Standard Error: 27_000
+ .saturating_add((161_556_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64store(r: u32) -> Weight {
+ (26_499_000 as Weight)
+ // Standard Error: 59_000
+ .saturating_add((233_755_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_select(r: u32) -> Weight {
+ (24_175_000 as Weight)
+ // Standard Error: 16_000
+ .saturating_add((12_450_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_if(r: u32) -> Weight {
+ (24_219_000 as Weight)
+ // Standard Error: 26_000
+ .saturating_add((12_058_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_br(r: u32) -> Weight {
+ (24_146_000 as Weight)
+ // Standard Error: 20_000
+ .saturating_add((6_017_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_br_if(r: u32) -> Weight {
+ (24_229_000 as Weight)
+ // Standard Error: 24_000
+ .saturating_add((13_726_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_br_table(r: u32) -> Weight {
+ (24_219_000 as Weight)
+ // Standard Error: 27_000
+ .saturating_add((15_115_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_br_table_per_entry(e: u32) -> Weight {
+ (34_981_000 as Weight)
+ // Standard Error: 1_000
+ .saturating_add((156_000 as Weight).saturating_mul(e as Weight))
+ }
+ fn instr_call(r: u32) -> Weight {
+ (24_599_000 as Weight)
+ // Standard Error: 102_000
+ .saturating_add((95_771_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_call_indirect(r: u32) -> Weight {
+ (32_584_000 as Weight)
+ // Standard Error: 176_000
+ .saturating_add((193_216_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_call_indirect_per_param(p: u32) -> Weight {
+ (240_739_000 as Weight)
+ // Standard Error: 6_000
+ .saturating_add((3_407_000 as Weight).saturating_mul(p as Weight))
+ }
+ fn instr_local_get(r: u32) -> Weight {
+ (41_963_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((3_110_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_local_set(r: u32) -> Weight {
+ (41_956_000 as Weight)
+ // Standard Error: 9_000
+ .saturating_add((3_460_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_local_tee(r: u32) -> Weight {
+ (42_002_000 as Weight)
+ // Standard Error: 20_000
+ .saturating_add((4_591_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_global_get(r: u32) -> Weight {
+ (27_646_000 as Weight)
+ // Standard Error: 23_000
+ .saturating_add((7_821_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_global_set(r: u32) -> Weight {
+ (27_615_000 as Weight)
+ // Standard Error: 27_000
+ .saturating_add((11_807_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_memory_current(r: u32) -> Weight {
+ (27_106_000 as Weight)
+ // Standard Error: 78_000
+ .saturating_add((2_952_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_memory_grow(r: u32) -> Weight {
+ (24_956_000 as Weight)
+ // Standard Error: 3_541_000
+ .saturating_add((2_332_414_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64clz(r: u32) -> Weight {
+ (24_183_000 as Weight)
+ // Standard Error: 18_000
+ .saturating_add((5_166_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64ctz(r: u32) -> Weight {
+ (24_142_000 as Weight)
+ // Standard Error: 17_000
+ .saturating_add((5_146_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64popcnt(r: u32) -> Weight {
+ (24_161_000 as Weight)
+ // Standard Error: 23_000
+ .saturating_add((5_807_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64eqz(r: u32) -> Weight {
+ (24_167_000 as Weight)
+ // Standard Error: 24_000
+ .saturating_add((5_288_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64extendsi32(r: u32) -> Weight {
+ (24_252_000 as Weight)
+ // Standard Error: 9_000
+ .saturating_add((5_091_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64extendui32(r: u32) -> Weight {
+ (24_243_000 as Weight)
+ // Standard Error: 16_000
+ .saturating_add((5_076_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i32wrapi64(r: u32) -> Weight {
+ (24_227_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((5_135_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64eq(r: u32) -> Weight {
+ (24_278_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((7_124_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64ne(r: u32) -> Weight {
+ (24_254_000 as Weight)
+ // Standard Error: 19_000
+ .saturating_add((7_067_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64lts(r: u32) -> Weight {
+ (24_220_000 as Weight)
+ // Standard Error: 14_000
+ .saturating_add((7_122_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64ltu(r: u32) -> Weight {
+ (24_221_000 as Weight)
+ // Standard Error: 19_000
+ .saturating_add((7_221_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64gts(r: u32) -> Weight {
+ (24_259_000 as Weight)
+ // Standard Error: 13_000
+ .saturating_add((7_135_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64gtu(r: u32) -> Weight {
+ (24_245_000 as Weight)
+ // Standard Error: 10_000
+ .saturating_add((7_193_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64les(r: u32) -> Weight {
+ (24_289_000 as Weight)
+ // Standard Error: 22_000
+ .saturating_add((7_023_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64leu(r: u32) -> Weight {
+ (24_239_000 as Weight)
+ // Standard Error: 21_000
+ .saturating_add((7_065_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64ges(r: u32) -> Weight {
+ (24_256_000 as Weight)
+ // Standard Error: 13_000
+ .saturating_add((7_119_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64geu(r: u32) -> Weight {
+ (24_240_000 as Weight)
+ // Standard Error: 18_000
+ .saturating_add((7_225_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64add(r: u32) -> Weight {
+ (24_266_000 as Weight)
+ // Standard Error: 24_000
+ .saturating_add((6_996_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64sub(r: u32) -> Weight {
+ (24_265_000 as Weight)
+ // Standard Error: 17_000
+ .saturating_add((6_974_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64mul(r: u32) -> Weight {
+ (24_232_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((7_103_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64divs(r: u32) -> Weight {
+ (24_245_000 as Weight)
+ // Standard Error: 20_000
+ .saturating_add((12_915_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64divu(r: u32) -> Weight {
+ (24_177_000 as Weight)
+ // Standard Error: 21_000
+ .saturating_add((12_232_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64rems(r: u32) -> Weight {
+ (24_171_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((12_939_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64remu(r: u32) -> Weight {
+ (24_788_000 as Weight)
+ // Standard Error: 22_000
+ .saturating_add((11_657_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64and(r: u32) -> Weight {
+ (24_252_000 as Weight)
+ // Standard Error: 19_000
+ .saturating_add((7_003_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64or(r: u32) -> Weight {
+ (24_263_000 as Weight)
+ // Standard Error: 12_000
+ .saturating_add((7_005_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64xor(r: u32) -> Weight {
+ (24_239_000 as Weight)
+ // Standard Error: 17_000
+ .saturating_add((7_020_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64shl(r: u32) -> Weight {
+ (24_212_000 as Weight)
+ // Standard Error: 13_000
+ .saturating_add((7_172_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64shrs(r: u32) -> Weight {
+ (24_220_000 as Weight)
+ // Standard Error: 27_000
+ .saturating_add((7_246_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64shru(r: u32) -> Weight {
+ (24_213_000 as Weight)
+ // Standard Error: 14_000
+ .saturating_add((7_191_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64rotl(r: u32) -> Weight {
+ (24_221_000 as Weight)
+ // Standard Error: 18_000
+ .saturating_add((7_192_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64rotr(r: u32) -> Weight {
+ (24_235_000 as Weight)
+ // Standard Error: 12_000
+ .saturating_add((7_106_000 as Weight).saturating_mul(r as Weight))
+ }
+}
+
+// For backwards compatibility and tests
+impl WeightInfo for () {
+ fn on_initialize() -> Weight {
+ (3_850_000 as Weight).saturating_add(RocksDbWeight::get().reads(1 as Weight))
+ }
+ fn on_initialize_per_trie_key(k: u32) -> Weight {
+ (52_925_000 as Weight)
+ // Standard Error: 5_000
+ .saturating_add((2_297_000 as Weight).saturating_mul(k as Weight))
+ .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight)))
+ }
+ fn on_initialize_per_queue_item(q: u32) -> Weight {
+ (434_698_000 as Weight)
+ // Standard Error: 210_000
+ .saturating_add((166_559_000 as Weight).saturating_mul(q as Weight))
+ .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ }
+ fn instrument(c: u32) -> Weight {
+ (29_918_000 as Weight)
+ // Standard Error: 185_000
+ .saturating_add((123_774_000 as Weight).saturating_mul(c as Weight))
+ .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ }
+ fn update_schedule() -> Weight {
+ (29_795_000 as Weight)
+ .saturating_add(RocksDbWeight::get().reads(1 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ }
+ fn instantiate_with_code(c: u32, s: u32) -> Weight {
+ (225_834_000 as Weight)
+ // Standard Error: 144_000
+ .saturating_add((165_632_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 9_000
+ .saturating_add((2_563_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(RocksDbWeight::get().reads(6 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(5 as Weight))
+ }
+ fn instantiate(c: u32, s: u32) -> Weight {
+ (190_482_000 as Weight)
+ // Standard Error: 12_000
+ .saturating_add((8_724_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 1_000
+ .saturating_add((2_512_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(RocksDbWeight::get().reads(6 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(4 as Weight))
+ }
+ fn call(c: u32) -> Weight {
+ (195_414_000 as Weight)
+ // Standard Error: 2_000
+ .saturating_add((3_920_000 as Weight).saturating_mul(c as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+ }
+ fn claim_surcharge(c: u32) -> Weight {
+ (336_867_000 as Weight)
+ // Standard Error: 10_000
+ .saturating_add((5_262_000 as Weight).saturating_mul(c as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(4 as Weight))
+ }
+ fn seal_caller(r: u32) -> Weight {
+ (143_935_000 as Weight)
+ // Standard Error: 128_000
+ .saturating_add((266_876_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_address(r: u32) -> Weight {
+ (150_342_000 as Weight)
+ // Standard Error: 127_000
+ .saturating_add((266_051_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_gas_left(r: u32) -> Weight {
+ (144_833_000 as Weight)
+ // Standard Error: 124_000
+ .saturating_add((259_279_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_balance(r: u32) -> Weight {
+ (152_032_000 as Weight)
+ // Standard Error: 218_000
+ .saturating_add((573_038_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_value_transferred(r: u32) -> Weight {
+ (148_831_000 as Weight)
+ // Standard Error: 147_000
+ .saturating_add((260_718_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_minimum_balance(r: u32) -> Weight {
+ (142_925_000 as Weight)
+ // Standard Error: 130_000
+ .saturating_add((260_426_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_tombstone_deposit(r: u32) -> Weight {
+ (143_151_000 as Weight)
+ // Standard Error: 119_000
+ .saturating_add((260_964_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_rent_allowance(r: u32) -> Weight {
+ (155_126_000 as Weight)
+ // Standard Error: 225_000
+ .saturating_add((599_056_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_block_number(r: u32) -> Weight {
+ (144_566_000 as Weight)
+ // Standard Error: 110_000
+ .saturating_add((257_620_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_now(r: u32) -> Weight {
+ (147_274_000 as Weight)
+ // Standard Error: 115_000
+ .saturating_add((258_627_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_rent_params(r: u32) -> Weight {
+ (168_575_000 as Weight)
+ // Standard Error: 394_000
+ .saturating_add((397_754_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_weight_to_fee(r: u32) -> Weight {
+ (148_102_000 as Weight)
+ // Standard Error: 201_000
+ .saturating_add((537_088_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(6 as Weight))
+ }
+ fn seal_gas(r: u32) -> Weight {
+ (125_122_000 as Weight)
+ // Standard Error: 89_000
+ .saturating_add((122_350_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_input(r: u32) -> Weight {
+ (137_334_000 as Weight)
+ // Standard Error: 99_000
+ .saturating_add((7_359_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_input_per_kb(n: u32) -> Weight {
+ (145_094_000 as Weight)
+ // Standard Error: 0
+ .saturating_add((283_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_return(r: u32) -> Weight {
+ (127_544_000 as Weight)
+ // Standard Error: 138_000
+ .saturating_add((4_640_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_return_per_kb(n: u32) -> Weight {
+ (137_517_000 as Weight)
+ // Standard Error: 0
+ .saturating_add((783_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_terminate(r: u32) -> Weight {
+ (138_292_000 as Weight)
+ // Standard Error: 689_000
+ .saturating_add((111_698_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(r as Weight)))
+ .saturating_add(RocksDbWeight::get().writes((5 as Weight).saturating_mul(r as Weight)))
+ }
+ fn seal_terminate_per_code_kb(c: u32) -> Weight {
+ (263_507_000 as Weight)
+ // Standard Error: 12_000
+ .saturating_add((8_409_000 as Weight).saturating_mul(c as Weight))
+ .saturating_add(RocksDbWeight::get().reads(7 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(5 as Weight))
+ }
+ fn seal_restore_to(r: u32) -> Weight {
+ (232_291_000 as Weight)
+ // Standard Error: 301_000
+ .saturating_add((136_379_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(r as Weight)))
+ .saturating_add(RocksDbWeight::get().writes((6 as Weight).saturating_mul(r as Weight)))
+ }
+ fn seal_restore_to_per_code_kb_delta(c: u32, t: u32, d: u32) -> Weight {
+ (0 as Weight)
+ // Standard Error: 162_000
+ .saturating_add((8_619_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 162_000
+ .saturating_add((4_877_000 as Weight).saturating_mul(t as Weight))
+ // Standard Error: 1_433_000
+ .saturating_add((3_762_810_000 as Weight).saturating_mul(d as Weight))
+ .saturating_add(RocksDbWeight::get().reads(8 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(d as Weight)))
+ .saturating_add(RocksDbWeight::get().writes(7 as Weight))
+ .saturating_add(
+ RocksDbWeight::get().writes((100 as Weight).saturating_mul(d as Weight)),
+ )
+ }
+ fn seal_random(r: u32) -> Weight {
+ (153_634_000 as Weight)
+ // Standard Error: 267_000
+ .saturating_add((650_160_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(6 as Weight))
+ }
+ fn seal_deposit_event(r: u32) -> Weight {
+ (137_080_000 as Weight)
+ // Standard Error: 1_009_000
+ .saturating_add((949_228_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32) -> Weight {
+ (1_259_129_000 as Weight)
+ // Standard Error: 2_542_000
+ .saturating_add((609_859_000 as Weight).saturating_mul(t as Weight))
+ // Standard Error: 501_000
+ .saturating_add((249_496_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(t as Weight)))
+ .saturating_add(
+ RocksDbWeight::get().writes((100 as Weight).saturating_mul(t as Weight)),
+ )
+ }
+ fn seal_set_rent_allowance(r: u32) -> Weight {
+ (170_417_000 as Weight)
+ // Standard Error: 434_000
+ .saturating_add((721_511_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ }
+ fn seal_set_storage(r: u32) -> Weight {
+ (1_870_542_000 as Weight)
+ // Standard Error: 26_871_000
+ .saturating_add((18_312_239_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight)))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ .saturating_add(
+ RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight)),
+ )
+ }
+ fn seal_set_storage_per_kb(n: u32) -> Weight {
+ (1_763_732_000 as Weight)
+ // Standard Error: 258_000
+ .saturating_add((74_848_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(6 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+ }
+ fn seal_clear_storage(r: u32) -> Weight {
+ (0 as Weight)
+ // Standard Error: 2_745_000
+ .saturating_add((2_316_433_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight)))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ .saturating_add(
+ RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight)),
+ )
+ }
+ fn seal_get_storage(r: u32) -> Weight {
+ (87_218_000 as Weight)
+ // Standard Error: 745_000
+ .saturating_add((948_121_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight)))
+ }
+ fn seal_get_storage_per_kb(n: u32) -> Weight {
+ (719_050_000 as Weight)
+ // Standard Error: 266_000
+ .saturating_add((154_812_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(6 as Weight))
+ }
+ fn seal_transfer(r: u32) -> Weight {
+ (19_439_000 as Weight)
+ // Standard Error: 2_468_000
+ .saturating_add((5_674_822_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight)))
+ .saturating_add(RocksDbWeight::get().writes(1 as Weight))
+ .saturating_add(
+ RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight)),
+ )
+ }
+ fn seal_call(r: u32) -> Weight {
+ (0 as Weight)
+ // Standard Error: 7_465_000
+ .saturating_add((11_066_530_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(6 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((200 as Weight).saturating_mul(r as Weight)))
+ }
+ fn seal_call_per_code_transfer_input_output_kb(c: u32, t: u32, i: u32, o: u32) -> Weight {
+ (9_916_288_000 as Weight)
+ // Standard Error: 552_000
+ .saturating_add((397_842_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 229_902_000
+ .saturating_add((5_243_673_000 as Weight).saturating_mul(t as Weight))
+ // Standard Error: 72_000
+ .saturating_add((59_737_000 as Weight).saturating_mul(i as Weight))
+ // Standard Error: 77_000
+ .saturating_add((82_259_000 as Weight).saturating_mul(o as Weight))
+ .saturating_add(RocksDbWeight::get().reads(206 as Weight))
+ .saturating_add(
+ RocksDbWeight::get().writes((101 as Weight).saturating_mul(t as Weight)),
+ )
+ }
+ fn seal_instantiate(r: u32) -> Weight {
+ (0 as Weight)
+ // Standard Error: 32_016_000
+ .saturating_add((22_206_489_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(6 as Weight))
+ .saturating_add(RocksDbWeight::get().reads((300 as Weight).saturating_mul(r as Weight)))
+ .saturating_add(RocksDbWeight::get().writes(2 as Weight))
+ .saturating_add(
+ RocksDbWeight::get().writes((300 as Weight).saturating_mul(r as Weight)),
+ )
+ }
+ fn seal_instantiate_per_code_input_output_salt_kb(c: u32, i: u32, o: u32, s: u32) -> Weight {
+ (9_991_947_000 as Weight)
+ // Standard Error: 637_000
+ .saturating_add((881_981_000 as Weight).saturating_mul(c as Weight))
+ // Standard Error: 90_000
+ .saturating_add((63_638_000 as Weight).saturating_mul(i as Weight))
+ // Standard Error: 90_000
+ .saturating_add((87_288_000 as Weight).saturating_mul(o as Weight))
+ // Standard Error: 90_000
+ .saturating_add((311_808_000 as Weight).saturating_mul(s as Weight))
+ .saturating_add(RocksDbWeight::get().reads(207 as Weight))
+ .saturating_add(RocksDbWeight::get().writes(203 as Weight))
+ }
+ fn seal_hash_sha2_256(r: u32) -> Weight {
+ (132_452_000 as Weight)
+ // Standard Error: 227_000
+ .saturating_add((239_671_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_sha2_256_per_kb(n: u32) -> Weight {
+ (756_802_000 as Weight)
+ // Standard Error: 48_000
+ .saturating_add((429_454_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_keccak_256(r: u32) -> Weight {
+ (139_440_000 as Weight)
+ // Standard Error: 128_000
+ .saturating_add((249_514_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_keccak_256_per_kb(n: u32) -> Weight {
+ (658_595_000 as Weight)
+ // Standard Error: 35_000
+ .saturating_add((343_814_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_blake2_256(r: u32) -> Weight {
+ (138_124_000 as Weight)
+ // Standard Error: 140_000
+ .saturating_add((223_189_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_blake2_256_per_kb(n: u32) -> Weight {
+ (689_667_000 as Weight)
+ // Standard Error: 41_000
+ .saturating_add((160_006_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_blake2_128(r: u32) -> Weight {
+ (140_225_000 as Weight)
+ // Standard Error: 156_000
+ .saturating_add((223_696_000 as Weight).saturating_mul(r as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn seal_hash_blake2_128_per_kb(n: u32) -> Weight {
+ (693_756_000 as Weight)
+ // Standard Error: 40_000
+ .saturating_add((159_996_000 as Weight).saturating_mul(n as Weight))
+ .saturating_add(RocksDbWeight::get().reads(5 as Weight))
+ }
+ fn instr_i64const(r: u32) -> Weight {
+ (24_250_000 as Weight)
+ // Standard Error: 14_000
+ .saturating_add((3_134_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64load(r: u32) -> Weight {
+ (26_509_000 as Weight)
+ // Standard Error: 27_000
+ .saturating_add((161_556_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64store(r: u32) -> Weight {
+ (26_499_000 as Weight)
+ // Standard Error: 59_000
+ .saturating_add((233_755_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_select(r: u32) -> Weight {
+ (24_175_000 as Weight)
+ // Standard Error: 16_000
+ .saturating_add((12_450_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_if(r: u32) -> Weight {
+ (24_219_000 as Weight)
+ // Standard Error: 26_000
+ .saturating_add((12_058_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_br(r: u32) -> Weight {
+ (24_146_000 as Weight)
+ // Standard Error: 20_000
+ .saturating_add((6_017_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_br_if(r: u32) -> Weight {
+ (24_229_000 as Weight)
+ // Standard Error: 24_000
+ .saturating_add((13_726_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_br_table(r: u32) -> Weight {
+ (24_219_000 as Weight)
+ // Standard Error: 27_000
+ .saturating_add((15_115_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_br_table_per_entry(e: u32) -> Weight {
+ (34_981_000 as Weight)
+ // Standard Error: 1_000
+ .saturating_add((156_000 as Weight).saturating_mul(e as Weight))
+ }
+ fn instr_call(r: u32) -> Weight {
+ (24_599_000 as Weight)
+ // Standard Error: 102_000
+ .saturating_add((95_771_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_call_indirect(r: u32) -> Weight {
+ (32_584_000 as Weight)
+ // Standard Error: 176_000
+ .saturating_add((193_216_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_call_indirect_per_param(p: u32) -> Weight {
+ (240_739_000 as Weight)
+ // Standard Error: 6_000
+ .saturating_add((3_407_000 as Weight).saturating_mul(p as Weight))
+ }
+ fn instr_local_get(r: u32) -> Weight {
+ (41_963_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((3_110_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_local_set(r: u32) -> Weight {
+ (41_956_000 as Weight)
+ // Standard Error: 9_000
+ .saturating_add((3_460_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_local_tee(r: u32) -> Weight {
+ (42_002_000 as Weight)
+ // Standard Error: 20_000
+ .saturating_add((4_591_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_global_get(r: u32) -> Weight {
+ (27_646_000 as Weight)
+ // Standard Error: 23_000
+ .saturating_add((7_821_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_global_set(r: u32) -> Weight {
+ (27_615_000 as Weight)
+ // Standard Error: 27_000
+ .saturating_add((11_807_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_memory_current(r: u32) -> Weight {
+ (27_106_000 as Weight)
+ // Standard Error: 78_000
+ .saturating_add((2_952_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_memory_grow(r: u32) -> Weight {
+ (24_956_000 as Weight)
+ // Standard Error: 3_541_000
+ .saturating_add((2_332_414_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64clz(r: u32) -> Weight {
+ (24_183_000 as Weight)
+ // Standard Error: 18_000
+ .saturating_add((5_166_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64ctz(r: u32) -> Weight {
+ (24_142_000 as Weight)
+ // Standard Error: 17_000
+ .saturating_add((5_146_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64popcnt(r: u32) -> Weight {
+ (24_161_000 as Weight)
+ // Standard Error: 23_000
+ .saturating_add((5_807_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64eqz(r: u32) -> Weight {
+ (24_167_000 as Weight)
+ // Standard Error: 24_000
+ .saturating_add((5_288_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64extendsi32(r: u32) -> Weight {
+ (24_252_000 as Weight)
+ // Standard Error: 9_000
+ .saturating_add((5_091_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64extendui32(r: u32) -> Weight {
+ (24_243_000 as Weight)
+ // Standard Error: 16_000
+ .saturating_add((5_076_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i32wrapi64(r: u32) -> Weight {
+ (24_227_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((5_135_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64eq(r: u32) -> Weight {
+ (24_278_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((7_124_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64ne(r: u32) -> Weight {
+ (24_254_000 as Weight)
+ // Standard Error: 19_000
+ .saturating_add((7_067_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64lts(r: u32) -> Weight {
+ (24_220_000 as Weight)
+ // Standard Error: 14_000
+ .saturating_add((7_122_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64ltu(r: u32) -> Weight {
+ (24_221_000 as Weight)
+ // Standard Error: 19_000
+ .saturating_add((7_221_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64gts(r: u32) -> Weight {
+ (24_259_000 as Weight)
+ // Standard Error: 13_000
+ .saturating_add((7_135_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64gtu(r: u32) -> Weight {
+ (24_245_000 as Weight)
+ // Standard Error: 10_000
+ .saturating_add((7_193_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64les(r: u32) -> Weight {
+ (24_289_000 as Weight)
+ // Standard Error: 22_000
+ .saturating_add((7_023_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64leu(r: u32) -> Weight {
+ (24_239_000 as Weight)
+ // Standard Error: 21_000
+ .saturating_add((7_065_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64ges(r: u32) -> Weight {
+ (24_256_000 as Weight)
+ // Standard Error: 13_000
+ .saturating_add((7_119_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64geu(r: u32) -> Weight {
+ (24_240_000 as Weight)
+ // Standard Error: 18_000
+ .saturating_add((7_225_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64add(r: u32) -> Weight {
+ (24_266_000 as Weight)
+ // Standard Error: 24_000
+ .saturating_add((6_996_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64sub(r: u32) -> Weight {
+ (24_265_000 as Weight)
+ // Standard Error: 17_000
+ .saturating_add((6_974_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64mul(r: u32) -> Weight {
+ (24_232_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((7_103_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64divs(r: u32) -> Weight {
+ (24_245_000 as Weight)
+ // Standard Error: 20_000
+ .saturating_add((12_915_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64divu(r: u32) -> Weight {
+ (24_177_000 as Weight)
+ // Standard Error: 21_000
+ .saturating_add((12_232_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64rems(r: u32) -> Weight {
+ (24_171_000 as Weight)
+ // Standard Error: 15_000
+ .saturating_add((12_939_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64remu(r: u32) -> Weight {
+ (24_788_000 as Weight)
+ // Standard Error: 22_000
+ .saturating_add((11_657_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64and(r: u32) -> Weight {
+ (24_252_000 as Weight)
+ // Standard Error: 19_000
+ .saturating_add((7_003_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64or(r: u32) -> Weight {
+ (24_263_000 as Weight)
+ // Standard Error: 12_000
+ .saturating_add((7_005_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64xor(r: u32) -> Weight {
+ (24_239_000 as Weight)
+ // Standard Error: 17_000
+ .saturating_add((7_020_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64shl(r: u32) -> Weight {
+ (24_212_000 as Weight)
+ // Standard Error: 13_000
+ .saturating_add((7_172_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64shrs(r: u32) -> Weight {
+ (24_220_000 as Weight)
+ // Standard Error: 27_000
+ .saturating_add((7_246_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64shru(r: u32) -> Weight {
+ (24_213_000 as Weight)
+ // Standard Error: 14_000
+ .saturating_add((7_191_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64rotl(r: u32) -> Weight {
+ (24_221_000 as Weight)
+ // Standard Error: 18_000
+ .saturating_add((7_192_000 as Weight).saturating_mul(r as Weight))
+ }
+ fn instr_i64rotr(r: u32) -> Weight {
+ (24_235_000 as Weight)
+ // Standard Error: 12_000
+ .saturating_add((7_106_000 as Weight).saturating_mul(r as Weight))
+ }
+}
pallets/nft/src/lib.rsdiffbeforeafterboth--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -175,7 +175,7 @@
BadCreateRefungibleCall,
/// Gas limit exceeded
OutOfGas,
- /// Metadata update denied by collection settings
+ /// Metadata update denied by collection settings
MetadataUpdateDenied,
/// Metadata update flag become unmutable with None option
MetadataFlagFrozen,
@@ -951,7 +951,7 @@
Error::<T>::MetadataFlagFrozen
);
Self::check_owner_permissions(&target_collection, &sender)?;
-
+
target_collection.meta_update_permission = value;
Self::save_collection(target_collection);
@@ -1531,27 +1531,27 @@
Ok(())
}
- pub fn meta_update_check(
+ pub fn meta_update_check(
sender: &T::CrossAccountId,
collection: &CollectionHandle<T>,
- item_id: TokenId) -> DispatchResult {
-
+ item_id: TokenId,
+ ) -> DispatchResult {
match collection.meta_update_permission {
MetaUpdatePermission::ItemOwner => {
ensure!(
- Self::is_item_owner(sender, collection, item_id),
+ Self::is_item_owner(sender, collection, item_id),
Error::<T>::NoPermission
);
- },
+ }
MetaUpdatePermission::Admin => {
ensure!(
- Self::is_owner_or_admin_permissions(collection, sender),
+ Self::is_owner_or_admin_permissions(collection, sender),
Error::<T>::NoPermission
);
- },
+ }
MetaUpdatePermission::None => {
fail!(Error::<T>::MetadataUpdateDenied);
- },
+ }
}
Ok(())
pallets/nft/src/tests.rsdiffbeforeafterboth--- a/pallets/nft/src/tests.rs
+++ b/pallets/nft/src/tests.rs
@@ -2364,7 +2364,11 @@
let data = default_nft_data();
create_test_item(1, &data.into());
- TemplateModule::set_meta_update_permission_flag(origin1.clone(), collection_id, MetaUpdatePermission::ItemOwner);
+ TemplateModule::set_meta_update_permission_flag(
+ origin1.clone(),
+ collection_id,
+ MetaUpdatePermission::ItemOwner,
+ );
let variable_data = b"test set_variable_meta_data method.".to_vec();
assert_ok!(TemplateModule::set_variable_meta_data(
@@ -2407,15 +2411,21 @@
let data = default_nft_data();
create_test_item(1, &data.into());
- TemplateModule::set_meta_update_permission_flag(origin2.clone(), collection_id, MetaUpdatePermission::ItemOwner);
+ TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::ItemOwner,
+ );
let variable_data = b"test set_variable_meta_data method.".to_vec();
- assert_noop!(TemplateModule::set_variable_meta_data(
- origin2,
- collection_id,
- 1,
- variable_data.clone()),
- Error::<Test>::NoPermission
+ assert_noop!(
+ TemplateModule::set_variable_meta_data(
+ origin2,
+ collection_id,
+ 1,
+ variable_data.clone()
+ ),
+ Error::<Test>::NoPermission
);
});
}
@@ -2450,7 +2460,11 @@
let data = default_nft_data();
create_test_item(1, &data.into());
- TemplateModule::set_meta_update_permission_flag(origin2.clone(), collection_id, MetaUpdatePermission::Admin);
+ TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::Admin,
+ );
let variable_data = b"test set_variable_meta_data method.".to_vec();
assert_ok!(TemplateModule::set_variable_meta_data(
@@ -2493,15 +2507,21 @@
let data = default_nft_data();
create_test_item(1, &data.into());
- TemplateModule::set_meta_update_permission_flag(origin2.clone(), collection_id, MetaUpdatePermission::Admin);
+ TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::Admin,
+ );
let variable_data = b"test set_variable_meta_data method.".to_vec();
- assert_noop!(TemplateModule::set_variable_meta_data(
- origin1,
- collection_id,
- 1,
- variable_data.clone()),
- Error::<Test>::NoPermission
+ assert_noop!(
+ TemplateModule::set_variable_meta_data(
+ origin1,
+ collection_id,
+ 1,
+ variable_data.clone()
+ ),
+ Error::<Test>::NoPermission
);
});
}
@@ -2515,12 +2535,18 @@
let origin2 = Origin::signed(2);
- TemplateModule::set_meta_update_permission_flag(origin2.clone(), collection_id, MetaUpdatePermission::None);
- assert_noop!(TemplateModule::set_meta_update_permission_flag(
+ TemplateModule::set_meta_update_permission_flag(
origin2.clone(),
collection_id,
- MetaUpdatePermission::Admin),
- Error::<Test>::MetadataFlagFrozen
+ MetaUpdatePermission::None,
+ );
+ assert_noop!(
+ TemplateModule::set_meta_update_permission_flag(
+ origin2.clone(),
+ collection_id,
+ MetaUpdatePermission::Admin
+ ),
+ Error::<Test>::MetadataFlagFrozen
);
});
}
@@ -2536,15 +2562,21 @@
let data = default_nft_data();
create_test_item(1, &data.into());
- TemplateModule::set_meta_update_permission_flag(origin1.clone(), collection_id, MetaUpdatePermission::None);
+ TemplateModule::set_meta_update_permission_flag(
+ origin1.clone(),
+ collection_id,
+ MetaUpdatePermission::None,
+ );
let variable_data = b"test set_variable_meta_data method.".to_vec();
- assert_noop!(TemplateModule::set_variable_meta_data(
- origin1.clone(),
- collection_id,
- 1,
- variable_data.clone()),
- Error::<Test>::MetadataUpdateDenied
+ assert_noop!(
+ TemplateModule::set_variable_meta_data(
+ origin1.clone(),
+ collection_id,
+ 1,
+ variable_data.clone()
+ ),
+ Error::<Test>::MetadataUpdateDenied
);
});
-}
\ No newline at end of file
+}
primitives/nft/src/lib.rsdiffbeforeafterboth--- a/primitives/nft/src/lib.rs
+++ b/primitives/nft/src/lib.rs
@@ -242,7 +242,7 @@
#[derive(Encode, Decode, Debug, Clone, PartialEq)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub enum MetaUpdatePermission {
+pub enum MetaUpdatePermission {
ItemOwner,
Admin,
None,
tests/src/change-collection-owner.test.tsdiffbeforeafterboth--- a/tests/src/change-collection-owner.test.ts
+++ b/tests/src/change-collection-owner.test.ts
@@ -7,7 +7,7 @@
import chaiAsPromised from 'chai-as-promised';
import privateKey from './substrate/privateKey';
import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from './substrate/substrate-api';
-import { createCollectionExpectSuccess, normalizeAccountId } from './util/helpers';
+import { createCollectionExpectSuccess } from './util/helpers';
chai.use(chaiAsPromised);
const expect = chai.expect;
tests/src/metadataUpdate.test.tsdiffbeforeafterboth--- a/tests/src/metadataUpdate.test.ts
+++ b/tests/src/metadataUpdate.test.ts
@@ -17,7 +17,7 @@
addToWhiteListExpectSuccess,
addCollectionAdminExpectSuccess,
setVariableMetaDataExpectFailure,
- setMetadataUpdatePermissionFlagExpectFailure
+ setMetadataUpdatePermissionFlagExpectFailure,
} from './util/helpers';
chai.use(chaiAsPromised);
@@ -26,14 +26,13 @@
it('Set variable metadata with ItemOwner permission flag', async () => {
await usingApi(async () => {
const Alice = privateKey('//Alice');
- const Bob = privateKey('//Bob');
const data = [1, 2, 254, 255];
// nft
const nftCollectionId = await createCollectionExpectSuccess();
const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
- await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, "ItemOwner");
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'ItemOwner');
await setVariableMetaDataExpectSuccess(Alice, nftCollectionId, newNftTokenId, data);
});
@@ -41,71 +40,67 @@
it('User can\'n set variable metadata with ItemOwner permission flag', async () => {
await usingApi(async () => {
- const Alice = privateKey('//Alice');
- const Bob = privateKey('//Bob');
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
- const data = [1, 2, 254, 255];
+ const data = [1, 2, 254, 255];
- // nft
- const nftCollectionId = await createCollectionExpectSuccess();
- const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
- await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, "ItemOwner");
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'ItemOwner');
- await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
- await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
- await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+ await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+ await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+ await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
- await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
+ await setVariableMetaDataExpectFailure(Bob, nftCollectionId, newNftTokenId, data);
});
});
it('Admin can set variable metadata with Admin permission flag', async () => {
await usingApi(async () => {
- const Alice = privateKey('//Alice');
- const Bob = privateKey('//Bob');
+ const Alice = privateKey('//Alice');
+ const Bob = privateKey('//Bob');
- const data = [1, 2, 254, 255];
+ const data = [1, 2, 254, 255];
- // nft
- const nftCollectionId = await createCollectionExpectSuccess();
- const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
- await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, "Admin");
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'Admin');
- await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
- await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
- await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
+ await setMintPermissionExpectSuccess(Alice, nftCollectionId, true);
+ await addToWhiteListExpectSuccess(Alice, nftCollectionId, Bob.address);
+ await addCollectionAdminExpectSuccess(Alice, nftCollectionId, Bob);
- await setVariableMetaDataExpectSuccess(Bob, nftCollectionId, newNftTokenId, data);
+ await setVariableMetaDataExpectSuccess(Bob, nftCollectionId, newNftTokenId, data);
});
});
it('Nobody can set variable metadata with None flag', async () => {
await usingApi(async () => {
- const Alice = privateKey('//Alice');
- const Bob = privateKey('//Bob');
+ const Alice = privateKey('//Alice');
- const data = [1, 2, 254, 255];
+ const data = [1, 2, 254, 255];
- // nft
- const nftCollectionId = await createCollectionExpectSuccess();
- const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
- await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, "None");
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ const newNftTokenId = await createItemExpectSuccess(Alice, nftCollectionId, 'NFT');
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
- await setVariableMetaDataExpectFailure(Alice, nftCollectionId, newNftTokenId, data);
+ await setVariableMetaDataExpectFailure(Alice, nftCollectionId, newNftTokenId, data);
});
});
it('Nobody can set variable metadata flag after freeze', async () => {
await usingApi(async () => {
- const Alice = privateKey('//Alice');
- const Bob = privateKey('//Bob');
-
- const data = [1, 2, 254, 255];
+ const Alice = privateKey('//Alice');
- // nft
- const nftCollectionId = await createCollectionExpectSuccess();
- await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, "None");
- await setMetadataUpdatePermissionFlagExpectFailure(Alice, nftCollectionId, "Admin");
+ // nft
+ const nftCollectionId = await createCollectionExpectSuccess();
+ await setMetadataUpdatePermissionFlagExpectSuccess(Alice, nftCollectionId, 'None');
+ await setMetadataUpdatePermissionFlagExpectFailure(Alice, nftCollectionId, 'Admin');
});
});
tests/src/pallet-presence.test.tsdiffbeforeafterboth--- a/tests/src/pallet-presence.test.ts
+++ b/tests/src/pallet-presence.test.ts
@@ -32,14 +32,14 @@
'nft',
'scheduler',
'nftpayment',
- 'charging'
+ 'charging',
];
// Pallets that depend on consensus and governance configuration
const consensusPallets = [
'sudo',
'aura',
- 'auraext'
+ 'auraext',
];
describe('Pallet presence', () => {
tests/src/setConstOnChainSchema.test.tsdiffbeforeafterboth--- a/tests/src/setConstOnChainSchema.test.ts
+++ b/tests/src/setConstOnChainSchema.test.ts
@@ -11,7 +11,6 @@
import {
createCollectionExpectSuccess,
destroyCollectionExpectSuccess,
- normalizeAccountId,
} from './util/helpers';
chai.use(chaiAsPromised);
tests/src/setVariableOnChainSchema.test.tsdiffbeforeafterboth--- a/tests/src/setVariableOnChainSchema.test.ts
+++ b/tests/src/setVariableOnChainSchema.test.ts
@@ -11,7 +11,6 @@
import {
createCollectionExpectSuccess,
destroyCollectionExpectSuccess,
- normalizeAccountId,
} from './util/helpers';
chai.use(chaiAsPromised);
tests/src/substrate/substrate-api.tsdiffbeforeafterboth--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -48,12 +48,12 @@
console.warn = outFn;
try {
- await promisifySubstrate(api, async () => {
+ await promisifySubstrate(api, async () => {
if (api) {
await api.isReadyOrError;
result = await action(api);
}
- })();
+ })();
} finally {
await api.disconnect();
console.error = consoleErr;
tests/src/util/helpers.tsdiffbeforeafterboth--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -565,7 +565,7 @@
});
}
-export async function toggleContractWhitelistExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, value: boolean = true) {
+export async function toggleContractWhitelistExpectSuccess(sender: IKeyringPair, contractAddress: AccountId | string, value = true) {
await usingApi(async (api) => {
const tx = api.tx.nft.toggleContractWhiteList(contractAddress, value);
const events = await submitTransactionAsync(sender, tx);
@@ -771,7 +771,7 @@
recipient: IKeyringPair,
value: number | bigint = 1,
blockTimeMs: number,
- blockSchedule: number
+ blockSchedule: number,
) {
await usingApi(async (api: ApiPromise) => {
const blockNumber: number | undefined = await getBlockNumber(api);