1// This file is part of Substrate.
23// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: MIT-0
56// 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:
1213// The above copyright notice and this permission notice shall be included in all
14// copies or substantial portions of the Software.
1516// 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.
2324use core::{fmt, marker::PhantomData};
2526use 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};
3738use crate::pallet_coownership::{Config, Origin};
3940/// 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}
4647/// 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}
5859impl<T: Config, Signer, Signature> Default for AuthorizeCoownership<T, Signer, Signature> {
60fn default() -> Self {
61Self { inner: None, _phantom: Default::default() }
62 }
63}
6465impl<T: Config, Signer, Signature> AuthorizeCoownership<T, Signer, Signature> {
66/// Creates an active extension that will try to authorize the coownership origin.
67pub fn new(first: (Signer, Signature), second: (Signer, Signature)) -> Self {
68Self { inner: Some(AuthCredentials { first, second }), _phantom: Default::default() }
69 }
70}
7172impl<T: Config, Signer, Signature> fmt::Debug for AuthorizeCoownership<T, Signer, Signature> {
73#[cfg(feature = "std")]
74fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75write!(f, "AuthorizeCoownership")
76 }
7778#[cfg(not(feature = "std"))]
79fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
80Ok(())
81 }
82}
8384impl<T: Config + Send + Sync, Signer, Signature> TransactionExtension<T::RuntimeCall>
85for AuthorizeCoownership<T, Signer, Signature>
86where
87Signer: IdentifyAccount<AccountId = T::AccountId> + Parameter + Send + Sync + 'static,
88 Signature: Verify<Signer = Signer> + Parameter + Send + Sync + 'static,
89{
90const IDENTIFIER: &'static str = "AuthorizeCoownership";
91type Implicit = ();
92type Val = ();
93type Pre = ();
9495fn validate(
96&self,
97mut 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.
106let Some(auth) = &self.inner else {
107return Ok((ValidTransaction::default(), (), origin));
108 };
109let first_account = auth.first.0.clone().into_account();
110let second_account = auth.second.0.clone().into_account();
111112// Construct the payload to sign using the `inherited_implication`.
113let msg = inherited_implication.using_encoded(sp_io::hashing::blake2_256);
114115// 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.
119if !auth.first.1.verify(&msg[..], &first_account) {
120Err(InvalidTransaction::Custom(100))?
121}
122if !auth.second.1.verify(&msg[..], &second_account) {
123Err(InvalidTransaction::Custom(200))?
124}
125// Construct a `pallet_coownership::Origin`.
126let local_origin = Origin::Coowners(first_account, second_account);
127// Turn it into a local `PalletsOrigin`.
128let local_origin = <T as Config>::PalletsOrigin::from(local_origin);
129// Then finally into a pallet `RuntimeOrigin`.
130let 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!`.
133origin.set_caller_from(local_origin);
134// Make sure to return the new origin.
135Ok((ValidTransaction::default(), (), origin))
136 }
137// We're not doing any special logic in `TransactionExtension::prepare`, so just impl a default.
138impl_tx_ext_default!(T::RuntimeCall; weight prepare);
139}