Expand description
Learn about Origins, a topic in FRAME that enables complex account abstractions to be built.
§FRAME Origin
Let’s start by clarifying a common wrong assumption about Origin:
ORIGIN IS NOT AN ACCOUNT ID.
FRAME’s origin abstractions allow you to convey meanings far beyond just an account-id being the
caller of an extrinsic. Nonetheless, an account-id having signed an extrinsic is one of the
meanings that an origin can convey. This is the commonly used
[pezframe_system::ensure_signed], where the return value happens to be an account-id.
Instead, let’s establish the following as the correct definition of an origin:
The origin type represents the privilege level of the caller of an extrinsic.
That is, an extrinsic, through checking the origin, can express what privilege level it wishes to impose on the caller of the extrinsic. One of those checks can be as simple as “any account that has signed a statement can pass”.
But the origin system can also express more abstract and complicated privilege levels. For example:
- If the majority of token holders agreed upon this. This is more or less what the
[
pezpallet_democracy] does under the hood (reference). - If a specific ratio of an instance of [
pezpallet_collective]/DAO agrees upon this. - If another consensus system, for example a bridged network or a teyrchain, agrees upon this.
- If the majority of validator/authority set agrees upon this1.
- If caller holds a particular NFT.
and many more.
§Context
First, let’s look at where the origin type is encountered in a typical pezpallet. The origin: OriginFor<T> has to be the first argument of any given callable extrinsic in FRAME:
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
pub fn do_something(_origin: OriginFor<T>) -> DispatchResult {
// ^^^^^^^^^^^^^^^^^^^^^
todo!();
}
}Typically, the code of an extrinsic starts with an origin check, such as
[pezframe_system::ensure_signed].
Note that OriginFor is merely a shorthand for
[pezframe_system::Config::RuntimeOrigin]. Given the name prefix Runtime, we can learn that
RuntimeOrigin is similar to RuntimeCall and others, a runtime composite enum that is
amalgamated at the runtime level. Read crate::reference_docs::frame_runtime_types to
familiarize yourself with these types.
To understand this better, we will next create a pezpallet with a custom origin, which will add
a new variant to RuntimeOrigin.
§Adding Custom Pezpallet Origin to the Runtime
For example, given a pezpallet that defines the following custom origin:
#[pezpallet::origin]
#[derive(
PartialEq,
Eq,
Clone,
RuntimeDebug,
Encode,
Decode,
DecodeWithMemTracking,
TypeInfo,
MaxEncodedLen,
)]
pub enum Origin {
/// If all holders of a particular NFT have agreed upon this.
AllNftHolders,
/// If all validators have agreed upon this.
ValidatorSet,
}And a runtime with the following pallets:
construct_runtime!(
pub struct Runtime {
System: pezframe_system,
PalletWithCustomOrigin: pezpallet_with_custom_origin,
}
);The type crate::reference_docs::frame_origin::runtime_for_origin::RuntimeOrigin is expanded.
This RuntimeOrigin contains a variant for the [pezframe_system::RawOrigin] and the custom
origin of the pezpallet.
Notice how the [
pezframe_system::ensure_signed] is nothing more than amatchstatement. If you want to know where the actual origin of an extrinsic is set (and the signature verification happens, if any), see [pezsp_runtime::generic::CheckedExtrinsic#trait-implementations], specifically [pezsp_runtime::traits::Applyable]’s implementation.
§Asserting on a Custom Internal Origin
In order to assert on a custom origin that is defined within your pezpallet, we need a way to
first convert the <T as pezframe_system::Config>::RuntimeOrigin into the local enum Origin
of the current pezpallet. This is a common process that is explained in
crate::reference_docs::frame_runtime_types.
We use the same process here to express that RuntimeOrigin has a number of additional bounds,
as follows.
- Defining a custom
RuntimeOriginwith further bounds in the pezpallet.
#[pezpallet::config]
pub trait Config: pezframe_system::Config {
type RuntimeOrigin: From<<Self as pezframe_system::Config>::RuntimeOrigin>
+ Into<Result<Origin, <Self as Config>::RuntimeOrigin>>;
}- Using it in the pezpallet.
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
pub fn only_validators(origin: OriginFor<T>) -> DispatchResult {
// first, we convert from `<T as pezframe_system::Config>::RuntimeOrigin` to `<T as
// Config>::RuntimeOrigin`
let local_runtime_origin = <<T as Config>::RuntimeOrigin as From<
<T as pezframe_system::Config>::RuntimeOrigin,
>>::from(origin);
// then we convert to `origin`, if possible
let local_origin =
local_runtime_origin.into().map_err(|_| "invalid origin type provided")?;
ensure!(matches!(local_origin, Origin::ValidatorSet), "Not authorized");
todo!();
}
}§Asserting on a Custom External Origin
Very often, a pezpallet wants to have a parameterized origin that is NOT defined within the
pezpallet. In other words, a pezpallet wants to delegate an origin check to something that is
specified later at the runtime level. Like many other parameterizations in FRAME, this implies
adding a new associated type to trait Config.
#[pezpallet::config]
pub trait Config: pezframe_system::Config {
type ExternalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}Then, within the pezpallet, we can simply use this “unknown” origin check type:
#[pezpallet::call]
impl<T: Config> Pezpallet<T> {
pub fn externally_checked_ext(origin: OriginFor<T>) -> DispatchResult {
T::ExternalOrigin::ensure_origin(origin)?;
todo!();
}
}Finally, at the runtime, any implementation of [pezframe::traits::EnsureOrigin] can be passed.
impl pezpallet_with_external_origin::Config for Runtime {
type ExternalOrigin = EnsureSigned<<Self as pezframe_system::Config>::AccountId>;
}Indeed, some of these implementations of [pezframe::traits::EnsureOrigin] are similar to the ones
that we know about: [pezframe::runtime::prelude::EnsureSigned],
[pezframe::runtime::prelude::EnsureSignedBy], [pezframe::runtime::prelude::EnsureRoot],
[pezframe::runtime::prelude::EnsureNone], etc. But, there are also many more that are not known
to us, and are defined in other pallets.
For example, [pezpallet_collective] defines [pezpallet_collective::EnsureMember] and
[pezpallet_collective::EnsureProportionMoreThan] and many more, which is exactly what we
alluded to earlier in this document.
Make sure to check the full list of implementors of
EnsureOrigin for more inspiration.
§Obtaining Abstract Origins
So far we have learned that FRAME pallets can assert on custom and abstract origin types, whether they are defined within the pezpallet or not. But how can we obtain these abstract origins?
All extrinsics that come from the outer world can generally only be obtained as either
signedornoneorigin.
Generally, these abstract origins are only obtained within the runtime, when a call is dispatched within the runtime.
§Further References
Inherents are essentially unsigned extrinsics that need an [
pezframe_system::ensure_none] origin check, and through the virtue of being an inherent, are agreed upon by all validators. ↩
Modules§
- pezpallet_
for_ origin - The
pezpalletmodule in each FRAME pezpallet hosts the most important items needed to construct this pezpallet. - pezpallet_
with_ custom_ origin - The
pezpalletmodule in each FRAME pezpallet hosts the most important items needed to construct this pezpallet. - pezpallet_
with_ external_ origin - The
pezpalletmodule in each FRAME pezpallet hosts the most important items needed to construct this pezpallet. - runtime_
for_ external_ origin - runtime_
for_ origin