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,
102 PartialEq,
103 Eq,
104 RuntimeDebug,
105 Encode,
106 Decode,
107 DecodeWithMemTracking,
108 MaxEncodedLen,
109 TypeInfo,
110 )]
111 pub enum Origin<T: Config> {
112 Coowners(T::AccountId, T::AccountId),
113 }
114}
115
116#[frame_support::pallet(dev_mode)]
117pub mod pallet_assets {
118 use super::*;
119
120 pub type AssetId = u32;
121
122 /// Type that describes possible owners of a particular asset.
123 #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
124 pub enum Owner<AccountId> {
125 Single(AccountId),
126 Double(AccountId, AccountId),
127 }
128
129 #[pallet::config]
130 pub trait Config: frame_system::Config {
131 /// Type that can authorize an account pair coowner origin.
132 type CoownerOrigin: EnsureOrigin<
133 Self::RuntimeOrigin,
134 Success = (Self::AccountId, Self::AccountId),
135 >;
136 }
137
138 /// Map that holds the owner information for each asset it manages.
139 #[pallet::storage]
140 pub type AssetOwners<T> =
141 StorageMap<_, Blake2_128Concat, AssetId, Owner<<T as frame_system::Config>::AccountId>>;
142
143 #[pallet::pallet]
144 pub struct Pallet<T>(_);
145
146 #[pallet::error]
147 pub enum Error<T> {
148 /// Asset already exists.
149 AlreadyExists,
150 }
151
152 #[pallet::call]
153 impl<T: Config> Pallet<T> {
154 /// Simple call that just creates an asset with a specific `AssetId`. This call will fail if
155 /// there is already an asset with the same `AssetId`.
156 ///
157 /// The origin is either a single account (traditionally signed origin) or a coowner origin.
158 #[pallet::call_index(0)]
159 pub fn create_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
160 let owner: Owner<T::AccountId> = match T::CoownerOrigin::try_origin(origin) {
161 Ok((first, second)) => Owner::Double(first, second),
162 Err(origin) => ensure_signed(origin).map(|account| Owner::Single(account))?,
163 };
164 AssetOwners::<T>::try_mutate(asset_id, |maybe_owner| {
165 if maybe_owner.is_some() {
166 return Err(Error::<T>::AlreadyExists);
167 }
168 *maybe_owner = Some(owner);
169 Ok(())
170 })?;
171 Ok(())
172 }
173 }
174}