mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 05:11:09 +00:00
contracts: Charge rent for code storage (#7935)
* contracts: Implement refcounting for wasm code * contracts: Charge rent for code storage * contracts: Fix dispatchables erroneously refunding base costs * Fixed typos in comments. Co-authored-by: Andrew Jones <ascjones@gmail.com> * Remove awkward empty line * Fix more typos in docs * Fix typos in docs Co-authored-by: Andrew Jones <ascjones@gmail.com> * Split up complicated expression Co-authored-by: Andrew Jones <ascjones@gmail.com> * review: Remove unused return value * Fix typos Co-authored-by: Andrew Jones <ascjones@gmail.com> * review: Fix refcount being reset to one on re-instrumentation * Document evictable_code parameter * Make Executable::execute consume and store itself * Added comments about stale values * Disregard struct size in occupied_storage() Co-authored-by: Andrew Jones <ascjones@gmail.com>
This commit is contained in:
committed by
GitHub
parent
5569313bd6
commit
8e49a8a6a6
@@ -18,114 +18,163 @@
|
||||
//! This module provides a means for executing contracts
|
||||
//! represented in wasm.
|
||||
|
||||
use crate::{
|
||||
CodeHash, Schedule, Config,
|
||||
wasm::env_def::FunctionImplProvider,
|
||||
exec::Ext,
|
||||
gas::GasMeter,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use codec::{Encode, Decode};
|
||||
|
||||
#[macro_use]
|
||||
mod env_def;
|
||||
mod code_cache;
|
||||
mod prepare;
|
||||
mod runtime;
|
||||
|
||||
use self::code_cache::load as load_code;
|
||||
use crate::{
|
||||
CodeHash, Schedule, Config,
|
||||
wasm::env_def::FunctionImplProvider,
|
||||
exec::{Ext, Executable, ExportedFunction},
|
||||
gas::GasMeter,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
use sp_core::crypto::UncheckedFrom;
|
||||
use codec::{Encode, Decode};
|
||||
use frame_support::dispatch::{DispatchError, DispatchResult};
|
||||
use pallet_contracts_primitives::ExecResult;
|
||||
|
||||
pub use self::code_cache::save as save_code;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use self::code_cache::save_raw as save_code_raw;
|
||||
pub use self::runtime::{ReturnCode, Runtime, RuntimeToken};
|
||||
|
||||
/// A prepared wasm module ready for execution.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This data structure is mostly immutable once created and stored. The exceptions that
|
||||
/// can be changed by calling a contract are `refcount`, `schedule_version` and `code`.
|
||||
/// `refcount` can change when a contract instantiates a new contract or self terminates.
|
||||
/// `schedule_version` and `code` when a contract with an outdated instrumention is called.
|
||||
/// Therefore one must be careful when holding any in-memory representation of this type while
|
||||
/// calling into a contract as those fields can get out of date.
|
||||
#[derive(Clone, Encode, Decode)]
|
||||
pub struct PrefabWasmModule {
|
||||
pub struct PrefabWasmModule<T: Config> {
|
||||
/// Version of the schedule with which the code was instrumented.
|
||||
#[codec(compact)]
|
||||
schedule_version: u32,
|
||||
/// Initial memory size of a contract's sandbox.
|
||||
#[codec(compact)]
|
||||
initial: u32,
|
||||
/// The maximum memory size of a contract's sandbox.
|
||||
#[codec(compact)]
|
||||
maximum: u32,
|
||||
/// The number of alive contracts that use this as their contract code.
|
||||
///
|
||||
/// If this number drops to zero this module is removed from storage.
|
||||
#[codec(compact)]
|
||||
refcount: u64,
|
||||
/// This field is reserved for future evolution of format.
|
||||
///
|
||||
/// Basically, for now this field will be serialized as `None`. In the future
|
||||
/// we would be able to extend this structure with.
|
||||
/// For now this field is serialized as `None`. In the future we are able to change the
|
||||
/// type parameter to a new struct that contains the fields that we want to add.
|
||||
/// That new struct would also contain a reserved field for its future extensions.
|
||||
/// This works because in SCALE `None` is encoded independently from the type parameter
|
||||
/// of the option.
|
||||
_reserved: Option<()>,
|
||||
/// Code instrumented with the latest schedule.
|
||||
code: Vec<u8>,
|
||||
/// The size of the uninstrumented code.
|
||||
///
|
||||
/// We cache this value here in order to avoid the need to pull the pristine code
|
||||
/// from storage when we only need its length for rent calculations.
|
||||
original_code_len: u32,
|
||||
/// The uninstrumented, pristine version of the code.
|
||||
///
|
||||
/// It is not stored because the pristine code has its own storage item. The value
|
||||
/// is only `Some` when this module was created from an `original_code` and `None` if
|
||||
/// it was loaded from storage.
|
||||
#[codec(skip)]
|
||||
original_code: Option<Vec<u8>>,
|
||||
/// The code hash of the stored code which is defined as the hash over the `original_code`.
|
||||
///
|
||||
/// As the map key there is no need to store the hash in the value, too. It is set manually
|
||||
/// when loading the module from storage.
|
||||
#[codec(skip)]
|
||||
code_hash: CodeHash<T>,
|
||||
}
|
||||
|
||||
/// Wasm executable loaded by `WasmLoader` and executed by `WasmVm`.
|
||||
pub struct WasmExecutable {
|
||||
entrypoint_name: &'static str,
|
||||
prefab_module: PrefabWasmModule,
|
||||
}
|
||||
|
||||
/// Loader which fetches `WasmExecutable` from the code cache.
|
||||
pub struct WasmLoader<'a, T: Config> {
|
||||
schedule: &'a Schedule<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Config> WasmLoader<'a, T> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
|
||||
pub fn new(schedule: &'a Schedule<T>) -> Self {
|
||||
WasmLoader { schedule }
|
||||
impl ExportedFunction {
|
||||
/// The wasm export name for the function.
|
||||
fn identifier(&self) -> &str {
|
||||
match self {
|
||||
Self::Constructor => "deploy",
|
||||
Self::Call => "call",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Config> crate::exec::Loader<T> for WasmLoader<'a, T>
|
||||
impl<T: Config> PrefabWasmModule<T>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
{
|
||||
type Executable = WasmExecutable;
|
||||
|
||||
fn load_init(&self, code_hash: &CodeHash<T>) -> Result<WasmExecutable, &'static str> {
|
||||
let prefab_module = load_code::<T>(code_hash, self.schedule)?;
|
||||
Ok(WasmExecutable {
|
||||
entrypoint_name: "deploy",
|
||||
prefab_module,
|
||||
})
|
||||
/// Create the module by checking and instrumenting `original_code`.
|
||||
pub fn from_code(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>
|
||||
) -> Result<Self, DispatchError> {
|
||||
prepare::prepare_contract(original_code, schedule).map_err(Into::into)
|
||||
}
|
||||
fn load_main(&self, code_hash: &CodeHash<T>) -> Result<WasmExecutable, &'static str> {
|
||||
let prefab_module = load_code::<T>(code_hash, self.schedule)?;
|
||||
Ok(WasmExecutable {
|
||||
entrypoint_name: "call",
|
||||
prefab_module,
|
||||
})
|
||||
|
||||
/// Create and store the module without checking nor instrumenting the passed code.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// This is useful for benchmarking where we don't want instrumentation to skew
|
||||
/// our results.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn store_code_unchecked(
|
||||
original_code: Vec<u8>,
|
||||
schedule: &Schedule<T>
|
||||
) -> DispatchResult {
|
||||
let executable = prepare::benchmarking::prepare_contract(original_code, schedule)
|
||||
.map_err::<DispatchError, _>(Into::into)?;
|
||||
code_cache::store(executable);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the refcount of the module.
|
||||
#[cfg(test)]
|
||||
pub fn refcount(&self) -> u64 {
|
||||
self.refcount
|
||||
}
|
||||
}
|
||||
|
||||
/// Implementation of `Vm` that takes `WasmExecutable` and executes it.
|
||||
pub struct WasmVm<'a, T: Config> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
|
||||
schedule: &'a Schedule<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: Config> WasmVm<'a, T> where T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]> {
|
||||
pub fn new(schedule: &'a Schedule<T>) -> Self {
|
||||
WasmVm { schedule }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Config> crate::exec::Vm<T> for WasmVm<'a, T>
|
||||
impl<T: Config> Executable<T> for PrefabWasmModule<T>
|
||||
where
|
||||
T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>
|
||||
{
|
||||
type Executable = WasmExecutable;
|
||||
fn from_storage(
|
||||
code_hash: CodeHash<T>,
|
||||
schedule: &Schedule<T>
|
||||
) -> Result<Self, DispatchError> {
|
||||
code_cache::load(code_hash, Some(schedule))
|
||||
}
|
||||
|
||||
fn from_storage_noinstr(code_hash: CodeHash<T>) -> Result<Self, DispatchError> {
|
||||
code_cache::load(code_hash, None)
|
||||
}
|
||||
|
||||
fn drop_from_storage(self) {
|
||||
code_cache::store_decremented(self);
|
||||
}
|
||||
|
||||
fn add_user(code_hash: CodeHash<T>) -> DispatchResult {
|
||||
code_cache::increment_refcount::<T>(code_hash)
|
||||
}
|
||||
|
||||
fn remove_user(code_hash: CodeHash<T>) {
|
||||
code_cache::decrement_refcount::<T>(code_hash)
|
||||
}
|
||||
|
||||
fn execute<E: Ext<T = T>>(
|
||||
&self,
|
||||
exec: &WasmExecutable,
|
||||
self,
|
||||
mut ext: E,
|
||||
function: &ExportedFunction,
|
||||
input_data: Vec<u8>,
|
||||
gas_meter: &mut GasMeter<E::T>,
|
||||
) -> ExecResult {
|
||||
let memory =
|
||||
sp_sandbox::Memory::new(exec.prefab_module.initial, Some(exec.prefab_module.maximum))
|
||||
sp_sandbox::Memory::new(self.initial, Some(self.maximum))
|
||||
.unwrap_or_else(|_| {
|
||||
// unlike `.expect`, explicit panic preserves the source location.
|
||||
// Needed as we can't use `RUST_BACKTRACE` in here.
|
||||
@@ -145,17 +194,34 @@ where
|
||||
let mut runtime = Runtime::new(
|
||||
&mut ext,
|
||||
input_data,
|
||||
&self.schedule,
|
||||
memory,
|
||||
gas_meter,
|
||||
);
|
||||
|
||||
// We store before executing so that the code hash is available in the constructor.
|
||||
let code = self.code.clone();
|
||||
if let &ExportedFunction::Constructor = function {
|
||||
code_cache::store(self)
|
||||
}
|
||||
|
||||
// Instantiate the instance from the instrumented module code and invoke the contract
|
||||
// entrypoint.
|
||||
let result = sp_sandbox::Instance::new(&exec.prefab_module.code, &imports, &mut runtime)
|
||||
.and_then(|mut instance| instance.invoke(exec.entrypoint_name, &[], &mut runtime));
|
||||
let result = sp_sandbox::Instance::new(&code, &imports, &mut runtime)
|
||||
.and_then(|mut instance| instance.invoke(function.identifier(), &[], &mut runtime));
|
||||
|
||||
runtime.to_execution_result(result)
|
||||
}
|
||||
|
||||
fn code_hash(&self) -> &CodeHash<T> {
|
||||
&self.code_hash
|
||||
}
|
||||
|
||||
fn occupied_storage(&self) -> u32 {
|
||||
// We disregard the size of the struct itself as the size is completely
|
||||
// dominated by the code size.
|
||||
let len = self.original_code_len.saturating_add(self.code.len() as u32);
|
||||
len.checked_div(self.refcount as u32).unwrap_or(len)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -163,10 +229,9 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
CodeHash, BalanceOf, Error, Module as Contracts,
|
||||
exec::{Ext, StorageKey, AccountIdOf},
|
||||
exec::{Ext, StorageKey, AccountIdOf, Executable},
|
||||
gas::{Gas, GasMeter},
|
||||
tests::{Test, Call, ALICE, BOB},
|
||||
wasm::prepare::prepare_contract,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use sp_core::H256;
|
||||
@@ -220,6 +285,7 @@ mod tests {
|
||||
restores: Vec<RestoreEntry>,
|
||||
// (topics, data)
|
||||
events: Vec<(Vec<H256>, Vec<u8>)>,
|
||||
schedule: Schedule<Test>,
|
||||
}
|
||||
|
||||
impl Ext for MockExt {
|
||||
@@ -234,7 +300,7 @@ mod tests {
|
||||
}
|
||||
fn instantiate(
|
||||
&mut self,
|
||||
code_hash: &CodeHash<Test>,
|
||||
code_hash: CodeHash<Test>,
|
||||
endowment: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
data: Vec<u8>,
|
||||
@@ -248,7 +314,7 @@ mod tests {
|
||||
salt: salt.to_vec(),
|
||||
});
|
||||
Ok((
|
||||
Contracts::<Test>::contract_address(&ALICE, code_hash, salt),
|
||||
Contracts::<Test>::contract_address(&ALICE, &code_hash, salt),
|
||||
ExecReturnValue {
|
||||
flags: ReturnFlags::empty(),
|
||||
data: Vec::new(),
|
||||
@@ -355,6 +421,10 @@ mod tests {
|
||||
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
|
||||
BalanceOf::<Self::T>::from(1312_u32).saturating_mul(weight.into())
|
||||
}
|
||||
|
||||
fn schedule(&self) -> &Schedule<Self::T> {
|
||||
&self.schedule
|
||||
}
|
||||
}
|
||||
|
||||
impl Ext for &mut MockExt {
|
||||
@@ -368,7 +438,7 @@ mod tests {
|
||||
}
|
||||
fn instantiate(
|
||||
&mut self,
|
||||
code: &CodeHash<Test>,
|
||||
code: CodeHash<Test>,
|
||||
value: u64,
|
||||
gas_meter: &mut GasMeter<Test>,
|
||||
input_data: Vec<u8>,
|
||||
@@ -454,6 +524,9 @@ mod tests {
|
||||
fn get_weight_price(&self, weight: Weight) -> BalanceOf<Self::T> {
|
||||
(**self).get_weight_price(weight)
|
||||
}
|
||||
fn schedule(&self) -> &Schedule<Self::T> {
|
||||
(**self).schedule()
|
||||
}
|
||||
}
|
||||
|
||||
fn execute<E: Ext>(
|
||||
@@ -466,23 +539,10 @@ mod tests {
|
||||
<E::T as frame_system::Config>::AccountId:
|
||||
UncheckedFrom<<E::T as frame_system::Config>::Hash> + AsRef<[u8]>
|
||||
{
|
||||
use crate::exec::Vm;
|
||||
|
||||
let wasm = wat::parse_str(wat).unwrap();
|
||||
let schedule = crate::Schedule::default();
|
||||
let prefab_module =
|
||||
prepare_contract::<super::runtime::Env, E::T>(&wasm, &schedule).unwrap();
|
||||
|
||||
let exec = WasmExecutable {
|
||||
// Use a "call" convention.
|
||||
entrypoint_name: "call",
|
||||
prefab_module,
|
||||
};
|
||||
|
||||
let cfg = Default::default();
|
||||
let vm = WasmVm::new(&cfg);
|
||||
|
||||
vm.execute(&exec, ext, input_data, gas_meter)
|
||||
let executable = PrefabWasmModule::<E::T>::from_code(wasm, &schedule).unwrap();
|
||||
executable.execute(ext, &ExportedFunction::Call, input_data, gas_meter)
|
||||
}
|
||||
|
||||
const CODE_TRANSFER: &str = r#"
|
||||
|
||||
Reference in New Issue
Block a user