referrerpolicy=no-referrer-when-downgrade

pallet_example_authorization_tx_extension/
extensions.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
24use core::{fmt, marker::PhantomData};
25
26use codec::{Decode, DecodeWithMemTracking, Encode};
27use frame_support::{pallet_prelude::TransactionSource, traits::OriginTrait, Parameter};
28use scale_info::TypeInfo;
29use sp_runtime::{
30	impl_tx_ext_default,
31	traits::{
32		DispatchInfoOf, DispatchOriginOf, IdentifyAccount, TransactionExtension, ValidateResult,
33		Verify,
34	},
35	transaction_validity::{InvalidTransaction, ValidTransaction},
36};
37
38use crate::pallet_coownership::{Config, Origin};
39
40/// Helper struct to organize the data needed for signature verification of both parties involved.
41#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
42pub struct AuthCredentials<Signer, Signature> {
43	first: (Signer, Signature),
44	second: (Signer, Signature),
45}
46
47/// Extension that, if activated by providing a pair of signers and signatures, will authorize a
48/// coowner origin of the two signers. Both signers have to construct their signatures on all of the
49/// data that follows this extension in the `TransactionExtension` pipeline, their implications and
50/// the call. Essentially re-sign the transaction from this point onwards in the pipeline by using
51/// the `inherited_implication`, as shown below.
52#[derive(Clone, Eq, PartialEq, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
53#[scale_info(skip_type_params(T))]
54pub struct AuthorizeCoownership<T, Signer, Signature> {
55	inner: Option<AuthCredentials<Signer, Signature>>,
56	_phantom: PhantomData<T>,
57}
58
59impl<T: Config, Signer, Signature> Default for AuthorizeCoownership<T, Signer, Signature> {
60	fn default() -> Self {
61		Self { inner: None, _phantom: Default::default() }
62	}
63}
64
65impl<T: Config, Signer, Signature> AuthorizeCoownership<T, Signer, Signature> {
66	/// Creates an active extension that will try to authorize the coownership origin.
67	pub fn new(first: (Signer, Signature), second: (Signer, Signature)) -> Self {
68		Self { inner: Some(AuthCredentials { first, second }), _phantom: Default::default() }
69	}
70}
71
72impl<T: Config, Signer, Signature> fmt::Debug for AuthorizeCoownership<T, Signer, Signature> {
73	#[cfg(feature = "std")]
74	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75		write!(f, "AuthorizeCoownership")
76	}
77
78	#[cfg(not(feature = "std"))]
79	fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
80		Ok(())
81	}
82}
83
84impl<T: Config + Send + Sync, Signer, Signature> TransactionExtension<T::RuntimeCall>
85	for AuthorizeCoownership<T, Signer, Signature>
86where
87	Signer: IdentifyAccount<AccountId = T::AccountId> + Parameter + Send + Sync + 'static,
88	Signature: Verify<Signer = Signer> + Parameter + Send + Sync + 'static,
89{
90	const IDENTIFIER: &'static str = "AuthorizeCoownership";
91	type Implicit = ();
92	type Val = ();
93	type Pre = ();
94
95	fn validate(
96		&self,
97		mut origin: DispatchOriginOf<T::RuntimeCall>,
98		_call: &T::RuntimeCall,
99		_info: &DispatchInfoOf<T::RuntimeCall>,
100		_len: usize,
101		_self_implicit: Self::Implicit,
102		inherited_implication: &impl codec::Encode,
103		_source: TransactionSource,
104	) -> ValidateResult<Self::Val, T::RuntimeCall> {
105		// If the extension is inactive, just move on in the pipeline.
106		let Some(auth) = &self.inner else {
107			return Ok((ValidTransaction::default(), (), origin));
108		};
109		let first_account = auth.first.0.clone().into_account();
110		let second_account = auth.second.0.clone().into_account();
111
112		// Construct the payload to sign using the `inherited_implication`.
113		let msg = inherited_implication.using_encoded(sp_io::hashing::blake2_256);
114
115		// Both parties' signatures must be correct for the origin to be authorized.
116		// In a prod environment, we're just return a `InvalidTransaction::BadProof` if the
117		// signature isn't valid, but we return these custom errors to be able to assert them in
118		// tests.
119		if !auth.first.1.verify(&msg[..], &first_account) {
120			Err(InvalidTransaction::Custom(100))?
121		}
122		if !auth.second.1.verify(&msg[..], &second_account) {
123			Err(InvalidTransaction::Custom(200))?
124		}
125		// Construct a `pallet_coownership::Origin`.
126		let local_origin = Origin::Coowners(first_account, second_account);
127		// Turn it into a local `PalletsOrigin`.
128		let local_origin = <T as Config>::PalletsOrigin::from(local_origin);
129		// Then finally into a pallet `RuntimeOrigin`.
130		let local_origin = <T as Config>::RuntimeOrigin::from(local_origin);
131		// Which the `set_caller_from` function will convert into the overarching `RuntimeOrigin`
132		// created by `construct_runtime!`.
133		origin.set_caller_from(local_origin);
134		// Make sure to return the new origin.
135		Ok((ValidTransaction::default(), (), origin))
136	}
137	// We're not doing any special logic in `TransactionExtension::prepare`, so just impl a default.
138	impl_tx_ext_default!(T::RuntimeCall; weight prepare);
139}