pezkuwi_sdk_docs/reference_docs/frame_origin.rs
1//! # FRAME Origin
2//!
3//! Let's start by clarifying a common wrong assumption about Origin:
4//!
5//! **ORIGIN IS NOT AN ACCOUNT ID**.
6//!
7//! FRAME's origin abstractions allow you to convey meanings far beyond just an account-id being the
8//! caller of an extrinsic. Nonetheless, an account-id having signed an extrinsic is one of the
9//! meanings that an origin can convey. This is the commonly used
10//! [`pezframe_system::ensure_signed`], where the return value happens to be an account-id.
11//!
12//! Instead, let's establish the following as the correct definition of an origin:
13//!
14//! > The origin type represents the privilege level of the caller of an extrinsic.
15//!
16//! That is, an extrinsic, through checking the origin, can *express what privilege level it wishes
17//! to impose on the caller of the extrinsic*. One of those checks can be as simple as "*any account
18//! that has signed a statement can pass*".
19//!
20//! But the origin system can also express more abstract and complicated privilege levels. For
21//! example:
22//!
23//! * If the majority of token holders agreed upon this. This is more or less what the
24//! [`pezpallet_democracy`] does under the hood ([reference](https://github.com/pezkuwichain/pezkuwi-sdk/blob/edd95b3749754d2ed0c5738588e872c87be91624/bizinikiwi/pezframe/democracy/src/lib.rs#L1603-L1633)).
25//! * If a specific ratio of an instance of [`pezpallet_collective`]/DAO agrees upon this.
26//! * If another consensus system, for example a bridged network or a teyrchain, agrees upon this.
27//! * If the majority of validator/authority set agrees upon this[^1].
28//! * If caller holds a particular NFT.
29//!
30//! and many more.
31//!
32//! ## Context
33//!
34//! First, let's look at where the `origin` type is encountered in a typical pezpallet. The `origin:
35//! OriginFor<T>` has to be the first argument of any given callable extrinsic in FRAME:
36#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", call_simple)]
37//!
38//! Typically, the code of an extrinsic starts with an origin check, such as
39//! [`pezframe_system::ensure_signed`].
40//!
41//! Note that [`OriginFor`](pezframe_system::pezpallet_prelude::OriginFor) is merely a shorthand for
42//! [`pezframe_system::Config::RuntimeOrigin`]. Given the name prefix `Runtime`, we can learn that
43//! `RuntimeOrigin` is similar to `RuntimeCall` and others, a runtime composite enum that is
44//! amalgamated at the runtime level. Read [`crate::reference_docs::frame_runtime_types`] to
45//! familiarize yourself with these types.
46//!
47//! To understand this better, we will next create a pezpallet with a custom origin, which will add
48//! a new variant to `RuntimeOrigin`.
49//!
50//! ## Adding Custom Pezpallet Origin to the Runtime
51//!
52//! For example, given a pezpallet that defines the following custom origin:
53#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin)]
54//!
55//! And a runtime with the following pallets:
56#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", runtime_exp)]
57//!
58//! The type [`crate::reference_docs::frame_origin::runtime_for_origin::RuntimeOrigin`] is expanded.
59//! This `RuntimeOrigin` contains a variant for the [`pezframe_system::RawOrigin`] and the custom
60//! origin of the pezpallet.
61//!
62//! > Notice how the [`pezframe_system::ensure_signed`] is nothing more than a `match` statement. If
63//! > you want to know where the actual origin of an extrinsic is set (and the signature
64//! > verification happens, if any), see
65//! > [`pezsp_runtime::generic::CheckedExtrinsic#trait-implementations`], specifically
66//! > [`pezsp_runtime::traits::Applyable`]'s implementation.
67//!
68//! ## Asserting on a Custom Internal Origin
69//!
70//! In order to assert on a custom origin that is defined within your pezpallet, we need a way to
71//! first convert the `<T as pezframe_system::Config>::RuntimeOrigin` into the local `enum Origin`
72//! of the current pezpallet. This is a common process that is explained in
73//! [`crate::reference_docs::frame_runtime_types#
74//! adding-further-constraints-to-runtime-composite-enums`].
75//!
76//! We use the same process here to express that `RuntimeOrigin` has a number of additional bounds,
77//! as follows.
78//!
79//! 1. Defining a custom `RuntimeOrigin` with further bounds in the pezpallet.
80#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin_bound)]
81//!
82//! 2. Using it in the pezpallet.
83#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", custom_origin_usage)]
84//!
85//! ## Asserting on a Custom External Origin
86//!
87//! Very often, a pezpallet wants to have a parameterized origin that is **NOT** defined within the
88//! pezpallet. In other words, a pezpallet wants to delegate an origin check to something that is
89//! specified later at the runtime level. Like many other parameterizations in FRAME, this implies
90//! adding a new associated type to `trait Config`.
91#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_def)]
92//!
93//! Then, within the pezpallet, we can simply use this "unknown" origin check type:
94#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_usage)]
95//!
96//! Finally, at the runtime, any implementation of [`pezframe::traits::EnsureOrigin`] can be passed.
97#![doc = docify::embed!("./src/reference_docs/frame_origin.rs", external_origin_provide)]
98//!
99//! Indeed, some of these implementations of [`pezframe::traits::EnsureOrigin`] are similar to the ones
100//! that we know about: [`pezframe::runtime::prelude::EnsureSigned`],
101//! [`pezframe::runtime::prelude::EnsureSignedBy`], [`pezframe::runtime::prelude::EnsureRoot`],
102//! [`pezframe::runtime::prelude::EnsureNone`], etc. But, there are also many more that are not known
103//! to us, and are defined in other pallets.
104//!
105//! For example, [`pezpallet_collective`] defines [`pezpallet_collective::EnsureMember`] and
106//! [`pezpallet_collective::EnsureProportionMoreThan`] and many more, which is exactly what we
107//! alluded to earlier in this document.
108//!
109//! Make sure to check the full list of [implementors of
110//! `EnsureOrigin`](pezframe::traits::EnsureOrigin#implementors) for more inspiration.
111//!
112//! ## Obtaining Abstract Origins
113//!
114//! So far we have learned that FRAME pallets can assert on custom and abstract origin types,
115//! whether they are defined within the pezpallet or not. But how can we obtain these abstract
116//! origins?
117//!
118//! > All extrinsics that come from the outer world can generally only be obtained as either
119//! > `signed` or `none` origin.
120//!
121//! Generally, these abstract origins are only obtained within the runtime, when a call is
122//! dispatched within the runtime.
123//!
124//! ## Further References
125//!
126//! - [Gavin Wood's speech about FRAME features at Protocol Berg 2023.](https://youtu.be/j7b8Upipmeg?si=83_XUgYuJxMwWX4g&t=195)
127//! - [A related StackExchange question.](https://exchange.pezkuwichain.app/questions/10992/how-do-you-find-the-public-key-for-the-medium-spender-track-origin)
128//!
129//! [^1]: Inherents are essentially unsigned extrinsics that need an [`pezframe_system::ensure_none`]
130//! origin check, and through the virtue of being an inherent, are agreed upon by all validators.
131
132use pezframe::prelude::*;
133
134#[pezframe::pezpallet(dev_mode)]
135pub mod pezpallet_for_origin {
136 use super::*;
137
138 #[pezpallet::config]
139 pub trait Config: pezframe_system::Config {}
140
141 #[pezpallet::pezpallet]
142 pub struct Pezpallet<T>(_);
143
144 #[docify::export(call_simple)]
145 #[pezpallet::call]
146 impl<T: Config> Pezpallet<T> {
147 pub fn do_something(_origin: OriginFor<T>) -> DispatchResult {
148 // ^^^^^^^^^^^^^^^^^^^^^
149 todo!();
150 }
151 }
152}
153
154#[pezframe::pezpallet(dev_mode)]
155pub mod pezpallet_with_custom_origin {
156 use super::*;
157
158 #[docify::export(custom_origin_bound)]
159 #[pezpallet::config]
160 pub trait Config: pezframe_system::Config {
161 type RuntimeOrigin: From<<Self as pezframe_system::Config>::RuntimeOrigin>
162 + Into<Result<Origin, <Self as Config>::RuntimeOrigin>>;
163 }
164
165 #[pezpallet::pezpallet]
166 pub struct Pezpallet<T>(_);
167
168 #[docify::export(custom_origin)]
169 /// A dummy custom origin.
170 #[pezpallet::origin]
171 #[derive(
172 PartialEq,
173 Eq,
174 Clone,
175 RuntimeDebug,
176 Encode,
177 Decode,
178 DecodeWithMemTracking,
179 TypeInfo,
180 MaxEncodedLen,
181 )]
182 pub enum Origin {
183 /// If all holders of a particular NFT have agreed upon this.
184 AllNftHolders,
185 /// If all validators have agreed upon this.
186 ValidatorSet,
187 }
188
189 #[docify::export(custom_origin_usage)]
190 #[pezpallet::call]
191 impl<T: Config> Pezpallet<T> {
192 pub fn only_validators(origin: OriginFor<T>) -> DispatchResult {
193 // first, we convert from `<T as pezframe_system::Config>::RuntimeOrigin` to `<T as
194 // Config>::RuntimeOrigin`
195 let local_runtime_origin = <<T as Config>::RuntimeOrigin as From<
196 <T as pezframe_system::Config>::RuntimeOrigin,
197 >>::from(origin);
198 // then we convert to `origin`, if possible
199 let local_origin =
200 local_runtime_origin.into().map_err(|_| "invalid origin type provided")?;
201 ensure!(matches!(local_origin, Origin::ValidatorSet), "Not authorized");
202 todo!();
203 }
204 }
205}
206
207pub mod runtime_for_origin {
208 use super::pezpallet_with_custom_origin;
209 use pezframe::{runtime::prelude::*, testing_prelude::*};
210
211 #[docify::export(runtime_exp)]
212 construct_runtime!(
213 pub struct Runtime {
214 System: pezframe_system,
215 PalletWithCustomOrigin: pezpallet_with_custom_origin,
216 }
217 );
218
219 #[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
220 impl pezframe_system::Config for Runtime {
221 type Block = MockBlock<Self>;
222 }
223
224 impl pezpallet_with_custom_origin::Config for Runtime {
225 type RuntimeOrigin = RuntimeOrigin;
226 }
227}
228
229#[pezframe::pezpallet(dev_mode)]
230pub mod pezpallet_with_external_origin {
231 use super::*;
232 #[docify::export(external_origin_def)]
233 #[pezpallet::config]
234 pub trait Config: pezframe_system::Config {
235 type ExternalOrigin: EnsureOrigin<Self::RuntimeOrigin>;
236 }
237
238 #[pezpallet::pezpallet]
239 pub struct Pezpallet<T>(_);
240
241 #[docify::export(external_origin_usage)]
242 #[pezpallet::call]
243 impl<T: Config> Pezpallet<T> {
244 pub fn externally_checked_ext(origin: OriginFor<T>) -> DispatchResult {
245 T::ExternalOrigin::ensure_origin(origin)?;
246 todo!();
247 }
248 }
249}
250
251pub mod runtime_for_external_origin {
252 use super::*;
253 use pezframe::{runtime::prelude::*, testing_prelude::*};
254
255 construct_runtime!(
256 pub struct Runtime {
257 System: pezframe_system,
258 PalletWithExternalOrigin: pezpallet_with_external_origin,
259 }
260 );
261
262 #[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
263 impl pezframe_system::Config for Runtime {
264 type Block = MockBlock<Self>;
265 }
266
267 #[docify::export(external_origin_provide)]
268 impl pezpallet_with_external_origin::Config for Runtime {
269 type ExternalOrigin = EnsureSigned<<Self as pezframe_system::Config>::AccountId>;
270 }
271}