contracts: Make Origin information available (#13708)

* contracts: allow root calls

* contracts: update ContractOrigin

* contracts: test allow root calls

* contracts: rustfmt

* contracts: test root caller traps

* contracts: add Caller enum

* contracts: improve some comments

* contracts: improve some comments

* contracts: format code with +nightly

* contracts: fix failing tests

* contracts: improve charte instantiate call when root origin

* contract: refactor common, call and instantiate inputs

* contracts: fix some failing test

* contracts: trap caller when there is no account id

* Update frame/contracts/src/lib.rs

Co-authored-by: PG Herveou <pgherveou@gmail.com>

* Update frame/contracts/src/exec.rs

Co-authored-by: PG Herveou <pgherveou@gmail.com>

* contracts: make `into_deposit` return zero when the origin is root

* contracts: cargo fmt

* contracts: charging and terminating tests refactored

* contracts: improved errors

* contracts: refactor & merge ContractOrigin cand Caller enums

* Update frame/contracts/src/lib.rs

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

* contracts: apply suggested pr changes

* contracts: rename Caller to Origin

* contracts: refactor run fn

* contracts: cr improvements

* contracts: allow root to use delegate call

* contracts: add caller_is_root weight

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

* contracts: add caller_is_root benchmarking

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

* contracts: add caller_is_root benchmarking

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

* contracts: add some minor improvements

* contracts: make caller_is_root runtime fn unstable

* contracts: fix failing wasm test

* contracts: update benchmarking for origin_is_root

* contracts: improve seal_caller_is_root benchmarking

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

* contracts: add small pr improvements

* contracts: fix broken tests after master merge

* contracts: acknowledge the default storage deposit limit

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

* contracts: move origin to CommonInput

* contracts: add some extra tests

* contracts: move ensure origin logic inside invokable::run_guarded

* contracts: add minor improvements

* contracts: fix caller_is_root benchmarking

* contracts: improve function description

* Update frame/contracts/src/lib.rs

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

* Update frame/contracts/src/lib.rs

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

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

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

* contracts: improve function description

---------

Co-authored-by: parity-processbot <>
Co-authored-by: PG Herveou <pgherveou@gmail.com>
Co-authored-by: Sasha Gryaznov <hi@agryaznov.com>
This commit is contained in:
juangirini
2023-05-02 18:45:14 +02:00
committed by GitHub
parent 0e28645153
commit 5db4119f93
10 changed files with 1835 additions and 1234 deletions
+84 -27
View File
@@ -97,7 +97,6 @@ pub mod weights;
#[cfg(test)]
mod tests;
use crate::{
exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Key, Stack as ExecStack},
gas::GasMeter,
@@ -105,19 +104,20 @@ use crate::{
wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate},
weights::WeightInfo,
};
use codec::{Codec, Encode, HasCompact};
use codec::{Codec, Decode, Encode, HasCompact};
use environmental::*;
use frame_support::{
dispatch::{Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo},
dispatch::{DispatchError, Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo, RawOrigin},
ensure,
error::BadOrigin,
traits::{
tokens::fungible::Inspect, ConstU32, Contains, Currency, Get, Randomness,
ReservableCurrency, Time,
},
weights::Weight,
BoundedVec, WeakBoundedVec,
BoundedVec, RuntimeDebugNoBound, WeakBoundedVec,
};
use frame_system::Pallet as System;
use frame_system::{ensure_signed, pallet_prelude::OriginFor, Pallet as System};
use pallet_contracts_primitives::{
Code, CodeUploadResult, CodeUploadReturnValue, ContractAccessError, ContractExecResult,
ContractInstantiateResult, ExecReturnValue, GetStorageResult, InstantiateReturnValue,
@@ -584,17 +584,15 @@ pub mod pallet {
storage_deposit_limit: Option<<BalanceOf<T> as codec::HasCompact>::Type>,
data: Vec<u8>,
) -> DispatchResultWithPostInfo {
let gas_limit: Weight = gas_limit.into();
let origin = ensure_signed(origin)?;
let dest = T::Lookup::lookup(dest)?;
let common = CommonInput {
origin,
origin: Origin::from_runtime_origin(origin)?,
value,
data,
gas_limit,
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 {
@@ -645,12 +643,11 @@ pub mod pallet {
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let code_len = code.len() as u32;
let data_len = data.len() as u32;
let salt_len = salt.len() as u32;
let common = CommonInput {
origin,
origin: Origin::from_runtime_origin(origin)?,
value,
data,
gas_limit,
@@ -688,11 +685,10 @@ pub mod pallet {
data: Vec<u8>,
salt: Vec<u8>,
) -> DispatchResultWithPostInfo {
let origin = ensure_signed(origin)?;
let data_len = data.len() as u32;
let salt_len = salt.len() as u32;
let common = CommonInput {
origin,
origin: Origin::from_runtime_origin(origin)?,
value,
data,
gas_limit,
@@ -764,8 +760,8 @@ pub mod pallet {
/// calls. This is because on failure all storage changes including events are
/// rolled back.
Called {
/// The account that called the `contract`.
caller: T::AccountId,
/// The caller of the `contract`.
caller: Origin<T>,
/// The contract that was called.
contract: T::AccountId,
},
@@ -924,9 +920,38 @@ pub mod pallet {
StorageValue<_, DeletionQueueManager<T>, ValueQuery>;
}
/// The type of origins supported by the contracts pallet.
#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebugNoBound)]
pub enum Origin<T: Config> {
Root,
Signed(T::AccountId),
}
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),
}
}
}
/// Context of a contract invocation.
struct CommonInput<'a, T: Config> {
origin: T::AccountId,
origin: Origin<T>,
value: BalanceOf<T>,
data: Vec<u8>,
gas_limit: Weight,
@@ -975,6 +1000,19 @@ trait Invokable<T: Config> {
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 }),
}
}
executing_contract::using_once(&mut false, || {
executing_contract::with(|f| {
// Fail if already entered contract execution
@@ -1010,6 +1048,11 @@ trait Invokable<T: Config> {
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>;
}
impl<T: Config> Invokable<T> for CallInput<T> {
@@ -1020,8 +1063,10 @@ impl<T: Config> Invokable<T> for CallInput<T> {
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(&common.origin, common.storage_deposit_limit, common.value) {
match StorageMeter::new(&origin, common.storage_deposit_limit, common.value) {
Ok(meter) => meter,
Err(err) =>
return InternalOutput {
@@ -1031,8 +1076,6 @@ impl<T: Config> Invokable<T> for CallInput<T> {
},
};
let schedule = T::Schedule::get();
let CallInput { dest, determinism } = self;
let CommonInput { origin, value, data, debug_message, .. } = common;
let result = ExecStack::<T, PrefabWasmModule<T>>::run_call(
origin.clone(),
dest.clone(),
@@ -1054,6 +1097,10 @@ impl<T: Config> Invokable<T> for CallInput<T> {
},
}
}
fn ensure_origin(&self, _origin: Origin<T>) -> Result<(), DispatchError> {
Ok(())
}
}
impl<T: Config> Invokable<T> for InstantiateInput<T> {
@@ -1067,12 +1114,15 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
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 (extra_deposit, executable) = match &self.code {
Code::Upload(binary) => {
let executable = PrefabWasmModule::from_code(
binary.clone(),
&schedule,
common.origin.clone(),
origin.clone(),
Determinism::Enforced,
TryInstantiate::Skip,
)
@@ -1094,14 +1144,13 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
PrefabWasmModule::from_storage(*hash, &schedule, &mut gas_meter)?,
),
};
let contract_origin = Origin::from_account_id(origin.clone());
let mut storage_meter = StorageMeter::new(
&common.origin,
&contract_origin,
common.storage_deposit_limit,
common.value.saturating_add(extra_deposit),
)?;
let InstantiateInput { salt, .. } = self;
let CommonInput { origin, value, data, debug_message, .. } = common;
let CommonInput { value, data, debug_message, .. } = common;
let result = ExecStack::<T, PrefabWasmModule<T>>::run_instantiate(
origin.clone(),
executable,
@@ -1115,12 +1164,19 @@ impl<T: Config> Invokable<T> for InstantiateInput<T> {
);
storage_deposit = storage_meter
.try_into_deposit(&origin)?
.try_into_deposit(&contract_origin)?
.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),
}
}
}
impl<T: Config> Pallet<T> {
@@ -1147,6 +1203,7 @@ impl<T: Config> Pallet<T> {
determinism: Determinism,
) -> ContractExecResult<BalanceOf<T>> {
let mut debug_message = if debug { Some(DebugBufferVec::<T>::default()) } else { None };
let origin = Origin::from_account_id(origin);
let common = CommonInput {
origin,
value,
@@ -1189,7 +1246,7 @@ impl<T: Config> Pallet<T> {
) -> ContractInstantiateResult<T::AccountId, BalanceOf<T>> {
let mut debug_message = if debug { Some(DebugBufferVec::<T>::default()) } else { None };
let common = CommonInput {
origin,
origin: Origin::from_account_id(origin),
value,
data,
gas_limit,