use scale::Encode; use sp_core::sr25519::{Public, Signature}; use sp_runtime::traits::Verify; use serai_primitives::SeraiAddress; use frame_support::dispatch::GetDispatchInfo; pub trait TransactionMember: Clone + PartialEq + Eq + core::fmt::Debug + scale::Encode + scale::Decode + scale_info::TypeInfo { } impl< T: Clone + PartialEq + Eq + core::fmt::Debug + scale::Encode + scale::Decode + scale_info::TypeInfo, > TransactionMember for T { } type TransactionEncodeAs<'a, Extra> = (&'a crate::Call, &'a Option<(SeraiAddress, Signature, Extra)>); type TransactionDecodeAs = (crate::Call, Option<(SeraiAddress, Signature, Extra)>); // We use our own Transaction struct, over UncheckedExtrinsic, for more control, a bit more // simplicity, and in order to be immune to https://github.com/paritytech/polkadot-sdk/issues/2947 #[allow(private_bounds)] #[derive(Clone, PartialEq, Eq, Debug)] pub struct Transaction< Call: 'static + TransactionMember + From, Extra: 'static + TransactionMember, > { call: crate::Call, mapped_call: Call, signature: Option<(SeraiAddress, Signature, Extra)>, } impl, Extra: 'static + TransactionMember> Transaction { pub fn new(call: crate::Call, signature: Option<(SeraiAddress, Signature, Extra)>) -> Self { Self { call: call.clone(), mapped_call: call.into(), signature } } pub fn call(&self) -> &crate::Call { &self.call } } impl, Extra: 'static + TransactionMember> scale::Encode for Transaction { fn using_encoded R>(&self, f: F) -> R { let tx: TransactionEncodeAs = (&self.call, &self.signature); tx.using_encoded(f) } } impl, Extra: 'static + TransactionMember> scale::Decode for Transaction { fn decode(input: &mut I) -> Result { let (call, signature) = TransactionDecodeAs::decode(input)?; let mapped_call = Call::from(call.clone()); Ok(Self { call, mapped_call, signature }) } } impl, Extra: 'static + TransactionMember> scale_info::TypeInfo for Transaction { type Identity = TransactionDecodeAs; // Define the type info as the info of the type equivalent to what we encode as fn type_info() -> scale_info::Type { TransactionDecodeAs::::type_info() } } #[cfg(feature = "serde")] mod _serde { use scale::Encode; use serde::{ser::*, de::*}; use super::*; impl, Extra: 'static + TransactionMember> Serialize for Transaction { fn serialize(&self, serializer: S) -> Result { let encoded = self.encode(); serializer.serialize_bytes(&encoded) } } #[cfg(feature = "std")] impl< 'a, Call: 'static + TransactionMember + From, Extra: 'static + TransactionMember, > Deserialize<'a> for Transaction { fn deserialize>(de: D) -> Result { let bytes = sp_core::bytes::deserialize(de)?; ::decode(&mut &bytes[..]) .map_err(|e| serde::de::Error::custom(format!("invalid transaction: {e}"))) } } } impl< Call: 'static + TransactionMember + From + TryInto, Extra: 'static + TransactionMember, > sp_runtime::traits::Extrinsic for Transaction { type Call = Call; type SignaturePayload = (SeraiAddress, Signature, Extra); fn is_signed(&self) -> Option { Some(self.signature.is_some()) } fn new(call: Call, signature: Option) -> Option { Some(Self { call: call.clone().try_into().ok()?, mapped_call: call, signature }) } } impl< Call: 'static + TransactionMember + From + TryInto, Extra: 'static + TransactionMember, > frame_support::traits::ExtrinsicCall for Transaction { fn call(&self) -> &Call { &self.mapped_call } } impl< Call: 'static + TransactionMember + From, Extra: 'static + TransactionMember + sp_runtime::traits::SignedExtension, > sp_runtime::traits::ExtrinsicMetadata for Transaction { type SignedExtensions = Extra; const VERSION: u8 = 0; } impl< Call: 'static + TransactionMember + From + GetDispatchInfo, Extra: 'static + TransactionMember, > GetDispatchInfo for Transaction { fn get_dispatch_info(&self) -> frame_support::dispatch::DispatchInfo { self.mapped_call.get_dispatch_info() } } impl< Call: 'static + TransactionMember + From, Extra: 'static + TransactionMember + sp_runtime::traits::SignedExtension, > sp_runtime::traits::BlindCheckable for Transaction { type Checked = sp_runtime::generic::CheckedExtrinsic; fn check( self, ) -> Result { Ok(match self.signature { Some((signer, signature, extra)) => { if !signature.verify( (&self.call, &extra, extra.additional_signed()?).encode().as_slice(), &signer.into(), ) { Err(sp_runtime::transaction_validity::InvalidTransaction::BadProof)? } sp_runtime::generic::CheckedExtrinsic { signed: Some((signer.into(), extra)), function: self.mapped_call, } } None => sp_runtime::generic::CheckedExtrinsic { signed: None, function: self.mapped_call }, }) } }