referrerpolicy=no-referrer-when-downgrade

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//! ### Example usage
56#![doc = docify::embed!("src/tests.rs", create_coowned_asset_works)]
57//! This example does not focus on any pallet logic or syntax, but rather on `TransactionExtension`
58//! functionality. The pallets used are just skeletons to provide storage state and custom origin
59//! choices and requirements, as shown in the examples. Any weight and/or
60//! transaction fee is out of scope for this example.
61
62#![cfg_attr(not(feature = "std"), no_std)]
63
64pub mod extensions;
65
66#[cfg(test)]
67mod mock;
68#[cfg(test)]
69mod tests;
70
71extern crate alloc;
72
73use frame_support::pallet_prelude::*;
74use frame_system::pallet_prelude::*;
75
76#[frame_support::pallet(dev_mode)]
77pub mod pallet_coownership {
78	use super::*;
79	use frame_support::traits::OriginTrait;
80
81	#[pallet::config]
82	pub trait Config: frame_system::Config {
83		/// The aggregated origin which the dispatch will take.
84		type RuntimeOrigin: OriginTrait<PalletsOrigin = Self::PalletsOrigin>
85			+ From<Self::PalletsOrigin>
86			+ IsType<<Self as frame_system::Config>::RuntimeOrigin>;
87
88		/// The caller origin, overarching type of all pallets origins.
89		type PalletsOrigin: From<Origin<Self>> + TryInto<Origin<Self>, Error = Self::PalletsOrigin>;
90	}
91
92	#[pallet::pallet]
93	pub struct Pallet<T>(_);
94
95	/// Origin that this pallet can authorize. For the purposes of this example, it's just two
96	/// accounts that own something together.
97	#[pallet::origin]
98	#[derive(
99		Clone, PartialEq, Eq, Debug, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo,
100	)]
101	pub enum Origin<T: Config> {
102		Coowners(T::AccountId, T::AccountId),
103	}
104}
105
106#[frame_support::pallet(dev_mode)]
107pub mod pallet_assets {
108	use super::*;
109
110	pub type AssetId = u32;
111
112	/// Type that describes possible owners of a particular asset.
113	#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
114	pub enum Owner<AccountId> {
115		Single(AccountId),
116		Double(AccountId, AccountId),
117	}
118
119	#[pallet::config]
120	pub trait Config: frame_system::Config {
121		/// Type that can authorize an account pair coowner origin.
122		type CoownerOrigin: EnsureOrigin<
123			Self::RuntimeOrigin,
124			Success = (Self::AccountId, Self::AccountId),
125		>;
126	}
127
128	/// Map that holds the owner information for each asset it manages.
129	#[pallet::storage]
130	pub type AssetOwners<T> =
131		StorageMap<_, Blake2_128Concat, AssetId, Owner<<T as frame_system::Config>::AccountId>>;
132
133	#[pallet::pallet]
134	pub struct Pallet<T>(_);
135
136	#[pallet::error]
137	pub enum Error<T> {
138		/// Asset already exists.
139		AlreadyExists,
140	}
141
142	#[pallet::call]
143	impl<T: Config> Pallet<T> {
144		/// Simple call that just creates an asset with a specific `AssetId`. This call will fail if
145		/// there is already an asset with the same `AssetId`.
146		///
147		/// The origin is either a single account (traditionally signed origin) or a coowner origin.
148		#[pallet::call_index(0)]
149		pub fn create_asset(origin: OriginFor<T>, asset_id: AssetId) -> DispatchResult {
150			let owner: Owner<T::AccountId> = match T::CoownerOrigin::try_origin(origin) {
151				Ok((first, second)) => Owner::Double(first, second),
152				Err(origin) => ensure_signed(origin).map(|account| Owner::Single(account))?,
153			};
154			AssetOwners::<T>::try_mutate(asset_id, |maybe_owner| {
155				if maybe_owner.is_some() {
156					return Err(Error::<T>::AlreadyExists);
157				}
158				*maybe_owner = Some(owner);
159				Ok(())
160			})?;
161			Ok(())
162		}
163	}
164}