mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-19 15:45:41 +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:
@@ -161,12 +161,6 @@ pub enum Code<Hash> {
|
|||||||
Existing(Hash),
|
Existing(Hash),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Into<Vec<u8>>, Hash> From<T> for Code<Hash> {
|
|
||||||
fn from(from: T) -> Self {
|
|
||||||
Code::Upload(from.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The amount of balance that was either charged or refunded in order to pay for storage.
|
/// The amount of balance that was either charged or refunded in order to pay for storage.
|
||||||
#[derive(Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, Clone, TypeInfo)]
|
#[derive(Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, Clone, TypeInfo)]
|
||||||
pub enum StorageDeposit<Balance> {
|
pub enum StorageDeposit<Balance> {
|
||||||
|
|||||||
@@ -75,8 +75,8 @@
|
|||||||
//! allowed to code owner.
|
//! allowed to code owner.
|
||||||
//! * [`Pallet::set_code`] - Changes the code of an existing contract. Only allowed to `Root`
|
//! * [`Pallet::set_code`] - Changes the code of an existing contract. Only allowed to `Root`
|
||||||
//! origin.
|
//! origin.
|
||||||
//! * [`Pallet::migrate`] - Runs migration steps of curent multi-block migration in priority, before
|
//! * [`Pallet::migrate`] - Runs migration steps of current multi-block migration in priority,
|
||||||
//! [`Hooks::on_idle`][frame_support::traits::Hooks::on_idle] activates.
|
//! before [`Hooks::on_idle`][frame_support::traits::Hooks::on_idle] activates.
|
||||||
//!
|
//!
|
||||||
//! ## Usage
|
//! ## Usage
|
||||||
//!
|
//!
|
||||||
@@ -105,7 +105,7 @@ use crate::{
|
|||||||
exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Key, Stack as ExecStack},
|
exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Key, Stack as ExecStack},
|
||||||
gas::GasMeter,
|
gas::GasMeter,
|
||||||
storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
|
storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
|
||||||
wasm::{CodeInfo, TryInstantiate, WasmBlob},
|
wasm::{CodeInfo, WasmBlob},
|
||||||
};
|
};
|
||||||
use codec::{Codec, Decode, Encode, HasCompact};
|
use codec::{Codec, Decode, Encode, HasCompact};
|
||||||
use environmental::*;
|
use environmental::*;
|
||||||
@@ -695,24 +695,40 @@ pub mod pallet {
|
|||||||
salt: Vec<u8>,
|
salt: Vec<u8>,
|
||||||
) -> DispatchResultWithPostInfo {
|
) -> DispatchResultWithPostInfo {
|
||||||
Migration::<T>::ensure_migrated()?;
|
Migration::<T>::ensure_migrated()?;
|
||||||
|
let origin = ensure_signed(origin)?;
|
||||||
let code_len = code.len() as u32;
|
let code_len = code.len() as u32;
|
||||||
|
|
||||||
|
let (module, upload_deposit) = Self::try_upload_code(
|
||||||
|
origin.clone(),
|
||||||
|
code,
|
||||||
|
storage_deposit_limit.clone().map(Into::into),
|
||||||
|
Determinism::Enforced,
|
||||||
|
None,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Reduces the storage deposit limit by the amount that was reserved for the upload.
|
||||||
|
let storage_deposit_limit =
|
||||||
|
storage_deposit_limit.map(|limit| limit.into().saturating_sub(upload_deposit));
|
||||||
|
|
||||||
let data_len = data.len() as u32;
|
let data_len = data.len() as u32;
|
||||||
let salt_len = salt.len() as u32;
|
let salt_len = salt.len() as u32;
|
||||||
let common = CommonInput {
|
let common = CommonInput {
|
||||||
origin: Origin::from_runtime_origin(origin)?,
|
origin: Origin::from_account_id(origin),
|
||||||
value,
|
value,
|
||||||
data,
|
data,
|
||||||
gas_limit,
|
gas_limit,
|
||||||
storage_deposit_limit: storage_deposit_limit.map(Into::into),
|
storage_deposit_limit,
|
||||||
debug_message: None,
|
debug_message: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut output =
|
let mut output =
|
||||||
InstantiateInput::<T> { code: Code::Upload(code), salt }.run_guarded(common);
|
InstantiateInput::<T> { code: WasmCode::Wasm(module), salt }.run_guarded(common);
|
||||||
if let Ok(retval) = &output.result {
|
if let Ok(retval) = &output.result {
|
||||||
if retval.1.did_revert() {
|
if retval.1.did_revert() {
|
||||||
output.result = Err(<Error<T>>::ContractReverted.into());
|
output.result = Err(<Error<T>>::ContractReverted.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output.gas_meter.into_dispatch_result(
|
output.gas_meter.into_dispatch_result(
|
||||||
output.result.map(|(_address, result)| result),
|
output.result.map(|(_address, result)| result),
|
||||||
T::WeightInfo::instantiate_with_code(code_len, data_len, salt_len),
|
T::WeightInfo::instantiate_with_code(code_len, data_len, salt_len),
|
||||||
@@ -748,8 +764,8 @@ pub mod pallet {
|
|||||||
storage_deposit_limit: storage_deposit_limit.map(Into::into),
|
storage_deposit_limit: storage_deposit_limit.map(Into::into),
|
||||||
debug_message: None,
|
debug_message: None,
|
||||||
};
|
};
|
||||||
let mut output =
|
let mut output = InstantiateInput::<T> { code: WasmCode::CodeHash(code_hash), salt }
|
||||||
InstantiateInput::<T> { code: Code::Existing(code_hash), salt }.run_guarded(common);
|
.run_guarded(common);
|
||||||
if let Ok(retval) = &output.result {
|
if let Ok(retval) = &output.result {
|
||||||
if retval.1.did_revert() {
|
if retval.1.did_revert() {
|
||||||
output.result = Err(<Error<T>>::ContractReverted.into());
|
output.result = Err(<Error<T>>::ContractReverted.into());
|
||||||
@@ -1052,9 +1068,15 @@ struct CallInput<T: Config> {
|
|||||||
determinism: Determinism,
|
determinism: Determinism,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reference to an existing code hash or a new wasm module.
|
||||||
|
enum WasmCode<T: Config> {
|
||||||
|
Wasm(WasmBlob<T>),
|
||||||
|
CodeHash(CodeHash<T>),
|
||||||
|
}
|
||||||
|
|
||||||
/// Input specific to a contract instantiation invocation.
|
/// Input specific to a contract instantiation invocation.
|
||||||
struct InstantiateInput<T: Config> {
|
struct InstantiateInput<T: Config> {
|
||||||
code: Code<CodeHash<T>>,
|
code: WasmCode<T>,
|
||||||
salt: Vec<u8>,
|
salt: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1099,7 +1121,7 @@ struct InternalOutput<T: Config, O> {
|
|||||||
|
|
||||||
/// Helper trait to wrap contract execution entry points into a single function
|
/// Helper trait to wrap contract execution entry points into a single function
|
||||||
/// [`Invokable::run_guarded`].
|
/// [`Invokable::run_guarded`].
|
||||||
trait Invokable<T: Config> {
|
trait Invokable<T: Config>: Sized {
|
||||||
/// What is returned as a result of a successful invocation.
|
/// What is returned as a result of a successful invocation.
|
||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
@@ -1111,7 +1133,7 @@ trait Invokable<T: Config> {
|
|||||||
///
|
///
|
||||||
/// We enforce a re-entrancy guard here by initializing and checking a boolean flag through a
|
/// We enforce a re-entrancy guard here by initializing and checking a boolean flag through a
|
||||||
/// global reference.
|
/// global reference.
|
||||||
fn run_guarded(&self, common: CommonInput<T>) -> InternalOutput<T, Self::Output> {
|
fn run_guarded(self, common: CommonInput<T>) -> InternalOutput<T, Self::Output> {
|
||||||
// Set up a global reference to the boolean flag used for the re-entrancy guard.
|
// Set up a global reference to the boolean flag used for the re-entrancy guard.
|
||||||
environmental!(executing_contract: bool);
|
environmental!(executing_contract: bool);
|
||||||
|
|
||||||
@@ -1159,11 +1181,8 @@ trait Invokable<T: Config> {
|
|||||||
/// contract or a instantiation of a new one.
|
/// contract or a instantiation of a new one.
|
||||||
///
|
///
|
||||||
/// Called by dispatchables and public functions through the [`Invokable::run_guarded`].
|
/// Called by dispatchables and public functions through the [`Invokable::run_guarded`].
|
||||||
fn run(
|
fn run(self, common: CommonInput<T>, gas_meter: GasMeter<T>)
|
||||||
&self,
|
-> InternalOutput<T, Self::Output>;
|
||||||
common: CommonInput<T>,
|
|
||||||
gas_meter: GasMeter<T>,
|
|
||||||
) -> InternalOutput<T, Self::Output>;
|
|
||||||
|
|
||||||
/// This method ensures that the given `origin` is allowed to invoke the current `Invokable`.
|
/// This method ensures that the given `origin` is allowed to invoke the current `Invokable`.
|
||||||
///
|
///
|
||||||
@@ -1175,7 +1194,7 @@ impl<T: Config> Invokable<T> for CallInput<T> {
|
|||||||
type Output = ExecReturnValue;
|
type Output = ExecReturnValue;
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
self,
|
||||||
common: CommonInput<T>,
|
common: CommonInput<T>,
|
||||||
mut gas_meter: GasMeter<T>,
|
mut gas_meter: GasMeter<T>,
|
||||||
) -> InternalOutput<T, Self::Output> {
|
) -> InternalOutput<T, Self::Output> {
|
||||||
@@ -1201,7 +1220,7 @@ impl<T: Config> Invokable<T> for CallInput<T> {
|
|||||||
value,
|
value,
|
||||||
data.clone(),
|
data.clone(),
|
||||||
debug_message,
|
debug_message,
|
||||||
*determinism,
|
determinism,
|
||||||
);
|
);
|
||||||
|
|
||||||
match storage_meter.try_into_deposit(&origin) {
|
match storage_meter.try_into_deposit(&origin) {
|
||||||
@@ -1223,8 +1242,8 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
|
|||||||
type Output = (AccountIdOf<T>, ExecReturnValue);
|
type Output = (AccountIdOf<T>, ExecReturnValue);
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
self,
|
||||||
mut common: CommonInput<T>,
|
common: CommonInput<T>,
|
||||||
mut gas_meter: GasMeter<T>,
|
mut gas_meter: GasMeter<T>,
|
||||||
) -> InternalOutput<T, Self::Output> {
|
) -> InternalOutput<T, Self::Output> {
|
||||||
let mut storage_deposit = Default::default();
|
let mut storage_deposit = Default::default();
|
||||||
@@ -1233,37 +1252,15 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
|
|||||||
let InstantiateInput { salt, .. } = self;
|
let InstantiateInput { salt, .. } = self;
|
||||||
let CommonInput { origin: contract_origin, .. } = common;
|
let CommonInput { origin: contract_origin, .. } = common;
|
||||||
let origin = contract_origin.account_id()?;
|
let origin = contract_origin.account_id()?;
|
||||||
let (extra_deposit, executable) = match &self.code {
|
|
||||||
Code::Upload(binary) => {
|
let executable = match self.code {
|
||||||
let executable = WasmBlob::from_code(
|
WasmCode::Wasm(module) => module,
|
||||||
binary.clone(),
|
WasmCode::CodeHash(code_hash) => WasmBlob::from_storage(code_hash, &mut gas_meter)?,
|
||||||
&schedule,
|
|
||||||
origin.clone(),
|
|
||||||
Determinism::Enforced,
|
|
||||||
TryInstantiate::Skip,
|
|
||||||
)
|
|
||||||
.map_err(|(err, msg)| {
|
|
||||||
common
|
|
||||||
.debug_message
|
|
||||||
.as_mut()
|
|
||||||
.map(|buffer| buffer.try_extend(&mut msg.bytes()));
|
|
||||||
err
|
|
||||||
})?;
|
|
||||||
// The open deposit will be charged during execution when the
|
|
||||||
// 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.code_info()), executable)
|
|
||||||
},
|
|
||||||
Code::Existing(hash) =>
|
|
||||||
(Default::default(), WasmBlob::from_storage(*hash, &mut gas_meter)?),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let contract_origin = Origin::from_account_id(origin.clone());
|
let contract_origin = Origin::from_account_id(origin.clone());
|
||||||
let mut storage_meter = StorageMeter::new(
|
let mut storage_meter =
|
||||||
&contract_origin,
|
StorageMeter::new(&contract_origin, common.storage_deposit_limit, common.value)?;
|
||||||
common.storage_deposit_limit,
|
|
||||||
common.value.saturating_add(extra_deposit),
|
|
||||||
)?;
|
|
||||||
let CommonInput { value, data, debug_message, .. } = common;
|
let CommonInput { value, data, debug_message, .. } = common;
|
||||||
let result = ExecStack::<T, WasmBlob<T>>::run_instantiate(
|
let result = ExecStack::<T, WasmBlob<T>>::run_instantiate(
|
||||||
origin.clone(),
|
origin.clone(),
|
||||||
@@ -1277,9 +1274,7 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
|
|||||||
debug_message,
|
debug_message,
|
||||||
);
|
);
|
||||||
|
|
||||||
storage_deposit = storage_meter
|
storage_deposit = storage_meter.try_into_deposit(&contract_origin)?;
|
||||||
.try_into_deposit(&contract_origin)?
|
|
||||||
.saturating_add(&StorageDeposit::Charge(extra_deposit));
|
|
||||||
result
|
result
|
||||||
};
|
};
|
||||||
InternalOutput { result: try_exec(), gas_meter, storage_deposit }
|
InternalOutput { result: try_exec(), gas_meter, storage_deposit }
|
||||||
@@ -1383,7 +1378,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
origin: T::AccountId,
|
origin: T::AccountId,
|
||||||
value: BalanceOf<T>,
|
value: BalanceOf<T>,
|
||||||
gas_limit: Weight,
|
gas_limit: Weight,
|
||||||
storage_deposit_limit: Option<BalanceOf<T>>,
|
mut storage_deposit_limit: Option<BalanceOf<T>>,
|
||||||
code: Code<CodeHash<T>>,
|
code: Code<CodeHash<T>>,
|
||||||
data: Vec<u8>,
|
data: Vec<u8>,
|
||||||
salt: Vec<u8>,
|
salt: Vec<u8>,
|
||||||
@@ -1397,6 +1392,45 @@ impl<T: Config> Pallet<T> {
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
// collect events if CollectEvents is UnsafeCollect
|
||||||
|
let events = || {
|
||||||
|
if collect_events == CollectEvents::UnsafeCollect {
|
||||||
|
Some(System::<T>::read_events_no_consensus().map(|e| *e).collect())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (code, upload_deposit): (WasmCode<T>, BalanceOf<T>) = match code {
|
||||||
|
Code::Upload(code) => {
|
||||||
|
let result = Self::try_upload_code(
|
||||||
|
origin.clone(),
|
||||||
|
code,
|
||||||
|
storage_deposit_limit.map(Into::into),
|
||||||
|
Determinism::Enforced,
|
||||||
|
debug_message.as_mut(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (module, deposit) = match result {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(error) =>
|
||||||
|
return ContractResult {
|
||||||
|
gas_consumed: Zero::zero(),
|
||||||
|
gas_required: Zero::zero(),
|
||||||
|
storage_deposit: Default::default(),
|
||||||
|
debug_message: debug_message.unwrap_or(Default::default()).into(),
|
||||||
|
result: Err(error),
|
||||||
|
events: events(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
storage_deposit_limit =
|
||||||
|
storage_deposit_limit.map(|l| l.saturating_sub(deposit.into()));
|
||||||
|
(WasmCode::Wasm(module), deposit)
|
||||||
|
},
|
||||||
|
Code::Existing(hash) => (WasmCode::CodeHash(hash), Default::default()),
|
||||||
|
};
|
||||||
|
|
||||||
let common = CommonInput {
|
let common = CommonInput {
|
||||||
origin: Origin::from_account_id(origin),
|
origin: Origin::from_account_id(origin),
|
||||||
value,
|
value,
|
||||||
@@ -1405,13 +1439,8 @@ impl<T: Config> Pallet<T> {
|
|||||||
storage_deposit_limit,
|
storage_deposit_limit,
|
||||||
debug_message: debug_message.as_mut(),
|
debug_message: debug_message.as_mut(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let output = InstantiateInput::<T> { code, salt }.run_guarded(common);
|
let output = InstantiateInput::<T> { code, salt }.run_guarded(common);
|
||||||
// collect events if CollectEvents is UnsafeCollect
|
|
||||||
let events = if collect_events == CollectEvents::UnsafeCollect {
|
|
||||||
Some(System::<T>::read_events_no_consensus().map(|e| *e).collect())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
ContractInstantiateResult {
|
ContractInstantiateResult {
|
||||||
result: output
|
result: output
|
||||||
.result
|
.result
|
||||||
@@ -1419,9 +1448,11 @@ impl<T: Config> Pallet<T> {
|
|||||||
.map_err(|e| e.error),
|
.map_err(|e| e.error),
|
||||||
gas_consumed: output.gas_meter.gas_consumed(),
|
gas_consumed: output.gas_meter.gas_consumed(),
|
||||||
gas_required: output.gas_meter.gas_required(),
|
gas_required: output.gas_meter.gas_required(),
|
||||||
storage_deposit: output.storage_deposit,
|
storage_deposit: output
|
||||||
|
.storage_deposit
|
||||||
|
.saturating_add(&StorageDeposit::Charge(upload_deposit)),
|
||||||
debug_message: debug_message.unwrap_or_default().to_vec(),
|
debug_message: debug_message.unwrap_or_default().to_vec(),
|
||||||
events,
|
events: events(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1436,17 +1467,31 @@ impl<T: Config> Pallet<T> {
|
|||||||
determinism: Determinism,
|
determinism: Determinism,
|
||||||
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
|
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
|
||||||
Migration::<T>::ensure_migrated()?;
|
Migration::<T>::ensure_migrated()?;
|
||||||
|
let (module, deposit) =
|
||||||
|
Self::try_upload_code(origin, code, storage_deposit_limit, determinism, None)?;
|
||||||
|
Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Uploads new code and returns the Wasm blob and deposit amount collected.
|
||||||
|
fn try_upload_code(
|
||||||
|
origin: T::AccountId,
|
||||||
|
code: Vec<u8>,
|
||||||
|
storage_deposit_limit: Option<BalanceOf<T>>,
|
||||||
|
determinism: Determinism,
|
||||||
|
mut debug_message: Option<&mut DebugBufferVec<T>>,
|
||||||
|
) -> Result<(WasmBlob<T>, BalanceOf<T>), DispatchError> {
|
||||||
let schedule = T::Schedule::get();
|
let schedule = T::Schedule::get();
|
||||||
let module =
|
let mut module =
|
||||||
WasmBlob::from_code(code, &schedule, origin, determinism, TryInstantiate::Instantiate)
|
WasmBlob::from_code(code, &schedule, origin, determinism).map_err(|(err, msg)| {
|
||||||
.map_err(|(err, _)| err)?;
|
debug_message.as_mut().map(|d| d.try_extend(msg.bytes()));
|
||||||
let deposit = module.open_deposit(&module.code_info());
|
err
|
||||||
|
})?;
|
||||||
|
let deposit = module.store_code()?;
|
||||||
if let Some(storage_deposit_limit) = storage_deposit_limit {
|
if let Some(storage_deposit_limit) = storage_deposit_limit {
|
||||||
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
|
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
|
||||||
}
|
}
|
||||||
let result = CodeUploadReturnValue { code_hash: *module.code_hash(), deposit };
|
|
||||||
module.store()?;
|
Ok((module, deposit))
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query storage of a specified contract under a specified key.
|
/// Query storage of a specified contract under a specified key.
|
||||||
@@ -1490,7 +1535,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
owner: T::AccountId,
|
owner: T::AccountId,
|
||||||
) -> frame_support::dispatch::DispatchResult {
|
) -> frame_support::dispatch::DispatchResult {
|
||||||
let schedule = T::Schedule::get();
|
let schedule = T::Schedule::get();
|
||||||
WasmBlob::store_code_unchecked(code, &schedule, owner)?;
|
WasmBlob::<T>::from_code_unchecked(code, &schedule, owner)?.store_code()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,9 @@ impl<T: Config> MigrationStep for Migration<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn step(&mut self) -> (IsFinished, Weight) {
|
fn step(&mut self) -> (IsFinished, Weight) {
|
||||||
let Some(old_queue) = old::DeletionQueue::<T>::take() else { return (IsFinished::Yes, Weight::zero()) };
|
let Some(old_queue) = old::DeletionQueue::<T>::take() else {
|
||||||
|
return (IsFinished::Yes, Weight::zero())
|
||||||
|
};
|
||||||
let len = old_queue.len();
|
let len = old_queue.len();
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
|
|||||||
@@ -3753,6 +3753,19 @@ fn instantiate_with_zero_balance_works() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
vec![
|
vec![
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
||||||
|
who: ALICE,
|
||||||
|
amount: deposit_expected,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }),
|
||||||
|
topics: vec![code_hash],
|
||||||
|
},
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
@@ -3801,19 +3814,6 @@ fn instantiate_with_zero_balance_works() {
|
|||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
|
||||||
who: ALICE,
|
|
||||||
amount: deposit_expected,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }),
|
|
||||||
topics: vec![code_hash],
|
|
||||||
},
|
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Contracts(crate::Event::Instantiated {
|
event: RuntimeEvent::Contracts(crate::Event::Instantiated {
|
||||||
@@ -3865,6 +3865,19 @@ fn instantiate_with_below_existential_deposit_works() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
System::events(),
|
System::events(),
|
||||||
vec![
|
vec![
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
||||||
|
who: ALICE,
|
||||||
|
amount: deposit_expected,
|
||||||
|
}),
|
||||||
|
topics: vec![],
|
||||||
|
},
|
||||||
|
EventRecord {
|
||||||
|
phase: Phase::Initialization,
|
||||||
|
event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }),
|
||||||
|
topics: vec![code_hash],
|
||||||
|
},
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
event: RuntimeEvent::System(frame_system::Event::NewAccount {
|
||||||
@@ -3922,19 +3935,6 @@ fn instantiate_with_below_existential_deposit_works() {
|
|||||||
}),
|
}),
|
||||||
topics: vec![],
|
topics: vec![],
|
||||||
},
|
},
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Balances(pallet_balances::Event::Reserved {
|
|
||||||
who: ALICE,
|
|
||||||
amount: deposit_expected,
|
|
||||||
}),
|
|
||||||
topics: vec![],
|
|
||||||
},
|
|
||||||
EventRecord {
|
|
||||||
phase: Phase::Initialization,
|
|
||||||
event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }),
|
|
||||||
topics: vec![code_hash],
|
|
||||||
},
|
|
||||||
EventRecord {
|
EventRecord {
|
||||||
phase: Phase::Initialization,
|
phase: Phase::Initialization,
|
||||||
event: RuntimeEvent::Contracts(crate::Event::Instantiated {
|
event: RuntimeEvent::Contracts(crate::Event::Instantiated {
|
||||||
@@ -4587,11 +4587,29 @@ fn set_code_hash() {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn storage_deposit_limit_is_enforced() {
|
fn storage_deposit_limit_is_enforced() {
|
||||||
|
let ed = 200;
|
||||||
let (wasm, _code_hash) = compile_module::<Test>("store_call").unwrap();
|
let (wasm, _code_hash) = compile_module::<Test>("store_call").unwrap();
|
||||||
ExtBuilder::default().existential_deposit(200).build().execute_with(|| {
|
ExtBuilder::default().existential_deposit(ed).build().execute_with(|| {
|
||||||
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
let _ = Balances::deposit_creating(&ALICE, 1_000_000);
|
||||||
let min_balance = <Test as Config>::Currency::minimum_balance();
|
let min_balance = <Test as Config>::Currency::minimum_balance();
|
||||||
|
|
||||||
|
// Setting insufficient storage_deposit should fail.
|
||||||
|
assert_err!(
|
||||||
|
Contracts::bare_instantiate(
|
||||||
|
ALICE,
|
||||||
|
0,
|
||||||
|
GAS_LIMIT,
|
||||||
|
Some((2 * ed + 3 - 1).into()), // expected deposit is 2 * ed + 3 for the call
|
||||||
|
Code::Upload(wasm.clone()),
|
||||||
|
vec![],
|
||||||
|
vec![],
|
||||||
|
DebugInfo::Skip,
|
||||||
|
CollectEvents::Skip,
|
||||||
|
)
|
||||||
|
.result,
|
||||||
|
<Error<Test>>::StorageDepositLimitExhausted,
|
||||||
|
);
|
||||||
|
|
||||||
// Instantiate the BOB contract.
|
// Instantiate the BOB contract.
|
||||||
let addr = Contracts::bare_instantiate(
|
let addr = Contracts::bare_instantiate(
|
||||||
ALICE,
|
ALICE,
|
||||||
@@ -5591,7 +5609,7 @@ fn root_cannot_instantiate_with_code() {
|
|||||||
vec![],
|
vec![],
|
||||||
vec![],
|
vec![],
|
||||||
),
|
),
|
||||||
DispatchError::RootNotAllowed,
|
DispatchError::BadOrigin
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,9 @@ pub use crate::wasm::runtime::api_doc;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use tests::MockExt;
|
pub use tests::MockExt;
|
||||||
|
|
||||||
pub use crate::wasm::{
|
pub use crate::wasm::runtime::{
|
||||||
prepare::TryInstantiate,
|
AllowDeprecatedInterface, AllowUnstableInterface, CallFlags, Environment, ReturnCode, Runtime,
|
||||||
runtime::{
|
RuntimeCosts,
|
||||||
AllowDeprecatedInterface, AllowUnstableInterface, CallFlags, Environment, ReturnCode,
|
|
||||||
Runtime, RuntimeCosts,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -147,33 +144,20 @@ impl<T: Config> Token<T> for CodeLoadToken {
|
|||||||
|
|
||||||
impl<T: Config> WasmBlob<T> {
|
impl<T: Config> WasmBlob<T> {
|
||||||
/// Create the module by checking the `code`.
|
/// 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(
|
pub fn from_code(
|
||||||
code: Vec<u8>,
|
code: Vec<u8>,
|
||||||
schedule: &Schedule<T>,
|
schedule: &Schedule<T>,
|
||||||
owner: AccountIdOf<T>,
|
owner: AccountIdOf<T>,
|
||||||
determinism: Determinism,
|
determinism: Determinism,
|
||||||
try_instantiate: TryInstantiate,
|
|
||||||
) -> Result<Self, (DispatchError, &'static str)> {
|
) -> Result<Self, (DispatchError, &'static str)> {
|
||||||
prepare::prepare::<runtime::Env, T>(
|
prepare::prepare::<runtime::Env, T>(
|
||||||
code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
code.try_into().map_err(|_| (<Error<T>>::CodeTooLarge.into(), ""))?,
|
||||||
schedule,
|
schedule,
|
||||||
owner,
|
owner,
|
||||||
determinism,
|
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.
|
/// Remove the code from storage and refund the deposit to its owner.
|
||||||
///
|
///
|
||||||
/// Applies all necessary checks before removing the code.
|
/// Applies all necessary checks before removing the code.
|
||||||
@@ -181,18 +165,6 @@ impl<T: Config> WasmBlob<T> {
|
|||||||
Self::try_remove_code(origin, code_hash)
|
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.
|
/// Creates and returns an instance of the supplied code.
|
||||||
///
|
///
|
||||||
/// This is either used for later executing a contract or for validation of a contract.
|
/// 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.
|
// Query wasmi for memory limits specified in the module's import entry.
|
||||||
let memory_limits = contract.scan_imports::<T>(schedule)?;
|
let memory_limits = contract.scan_imports::<T>(schedule)?;
|
||||||
// Here we allocate this memory in the _store_. It allocates _inital_ value, but allows it
|
// 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,
|
let qed = "We checked the limits versus our Schedule,
|
||||||
which specifies the max amount of memory pages
|
which specifies the max amount of memory pages
|
||||||
well below u16::MAX; qed";
|
well below u16::MAX; qed";
|
||||||
@@ -250,50 +222,26 @@ impl<T: Config> WasmBlob<T> {
|
|||||||
Ok((store, memory, instance))
|
Ok((store, memory, instance))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Getter method for the code_info.
|
/// Puts the module blob into storage, and returns the deposit collected for the storage.
|
||||||
pub fn code_info(&self) -> &CodeInfo<T> {
|
pub fn store_code(&mut self) -> Result<BalanceOf<T>, Error<T>> {
|
||||||
&self.code_info
|
let code_hash = *self.code_hash();
|
||||||
}
|
|
||||||
|
|
||||||
/// 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();
|
|
||||||
<CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
|
<CodeInfoOf<T>>::mutate(code_hash, |stored_code_info| {
|
||||||
match 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.
|
// Contract code is already stored in storage. Nothing to be done here.
|
||||||
Some(_) => Ok(()),
|
Some(_) => Ok(Default::default()),
|
||||||
// Upload a new contract code.
|
// Upload a new contract code.
|
||||||
//
|
|
||||||
// We need to store the code and its code_info, and collect the deposit.
|
// 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 => {
|
None => {
|
||||||
// This `None` case happens only in freshly uploaded modules. This means that
|
let deposit = self.code_info.deposit;
|
||||||
// the `owner` is always the origin of the current transaction.
|
T::Currency::reserve(&self.code_info.owner, deposit)
|
||||||
T::Currency::reserve(&module.code_info.owner, module.code_info.deposit)
|
|
||||||
.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
|
.map_err(|_| <Error<T>>::StorageDepositNotEnoughFunds)?;
|
||||||
module.code_info.refcount = if instantiated { 1 } else { 0 };
|
self.code_info.refcount = 0;
|
||||||
<PristineCode<T>>::insert(code_hash, module.code);
|
<PristineCode<T>>::insert(code_hash, &self.code);
|
||||||
*stored_code_info = Some(module.code_info);
|
*stored_code_info = Some(self.code_info.clone());
|
||||||
<Pallet<T>>::deposit_event(
|
<Pallet<T>>::deposit_event(vec![code_hash], Event::CodeStored { code_hash });
|
||||||
vec![*code_hash],
|
Ok(deposit)
|
||||||
Event::CodeStored { code_hash: *code_hash },
|
|
||||||
);
|
|
||||||
Ok(())
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -331,17 +279,6 @@ impl<T: Config> WasmBlob<T> {
|
|||||||
Ok(code)
|
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.
|
/// Create the module without checking the passed code.
|
||||||
///
|
///
|
||||||
/// # Note
|
/// # Note
|
||||||
@@ -350,7 +287,7 @@ impl<T: Config> WasmBlob<T> {
|
|||||||
/// our results. This also does not collect any deposit from the `owner`. Also useful
|
/// 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.
|
/// during testing when we want to deploy codes that do not pass the instantiation checks.
|
||||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
||||||
fn from_code_unchecked(
|
pub fn from_code_unchecked(
|
||||||
code: Vec<u8>,
|
code: Vec<u8>,
|
||||||
schedule: &Schedule<T>,
|
schedule: &Schedule<T>,
|
||||||
owner: T::AccountId,
|
owner: T::AccountId,
|
||||||
@@ -450,9 +387,8 @@ impl<T: Config> Executable<T> for WasmBlob<T> {
|
|||||||
Error::<T>::CodeRejected
|
Error::<T>::CodeRejected
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// We store before executing so that the code hash is available in the constructor.
|
|
||||||
if let &ExportedFunction::Constructor = function {
|
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 []);
|
let result = exported_func.call(&mut store, &[], &mut []);
|
||||||
@@ -790,7 +726,6 @@ mod tests {
|
|||||||
ext.borrow_mut().schedule(),
|
ext.borrow_mut().schedule(),
|
||||||
ALICE,
|
ALICE,
|
||||||
Determinism::Enforced,
|
Determinism::Enforced,
|
||||||
TryInstantiate::Instantiate,
|
|
||||||
)
|
)
|
||||||
.map_err(|err| err.0)?
|
.map_err(|err| err.0)?
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,21 +41,6 @@ use wasmi::{
|
|||||||
/// compiler toolchains might not support specifying other modules than "env" for memory imports.
|
/// compiler toolchains might not support specifying other modules than "env" for memory imports.
|
||||||
pub const IMPORT_MODULE_MEMORY: &str = "env";
|
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.
|
/// The inner deserialized module is valid and contains only allowed WebAssembly features.
|
||||||
/// This is checked by loading it into wasmi interpreter `engine`.
|
/// This is checked by loading it into wasmi interpreter `engine`.
|
||||||
pub struct LoadedModule {
|
pub struct LoadedModule {
|
||||||
@@ -237,7 +222,6 @@ fn validate<E, T>(
|
|||||||
code: &[u8],
|
code: &[u8],
|
||||||
schedule: &Schedule<T>,
|
schedule: &Schedule<T>,
|
||||||
determinism: Determinism,
|
determinism: Determinism,
|
||||||
try_instantiate: TryInstantiate,
|
|
||||||
) -> Result<(), (DispatchError, &'static str)>
|
) -> Result<(), (DispatchError, &'static str)>
|
||||||
where
|
where
|
||||||
E: Environment<()>,
|
E: Environment<()>,
|
||||||
@@ -261,23 +245,22 @@ where
|
|||||||
//
|
//
|
||||||
// - It doesn't use any unknown imports.
|
// - It doesn't use any unknown imports.
|
||||||
// - It doesn't explode the wasmi bytecode generation.
|
// - 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
|
// 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.
|
// reduces the amount of memory that needs to be zeroed.
|
||||||
let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed");
|
let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed");
|
||||||
WasmBlob::<T>::instantiate::<E, _>(
|
WasmBlob::<T>::instantiate::<E, _>(
|
||||||
&code,
|
&code,
|
||||||
(),
|
(),
|
||||||
schedule,
|
schedule,
|
||||||
determinism,
|
determinism,
|
||||||
stack_limits,
|
stack_limits,
|
||||||
AllowDeprecatedInterface::No,
|
AllowDeprecatedInterface::No,
|
||||||
)
|
)
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
log::debug!(target: LOG_TARGET, "{}", err);
|
log::debug!(target: LOG_TARGET, "{}", err);
|
||||||
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
|
(Error::<T>::CodeRejected.into(), "New code rejected on wasmi instantiation!")
|
||||||
})?;
|
})?;
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -295,13 +278,12 @@ pub fn prepare<E, T>(
|
|||||||
schedule: &Schedule<T>,
|
schedule: &Schedule<T>,
|
||||||
owner: AccountIdOf<T>,
|
owner: AccountIdOf<T>,
|
||||||
determinism: Determinism,
|
determinism: Determinism,
|
||||||
try_instantiate: TryInstantiate,
|
|
||||||
) -> Result<WasmBlob<T>, (DispatchError, &'static str)>
|
) -> Result<WasmBlob<T>, (DispatchError, &'static str)>
|
||||||
where
|
where
|
||||||
E: Environment<()>,
|
E: Environment<()>,
|
||||||
T: Config,
|
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.
|
// 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;
|
let bytes_added = code.len().saturating_add(<CodeInfo<T>>::max_encoded_len()) as u32;
|
||||||
@@ -416,7 +398,6 @@ mod tests {
|
|||||||
&schedule,
|
&schedule,
|
||||||
ALICE,
|
ALICE,
|
||||||
Determinism::Enforced,
|
Determinism::Enforced,
|
||||||
TryInstantiate::Instantiate,
|
|
||||||
);
|
);
|
||||||
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
|
assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*);
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+581
-585
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user