mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 07:17:56 +00:00
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:
@@ -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)?
|
||||
};
|
||||
|
||||
@@ -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)*);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user