mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-21 06:15:41 +00:00
contracts: switch to wasmi gas metering (#14084)
* upgrade to wasmi 0.29 * prepare cleanup * sync ref_time w engine from the stack frame * proc_macro: sync gas in host funcs save: compiles, only gas pushing left to macro WIP proc macro proc macro: done * clean benchmarks & schedule: w_base = w_i64const * scale gas values btw engine and gas meter * (re)instrumentation & code_cache removed * remove gas() host fn, continue clean-up save * address review comments * move from CodeStorage&PrefabWasmModule to PristineCode&WasmBlob * refactor: no reftime_limit&schedule passes, no CodeStorage * bugs fixing * fix tests: expected deposit amount * fix prepare::tests * update tests and fix bugs tests::run_out_of_gas_engine, need 2 more save: 2 bugs with gas syncs: 1 of 2 tests done gas_syncs_no_overcharge bug fixed, test passes! cleaned out debug prints second bug is not a bug disabled_chain_extension test fix (err msg) tests run_out_of_fuel_host, chain_extension pass all tests pass * update docs * bump wasmi 0.30.0 * benchmarks updated, tests pass * refactoring * s/OwnerInfo/CodeInfo/g; * migration: draft, compiles * migration: draft, runs * migration: draft, runs (fixing) * deposits repaid non pro rata * deposits repaid pro rata * better try-runtime output * even better try-runtime output * benchmark migration * fix merge leftover * add forgotten fixtures, fix docs * address review comments * ci fixes * cleanup * benchmarks::prepare to return DispatchError * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * store memory limits to CodeInfo * ci: roll back weights * ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts * drive-by: update Readme and pallet rustdoc * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * use wasmi 0.29 * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * use wasmi 0.30 again * query memory limits from wasmi * better migration types * ci: pull weights from master * refactoring * ".git/.scripts/commands/bench-vm/bench-vm.sh" pallet dev pallet_contracts * addressing review comments * refactor * address review comments * optimize migration * ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts * another review round comments addressed * ci fix one * clippy fix * ci fix two --------- Co-authored-by: command-bot <>
This commit is contained in:
@@ -30,7 +30,7 @@
|
||||
//! 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.
|
||||
//! The smart-contract code is stored once, 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.
|
||||
//!
|
||||
@@ -41,14 +41,14 @@
|
||||
//! Finally, when an account is reaped, its associated code and storage of the smart-contract
|
||||
//! account will also be deleted.
|
||||
//!
|
||||
//! ### Gas
|
||||
//! ### Weight
|
||||
//!
|
||||
//! 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.
|
||||
//! Senders must specify a [`Weight`] limit with every call, as all instructions invoked by the
|
||||
//! smart-contract require weight. Unused weight 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
|
||||
//! If the weight 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.
|
||||
//!
|
||||
@@ -63,22 +63,25 @@
|
||||
//!
|
||||
//! ### Dispatchable functions
|
||||
//!
|
||||
//! * [`Pallet::instantiate_with_code`] - Deploys a new contract from the supplied wasm binary,
|
||||
//! * [`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::upload_code`] - Uploads new code without instantiating a contract from it.
|
||||
//! * [`Pallet::remove_code`] - Removes the stored code and refunds the deposit to its owner. Only
|
||||
//! allowed to code owner.
|
||||
//! * [`Pallet::set_code`] - Changes the code of an existing contract. Only allowed to `Root`
|
||||
//! origin.
|
||||
//! * [`Pallet::migrate`] - Runs migration steps of curent multi-block migration in priority, before
|
||||
//! [`Hooks::on_idle`][frame_support::traits::Hooks::on_idle] activates.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! The Contracts module is a work in progress. The following examples show how this module
|
||||
//! can be used to instantiate and call contracts.
|
||||
//!
|
||||
//! * [`ink!`](https://use.ink) is
|
||||
//! an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables writing
|
||||
//! WebAssembly based smart contracts in the Rust programming language.
|
||||
//! * [`ink!`](https://use.ink) is language that enables writing Wasm-based smart contracts in plain
|
||||
//! Rust.
|
||||
|
||||
#![allow(rustdoc::private_intra_doc_links)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
@@ -102,7 +105,7 @@ use crate::{
|
||||
exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Key, Stack as ExecStack},
|
||||
gas::GasMeter,
|
||||
storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
|
||||
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
|
||||
wasm::{CodeInfo, TryInstantiate, WasmBlob},
|
||||
};
|
||||
use codec::{Codec, Decode, Encode, HasCompact};
|
||||
use environmental::*;
|
||||
@@ -118,7 +121,7 @@ use frame_support::{
|
||||
ReservableCurrency, Time,
|
||||
},
|
||||
weights::Weight,
|
||||
BoundedVec, RuntimeDebugNoBound, WeakBoundedVec,
|
||||
BoundedVec, RuntimeDebugNoBound,
|
||||
};
|
||||
use frame_system::{ensure_signed, pallet_prelude::OriginFor, EventRecord, Pallet as System};
|
||||
use pallet_contracts_primitives::{
|
||||
@@ -149,7 +152,6 @@ type TrieId = BoundedVec<u8, ConstU32<128>>;
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type CodeVec<T> = BoundedVec<u8, <T as Config>::MaxCodeLen>;
|
||||
type RelaxedCodeVec<T> = WeakBoundedVec<u8, <T as Config>::MaxCodeLen>;
|
||||
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
|
||||
type DebugBufferVec<T> = BoundedVec<u8, <T as Config>::MaxDebugBufferLen>;
|
||||
type EventRecordOf<T> =
|
||||
@@ -184,7 +186,7 @@ pub mod pallet {
|
||||
|
||||
/// The current storage version.
|
||||
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(11);
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(12);
|
||||
|
||||
/// Hard coded storage version for running tests that depend on the current storage version.
|
||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
@@ -291,9 +293,7 @@ pub mod pallet {
|
||||
/// The address generator used to generate the addresses of contracts.
|
||||
type AddressGenerator: AddressGenerator<Self>;
|
||||
|
||||
/// 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.
|
||||
/// The maximum length of a contract code in bytes.
|
||||
///
|
||||
/// The value should be chosen carefully taking into the account the overall memory limit
|
||||
/// your runtime has, as well as the [maximum allowed callstack
|
||||
@@ -380,19 +380,19 @@ pub mod pallet {
|
||||
|
||||
// Check that given configured `MaxCodeLen`, runtime heap memory limit can't be broken.
|
||||
//
|
||||
// In worst case, the decoded wasm contract code would be `x16` times larger than the
|
||||
// In worst case, the decoded Wasm contract code would be `x16` times larger than the
|
||||
// encoded one. This is because even a single-byte wasm instruction has 16-byte size in
|
||||
// wasmi. This gives us `MaxCodeLen*16` safety margin.
|
||||
//
|
||||
// Next, the pallet keeps both the original and instrumented wasm blobs for each
|
||||
// contract, hence we add up `MaxCodeLen*2` more to the safety margin.
|
||||
// Next, the pallet keeps the Wasm blob for each
|
||||
// contract, hence we add up `MaxCodeLen` to the safety margin.
|
||||
//
|
||||
// Finally, the inefficiencies of the freeing-bump allocator
|
||||
// being used in the client for the runtime memory allocations, could lead to possible
|
||||
// memory allocations for contract code grow up to `x4` times in some extreme cases,
|
||||
// which gives us total multiplier of `18*4` for `MaxCodeLen`.
|
||||
// which gives us total multiplier of `17*4` for `MaxCodeLen`.
|
||||
//
|
||||
// That being said, for every contract executed in runtime, at least `MaxCodeLen*18*4`
|
||||
// That being said, for every contract executed in runtime, at least `MaxCodeLen*17*4`
|
||||
// memory should be available. Note that maximum allowed heap memory and stack size per
|
||||
// each contract (stack frame) should also be counted.
|
||||
//
|
||||
@@ -401,7 +401,7 @@ pub mod pallet {
|
||||
//
|
||||
// This gives us the following formula:
|
||||
//
|
||||
// `(MaxCodeLen * 18 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth <
|
||||
// `(MaxCodeLen * 17 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth <
|
||||
// max_runtime_mem/2`
|
||||
//
|
||||
// Hence the upper limit for the `MaxCodeLen` can be defined as follows:
|
||||
@@ -410,7 +410,7 @@ pub mod pallet {
|
||||
.saturating_div(max_call_depth)
|
||||
.saturating_sub(max_heap_size)
|
||||
.saturating_sub(MAX_STACK_SIZE)
|
||||
.saturating_div(18 * 4);
|
||||
.saturating_div(17 * 4);
|
||||
|
||||
assert!(
|
||||
T::MaxCodeLen::get() < code_len_limit,
|
||||
@@ -521,7 +521,7 @@ pub mod pallet {
|
||||
///
|
||||
/// If the code does not already exist a deposit is reserved from the caller
|
||||
/// and unreserved only when [`Self::remove_code`] is called. The size of the reserve
|
||||
/// depends on the instrumented size of the the supplied `code`.
|
||||
/// depends on the size of the supplied `code`.
|
||||
///
|
||||
/// If the code already exists in storage it will still return `Ok` and upgrades
|
||||
/// the in storage version to the current
|
||||
@@ -563,7 +563,7 @@ pub mod pallet {
|
||||
) -> DispatchResultWithPostInfo {
|
||||
Migration::<T>::ensure_migrated()?;
|
||||
let origin = ensure_signed(origin)?;
|
||||
<PrefabWasmModule<T>>::remove(&origin, code_hash)?;
|
||||
<WasmBlob<T>>::remove(&origin, code_hash)?;
|
||||
// we waive the fee because removing unused code is beneficial
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
@@ -594,8 +594,8 @@ pub mod pallet {
|
||||
} else {
|
||||
return Err(<Error<T>>::ContractNotFound.into())
|
||||
};
|
||||
<PrefabWasmModule<T>>::add_user(code_hash)?;
|
||||
<PrefabWasmModule<T>>::remove_user(contract.code_hash);
|
||||
<WasmBlob<T>>::increment_refcount(code_hash)?;
|
||||
<WasmBlob<T>>::decrement_refcount(contract.code_hash);
|
||||
Self::deposit_event(
|
||||
vec![T::Hashing::hash_of(&dest), code_hash, contract.code_hash],
|
||||
Event::ContractCodeUpdated {
|
||||
@@ -674,8 +674,7 @@ pub mod pallet {
|
||||
///
|
||||
/// Instantiation is executed as follows:
|
||||
///
|
||||
/// - The supplied `code` is instrumented, deployed, and a `code_hash` is created for that
|
||||
/// code.
|
||||
/// - The supplied `code` is 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.
|
||||
@@ -865,8 +864,8 @@ pub mod pallet {
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// A new schedule must have a greater version than the current one.
|
||||
InvalidScheduleVersion,
|
||||
/// Invalid schedule supplied, e.g. with zero weight of a basic operation.
|
||||
InvalidSchedule,
|
||||
/// Invalid combination of flags supplied to `seal_call` or `seal_delegate_call`.
|
||||
InvalidCallFlags,
|
||||
/// The executed contract exhausted its gas limit.
|
||||
@@ -929,7 +928,7 @@ pub mod pallet {
|
||||
/// or via RPC an `Ok` will be returned. In this case the caller needs to inspect the flags
|
||||
/// to determine whether a reversion has taken place.
|
||||
ContractReverted,
|
||||
/// The contract's code was found to be invalid during validation or instrumentation.
|
||||
/// The contract's code was found to be invalid during validation.
|
||||
///
|
||||
/// The most likely cause of this is that an API was used which is not supported by the
|
||||
/// node. This happens if an older node is used with a new version of ink!. Try updating
|
||||
@@ -946,18 +945,13 @@ pub mod pallet {
|
||||
NoMigrationPerformed,
|
||||
}
|
||||
|
||||
/// A mapping from an original code hash to the original code, untouched by instrumentation.
|
||||
/// A mapping from a contract's code hash to its code.
|
||||
#[pallet::storage]
|
||||
pub(crate) type PristineCode<T: Config> = StorageMap<_, Identity, CodeHash<T>, CodeVec<T>>;
|
||||
|
||||
/// A mapping between an original code hash and instrumented wasm code, ready for execution.
|
||||
/// A mapping from a contract's code hash to its code info.
|
||||
#[pallet::storage]
|
||||
pub(crate) type CodeStorage<T: Config> =
|
||||
StorageMap<_, Identity, CodeHash<T>, PrefabWasmModule<T>>;
|
||||
|
||||
/// A mapping between an original code hash and its owner information.
|
||||
#[pallet::storage]
|
||||
pub(crate) type OwnerInfoOf<T: Config> = StorageMap<_, Identity, CodeHash<T>, OwnerInfo<T>>;
|
||||
pub(crate) type CodeInfoOf<T: Config> = StorageMap<_, Identity, CodeHash<T>, CodeInfo<T>>;
|
||||
|
||||
/// This is a **monotonic** counter incremented on contract instantiation.
|
||||
///
|
||||
@@ -1196,7 +1190,7 @@ impl<T: Config> Invokable<T> for CallInput<T> {
|
||||
},
|
||||
};
|
||||
let schedule = T::Schedule::get();
|
||||
let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
|
||||
let result = ExecStack::<T, WasmBlob<T>>::run_call(
|
||||
origin.clone(),
|
||||
dest.clone(),
|
||||
&mut gas_meter,
|
||||
@@ -1239,7 +1233,7 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
|
||||
let origin = contract_origin.account_id()?;
|
||||
let (extra_deposit, executable) = match &self.code {
|
||||
Code::Upload(binary) => {
|
||||
let executable = PrefabWasmModule::from_code(
|
||||
let executable = WasmBlob::from_code(
|
||||
binary.clone(),
|
||||
&schedule,
|
||||
origin.clone(),
|
||||
@@ -1257,12 +1251,10 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
|
||||
// uploaded module does not already exist. This deposit is not part of the
|
||||
// storage meter because it is not transferred to the contract but
|
||||
// reserved on the uploading account.
|
||||
(executable.open_deposit(), executable)
|
||||
(executable.open_deposit(&executable.code_info()), executable)
|
||||
},
|
||||
Code::Existing(hash) => (
|
||||
Default::default(),
|
||||
PrefabWasmModule::from_storage(*hash, &schedule, &mut gas_meter)?,
|
||||
),
|
||||
Code::Existing(hash) =>
|
||||
(Default::default(), WasmBlob::from_storage(*hash, &mut gas_meter)?),
|
||||
};
|
||||
let contract_origin = Origin::from_account_id(origin.clone());
|
||||
let mut storage_meter = StorageMeter::new(
|
||||
@@ -1271,7 +1263,7 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
|
||||
common.value.saturating_add(extra_deposit),
|
||||
)?;
|
||||
let CommonInput { value, data, debug_message, .. } = common;
|
||||
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
|
||||
let result = ExecStack::<T, WasmBlob<T>>::run_instantiate(
|
||||
origin.clone(),
|
||||
executable,
|
||||
&mut gas_meter,
|
||||
@@ -1443,15 +1435,10 @@ impl<T: Config> Pallet<T> {
|
||||
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
|
||||
Migration::<T>::ensure_migrated()?;
|
||||
let schedule = T::Schedule::get();
|
||||
let module = PrefabWasmModule::from_code(
|
||||
code,
|
||||
&schedule,
|
||||
origin,
|
||||
determinism,
|
||||
TryInstantiate::Instantiate,
|
||||
)
|
||||
.map_err(|(err, _)| err)?;
|
||||
let deposit = module.open_deposit();
|
||||
let module =
|
||||
WasmBlob::from_code(code, &schedule, origin, determinism, TryInstantiate::Instantiate)
|
||||
.map_err(|(err, _)| err)?;
|
||||
let deposit = module.open_deposit(&module.code_info());
|
||||
if let Some(storage_deposit_limit) = storage_deposit_limit {
|
||||
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
|
||||
}
|
||||
@@ -1494,26 +1481,17 @@ impl<T: Config> Pallet<T> {
|
||||
ContractInfo::<T>::load_code_hash(account)
|
||||
}
|
||||
|
||||
/// Store code for benchmarks which does not check nor instrument the code.
|
||||
/// Store code for benchmarks which does not validate the code.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn store_code_raw(
|
||||
code: Vec<u8>,
|
||||
owner: T::AccountId,
|
||||
) -> frame_support::dispatch::DispatchResult {
|
||||
let schedule = T::Schedule::get();
|
||||
PrefabWasmModule::store_code_unchecked(code, &schedule, owner)?;
|
||||
WasmBlob::store_code_unchecked(code, &schedule, owner)?;
|
||||
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).map(|_| ())
|
||||
}
|
||||
|
||||
/// Deposit a pallet contracts event. Handles the conversion to the overarching event type.
|
||||
fn deposit_event(topics: Vec<T::Hash>, event: Event<T>) {
|
||||
<frame_system::Pallet<T>>::deposit_event_indexed(
|
||||
|
||||
Reference in New Issue
Block a user