pub mod payload;
pub mod signer;
use crate::config::{Config, ExtrinsicParams, ExtrinsicParamsEncoder, HashFor, Hasher};
use crate::error::{Error, ExtrinsicError, MetadataError};
use crate::metadata::Metadata;
use crate::utils::Encoded;
use alloc::borrow::{Cow, ToOwned};
use alloc::vec::Vec;
use codec::{Compact, Encode};
use payload::Payload;
use signer::Signer as SignerT;
use sp_crypto_hashing::blake2_256;
pub use crate::client::{ClientState, RuntimeVersion};
pub fn validate<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<(), Error> {
if let Some(details) = call.validation_details() {
let expected_hash = metadata
.pallet_by_name_err(details.pallet_name)?
.call_hash(details.call_name)
.ok_or_else(|| MetadataError::CallNameNotFound(details.call_name.to_owned()))?;
if details.hash != expected_hash {
return Err(MetadataError::IncompatibleCodegen.into());
}
}
Ok(())
}
pub fn suggested_version(metadata: &Metadata) -> Result<TransactionVersion, Error> {
let versions = metadata.extrinsic().supported_versions();
if versions.contains(&4) {
Ok(TransactionVersion::V4)
} else if versions.contains(&5) {
Ok(TransactionVersion::V5)
} else {
Err(ExtrinsicError::UnsupportedVersion.into())
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum TransactionVersion {
V4,
V5,
}
pub fn call_data<Call: Payload>(call: &Call, metadata: &Metadata) -> Result<Vec<u8>, Error> {
let mut bytes = Vec::new();
call.encode_call_data_to(metadata, &mut bytes)?;
Ok(bytes)
}
pub fn create_v4_unsigned<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
create_unsigned_at_version(call, 4, metadata)
}
pub fn create_v5_bare<T: Config, Call: Payload>(
call: &Call,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
create_unsigned_at_version(call, 5, metadata)
}
fn create_unsigned_at_version<T: Config, Call: Payload>(
call: &Call,
tx_version: u8,
metadata: &Metadata,
) -> Result<Transaction<T>, Error> {
validate(call, metadata)?;
let extrinsic = {
let mut encoded_inner = Vec::new();
tx_version.encode_to(&mut encoded_inner);
call.encode_call_data_to(metadata, &mut encoded_inner)?;
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
Ok(Transaction::from_bytes(extrinsic))
}
pub fn create_v4_signed<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransactionV4<T>, Error> {
validate(call, &client_state.metadata)?;
let call_data = call_data(call, &client_state.metadata)?;
let additional_and_extra_params =
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
Ok(PartialTransactionV4 {
call_data,
additional_and_extra_params,
})
}
pub fn create_v5_general<T: Config, Call: Payload>(
call: &Call,
client_state: &ClientState<T>,
params: <T::ExtrinsicParams as ExtrinsicParams<T>>::Params,
) -> Result<PartialTransactionV5<T>, Error> {
validate(call, &client_state.metadata)?;
let tx_extensions_version = client_state
.metadata
.extrinsic()
.transaction_extension_version_to_use_for_encoding();
let call_data = call_data(call, &client_state.metadata)?;
let additional_and_extra_params =
<T::ExtrinsicParams as ExtrinsicParams<T>>::new(client_state, params)?;
Ok(PartialTransactionV5 {
call_data,
additional_and_extra_params,
tx_extensions_version,
})
}
pub struct PartialTransactionV4<T: Config> {
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
}
impl<T: Config> PartialTransactionV4<T> {
pub fn call_data(&self) -> &[u8] {
&self.call_data
}
fn with_signer_payload<F, R>(&self, f: F) -> R
where
F: for<'a> FnOnce(Cow<'a, [u8]>) -> R,
{
let mut bytes = self.call_data.clone();
self.additional_and_extra_params
.encode_signer_payload_value_to(&mut bytes);
self.additional_and_extra_params
.encode_implicit_to(&mut bytes);
if bytes.len() > 256 {
f(Cow::Borrowed(&blake2_256(&bytes)))
} else {
f(Cow::Owned(bytes))
}
}
pub fn signer_payload(&self) -> Vec<u8> {
self.with_signer_payload(|bytes| bytes.to_vec())
}
pub fn sign<Signer>(&self, signer: &Signer) -> Transaction<T>
where
Signer: SignerT<T>,
{
let signature = self.with_signer_payload(|bytes| signer.sign(&bytes));
self.sign_with_account_and_signature(signer.account_id(), &signature)
}
pub fn sign_with_account_and_signature(
&self,
account_id: T::AccountId,
signature: &T::Signature,
) -> Transaction<T> {
let extrinsic = {
let mut encoded_inner = Vec::new();
(0b10000000 + 4u8).encode_to(&mut encoded_inner);
let address: T::Address = account_id.into();
address.encode_to(&mut encoded_inner);
signature.encode_to(&mut encoded_inner);
self.additional_and_extra_params
.encode_value_to(&mut encoded_inner);
encoded_inner.extend(&self.call_data);
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
Transaction::from_bytes(extrinsic)
}
}
pub struct PartialTransactionV5<T: Config> {
call_data: Vec<u8>,
additional_and_extra_params: T::ExtrinsicParams,
tx_extensions_version: u8,
}
impl<T: Config> PartialTransactionV5<T> {
pub fn call_data(&self) -> &[u8] {
&self.call_data
}
pub fn signer_payload(&self) -> [u8; 32] {
let mut bytes = self.call_data.clone();
self.additional_and_extra_params
.encode_signer_payload_value_to(&mut bytes);
self.additional_and_extra_params
.encode_implicit_to(&mut bytes);
blake2_256(&bytes)
}
pub fn to_transaction(&self) -> Transaction<T> {
let extrinsic = {
let mut encoded_inner = Vec::new();
(0b01000000 + 5u8).encode_to(&mut encoded_inner);
self.tx_extensions_version.encode_to(&mut encoded_inner);
self.additional_and_extra_params
.encode_value_to(&mut encoded_inner);
encoded_inner.extend(&self.call_data);
let len = Compact(
u32::try_from(encoded_inner.len()).expect("extrinsic size expected to be <4GB"),
);
let mut encoded = Vec::new();
len.encode_to(&mut encoded);
encoded.extend(encoded_inner);
encoded
};
Transaction::from_bytes(extrinsic)
}
pub fn sign<Signer>(&mut self, signer: &Signer) -> Transaction<T>
where
Signer: SignerT<T>,
{
let signature = signer.sign(&self.signer_payload());
self.sign_with_account_and_signature(&signer.account_id(), &signature)
}
pub fn sign_with_account_and_signature(
&mut self,
account_id: &T::AccountId,
signature: &T::Signature,
) -> Transaction<T> {
self.additional_and_extra_params
.inject_signature(account_id, signature);
self.to_transaction()
}
}
pub struct Transaction<T> {
encoded: Encoded,
marker: core::marker::PhantomData<T>,
}
impl<T: Config> Transaction<T> {
pub fn from_bytes(tx_bytes: Vec<u8>) -> Self {
Self {
encoded: Encoded(tx_bytes),
marker: core::marker::PhantomData,
}
}
pub fn hash_with(&self, hasher: T::Hasher) -> HashFor<T> {
hasher.hash_of(&self.encoded)
}
pub fn encoded(&self) -> &[u8] {
&self.encoded.0
}
pub fn into_encoded(self) -> Vec<u8> {
self.encoded.0
}
}