contracts: Refactor instantiate with code (#14503)

* wip

* fixes

* rm comment

* join fns

* clippy

* Fix limits

* reduce diff

* fix

* fix

* fix typo

* refactor store to  use self

* refactor run to take self by value

* pass tests

* rm comment

* fixes

* fix typo

* rm

* fix fmt

* clippy

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

* Update frame/contracts/src/lib.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Update frame/contracts/src/wasm/mod.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Update frame/contracts/src/wasm/mod.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* PR review, rm duplicate increment_refcount

* PR review

* Update frame/contracts/src/wasm/prepare.rs

Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>

* Add test for failing storage_deposit

* fix lint

* ".git/.scripts/commands/bench/bench.sh" pallet dev pallet_contracts

---------

Co-authored-by: command-bot <>
Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
PG Herveou
2023-07-07 15:46:16 +02:00
committed by GitHub
parent 9510ad7310
commit d3ef2badcb
7 changed files with 780 additions and 809 deletions
+19 -84
View File
@@ -27,12 +27,9 @@ pub use crate::wasm::runtime::api_doc;
#[cfg(test)]
pub use tests::MockExt;
pub use crate::wasm::{
prepare::TryInstantiate,
runtime::{
AllowDeprecatedInterface, AllowUnstableInterface, CallFlags, Environment, ReturnCode,
Runtime, RuntimeCosts,
},
pub use crate::wasm::runtime::{
AllowDeprecatedInterface, AllowUnstableInterface, CallFlags, Environment, ReturnCode, Runtime,
RuntimeCosts,
};
use crate::{
@@ -147,33 +144,20 @@ impl<T: Config> Token<T> for CodeLoadToken {
impl<T: Config> WasmBlob<T> {
/// Create the module by checking the `code`.
///
/// This does **not** store the module. For this one need to either call [`Self::store`]
/// or [`<Self as Executable>::execute`][`Executable::execute`].
pub fn from_code(
code: Vec<u8>,
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
try_instantiate: TryInstantiate,
) -> Result<Self, (DispatchError, &'static str)> {
prepare::prepare::<runtime::Env, T>(
code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
schedule,
owner,
determinism,
try_instantiate,
)
}
/// Store the code without instantiating it.
///
/// Otherwise the code is stored when [`<Self as Executable>::execute`][`Executable::execute`]
/// is called.
pub fn store(self) -> DispatchResult {
Self::store_code(self, false)
}
/// Remove the code from storage and refund the deposit to its owner.
///
/// Applies all necessary checks before removing the code.
@@ -181,18 +165,6 @@ impl<T: Config> WasmBlob<T> {
Self::try_remove_code(origin, code_hash)
}
/// Returns whether there is a deposit to be paid for this module.
///
/// Returns `0` if the module is already in storage and hence no deposit will
/// be charged for storing it.
pub fn open_deposit(&self, code_info: &CodeInfo<T>) -> BalanceOf<T> {
if <CodeInfoOf<T>>::contains_key(self.code_hash()) {
0u32.into()
} else {
code_info.deposit
}
}
/// Creates and returns an instance of the supplied code.
///
/// This is either used for later executing a contract or for validation of a contract.
@@ -227,7 +199,7 @@ impl<T: Config> WasmBlob<T> {
// Query wasmi for memory limits specified in the module's import entry.
let memory_limits = contract.scan_imports::<T>(schedule)?;
// Here we allocate this memory in the _store_. It allocates _inital_ value, but allows it
// to grow up to maximum number of memory pages, if neccesary.
// to grow up to maximum number of memory pages, if necessary.
let qed = "We checked the limits versus our Schedule,
which specifies the max amount of memory pages
well below u16::MAX; qed";
@@ -250,50 +222,26 @@ impl<T: Config> WasmBlob<T> {
Ok((store, memory, instance))
}
/// Getter method for the code_info.
pub fn code_info(&self) -> &CodeInfo<T> {
&self.code_info
}
/// Put the module blob into storage.
///
/// Increments the reference count of the in-storage `WasmBlob`, if it already exists in
/// storage.
fn store_code(mut module: Self, instantiated: bool) -> DispatchResult {
let code_hash = &module.code_hash().clone();
/// Puts the module blob into storage, and returns the deposit collected for the storage.
pub fn store_code(&mut self) -> Result<BalanceOf<T>, Error<T>> {
let code_hash = *self.code_hash();
<CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
match stored_code_info {
// Instantiate existing contract.
Some(stored_code_info) if instantiated => {
stored_code_info.refcount = stored_code_info.refcount.checked_add(1).expect(
"
refcount is 64bit. Generating this overflow would require to store
_at least_ 18 exabyte of data assuming that a contract consumes only
one byte of data. Any node would run out of storage space before hitting
this overflow;
qed
",
);
Ok(())
},
// Contract code is already stored in storage. Nothing to be done here.
Some(_) => Ok(()),
Some(_) => Ok(Default::default()),
// Upload a new contract code.
//
// We need to store the code and its code_info, and collect the deposit.
// This `None` case happens only with freshly uploaded modules. This means that
// the `owner` is always the origin of the current transaction.
None => {
// This `None` case happens only in freshly uploaded modules. This means that
// the `owner` is always the origin of the current transaction.
T::Currency::reserve(&module.code_info.owner, module.code_info.deposit)
let deposit = self.code_info.deposit;
T::Currency::reserve(&self.code_info.owner, deposit)
.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
module.code_info.refcount = if instantiated { 1 } else { 0 };
<PristineCode<T>>::insert(code_hash, module.code);
*stored_code_info = Some(module.code_info);
<Pallet<T>>::deposit_event(
vec![*code_hash],
Event::CodeStored { code_hash: *code_hash },
);
Ok(())
self.code_info.refcount = 0;
<PristineCode<T>>::insert(code_hash, &self.code);
*stored_code_info = Some(self.code_info.clone());
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeStored { code_hash });
Ok(deposit)
},
}
})
@@ -331,17 +279,6 @@ impl<T: Config> WasmBlob<T> {
Ok(code)
}
/// See [`Self::from_code_unchecked`].
#[cfg(feature = "runtime-benchmarks")]
pub fn store_code_unchecked(
code: Vec<u8>,
schedule: &Schedule<T>,
owner: T::AccountId,
) -> DispatchResult {
let executable = Self::from_code_unchecked(code, schedule, owner)?;
Self::store_code(executable, false)
}
/// Create the module without checking the passed code.
///
/// # Note
@@ -350,7 +287,7 @@ impl<T: Config> WasmBlob<T> {
/// our results. This also does not collect any deposit from the `owner`. Also useful
/// during testing when we want to deploy codes that do not pass the instantiation checks.
#[cfg(any(test, feature = "runtime-benchmarks"))]
fn from_code_unchecked(
pub fn from_code_unchecked(
code: Vec<u8>,
schedule: &Schedule<T>,
owner: T::AccountId,
@@ -450,9 +387,8 @@ impl<T: Config> Executable<T> for WasmBlob<T> {
Error::<T>::CodeRejected
})?;
// We store before executing so that the code hash is available in the constructor.
if let &ExportedFunction::Constructor = function {
Self::store_code(self, true)?;
WasmBlob::<T>::increment_refcount(self.code_hash)?;
}
let result = exported_func.call(&mut store, &[], &mut []);
@@ -790,7 +726,6 @@ mod tests {
ext.borrow_mut().schedule(),
ALICE,
Determinism::Enforced,
TryInstantiate::Instantiate,
)
.map_err(|err| err.0)?
};
+17 -36
View File
@@ -41,21 +41,6 @@ use wasmi::{
/// compiler toolchains might not support specifying other modules than "env" for memory imports.
pub const IMPORT_MODULE_MEMORY: &str = "env";
/// Determines whether a module should be instantiated during preparation.
pub enum TryInstantiate {
/// Do the instantiation to make sure that the module is valid.
///
/// This should be used if a module is only uploaded but not executed. We need
/// to make sure that it can be actually instantiated.
Instantiate,
/// Skip the instantiation during preparation.
///
/// This makes sense when the preparation takes place as part of an instantiation. Then
/// this instantiation would fail the whole transaction and an extra check is not
/// necessary.
Skip,
}
/// The inner deserialized module is valid and contains only allowed WebAssembly features.
/// This is checked by loading it into wasmi interpreter `engine`.
pub struct LoadedModule {
@@ -237,7 +222,6 @@ fn validate<E, T>(
code: &[u8],
schedule: &Schedule<T>,
determinism: Determinism,
try_instantiate: TryInstantiate,
) -> Result<(), (DispatchError, &'static str)>
where
E: Environment<()>,
@@ -261,23 +245,22 @@ where
//
// - It doesn't use any unknown imports.
// - It doesn't explode the wasmi bytecode generation.
if matches!(try_instantiate, TryInstantiate::Instantiate) {
// We don't actually ever run any code so we can get away with a minimal stack which
// reduces the amount of memory that needs to be zeroed.
let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed");
WasmBlob::<T>::instantiate::<E, _>(
&code,
(),
schedule,
determinism,
stack_limits,
AllowDeprecatedInterface::No,
)
.map_err(|err| {
log::debug!(target: LOG_TARGET, "{}", err);
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
})?;
}
//
// We don't actually ever execute this instance so we can get away with a minimal stack which
// reduces the amount of memory that needs to be zeroed.
let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed");
WasmBlob::<T>::instantiate::<E, _>(
&code,
(),
schedule,
determinism,
stack_limits,
AllowDeprecatedInterface::No,
)
.map_err(|err| {
log::debug!(target: LOG_TARGET, "{}", err);
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
})?;
Ok(())
}
@@ -295,13 +278,12 @@ pub fn prepare<E, T>(
schedule: &Schedule<T>,
owner: AccountIdOf<T>,
determinism: Determinism,
try_instantiate: TryInstantiate,
) -> Result<WasmBlob<T>, (DispatchError, &'static str)>
where
E: Environment<()>,
T: Config,
{
validate::<E, T>(code.as_ref(), schedule, determinism, try_instantiate)?;
validate::<E, T>(code.as_ref(), schedule, determinism)?;
// Calculate deposit for storing contract code and `code_info` in two different storage items.
let bytes_added = code.len().saturating_add(<CodeInfo<T>>::max_encoded_len()) as u32;
@@ -416,7 +398,6 @@ mod tests {
&schedule,
ALICE,
Determinism::Enforced,
TryInstantiate::Instantiate,
);
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
}