#![doc = include_str!("../README.md")]
#![allow(clippy::expect_used)]
#![cfg_attr(not(feature = "std"), no_std)]
#![deny(
rustdoc::broken_intra_doc_links,
rustdoc::missing_crate_level_docs,
rustdoc::invalid_codeblock_attributes,
missing_docs
)]
use common_runtime::{extensions::check_nonce::CheckNonce, signature::check_signature};
use frame_support::{
dispatch::{DispatchInfo, GetDispatchInfo, PostDispatchInfo},
pallet_prelude::*,
traits::Contains,
};
use frame_system::pallet_prelude::*;
use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::{
generic::Era,
traits::{Convert, Dispatchable, SignedExtension, Zero},
transaction_validity::{TransactionValidity, TransactionValidityError},
AccountId32, MultiSignature,
};
use sp_std::{vec, vec::Vec};
pub(crate) type OnChargeTransactionOf<T> =
<T as pallet_transaction_payment::Config>::OnChargeTransaction;
pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;
#[cfg(any(feature = "runtime-benchmarks", test))]
mod test_common;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
pub use weights::*;
#[cfg(feature = "runtime-benchmarks")]
use frame_support::traits::tokens::fungible::Mutate;
use frame_system::CheckWeight;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod types;
pub use types::*;
pub use module::*;
#[frame_support::pallet]
pub mod module {
use super::*;
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
#[pallet::config]
pub trait Config:
frame_system::Config + pallet_transaction_payment::Config + Send + Sync
{
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo
+ From<frame_system::Call<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>
+ From<Call<Self>>;
type WeightInfo: WeightInfo;
type ConvertIntoAccountId32: Convert<Self::AccountId, AccountId32>;
type PasskeyCallFilter: Contains<<Self as Config>::RuntimeCall>;
#[cfg(feature = "runtime-benchmarks")]
type Currency: Mutate<Self::AccountId>;
}
#[pallet::error]
pub enum Error<T> {
InvalidAccountSignature,
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event<T: Config> {
TransactionExecutionSuccess {
account_id: T::AccountId,
},
}
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight({
let dispatch_info = payload.passkey_call.call.get_dispatch_info();
let overhead = T::WeightInfo::pre_dispatch();
let total = overhead.saturating_add(dispatch_info.weight);
(total, dispatch_info.class)
})]
pub fn proxy(
origin: OriginFor<T>,
payload: PasskeyPayload<T>,
) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
let transaction_account_id = payload.passkey_call.account_id.clone();
let main_origin = T::RuntimeOrigin::from(frame_system::RawOrigin::Signed(
transaction_account_id.clone(),
));
let result = payload.passkey_call.call.dispatch(main_origin);
if let Ok(_inner) = result {
Self::deposit_event(Event::TransactionExecutionSuccess {
account_id: transaction_account_id,
});
}
result
}
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T>
where
BalanceOf<T>: Send + Sync + From<u64>,
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
type Call = Call<T>;
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
let valid_tx = ValidTransaction::default();
let payload = Self::filter_valid_calls(&call)?;
let frame_system_validity =
FrameSystemChecks(payload.passkey_call.account_id.clone(), call.clone())
.validate()?;
let nonce_validity = PasskeyNonceCheck::new(payload.passkey_call.clone()).validate()?;
let weight_validity = PasskeyWeightCheck::new(call.clone()).validate()?;
let tx_payment_validity = ChargeTransactionPayment::<T>(
payload.passkey_call.account_id.clone(),
call.clone(),
)
.validate()?;
let signature_validity = PasskeySignatureCheck::new(payload.clone()).validate()?;
let valid_tx = valid_tx
.combine_with(frame_system_validity)
.combine_with(nonce_validity)
.combine_with(weight_validity)
.combine_with(tx_payment_validity)
.combine_with(signature_validity);
Ok(valid_tx)
}
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
let payload = Self::filter_valid_calls(&call)?;
FrameSystemChecks(payload.passkey_call.account_id.clone(), call.clone())
.pre_dispatch()?;
PasskeyNonceCheck::new(payload.passkey_call.clone()).pre_dispatch()?;
PasskeyWeightCheck::new(call.clone()).pre_dispatch()?;
ChargeTransactionPayment::<T>(payload.passkey_call.account_id.clone(), call.clone())
.pre_dispatch()?;
PasskeySignatureCheck::new(payload.clone()).pre_dispatch()
}
}
}
impl<T: Config> Pallet<T>
where
BalanceOf<T>: Send + Sync + From<u64>,
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
fn filter_valid_calls(call: &Call<T>) -> Result<PasskeyPayload<T>, TransactionValidityError> {
match call {
Call::proxy { payload }
if T::PasskeyCallFilter::contains(&payload.clone().passkey_call.call) =>
return Ok(payload.clone()),
_ => return Err(InvalidTransaction::Call.into()),
}
}
}
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
struct PasskeyNonceCheck<T: Config>(pub PasskeyCall<T>);
impl<T: Config> PasskeyNonceCheck<T>
where
<T as frame_system::Config>::RuntimeCall: Dispatchable<Info = DispatchInfo>,
{
pub fn new(passkey_call: PasskeyCall<T>) -> Self {
Self(passkey_call)
}
pub fn validate(&self) -> TransactionValidity {
let who = self.0.account_id.clone();
let nonce = self.0.account_nonce;
let some_call: &<T as Config>::RuntimeCall = &self.0.call;
let info = &some_call.get_dispatch_info();
let passkey_nonce = CheckNonce::<T>::from(nonce);
passkey_nonce.validate(&who, &some_call.clone().into(), info, 0usize)
}
pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let who = self.0.account_id.clone();
let nonce = self.0.account_nonce;
let some_call: &<T as Config>::RuntimeCall = &self.0.call;
let info = &some_call.get_dispatch_info();
let passkey_nonce = CheckNonce::<T>::from(nonce);
passkey_nonce.pre_dispatch(&who, &some_call.clone().into(), info, 0usize)
}
}
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
struct PasskeySignatureCheck<T: Config>(pub PasskeyPayload<T>);
impl<T: Config> PasskeySignatureCheck<T> {
pub fn new(passkey_payload: PasskeyPayload<T>) -> Self {
Self(passkey_payload)
}
pub fn validate(&self) -> TransactionValidity {
let signed_data = self.0.passkey_public_key.clone();
let signature = self.0.passkey_call.account_ownership_proof.clone();
let signer = &self.0.passkey_call.account_id;
Self::check_account_signature(signer, &signed_data.inner().to_vec(), &signature)
.map_err(|_e| TransactionValidityError::Invalid(InvalidTransaction::BadSigner))?;
let p256_signed_data = self.0.passkey_call.encode();
let p256_signature = self.0.verifiable_passkey_signature.clone();
let p256_signer = self.0.passkey_public_key.clone();
p256_signature
.try_verify(&p256_signed_data, &p256_signer)
.map_err(|e| match e {
PasskeyVerificationError::InvalidProof =>
TransactionValidityError::Invalid(InvalidTransaction::BadSigner),
_ => TransactionValidityError::Invalid(InvalidTransaction::Custom(e.into())),
})?;
Ok(ValidTransaction::default())
}
pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let _ = self.validate()?;
Ok(())
}
fn check_account_signature(
signer: &T::AccountId,
signed_data: &Vec<u8>,
signature: &MultiSignature,
) -> DispatchResult {
let key = T::ConvertIntoAccountId32::convert((*signer).clone());
if !check_signature(signature, key, signed_data.clone()) {
return Err(Error::<T>::InvalidAccountSignature.into());
}
Ok(())
}
}
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ChargeTransactionPayment<T: Config>(pub T::AccountId, pub Call<T>);
impl<T: Config> ChargeTransactionPayment<T>
where
BalanceOf<T>: Send + Sync + From<u64>,
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let info = &self.1.get_dispatch_info();
let len = self.1.using_encoded(|c| c.len());
let runtime_call: <T as frame_system::Config>::RuntimeCall =
<T as frame_system::Config>::RuntimeCall::from(self.1.clone());
let who = self.0.clone();
pallet_transaction_payment::ChargeTransactionPayment::<T>::from(Zero::zero())
.pre_dispatch(&who, &runtime_call, info, len)?;
Ok(())
}
pub fn validate(&self) -> TransactionValidity {
let info = &self.1.get_dispatch_info();
let len = self.1.using_encoded(|c| c.len());
let runtime_call: <T as frame_system::Config>::RuntimeCall =
<T as frame_system::Config>::RuntimeCall::from(self.1.clone());
let who = self.0.clone();
pallet_transaction_payment::ChargeTransactionPayment::<T>::from(Zero::zero()).validate(
&who,
&runtime_call,
info,
len,
)
}
}
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct FrameSystemChecks<T: Config + Send + Sync>(pub T::AccountId, pub Call<T>);
impl<T: Config + Send + Sync> FrameSystemChecks<T>
where
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let info = &self.1.get_dispatch_info();
let len = self.1.using_encoded(|c| c.len());
let runtime_call: <T as frame_system::Config>::RuntimeCall =
<T as frame_system::Config>::RuntimeCall::from(self.1.clone());
let who = self.0.clone();
let non_zero_sender_check = frame_system::CheckNonZeroSender::<T>::new();
let spec_version_check = frame_system::CheckSpecVersion::<T>::new();
let tx_version_check = frame_system::CheckTxVersion::<T>::new();
let genesis_hash_check = frame_system::CheckGenesis::<T>::new();
let era_check = frame_system::CheckEra::<T>::from(Era::immortal());
non_zero_sender_check.pre_dispatch(&who, &runtime_call, info, len)?;
spec_version_check.pre_dispatch(&who, &runtime_call, info, len)?;
tx_version_check.pre_dispatch(&who, &runtime_call, info, len)?;
genesis_hash_check.pre_dispatch(&who, &runtime_call, info, len)?;
era_check.pre_dispatch(&who, &runtime_call, info, len)
}
pub fn validate(&self) -> TransactionValidity {
let info = &self.1.get_dispatch_info();
let len = self.1.using_encoded(|c| c.len());
let runtime_call: <T as frame_system::Config>::RuntimeCall =
<T as frame_system::Config>::RuntimeCall::from(self.1.clone());
let who = self.0.clone();
let non_zero_sender_check = frame_system::CheckNonZeroSender::<T>::new();
let spec_version_check = frame_system::CheckSpecVersion::<T>::new();
let tx_version_check = frame_system::CheckTxVersion::<T>::new();
let genesis_hash_check = frame_system::CheckGenesis::<T>::new();
let era_check = frame_system::CheckEra::<T>::from(Era::immortal());
let non_zero_sender_validity =
non_zero_sender_check.validate(&who, &runtime_call, info, len)?;
let spec_version_validity = spec_version_check.validate(&who, &runtime_call, info, len)?;
let tx_version_validity = tx_version_check.validate(&who, &runtime_call, info, len)?;
let genesis_hash_validity = genesis_hash_check.validate(&who, &runtime_call, info, len)?;
let era_validity = era_check.validate(&who, &runtime_call, info, len)?;
Ok(non_zero_sender_validity
.combine_with(spec_version_validity)
.combine_with(tx_version_validity)
.combine_with(genesis_hash_validity)
.combine_with(era_validity))
}
}
#[derive(Encode, Decode, Clone, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct PasskeyWeightCheck<T: Config>(pub Call<T>);
impl<T: Config> PasskeyWeightCheck<T>
where
<T as frame_system::Config>::RuntimeCall:
From<Call<T>> + Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
{
pub fn new(call: Call<T>) -> Self {
Self(call)
}
pub fn validate(&self) -> TransactionValidity {
let len = self.0.encode().len();
CheckWeight::<T>::validate_unsigned(
&self.0.clone().into(),
&self.0.get_dispatch_info(),
len,
)
}
pub fn pre_dispatch(&self) -> Result<(), TransactionValidityError> {
let len = self.0.encode().len();
CheckWeight::<T>::pre_dispatch_unsigned(
&self.0.clone().into(),
&self.0.get_dispatch_info(),
len,
)
}
}