pallet_example_authorization_tx_extension/lib.rs
1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: MIT-0
5
6// Permission is hereby granted, free of charge, to any person obtaining a copy of
7// this software and associated documentation files (the "Software"), to deal in
8// the Software without restriction, including without limitation the rights to
9// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10// of the Software, and to permit persons to whom the Software is furnished to do
11// so, subject to the following conditions:
12
13// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
15
16// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22// SOFTWARE.
23
24//! # Authorization Transaction Extension Example Pallet
25//!
26//! **This pallet serves as an example and is not meant to be used in production.**
27//!
28//! FRAME Transaction Extension reference implementation, origin mutation, origin authorization and
29//! integration in a `TransactionExtension` pipeline.
30//!
31//! The [TransactionExtension](sp_runtime::traits::TransactionExtension) used in this example is
32//! [AuthorizeCoownership](extensions::AuthorizeCoownership). If activated, the extension will
33//! authorize 2 signers as coowners, with a [coowner origin](pallet_coownership::Origin) specific to
34//! the [coownership example pallet](pallet_coownership), by validating a signature of the rest of
35//! the transaction from each party. This means any extensions after ours in the pipeline, their
36//! implicits and the actual call. The extension pipeline used in our example checks the genesis
37//! hash, transaction version and mortality of the transaction after the `AuthorizeCoownership` runs
38//! as we want these transactions to run regardless of what origin passes through them and/or we
39//! want their implicit data in any signature authorization happening earlier in the pipeline.
40//!
41//! In this example, aside from the [AuthorizeCoownership](extensions::AuthorizeCoownership)
42//! extension, we use the following pallets:
43//! - [pallet_coownership] - provides a coowner origin and the functionality to authorize it.
44//! - [pallet_assets] - a dummy asset pallet that tracks assets, identified by an
45//! [AssetId](pallet_assets::AssetId), and their respective owners, which can be either an
46//! [account](pallet_assets::Owner::Single) or a [pair of owners](pallet_assets::Owner::Double).
47//!
48//! Assets are created in [pallet_assets] using the
49//! [create_asset](pallet_assets::Call::create_asset) call, which accepts traditionally signed
50//! origins (a single account) or coowner origins, authorized through the
51//! [CoownerOrigin](pallet_assets::Config::CoownerOrigin) type.
52//!
53//! ### Example runtime setup
54#![doc = docify::embed!("src/mock.rs", example_runtime)]
55//!
56//! ### Example usage
57#![doc = docify::embed!("src/tests.rs", create_coowned_asset_works)]
58//!
59//! This example does not focus on any pallet logic or syntax, but rather on `TransactionExtension`
60//! functionality. The pallets used are just skeletons to provide storage state and custom origin
61//! choices and requirements, as shown in the examples. Any weight and/or
62//! transaction fee is out of scope for this example.
63
64#![cfg_attr(not(feature = "std"), no_std)]
65
66pub mod extensions;
67
68#[cfg(test)]
69mod mock;
70#[cfg(test)]
71mod tests;
72
73extern crate alloc;
74
75use frame_support::pallet_prelude::*;
76use frame_system::pallet_prelude::*;
77
78#[frame_support::pallet(dev_mode)]
79pub mod pallet_coownership {
80 use super::*;
81 use frame_support::traits::OriginTrait;
82
83 #[pallet::config]
84 pub trait Config: frame_system::Config {
85 /// The aggregated origin which the dispatch will take.
86 type RuntimeOrigin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>
87 + From<Self::PalletsOrigin>
88 + IsType<<Self as frame_system::Config>::RuntimeOrigin>;
89
90 /// The caller origin, overarching type of all pallets origins.
91 type PalletsOrigin: From<Origin<Self>> + TryInto<Origin<Self>, Error = Self::PalletsOrigin>;
92 }
93
94 #[pallet::pallet]
95 pub struct Pallet<T>(_);
96
97 /// Origin that this pallet can authorize. For the purposes of this example, it's just two
98 /// accounts that own something together.
99 #[pallet::origin]
100 #[derive(
101 Clone, PartialEq, Eq, Debug, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo,
102 )]
103 pub enum Origin<T: Config> {
104 Coowners(T::AccountId, T::AccountId),
105 }
106}
107
108#[frame_support::pallet(dev_mode)]
109pub mod pallet_assets {
110 use super::*;
111
112 pub type AssetId = u32;
113
114 /// Type that describes possible owners of a particular asset.
115 #[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
116 pub enum Owner<AccountId> {
117 Single(AccountId),
118 Double(AccountId, AccountId),
119 }
120
121 #[pallet::config]
122 pub trait Config: frame_system::Config {
123 /// Type that can authorize an account pair coowner origin.
124 type CoownerOrigin: EnsureOrigin<
125 Self::RuntimeOrigin,
126 Success = (Self::AccountId, Self::AccountId),
127 >;
128 }
129
130 /// Map that holds the owner information for each asset it manages.
131 #[pallet::storage]
132 pub type AssetOwners<T> =
133 StorageMap<_, Blake2_128Concat, AssetId, Owner<<T as frame_system::Config>::AccountId>>;
134
135 #[pallet::pallet]
136 pub struct Pallet<T>(_);
137
138 #[pallet::error]
139 pub enum Error<T> {
140 /// Asset already exists.
141 AlreadyExists,
142 }
143
144 #[pallet::call]
145 impl<T: Config> Pallet<T> {
146 /// Simple call that just creates an asset with a specific `AssetId`. This call will fail if
147 /// there is already an asset with the same `AssetId`.
148 ///
149 /// The origin is either a single account (traditionally signed origin) or a coowner origin.
150 #[pallet::call_index(0)]
151 pub fn create_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
152 let owner: Owner<T::AccountId> = match T::CoownerOrigin::try_origin(origin) {
153 Ok((first, second)) => Owner::Double(first, second),
154 Err(origin) => ensure_signed(origin).map(|account| Owner::Single(account))?,
155 };
156 AssetOwners::<T>::try_mutate(asset_id, |maybe_owner| {
157 if maybe_owner.is_some() {
158 return Err(Error::<T>::AlreadyExists);
159 }
160 *maybe_owner = Some(owner);
161 Ok(())
162 })?;
163 Ok(())
164 }
165 }
166}