Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
impl<T: Config> Origin<T> {
/// Creates a new Signed Caller from an AccountId.
pub fn from_account_id(account_id: T::AccountId) -> Self {
Origin::Signed(account_id)
}
/// Creates a new Origin from a `RuntimeOrigin`.
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()),
}
}
/// Returns the AccountId of a Signed Origin or an error if the origin is Root.
pub fn account_id(&self) -> Result<&T::AccountId, DispatchError> {
match self {
Origin::Signed(id) => Ok(id),
Origin::Root => Err(DispatchError::RootNotAllowed),
}
}
}
Sasha Gryaznov
committed
/// Context of a contract invocation.
struct CommonInput<'a, T: Config> {
Sasha Gryaznov
committed
value: BalanceOf<T>,
data: Vec<u8>,
gas_limit: Weight,
storage_deposit_limit: Option<BalanceOf<T>>,
debug_message: Option<&'a mut DebugBufferVec<T>>,
}
Sasha Gryaznov
committed
/// Input specific to a call into contract.
struct CallInput<T: Config> {
dest: T::AccountId,
determinism: Determinism,
}
/// Input specific to a contract instantiation invocation.
struct InstantiateInput<T: Config> {
code: Code<CodeHash<T>>,
salt: Vec<u8>,
}
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
/// Determines whether events should be collected during execution.
#[derive(PartialEq)]
pub enum CollectEvents {
/// Collect events.
///
/// # Note
///
/// Events should only be collected when called off-chain, as this would otherwise
/// collect all the Events emitted in the block so far and put them into the PoV.
///
/// **Never** use this mode for on-chain execution.
UnsafeCollect,
/// Skip event collection.
Skip,
}
/// Determines whether debug messages will be collected.
#[derive(PartialEq)]
pub enum DebugInfo {
/// Collect debug messages.
/// # Note
///
/// This should only ever be set to `UnsafeDebug` when executing as an RPC because
/// it adds allocations and could be abused to drive the runtime into an OOM panic.
UnsafeDebug,
/// Skip collection of debug messages.
Skip,
}
/// Return type of private helper functions.
struct InternalOutput<T: Config, O> {
/// The gas meter that was used to execute the call.
gas_meter: GasMeter<T>,
/// The storage deposit used by the call.
storage_deposit: StorageDeposit<BalanceOf<T>>,
/// The result of the call.
result: Result<O, ExecError>,
}
/// Helper trait to wrap contract execution entry points into a single function
Sasha Gryaznov
committed
/// [`Invokable::run_guarded`].
trait Invokable<T: Config> {
/// What is returned as a result of a successful invocation.
type Output;
/// Single entry point to contract execution.
/// Downstream execution flow is branched by implementations of [`Invokable`] trait:
///
/// - [`InstantiateInput::run`] runs contract instantiation,
/// - [`CallInput::run`] runs contract call.
///
/// We enforce a re-entrancy guard here by initializing and checking a boolean flag through a
/// global reference.
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.
environmental!(executing_contract: bool);
let gas_limit = common.gas_limit;
// Check whether the origin is allowed here. The logic of the access rules
// is in the `ensure_origin`, this could vary for different implementations of this
// trait. For example, some actions might not allow Root origin as they could require an
// AccountId associated with the origin.
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 }),
}
}
Sasha Gryaznov
committed
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
executing_contract::using_once(&mut false, || {
executing_contract::with(|f| {
// Fail if already entered contract execution
if *f {
return Err(())
}
// We are entering contract execution
*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,
}),
},
// Enter contract call.
|_| self.run(common, GasMeter::new(gas_limit)),
)
})
}
/// Method that does the actual call to a contract. It can be either a call to a deployed
/// contract or a instantiation of a new one.
///
/// Called by dispatchables and public functions through the [`Invokable::run_guarded`].
fn run(
&self,
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`.
///
/// Called by dispatchables and public functions through the [`Invokable::run_guarded`].
fn ensure_origin(&self, origin: Origin<T>) -> Result<(), DispatchError>;
Sasha Gryaznov
committed
}
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;
Sasha Gryaznov
committed
let mut storage_meter =
match StorageMeter::new(&origin, common.storage_deposit_limit, common.value) {
Sasha Gryaznov
committed
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, PrefabWasmModule<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()),
},
}
Sasha Gryaznov
committed
}
fn ensure_origin(&self, _origin: Origin<T>) -> Result<(), DispatchError> {
Ok(())
}
Sasha Gryaznov
committed
}
impl<T: Config> Invokable<T> for InstantiateInput<T> {
type Output = (AccountIdOf<T>, ExecReturnValue);
fn run(
&self,
mut 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()?;
Sasha Gryaznov
committed
let (extra_deposit, executable) = match &self.code {
Code::Upload(binary) => {
let executable = PrefabWasmModule::from_code(
binary.clone(),
&schedule,
Sasha Gryaznov
committed
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::Existing(hash) => (
Default::default(),
PrefabWasmModule::from_storage(*hash, &schedule, &mut gas_meter)?,
),
};
let contract_origin = Origin::from_account_id(origin.clone());
Sasha Gryaznov
committed
let mut storage_meter = StorageMeter::new(
Sasha Gryaznov
committed
common.storage_deposit_limit,
common.value.saturating_add(extra_deposit),
)?;
let CommonInput { value, data, debug_message, .. } = common;
Sasha Gryaznov
committed
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
origin.clone(),
executable,
&mut gas_meter,
&mut storage_meter,
&schedule,
value,
data.clone(),
&salt,
debug_message,
);
Sasha Gryaznov
committed
storage_deposit = storage_meter
.try_into_deposit(&contract_origin)?
Sasha Gryaznov
committed
.saturating_add(&StorageDeposit::Charge(extra_deposit));
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),
}
}
Sasha Gryaznov
committed
}
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> {
/// Perform a call to a specified contract.
///
/// This function is similar to [`Self::call`], but doesn't perform any address lookups
/// and better suitable for calling directly from Rust.
/// # Note
///
/// If `debug` is set to `DebugInfo::UnsafeDebug` it returns additional human readable debugging
/// information.
/// If `collect_events` is set to `CollectEvents::UnsafeCollect` it collects all the Events
/// emitted in the block so far and the ones emitted during the execution of this contract.
pub fn bare_call(
origin: T::AccountId,
dest: T::AccountId,
value: BalanceOf<T>,
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);
Sasha Gryaznov
committed
let common = CommonInput {
Sasha Gryaznov
committed
data,
gas_limit,
storage_deposit_limit,
Sasha Gryaznov
committed
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
};
Alexander Theißen
committed
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(),
}
}
/// Instantiate a new contract.
///
/// This function is similar to [`Self::instantiate`], but doesn't perform any address lookups
/// and better suitable for calling directly from Rust.
///
/// It returns the execution result, account id and the amount of used weight.
///
/// # Note
///
/// If `debug` is set to `DebugInfo::UnsafeDebug` it returns additional human readable debugging
/// information.
///
/// If `collect_events` is set to `CollectEvents::UnsafeCollect` it collects all the Events
/// emitted in the block so far.
pub fn bare_instantiate(
origin: T::AccountId,
gas_limit: Weight,
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
};
Sasha Gryaznov
committed
let common = CommonInput {
origin: Origin::from_account_id(origin),
Sasha Gryaznov
committed
data,
gas_limit,
Sasha Gryaznov
committed
debug_message: debug_message.as_mut(),
};
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 {
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,
debug_message: debug_message.unwrap_or_default().to_vec(),
Alexander Theißen
committed
}
/// Upload new code without instantiating a contract from it.
///
/// This function is similar to [`Self::upload_code`], but doesn't perform any address lookups
/// and better suitable for calling directly from Rust.
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 schedule = T::Schedule::get();
Alexander Theißen
committed
let module = PrefabWasmModule::from_code(
code,
&schedule,
origin,
determinism,
TryInstantiate::Instantiate,
)
.map_err(|(err, _)| err)?;
let deposit = module.open_deposit();
if let Some(storage_deposit_limit) = storage_deposit_limit {
ensure!(storage_deposit_limit >= deposit, <Error<T>>::StorageDepositLimitExhausted);
}
let result = CodeUploadReturnValue { code_hash: *module.code_hash(), deposit };
module.store()?;
Ok(result)
}
/// Query storage of a specified contract under a specified key.
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)?;
Alexander Theißen
committed
let maybe_value = contract_info.read(
&Key::<T>::try_from_var(key)
.map_err(|_| ContractAccessError::KeyDecodingFailed)?
.into(),
);
/// Determine the address of a contract.
///
/// This is the address generation function used by contract instantiation. See
/// [`DefaultAddressGenerator`] for the default implementation.
pub fn contract_address(
deploying_address: &T::AccountId,
code_hash: &CodeHash<T>,
input_data: &[u8],
salt: &[u8],
Alexander Theißen
committed
T::AddressGenerator::contract_address(deploying_address, code_hash, input_data, salt)
/// Returns the code hash of the contract specified by `account` ID.
pub fn code_hash(account: &AccountIdOf<T>) -> Option<CodeHash<T>> {
Alexander Theißen
committed
ContractInfo::<T>::load_code_hash(account)
/// Store code for benchmarks which does not check nor instrument the code.
#[cfg(feature = "runtime-benchmarks")]
fn store_code_raw(
code: Vec<u8>,
owner: T::AccountId,
) -> frame_support::dispatch::DispatchResult {
let schedule = T::Schedule::get();
PrefabWasmModule::store_code_unchecked(code, &schedule, owner)?;
Ok(())
}
/// This exists so that benchmarks can determine the weight of running an instrumentation.
#[cfg(feature = "runtime-benchmarks")]
fn reinstrument_module(
module: &mut PrefabWasmModule<T>,
) -> frame_support::dispatch::DispatchResult {
self::wasm::reinstrument(module, schedule).map(|_| ())
Jim Posen
committed
}
Alexander Theißen
committed
/// Deposit a pallet contracts event. Handles the conversion to the overarching event type.
fn deposit_event(topics: Vec<T::Hash>, event: Event<T>) {
<frame_system::Pallet<T>>::deposit_event_indexed(
&topics,
<T as Config>::RuntimeEvent::from(event).into(),
Alexander Theißen
committed
/// Return the existential deposit of [`Config::Currency`].
fn min_balance() -> BalanceOf<T> {
<T::Currency as Inspect<AccountIdOf<T>>>::minimum_balance()
}
/// Convert gas_limit from 1D Weight to a 2D Weight.
/// Used by backwards compatible extrinsics. We cannot just set the proof_size weight limit to
/// zero or an old `Call` will just fail with OutOfGas.
fn compat_weight_limit(gas_limit: OldWeight) -> Weight {
Weight::from_parts(gas_limit, u64::from(T::MaxCodeLen::get()) * 2)
Jim Posen
committed
}
sp_api::decl_runtime_apis! {
/// The API used to dry-run contract interactions.
pub trait ContractsApi<AccountId, Balance, BlockNumber, Hash, EventRecord> where
AccountId: Codec,
Balance: Codec,
BlockNumber: Codec,
Hash: Codec,
{
/// Perform a call from a specified account to a given contract.
///
/// See [`crate::Pallet::bare_call`].
fn call(
origin: AccountId,
dest: AccountId,
value: Balance,
gas_limit: Option<Weight>,
storage_deposit_limit: Option<Balance>,
input_data: Vec<u8>,
) -> ContractExecResult<Balance, EventRecord>;
/// Instantiate a new contract.
///
/// See `[crate::Pallet::bare_instantiate]`.
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>;
/// Upload new code without instantiating a contract from it.
///
/// See [`crate::Pallet::bare_upload_code`].
fn upload_code(
origin: AccountId,
code: Vec<u8>,
storage_deposit_limit: Option<Balance>,
determinism: Determinism,
) -> CodeUploadResult<Hash, Balance>;
/// Query a given storage key in a given contract.
///
/// Returns `Ok(Some(Vec<u8>))` if the storage value exists under the given key in the
/// specified account and `Ok(None)` if it doesn't. If the account specified by the address
/// doesn't exist, or doesn't have a contract then `Err` is returned.
fn get_storage(
address: AccountId,
key: Vec<u8>,
) -> GetStorageResult;
}
}