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:
Alexander Theißen
2021-02-04 12:01:34 +01:00
committed by GitHub
parent 5569313bd6
commit 8e49a8a6a6
17 changed files with 1903 additions and 1529 deletions
+66 -29
View File
@@ -25,7 +25,7 @@ use crate::{
wasm::{PrefabWasmModule, env_def::ImportSatisfyCheck},
};
use parity_wasm::elements::{self, Internal, External, MemoryType, Type, ValueType};
use pwasm_utils;
use sp_runtime::traits::Hash;
use sp_std::prelude::*;
/// Currently, all imported functions must be located inside this module. We might support
@@ -407,22 +407,11 @@ fn get_memory_limits<T: Config>(module: Option<&MemoryType>, schedule: &Schedule
}
}
/// Loads the given module given in `original_code`, performs some checks on it and
/// does some preprocessing.
///
/// The checks are:
///
/// - provided code is a valid wasm module.
/// - the module doesn't define an internal memory instance,
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`,
/// - all imported functions from the external environment matches defined by `env` module,
///
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
pub fn prepare_contract<C: ImportSatisfyCheck, T: Config>(
fn check_and_instrument<C: ImportSatisfyCheck, T: Config>(
original_code: &[u8],
schedule: &Schedule<T>,
) -> Result<PrefabWasmModule, &'static str> {
let mut contract_module = ContractModule::new(original_code, schedule)?;
) -> Result<(Vec<u8>, (u32, u32)), &'static str> {
let contract_module = ContractModule::new(&original_code, schedule)?;
contract_module.scan_exports()?;
contract_module.ensure_no_internal_memory()?;
contract_module.ensure_table_size_limit(schedule.limits.table_size)?;
@@ -438,19 +427,65 @@ pub fn prepare_contract<C: ImportSatisfyCheck, T: Config>(
schedule
)?;
contract_module = contract_module
let code = contract_module
.inject_gas_metering()?
.inject_stack_height_metering()?;
.inject_stack_height_metering()?
.into_wasm_code()?;
Ok((code, memory_limits))
}
fn do_preparation<C: ImportSatisfyCheck, T: Config>(
original_code: Vec<u8>,
schedule: &Schedule<T>,
) -> Result<PrefabWasmModule<T>, &'static str> {
let (code, (initial, maximum)) = check_and_instrument::<C, T>(
original_code.as_ref(),
schedule,
)?;
Ok(PrefabWasmModule {
schedule_version: schedule.version,
initial: memory_limits.0,
maximum: memory_limits.1,
initial,
maximum,
_reserved: None,
code: contract_module.into_wasm_code()?,
code,
original_code_len: original_code.len() as u32,
refcount: 1,
code_hash: T::Hashing::hash(&original_code),
original_code: Some(original_code),
})
}
/// Loads the given module given in `original_code`, performs some checks on it and
/// does some preprocessing.
///
/// The checks are:
///
/// - provided code is a valid wasm module.
/// - the module doesn't define an internal memory instance,
/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule`,
/// - all imported functions from the external environment matches defined by `env` module,
///
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
pub fn prepare_contract<T: Config>(
original_code: Vec<u8>,
schedule: &Schedule<T>,
) -> Result<PrefabWasmModule<T>, &'static str> {
do_preparation::<super::runtime::Env, T>(original_code, schedule)
}
/// The same as [`prepare_contract`] but without constructing a new [`PrefabWasmModule`]
///
/// # Note
///
/// Use this when an existing contract should be re-instrumented with a newer schedule version.
pub fn reinstrument_contract<T: Config>(
original_code: Vec<u8>,
schedule: &Schedule<T>,
) -> Result<Vec<u8>, &'static str> {
Ok(check_and_instrument::<super::runtime::Env, T>(&original_code, schedule)?.0)
}
/// Alternate (possibly unsafe) preparation functions used only for benchmarking.
///
/// For benchmarking we need to construct special contracts that might not pass our
@@ -459,9 +494,7 @@ pub fn prepare_contract<C: ImportSatisfyCheck, T: Config>(
/// in production code.
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking {
use super::{
Config, ContractModule, PrefabWasmModule, ImportSatisfyCheck, Schedule, get_memory_limits
};
use super::*;
use parity_wasm::elements::FunctionType;
impl ImportSatisfyCheck for () {
@@ -471,10 +504,10 @@ pub mod benchmarking {
}
/// Prepare function that neither checks nor instruments the passed in code.
pub fn prepare_contract<T: Config>(original_code: &[u8], schedule: &Schedule<T>)
-> Result<PrefabWasmModule, &'static str>
pub fn prepare_contract<T: Config>(original_code: Vec<u8>, schedule: &Schedule<T>)
-> Result<PrefabWasmModule<T>, &'static str>
{
let contract_module = ContractModule::new(original_code, schedule)?;
let contract_module = ContractModule::new(&original_code, schedule)?;
let memory_limits = get_memory_limits(contract_module.scan_imports::<()>(&[])?, schedule)?;
Ok(PrefabWasmModule {
schedule_version: schedule.version,
@@ -482,6 +515,10 @@ pub mod benchmarking {
maximum: memory_limits.1,
_reserved: None,
code: contract_module.into_wasm_code()?,
original_code_len: original_code.len() as u32,
refcount: 1,
code_hash: T::Hashing::hash(&original_code),
original_code: Some(original_code),
})
}
}
@@ -493,7 +530,7 @@ mod tests {
use std::fmt;
use assert_matches::assert_matches;
impl fmt::Debug for PrefabWasmModule {
impl fmt::Debug for PrefabWasmModule<crate::tests::Test> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "PreparedContract {{ .. }}")
}
@@ -534,7 +571,7 @@ mod tests {
},
.. Default::default()
};
let r = prepare_contract::<env::Test, crate::tests::Test>(wasm.as_ref(), &schedule);
let r = do_preparation::<env::Test, crate::tests::Test>(wasm, &schedule);
assert_matches!(r, $($expected)*);
}
};
@@ -945,7 +982,7 @@ mod tests {
).unwrap();
let mut schedule = Schedule::default();
schedule.enable_println = true;
let r = prepare_contract::<env::Test, crate::tests::Test>(wasm.as_ref(), &schedule);
let r = do_preparation::<env::Test, crate::tests::Test>(wasm, &schedule);
assert_matches!(r, Ok(_));
}
}