#![allow(rustdoc::private_intra_doc_links)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "1024")]
extern crate alloc;
mod address;
mod benchmarking;
mod exec;
mod gas;
mod primitives;
pub use primitives::*;
mod schedule;
mod storage;
mod transient_storage;
mod wasm;
pub mod chain_extension;
pub mod debug;
pub mod migration;
pub mod test_utils;
pub mod weights;
#[cfg(test)]
mod tests;
use crate::{
exec::{
AccountIdOf, ErrorOrigin, ExecError, Executable, Ext, Key, MomentOf, Stack as ExecStack,
},
gas::GasMeter,
storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager},
wasm::{CodeInfo, RuntimeCosts, WasmBlob},
};
use codec::{Codec, Decode, DecodeWithMemTracking, Encode, HasCompact, MaxEncodedLen};
use core::fmt::Debug;
use environmental::*;
use frame_support::{
dispatch::{GetDispatchInfo, Pays, PostDispatchInfo, RawOrigin, WithPostDispatchInfo},
ensure,
traits::{
fungible::{Inspect, Mutate, MutateHold},
ConstU32, Contains, Get, Randomness, Time,
},
weights::{Weight, WeightMeter},
BoundedVec, DefaultNoBound, RuntimeDebugNoBound,
};
use frame_system::{
ensure_signed,
pallet_prelude::{BlockNumberFor, OriginFor},
EventRecord, Pallet as System,
};
use scale_info::TypeInfo;
use smallvec::Array;
use sp_runtime::{
traits::{BadOrigin, Convert, Dispatchable, Saturating, StaticLookup, Zero},
DispatchError, RuntimeDebug,
};
pub use crate::{
address::{AddressGenerator, DefaultAddressGenerator},
debug::Tracing,
exec::Frame,
migration::{MigrateSequence, Migration, NoopMigration},
pallet::*,
schedule::{InstructionWeights, Limits, Schedule},
wasm::Determinism,
};
pub use weights::WeightInfo;
#[cfg(doc)]
pub use crate::wasm::api_doc;
type CodeHash<T> = <T as frame_system::Config>::Hash;
type TrieId = BoundedVec<u8, ConstU32<128>>;
type BalanceOf<T> =
<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
type CodeVec<T> = BoundedVec<u8, <T as Config>::MaxCodeLen>;
type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
type DebugBufferVec<T> = BoundedVec<u8, <T as Config>::MaxDebugBufferLen>;
type EventRecordOf<T> =
EventRecord<<T as frame_system::Config>::RuntimeEvent, <T as frame_system::Config>::Hash>;
type OldWeight = u64;
const SENTINEL: u32 = u32::MAX;
const LOG_TARGET: &str = "runtime::contracts";
#[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct EnvironmentType<T>(PhantomData<T>);
#[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
#[scale_info(skip_type_params(T))]
pub struct Environment<T: Config> {
account_id: EnvironmentType<AccountIdOf<T>>,
balance: EnvironmentType<BalanceOf<T>>,
hash: EnvironmentType<<T as frame_system::Config>::Hash>,
hasher: EnvironmentType<<T as frame_system::Config>::Hashing>,
timestamp: EnvironmentType<MomentOf<T>>,
block_number: EnvironmentType<BlockNumberFor<T>>,
}
#[derive(Encode, Decode, TypeInfo)]
pub struct ApiVersion(u16);
impl Default for ApiVersion {
fn default() -> Self {
Self(4)
}
}
#[test]
fn api_version_is_up_to_date() {
assert_eq!(
111,
crate::wasm::STABLE_API_COUNT,
"Stable API count has changed. Bump the returned value of ApiVersion::default() and update the test."
);
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use crate::debug::Debugger;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::Perbill;
pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(16);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config(with_default)]
pub trait Config: frame_system::Config {
type Time: Time;
#[pallet::no_default_bounds]
type Randomness: Randomness<Self::Hash, BlockNumberFor<Self>>;
#[pallet::no_default]
type Currency: Inspect<Self::AccountId>
+ Mutate<Self::AccountId>
+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>;
#[pallet::no_default_bounds]
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
#[pallet::no_default_bounds]
type RuntimeCall: Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo
+ codec::Decode
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
#[pallet::no_default_bounds]
type RuntimeHoldReason: From<HoldReason>;
#[pallet::no_default_bounds]
type CallFilter: Contains<<Self as frame_system::Config>::RuntimeCall>;
#[pallet::no_default_bounds]
type WeightPrice: Convert<Weight, BalanceOf<Self>>;
type WeightInfo: WeightInfo;
#[pallet::no_default_bounds]
type ChainExtension: chain_extension::ChainExtension<Self> + Default;
#[pallet::constant]
#[pallet::no_default]
type Schedule: Get<Schedule<Self>>;
#[pallet::no_default]
type CallStack: Array<Item = Frame<Self>>;
#[pallet::constant]
#[pallet::no_default_bounds]
type DepositPerByte: Get<BalanceOf<Self>>;
#[pallet::constant]
#[pallet::no_default_bounds]
type DefaultDepositLimit: Get<BalanceOf<Self>>;
#[pallet::constant]
#[pallet::no_default_bounds]
type DepositPerItem: Get<BalanceOf<Self>>;
#[pallet::constant]
type CodeHashLockupDepositPercent: Get<Perbill>;
#[pallet::no_default_bounds]
type AddressGenerator: AddressGenerator<Self>;
#[pallet::constant]
type MaxCodeLen: Get<u32>;
#[pallet::constant]
type MaxStorageKeyLen: Get<u32>;
#[pallet::constant]
type MaxTransientStorageSize: Get<u32>;
#[pallet::constant]
type MaxDelegateDependencies: Get<u32>;
#[pallet::constant]
type UnsafeUnstableInterface: Get<bool>;
#[pallet::constant]
type MaxDebugBufferLen: Get<u32>;
#[pallet::no_default_bounds]
type UploadOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
#[pallet::no_default_bounds]
type InstantiateOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
type Migrations: MigrateSequence;
#[pallet::no_default_bounds]
type Debug: Debugger<Self>;
#[pallet::constant]
#[pallet::no_default_bounds]
type Environment: Get<Environment<Self>>;
#[pallet::constant]
#[pallet::no_default_bounds]
type ApiVersion: Get<ApiVersion>;
#[pallet::no_default_bounds]
type Xcm: xcm_builder::Controller<
OriginFor<Self>,
<Self as frame_system::Config>::RuntimeCall,
BlockNumberFor<Self>,
>;
}
pub mod config_preludes {
use super::*;
use frame_support::{
derive_impl,
traits::{ConstBool, ConstU32},
};
use frame_system::EnsureSigned;
use sp_core::parameter_types;
type AccountId = sp_runtime::AccountId32;
type Balance = u64;
const UNITS: Balance = 10_000_000_000;
const CENTS: Balance = UNITS / 100;
const fn deposit(items: u32, bytes: u32) -> Balance {
items as Balance * 1 * CENTS + (bytes as Balance) * 1 * CENTS
}
parameter_types! {
pub const DepositPerItem: Balance = deposit(1, 0);
pub const DepositPerByte: Balance = deposit(0, 1);
pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024);
pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0);
pub const MaxDelegateDependencies: u32 = 32;
}
pub struct TestDefaultConfig;
impl<Output, BlockNumber> Randomness<Output, BlockNumber> for TestDefaultConfig {
fn random(_subject: &[u8]) -> (Output, BlockNumber) {
unimplemented!("No default `random` implementation in `TestDefaultConfig`, provide a custom `T::Randomness` type.")
}
}
impl Time for TestDefaultConfig {
type Moment = u64;
fn now() -> Self::Moment {
unimplemented!("No default `now` implementation in `TestDefaultConfig` provide a custom `T::Time` type.")
}
}
impl<T: From<u64>> Convert<Weight, T> for TestDefaultConfig {
fn convert(w: Weight) -> T {
w.ref_time().into()
}
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
impl frame_system::DefaultConfig for TestDefaultConfig {}
#[frame_support::register_default_impl(TestDefaultConfig)]
impl DefaultConfig for TestDefaultConfig {
#[inject_runtime_type]
type RuntimeEvent = ();
#[inject_runtime_type]
type RuntimeHoldReason = ();
#[inject_runtime_type]
type RuntimeCall = ();
type AddressGenerator = DefaultAddressGenerator;
type CallFilter = ();
type ChainExtension = ();
type CodeHashLockupDepositPercent = CodeHashLockupDepositPercent;
type DefaultDepositLimit = DefaultDepositLimit;
type DepositPerByte = DepositPerByte;
type DepositPerItem = DepositPerItem;
type MaxCodeLen = ConstU32<{ 123 * 1024 }>;
type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>;
type MaxDelegateDependencies = MaxDelegateDependencies;
type MaxStorageKeyLen = ConstU32<128>;
type MaxTransientStorageSize = ConstU32<{ 1 * 1024 * 1024 }>;
type Migrations = ();
type Time = Self;
type Randomness = Self;
type UnsafeUnstableInterface = ConstBool<true>;
type UploadOrigin = EnsureSigned<AccountId>;
type InstantiateOrigin = EnsureSigned<AccountId>;
type WeightInfo = ();
type WeightPrice = Self;
type Debug = ();
type Environment = ();
type ApiVersion = ();
type Xcm = ();
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_idle(_block: BlockNumberFor<T>, limit: Weight) -> Weight {
use migration::MigrateResult::*;
let mut meter = WeightMeter::with_limit(limit);
loop {
match Migration::<T>::migrate(&mut meter) {
NoMigrationPerformed | InProgress { steps_done: 0 } => return meter.consumed(),
InProgress { .. } => continue,
Completed | NoMigrationInProgress => break,
}
}
ContractInfo::<T>::process_deletion_queue_batch(&mut meter);
meter.consumed()
}
fn integrity_test() {
Migration::<T>::integrity_test();
let max_runtime_mem: u32 = T::Schedule::get().limits.runtime_memory;
const MAX_STACK_SIZE: u32 = 1024 * 1024;
let max_heap_size = T::Schedule::get().limits.max_memory_size();
let max_call_depth = u32::try_from(T::CallStack::size().saturating_add(1))
.expect("CallStack size is too big");
let max_transient_storage_size = T::MaxTransientStorageSize::get()
.checked_mul(2)
.expect("MaxTransientStorageSize is too large");
let code_len_limit = max_runtime_mem
.saturating_div(2)
.saturating_sub(max_transient_storage_size)
.saturating_div(max_call_depth)
.saturating_sub(max_heap_size)
.saturating_sub(MAX_STACK_SIZE)
.saturating_div(17 * 4);
assert!(
T::MaxCodeLen::get() < code_len_limit,
"Given `CallStack` height {:?}, `MaxCodeLen` should be set less than {:?} \
(current value is {:?}), to avoid possible runtime oom issues.",
max_call_depth,
code_len_limit,
T::MaxCodeLen::get(),
);
const MIN_DEBUG_BUF_SIZE: u32 = 256;
assert!(
T::MaxDebugBufferLen::get() > MIN_DEBUG_BUF_SIZE,
"Debug buffer should have minimum size of {} (current setting is {})",
MIN_DEBUG_BUF_SIZE,
T::MaxDebugBufferLen::get(),
);
let max_block_ref_time = T::BlockWeights::get()
.get(DispatchClass::Normal)
.max_total
.unwrap_or_else(|| T::BlockWeights::get().max_block)
.ref_time();
let max_payload_size = T::Schedule::get().limits.payload_len;
let max_key_size =
Key::<T>::try_from_var(alloc::vec![0u8; T::MaxStorageKeyLen::get() as usize])
.expect("Key of maximal size shall be created")
.hash()
.len() as u32;
let max_storage_size: u32 = ((max_block_ref_time /
(<RuntimeCosts as gas::Token<T>>::weight(&RuntimeCosts::SetStorage {
new_bytes: max_payload_size,
old_bytes: 0,
})
.ref_time()))
.saturating_mul(max_payload_size.saturating_add(max_key_size) as u64))
.try_into()
.expect("Storage size too big");
let max_validator_runtime_mem: u32 = T::Schedule::get().limits.validator_runtime_memory;
let storage_size_limit = max_validator_runtime_mem.saturating_sub(max_runtime_mem) / 2;
assert!(
max_storage_size < storage_size_limit,
"Maximal storage size {} exceeds the storage limit {}",
max_storage_size,
storage_size_limit
);
let max_events_size: u32 = ((max_block_ref_time /
(<RuntimeCosts as gas::Token<T>>::weight(&RuntimeCosts::DepositEvent {
num_topic: 0,
len: max_payload_size,
})
.ref_time()))
.saturating_mul(max_payload_size as u64))
.try_into()
.expect("Events size too big");
assert!(
max_events_size < storage_size_limit,
"Maximal events size {} exceeds the events limit {}",
max_events_size,
storage_size_limit
);
}
}
#[pallet::call]
impl<T: Config> Pallet<T>
where
<BalanceOf<T> as HasCompact>::Type: Clone + Eq + PartialEq + Debug + TypeInfo + Encode,
{
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::call().saturating_add(<Pallet<T>>::compat_weight_limit(*gas_limit)))]
#[allow(deprecated)]
#[deprecated(note = "1D weight is used in this extrinsic, please migrate to `call`")]
pub fn call_old_weight(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
#[pallet::compact] value: BalanceOf<T>,
#[pallet::compact] gas_limit: OldWeight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo {
Self::call(
origin,
dest,
value,
<Pallet<T>>::compat_weight_limit(gas_limit),
storage_deposit_limit,
data,
)
}
#[pallet::call_index(1)]
#[pallet::weight(
T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32, salt.len() as u32)
.saturating_add(<Pallet<T>>::compat_weight_limit(*gas_limit))
)]
#[allow(deprecated)]
#[deprecated(
note = "1D weight is used in this extrinsic, please migrate to `instantiate_with_code`"
)]
pub fn instantiate_with_code_old_weight(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
#[pallet::compact] gas_limit: OldWeight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
code: Vec<u8>,
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
Self::instantiate_with_code(
origin,
value,
<Pallet<T>>::compat_weight_limit(gas_limit),
storage_deposit_limit,
code,
data,
salt,
)
}
#[pallet::call_index(2)]
#[pallet::weight(
T::WeightInfo::instantiate(data.len() as u32, salt.len() as u32).saturating_add(<Pallet<T>>::compat_weight_limit(*gas_limit))
)]
#[allow(deprecated)]
#[deprecated(note = "1D weight is used in this extrinsic, please migrate to `instantiate`")]
pub fn instantiate_old_weight(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
#[pallet::compact] gas_limit: OldWeight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
code_hash: CodeHash<T>,
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
Self::instantiate(
origin,
value,
<Pallet<T>>::compat_weight_limit(gas_limit),
storage_deposit_limit,
code_hash,
data,
salt,
)
}
#[pallet::call_index(3)]
#[pallet::weight(
match determinism {
Determinism::Enforced => T::WeightInfo::upload_code_determinism_enforced(code.len() as u32),
Determinism::Relaxed => T::WeightInfo::upload_code_determinism_relaxed(code.len() as u32),
}
)]
pub fn upload_code(
origin: OriginFor<T>,
code: Vec<u8>,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
determinism: Determinism,
) -> DispatchResult {
Migration::<T>::ensure_migrated()?;
let origin = T::UploadOrigin::ensure_origin(origin)?;
Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into), determinism)
.map(|_| ())
}
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::remove_code())]
pub fn remove_code(
origin: OriginFor<T>,
code_hash: CodeHash<T>,
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let origin = ensure_signed(origin)?;
<WasmBlob<T>>::remove(&origin, code_hash)?;
Ok(Pays::No.into())
}
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::set_code())]
pub fn set_code(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
code_hash: CodeHash<T>,
) -> DispatchResult {
Migration::<T>::ensure_migrated()?;
ensure_root(origin)?;
let dest = T::Lookup::lookup(dest)?;
<ContractInfoOf<T>>::try_mutate(&dest, |contract| {
let contract = if let Some(contract) = contract {
contract
} else {
return Err(<Error<T>>::ContractNotFound.into())
};
<ExecStack<T, WasmBlob<T>>>::increment_refcount(code_hash)?;
<ExecStack<T, WasmBlob<T>>>::decrement_refcount(contract.code_hash);
Self::deposit_event(Event::ContractCodeUpdated {
contract: dest.clone(),
new_code_hash: code_hash,
old_code_hash: contract.code_hash,
});
contract.code_hash = code_hash;
Ok(())
})
}
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::call().saturating_add(*gas_limit))]
pub fn call(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
#[pallet::compact] value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let common = CommonInput {
origin: Origin::from_runtime_origin(origin)?,
value,
data,
gas_limit: gas_limit.into(),
storage_deposit_limit: storage_deposit_limit.map(Into::into),
debug_message: None,
};
let dest = T::Lookup::lookup(dest)?;
let mut output =
CallInput::<T> { dest, determinism: Determinism::Enforced }.run_guarded(common);
if let Ok(retval) = &output.result {
if retval.did_revert() {
output.result = Err(<Error<T>>::ContractReverted.into());
}
}
output.gas_meter.into_dispatch_result(output.result, T::WeightInfo::call())
}
#[pallet::call_index(7)]
#[pallet::weight(
T::WeightInfo::instantiate_with_code(code.len() as u32, data.len() as u32, salt.len() as u32)
.saturating_add(*gas_limit)
)]
pub fn instantiate_with_code(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
code: Vec<u8>,
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let upload_origin = T::UploadOrigin::ensure_origin(origin.clone())?;
let instantiate_origin = T::InstantiateOrigin::ensure_origin(origin)?;
let code_len = code.len() as u32;
let (module, upload_deposit) = Self::try_upload_code(
upload_origin,
code,
storage_deposit_limit.clone().map(Into::into),
Determinism::Enforced,
None,
)?;
let storage_deposit_limit =
storage_deposit_limit.map(|limit| limit.into().saturating_sub(upload_deposit));
let data_len = data.len() as u32;
let salt_len = salt.len() as u32;
let common = CommonInput {
origin: Origin::from_account_id(instantiate_origin),
value,
data,
gas_limit,
storage_deposit_limit,
debug_message: None,
};
let mut output =
InstantiateInput::<T> { code: WasmCode::Wasm(module), salt }.run_guarded(common);
if let Ok(retval) = &output.result {
if retval.1.did_revert() {
output.result = Err(<Error<T>>::ContractReverted.into());
}
}
output.gas_meter.into_dispatch_result(
output.result.map(|(_address, output)| output),
T::WeightInfo::instantiate_with_code(code_len, data_len, salt_len),
)
}
#[pallet::call_index(8)]
#[pallet::weight(
T::WeightInfo::instantiate(data.len() as u32, salt.len() as u32).saturating_add(*gas_limit)
)]
pub fn instantiate(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
code_hash: CodeHash<T>,
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
Migration::<T>::ensure_migrated()?;
let origin = T::InstantiateOrigin::ensure_origin(origin)?;
let data_len = data.len() as u32;
let salt_len = salt.len() as u32;
let common = CommonInput {
origin: Origin::from_account_id(origin),
value,
data,
gas_limit,
storage_deposit_limit: storage_deposit_limit.map(Into::into),
debug_message: None,
};
let mut output = InstantiateInput::<T> { code: WasmCode::CodeHash(code_hash), salt }
.run_guarded(common);
if let Ok(retval) = &output.result {
if retval.1.did_revert() {
output.result = Err(<Error<T>>::ContractReverted.into());
}
}
output.gas_meter.into_dispatch_result(
output.result.map(|(_address, output)| output),
T::WeightInfo::instantiate(data_len, salt_len),
)
}
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::migrate().saturating_add(*weight_limit))]
pub fn migrate(origin: OriginFor<T>, weight_limit: Weight) -> DispatchResultWithPostInfo {
use migration::MigrateResult::*;
ensure_signed(origin)?;
let weight_limit = weight_limit.saturating_add(T::WeightInfo::migrate());
let mut meter = WeightMeter::with_limit(weight_limit);
let result = Migration::<T>::migrate(&mut meter);
match result {
Completed => Ok(PostDispatchInfo {
actual_weight: Some(meter.consumed()),
pays_fee: Pays::No,
}),
InProgress { steps_done, .. } if steps_done > 0 => Ok(PostDispatchInfo {
actual_weight: Some(meter.consumed()),
pays_fee: Pays::No,
}),
InProgress { .. } => Ok(PostDispatchInfo {
actual_weight: Some(meter.consumed()),
pays_fee: Pays::Yes,
}),
NoMigrationInProgress | NoMigrationPerformed => {
let err: DispatchError = <Error<T>>::NoMigrationPerformed.into();
Err(err.with_weight(meter.consumed()))
},
}
}
}
#[pallet::event]
pub enum Event<T: Config> {
Instantiated { deployer: T::AccountId, contract: T::AccountId },
Terminated {
contract: T::AccountId,
beneficiary: T::AccountId,
},
CodeStored { code_hash: T::Hash, deposit_held: BalanceOf<T>, uploader: T::AccountId },
ContractEmitted {
contract: T::AccountId,
data: Vec<u8>,
},
CodeRemoved { code_hash: T::Hash, deposit_released: BalanceOf<T>, remover: T::AccountId },
ContractCodeUpdated {
contract: T::AccountId,
new_code_hash: T::Hash,
old_code_hash: T::Hash,
},
Called {
caller: Origin<T>,
contract: T::AccountId,
},
DelegateCalled {
contract: T::AccountId,
code_hash: CodeHash<T>,
},
StorageDepositTransferredAndHeld {
from: T::AccountId,
to: T::AccountId,
amount: BalanceOf<T>,
},
StorageDepositTransferredAndReleased {
from: T::AccountId,
to: T::AccountId,
amount: BalanceOf<T>,
},
}
#[pallet::error]
pub enum Error<T> {
InvalidSchedule,
InvalidCallFlags,
OutOfGas,
OutputBufferTooSmall,
TransferFailed,
MaxCallDepthReached,
ContractNotFound,
CodeTooLarge,
CodeNotFound,
CodeInfoNotFound,
OutOfBounds,
DecodingFailed,
ContractTrapped,
ValueTooLarge,
TerminatedWhileReentrant,
InputForwarded,
RandomSubjectTooLong,
TooManyTopics,
NoChainExtension,
XCMDecodeFailed,
DuplicateContract,
TerminatedInConstructor,
ReentranceDenied,
StateChangeDenied,
StorageDepositNotEnoughFunds,
StorageDepositLimitExhausted,
CodeInUse,
ContractReverted,
CodeRejected,
Indeterministic,
MigrationInProgress,
NoMigrationPerformed,
MaxDelegateDependenciesReached,
DelegateDependencyNotFound,
DelegateDependencyAlreadyExists,
CannotAddSelfAsDelegateDependency,
OutOfTransientStorage,
}
#[pallet::composite_enum]
pub enum HoldReason {
CodeUploadDepositReserve,
StorageDepositReserve,
}
#[pallet::storage]
pub(crate) type PristineCode<T: Config> = StorageMap<_, Identity, CodeHash<T>, CodeVec<T>>;
#[pallet::storage]
pub(crate) type CodeInfoOf<T: Config> = StorageMap<_, Identity, CodeHash<T>, CodeInfo<T>>;
#[pallet::storage]
pub(crate) type Nonce<T: Config> = StorageValue<_, u64, ValueQuery>;
#[pallet::storage]
pub(crate) type ContractInfoOf<T: Config> =
StorageMap<_, Twox64Concat, T::AccountId, ContractInfo<T>>;
#[pallet::storage]
pub(crate) type DeletionQueue<T: Config> = StorageMap<_, Twox64Concat, u32, TrieId>;
#[pallet::storage]
pub(crate) type DeletionQueueCounter<T: Config> =
StorageValue<_, DeletionQueueManager<T>, ValueQuery>;
#[pallet::storage]
pub(crate) type MigrationInProgress<T: Config> =
StorageValue<_, migration::Cursor, OptionQuery>;
}
#[derive(
Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, RuntimeDebugNoBound,
)]
pub enum Origin<T: Config> {
Root,
Signed(T::AccountId),
}
impl<T: Config> Origin<T> {
pub fn from_account_id(account_id: T::AccountId) -> Self {
Origin::Signed(account_id)
}
pub fn from_runtime_origin(o: OriginFor<T>) -> Result<Self, DispatchError> {
match o.into() {
Ok(RawOrigin::Root) => Ok(Self::Root),
Ok(RawOrigin::Signed(t)) => Ok(Self::Signed(t)),
_ => Err(BadOrigin.into()),
}
}
pub fn account_id(&self) -> Result<&T::AccountId, DispatchError> {
match self {
Origin::Signed(id) => Ok(id),
Origin::Root => Err(DispatchError::RootNotAllowed),
}
}
}
struct CommonInput<'a, T: Config> {
origin: Origin<T>,
value: BalanceOf<T>,
data: Vec<u8>,
gas_limit: Weight,
storage_deposit_limit: Option<BalanceOf<T>>,
debug_message: Option<&'a mut DebugBufferVec<T>>,
}
struct CallInput<T: Config> {
dest: T::AccountId,
determinism: Determinism,
}
enum WasmCode<T: Config> {
Wasm(WasmBlob<T>),
CodeHash(CodeHash<T>),
}
struct InstantiateInput<T: Config> {
code: WasmCode<T>,
salt: Vec<u8>,
}
#[derive(
Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo,
)]
pub enum CollectEvents {
UnsafeCollect,
Skip,
}
#[derive(
Copy, Clone, PartialEq, Eq, RuntimeDebug, Decode, Encode, MaxEncodedLen, scale_info::TypeInfo,
)]
pub enum DebugInfo {
UnsafeDebug,
Skip,
}
struct InternalOutput<T: Config, O> {
gas_meter: GasMeter<T>,
storage_deposit: StorageDeposit<BalanceOf<T>>,
result: Result<O, ExecError>,
}
environmental!(executing_contract: bool);
trait Invokable<T: Config>: Sized {
type Output;
fn run_guarded(self, common: CommonInput<T>) -> InternalOutput<T, Self::Output> {
let gas_limit = common.gas_limit;
if let Err(e) = self.ensure_origin(common.origin.clone()) {
return InternalOutput {
gas_meter: GasMeter::new(gas_limit),
storage_deposit: Default::default(),
result: Err(ExecError { error: e.into(), origin: ErrorOrigin::Caller }),
}
}
executing_contract::using_once(&mut false, || {
executing_contract::with(|f| {
if *f {
return Err(())
}
*f = true;
Ok(())
})
.expect("Returns `Ok` if called within `using_once`. It is syntactically obvious that this is the case; qed")
.map_or_else(
|_| InternalOutput {
gas_meter: GasMeter::new(gas_limit),
storage_deposit: Default::default(),
result: Err(ExecError {
error: <Error<T>>::ReentranceDenied.into(),
origin: ErrorOrigin::Caller,
}),
},
|_| self.run(common, GasMeter::new(gas_limit)),
)
})
}
fn run(self, common: CommonInput<T>, gas_meter: GasMeter<T>)
-> InternalOutput<T, Self::Output>;
fn ensure_origin(&self, origin: Origin<T>) -> Result<(), DispatchError>;
}
impl<T: Config> Invokable<T> for CallInput<T> {
type Output = ExecReturnValue;
fn run(
self,
common: CommonInput<T>,
mut gas_meter: GasMeter<T>,
) -> InternalOutput<T, Self::Output> {
let CallInput { dest, determinism } = self;
let CommonInput { origin, value, data, debug_message, .. } = common;
let mut storage_meter =
match StorageMeter::new(&origin, common.storage_deposit_limit, common.value) {
Ok(meter) => meter,
Err(err) =>
return InternalOutput {
result: Err(err.into()),
gas_meter,
storage_deposit: Default::default(),
},
};
let schedule = T::Schedule::get();
let result = ExecStack::<T, WasmBlob<T>>::run_call(
origin.clone(),
dest.clone(),
&mut gas_meter,
&mut storage_meter,
&schedule,
value,
data.clone(),
debug_message,
determinism,
);
match storage_meter.try_into_deposit(&origin) {
Ok(storage_deposit) => InternalOutput { gas_meter, storage_deposit, result },
Err(err) => InternalOutput {
gas_meter,
storage_deposit: Default::default(),
result: Err(err.into()),
},
}
}
fn ensure_origin(&self, _origin: Origin<T>) -> Result<(), DispatchError> {
Ok(())
}
}
impl<T: Config> Invokable<T> for InstantiateInput<T> {
type Output = (AccountIdOf<T>, ExecReturnValue);
fn run(
self,
common: CommonInput<T>,
mut gas_meter: GasMeter<T>,
) -> InternalOutput<T, Self::Output> {
let mut storage_deposit = Default::default();
let try_exec = || {
let schedule = T::Schedule::get();
let InstantiateInput { salt, .. } = self;
let CommonInput { origin: contract_origin, .. } = common;
let origin = contract_origin.account_id()?;
let executable = match self.code {
WasmCode::Wasm(module) => module,
WasmCode::CodeHash(code_hash) => WasmBlob::from_storage(code_hash, &mut gas_meter)?,
};
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter =
StorageMeter::new(&contract_origin, common.storage_deposit_limit, common.value)?;
let CommonInput { value, data, debug_message, .. } = common;
let result = ExecStack::<T, WasmBlob<T>>::run_instantiate(
origin.clone(),
executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
value,
data.clone(),
&salt,
debug_message,
);
storage_deposit = storage_meter.try_into_deposit(&contract_origin)?;
result
};
InternalOutput { result: try_exec(), gas_meter, storage_deposit }
}
fn ensure_origin(&self, origin: Origin<T>) -> Result<(), DispatchError> {
match origin {
Origin::Signed(_) => Ok(()),
Origin::Root => Err(DispatchError::RootNotAllowed),
}
}
}
macro_rules! ensure_no_migration_in_progress {
() => {
if Migration::<T>::in_progress() {
return ContractResult {
gas_consumed: Zero::zero(),
gas_required: Zero::zero(),
storage_deposit: Default::default(),
debug_message: Vec::new(),
result: Err(Error::<T>::MigrationInProgress.into()),
events: None,
}
}
};
}
impl<T: Config> Pallet<T> {
pub fn bare_call(
origin: T::AccountId,
dest: T::AccountId,
value: BalanceOf<T>,
gas_limit: Weight,
storage_deposit_limit: Option<BalanceOf<T>>,
data: Vec<u8>,
debug: DebugInfo,
collect_events: CollectEvents,
determinism: Determinism,
) -> ContractExecResult<BalanceOf<T>, EventRecordOf<T>> {
ensure_no_migration_in_progress!();
let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) {
Some(DebugBufferVec::<T>::default())
} else {
None
};
let origin = Origin::from_account_id(origin);
let common = CommonInput {
origin,
value,
data,
gas_limit,
storage_deposit_limit,
debug_message: debug_message.as_mut(),
};
let output = CallInput::<T> { dest, determinism }.run_guarded(common);
let events = if matches!(collect_events, CollectEvents::UnsafeCollect) {
Some(System::<T>::read_events_no_consensus().map(|e| *e).collect())
} else {
None
};
ContractExecResult {
result: output.result.map_err(|r| r.error),
gas_consumed: output.gas_meter.gas_consumed(),
gas_required: output.gas_meter.gas_required(),
storage_deposit: output.storage_deposit,
debug_message: debug_message.unwrap_or_default().to_vec(),
events,
}
}
pub fn bare_instantiate(
origin: T::AccountId,
value: BalanceOf<T>,
gas_limit: Weight,
mut storage_deposit_limit: Option<BalanceOf<T>>,
code: Code<CodeHash<T>>,
data: Vec<u8>,
salt: Vec<u8>,
debug: DebugInfo,
collect_events: CollectEvents,
) -> ContractInstantiateResult<T::AccountId, BalanceOf<T>, EventRecordOf<T>> {
ensure_no_migration_in_progress!();
let mut debug_message = if debug == DebugInfo::UnsafeDebug {
Some(DebugBufferVec::<T>::default())
} else {
None
};
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 {
origin: Origin::from_account_id(origin),
value,
data,
gas_limit,
storage_deposit_limit,
debug_message: debug_message.as_mut(),
};
let output = InstantiateInput::<T> { code, salt }.run_guarded(common);
ContractInstantiateResult {
result: output
.result
.map(|(account_id, result)| InstantiateReturnValue { result, account_id })
.map_err(|e| e.error),
gas_consumed: output.gas_meter.gas_consumed(),
gas_required: output.gas_meter.gas_required(),
storage_deposit: output
.storage_deposit
.saturating_add(&StorageDeposit::Charge(upload_deposit)),
debug_message: debug_message.unwrap_or_default().to_vec(),
events: events(),
}
}
pub fn bare_upload_code(
origin: T::AccountId,
code: Vec<u8>,
storage_deposit_limit: Option<BalanceOf<T>>,
determinism: Determinism,
) -> CodeUploadResult<CodeHash<T>, BalanceOf<T>> {
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 })
}
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 mut module =
WasmBlob::from_code(code, &schedule, origin, determinism).map_err(|(err, msg)| {
debug_message.as_mut().map(|d| d.try_extend(msg.bytes()));
err
})?;
let deposit = module.store_code()?;
if let Some(storage_deposit_limit) = storage_deposit_limit {
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
}
Ok((module, deposit))
}
pub fn get_storage(address: T::AccountId, key: Vec<u8>) -> GetStorageResult {
if Migration::<T>::in_progress() {
return Err(ContractAccessError::MigrationInProgress)
}
let contract_info =
ContractInfoOf::<T>::get(&address).ok_or(ContractAccessError::DoesntExist)?;
let maybe_value = contract_info.read(
&Key::<T>::try_from_var(key)
.map_err(|_| ContractAccessError::KeyDecodingFailed)?
.into(),
);
Ok(maybe_value)
}
pub fn contract_address(
deploying_address: &T::AccountId,
code_hash: &CodeHash<T>,
input_data: &[u8],
salt: &[u8],
) -> T::AccountId {
T::AddressGenerator::contract_address(deploying_address, code_hash, input_data, salt)
}
pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
ContractInfo::<T>::load_code_hash(account)
}
#[cfg(feature = "runtime-benchmarks")]
fn store_code_raw(
code: Vec<u8>,
owner: T::AccountId,
) -> frame_support::dispatch::DispatchResult {
let schedule = T::Schedule::get();
WasmBlob::<T>::from_code_unchecked(code, &schedule, owner)?.store_code()?;
Ok(())
}
fn deposit_event(event: Event<T>) {
<frame_system::Pallet<T>>::deposit_event(<T as Config>::RuntimeEvent::from(event))
}
fn deposit_indexed_event(topics: Vec<T::Hash>, event: Event<T>) {
<frame_system::Pallet<T>>::deposit_event_indexed(
&topics,
<T as Config>::RuntimeEvent::from(event).into(),
)
}
fn min_balance() -> BalanceOf<T> {
<T::Currency as Inspect<AccountIdOf<T>>>::minimum_balance()
}
fn compat_weight_limit(gas_limit: OldWeight) -> Weight {
Weight::from_parts(gas_limit, u64::from(T::MaxCodeLen::get()) * 2)
}
}
sp_api::decl_runtime_apis! {
#[api_version(2)]
pub trait ContractsApi<AccountId, Balance, BlockNumber, Hash, EventRecord> where
AccountId: Codec,
Balance: Codec,
BlockNumber: Codec,
Hash: Codec,
EventRecord: Codec,
{
fn call(
origin: AccountId,
dest: AccountId,
value: Balance,
gas_limit: Option<Weight>,
storage_deposit_limit: Option<Balance>,
input_data: Vec<u8>,
) -> ContractExecResult<Balance, EventRecord>;
fn instantiate(
origin: AccountId,
value: Balance,
gas_limit: Option<Weight>,
storage_deposit_limit: Option<Balance>,
code: Code<Hash>,
data: Vec<u8>,
salt: Vec<u8>,
) -> ContractInstantiateResult<AccountId, Balance, EventRecord>;
fn upload_code(
origin: AccountId,
code: Vec<u8>,
storage_deposit_limit: Option<Balance>,
determinism: Determinism,
) -> CodeUploadResult<Hash, Balance>;
fn get_storage(
address: AccountId,
key: Vec<u8>,
) -> GetStorageResult;
}
}