use crate::{
codec::{Decode, Encode},
Debug,
};
use alloc::{vec, vec::Vec};
use scale_info::TypeInfo;
use sp_weights::Weight;
pub type TransactionPriority = u64;
pub type TransactionLongevity = u64;
pub type TransactionTag = Vec<u8>;
#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, Debug, TypeInfo)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(strum::AsRefStr))]
#[cfg_attr(feature = "std", strum(serialize_all = "snake_case"))]
pub enum InvalidTransaction {
Call,
Payment,
Future,
Stale,
BadProof,
AncientBirthBlock,
ExhaustsResources,
Custom(u8),
BadMandatory,
MandatoryValidation,
BadSigner,
IndeterminateImplicit,
UnknownOrigin,
}
impl InvalidTransaction {
pub fn exhausted_resources(&self) -> bool {
matches!(self, Self::ExhaustsResources)
}
pub fn was_mandatory(&self) -> bool {
matches!(self, Self::BadMandatory)
}
}
impl From<InvalidTransaction> for &'static str {
fn from(invalid: InvalidTransaction) -> &'static str {
match invalid {
InvalidTransaction::Call => "Transaction call is not expected",
InvalidTransaction::Future => "Transaction will be valid in the future",
InvalidTransaction::Stale => "Transaction is outdated",
InvalidTransaction::BadProof => "Transaction has a bad signature",
InvalidTransaction::AncientBirthBlock => "Transaction has an ancient birth block",
InvalidTransaction::ExhaustsResources => "Transaction would exhaust the block limits",
InvalidTransaction::Payment => {
"Inability to pay some fees (e.g. account balance too low)"
},
InvalidTransaction::BadMandatory => {
"A call was labelled as mandatory, but resulted in an Error."
},
InvalidTransaction::MandatoryValidation => {
"Transaction dispatch is mandatory; transactions must not be validated."
},
InvalidTransaction::Custom(_) => "InvalidTransaction custom error",
InvalidTransaction::BadSigner => "Invalid signing address",
InvalidTransaction::IndeterminateImplicit => {
"The implicit data was unable to be calculated"
},
InvalidTransaction::UnknownOrigin => {
"The transaction extension did not authorize any origin"
},
}
}
}
#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, Debug, TypeInfo)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(strum::AsRefStr))]
#[cfg_attr(feature = "std", strum(serialize_all = "snake_case"))]
pub enum UnknownTransaction {
CannotLookup,
NoUnsignedValidator,
Custom(u8),
}
impl From<UnknownTransaction> for &'static str {
fn from(unknown: UnknownTransaction) -> &'static str {
match unknown {
UnknownTransaction::CannotLookup => {
"Could not lookup information required to validate the transaction"
},
UnknownTransaction::NoUnsignedValidator => {
"Could not find an unsigned validator for the unsigned transaction"
},
UnknownTransaction::Custom(_) => "UnknownTransaction custom error",
}
}
}
#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, Debug, TypeInfo)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransactionValidityError {
Invalid(InvalidTransaction),
Unknown(UnknownTransaction),
}
impl TransactionValidityError {
pub fn exhausted_resources(&self) -> bool {
match self {
Self::Invalid(e) => e.exhausted_resources(),
Self::Unknown(_) => false,
}
}
pub fn was_mandatory(&self) -> bool {
match self {
Self::Invalid(e) => e.was_mandatory(),
Self::Unknown(_) => false,
}
}
}
impl From<TransactionValidityError> for &'static str {
fn from(err: TransactionValidityError) -> &'static str {
match err {
TransactionValidityError::Invalid(invalid) => invalid.into(),
TransactionValidityError::Unknown(unknown) => unknown.into(),
}
}
}
impl From<InvalidTransaction> for TransactionValidityError {
fn from(err: InvalidTransaction) -> Self {
TransactionValidityError::Invalid(err)
}
}
impl From<UnknownTransaction> for TransactionValidityError {
fn from(err: UnknownTransaction) -> Self {
TransactionValidityError::Unknown(err)
}
}
#[cfg(feature = "std")]
impl std::error::Error for TransactionValidityError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[cfg(feature = "std")]
impl std::fmt::Display for TransactionValidityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s: &'static str = (*self).into();
write!(f, "{}", s)
}
}
pub type TransactionValidity = Result<ValidTransaction, TransactionValidityError>;
pub type TransactionValidityWithRefund =
Result<(ValidTransaction, Weight), TransactionValidityError>;
impl From<InvalidTransaction> for TransactionValidity {
fn from(invalid_transaction: InvalidTransaction) -> Self {
Err(TransactionValidityError::Invalid(invalid_transaction))
}
}
impl From<UnknownTransaction> for TransactionValidity {
fn from(unknown_transaction: UnknownTransaction) -> Self {
Err(TransactionValidityError::Unknown(unknown_transaction))
}
}
#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo, Hash)]
pub enum TransactionSource {
InBlock,
Local,
External,
}
#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug, TypeInfo)]
pub struct ValidTransaction {
pub priority: TransactionPriority,
pub requires: Vec<TransactionTag>,
pub provides: Vec<TransactionTag>,
pub longevity: TransactionLongevity,
pub propagate: bool,
}
impl Default for ValidTransaction {
fn default() -> Self {
Self {
priority: 0,
requires: vec![],
provides: vec![],
longevity: TransactionLongevity::max_value(),
propagate: true,
}
}
}
impl ValidTransaction {
pub fn with_tag_prefix(prefix: &'static str) -> ValidTransactionBuilder {
ValidTransactionBuilder { prefix: Some(prefix), validity: Default::default() }
}
pub fn combine_with(mut self, mut other: ValidTransaction) -> Self {
Self {
priority: self.priority.saturating_add(other.priority),
requires: {
self.requires.append(&mut other.requires);
self.requires
},
provides: {
self.provides.append(&mut other.provides);
self.provides
},
longevity: self.longevity.min(other.longevity),
propagate: self.propagate && other.propagate,
}
}
}
#[derive(Default, Clone, Debug)]
pub struct ValidTransactionBuilder {
prefix: Option<&'static str>,
validity: ValidTransaction,
}
impl ValidTransactionBuilder {
pub fn priority(mut self, priority: TransactionPriority) -> Self {
self.validity.priority = priority;
self
}
pub fn longevity(mut self, longevity: TransactionLongevity) -> Self {
self.validity.longevity = longevity;
self
}
pub fn propagate(mut self, propagate: bool) -> Self {
self.validity.propagate = propagate;
self
}
pub fn and_requires(mut self, tag: impl Encode) -> Self {
self.validity.requires.push(match self.prefix.as_ref() {
Some(prefix) => (prefix, tag).encode(),
None => tag.encode(),
});
self
}
pub fn and_provides(mut self, tag: impl Encode) -> Self {
self.validity.provides.push(match self.prefix.as_ref() {
Some(prefix) => (prefix, tag).encode(),
None => tag.encode(),
});
self
}
pub fn combine_with(mut self, validity: ValidTransaction) -> Self {
self.validity = core::mem::take(&mut self.validity).combine_with(validity);
self
}
pub fn build(self) -> TransactionValidity {
self.into()
}
}
impl From<ValidTransactionBuilder> for TransactionValidity {
fn from(builder: ValidTransactionBuilder) -> Self {
Ok(builder.into())
}
}
impl From<ValidTransactionBuilder> for ValidTransaction {
fn from(builder: ValidTransactionBuilder) -> Self {
builder.validity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_encode_and_decode() {
let v: TransactionValidity = Ok(ValidTransaction {
priority: 5,
requires: vec![vec![1, 2, 3, 4]],
provides: vec![vec![4, 5, 6]],
longevity: 42,
propagate: false,
});
let encoded = v.encode();
assert_eq!(
encoded,
vec![
0, 5, 0, 0, 0, 0, 0, 0, 0, 4, 16, 1, 2, 3, 4, 4, 12, 4, 5, 6, 42, 0, 0, 0, 0, 0, 0,
0, 0
]
);
assert_eq!(TransactionValidity::decode(&mut &*encoded), Ok(v));
}
#[test]
fn builder_should_prefix_the_tags() {
const PREFIX: &str = "test";
let a: ValidTransaction = ValidTransaction::with_tag_prefix(PREFIX)
.and_requires(1)
.and_requires(2)
.and_provides(3)
.and_provides(4)
.propagate(false)
.longevity(5)
.priority(3)
.priority(6)
.into();
assert_eq!(
a,
ValidTransaction {
propagate: false,
longevity: 5,
priority: 6,
requires: vec![(PREFIX, 1).encode(), (PREFIX, 2).encode()],
provides: vec![(PREFIX, 3).encode(), (PREFIX, 4).encode()],
}
);
}
}