#![no_std]
#![forbid(unsafe_code)]
use core::marker::PhantomData;
use eth_valkyoth_primitives::{BlockNumber, ChainId, UnixTimestamp};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ForkActivation {
BlockOnly {
activation_block: BlockNumber,
},
BlockAndTimestamp {
activation_block: BlockNumber,
activation_timestamp: UnixTimestamp,
},
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ForkSpec {
pub chain_id: ChainId,
pub activation: ForkActivation,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ValidationContext {
pub fork: ForkSpec,
pub block_number: BlockNumber,
pub timestamp: UnixTimestamp,
}
impl ValidationContext {
#[must_use]
pub fn fork_is_active(self) -> bool {
match self.fork.activation {
ForkActivation::BlockOnly { activation_block } => self.block_number >= activation_block,
ForkActivation::BlockAndTimestamp {
activation_block,
activation_timestamp,
} => self.block_number >= activation_block && self.timestamp >= activation_timestamp,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Decoded;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Canonical;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ForkValidated;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SenderRecovered;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Transaction<State> {
_state: PhantomData<State>,
}
impl Transaction<Decoded> {
#[must_use]
pub const fn decoded() -> Self {
Self {
_state: PhantomData,
}
}
#[must_use]
pub const fn into_canonical(self) -> Transaction<Canonical> {
Transaction {
_state: PhantomData,
}
}
}
impl Transaction<Canonical> {
#[must_use]
pub const fn into_fork_validated(self) -> Transaction<ForkValidated> {
Transaction {
_state: PhantomData,
}
}
}
impl Transaction<ForkValidated> {
#[must_use]
pub const fn into_sender_recovered(self) -> Transaction<SenderRecovered> {
Transaction {
_state: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn activation_requires_block_and_time() {
let context = ValidationContext {
fork: ForkSpec {
chain_id: ChainId::new(1),
activation: ForkActivation::BlockAndTimestamp {
activation_block: BlockNumber::new(10),
activation_timestamp: UnixTimestamp::new(20),
},
},
block_number: BlockNumber::new(10),
timestamp: UnixTimestamp::new(19),
};
assert!(!context.fork_is_active());
}
#[test]
fn block_only_activation_ignores_timestamp() {
let context = ValidationContext {
fork: ForkSpec {
chain_id: ChainId::new(1),
activation: ForkActivation::BlockOnly {
activation_block: BlockNumber::new(10),
},
},
block_number: BlockNumber::new(10),
timestamp: UnixTimestamp::new(0),
};
assert!(context.fork_is_active());
}
#[test]
fn transaction_typestate_advances_in_order() {
let transaction = Transaction::decoded()
.into_canonical()
.into_fork_validated()
.into_sender_recovered();
assert_eq!(
transaction,
Transaction::<SenderRecovered> {
_state: PhantomData
}
);
}
}