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,
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}