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//!
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}