eth_valkyoth_protocol/
lib.rs1#![no_std]
2#![forbid(unsafe_code)]
3use core::marker::PhantomData;
6
7use eth_valkyoth_primitives::{BlockNumber, ChainId, UnixTimestamp};
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum ForkActivation {
12 BlockOnly {
14 activation_block: BlockNumber,
16 },
17 BlockAndTimestamp {
19 activation_block: BlockNumber,
21 activation_timestamp: UnixTimestamp,
23 },
24}
25
26#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub struct ForkSpec {
29 pub chain_id: ChainId,
31 pub activation: ForkActivation,
33}
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub struct ValidationContext {
38 pub fork: ForkSpec,
40 pub block_number: BlockNumber,
42 pub timestamp: UnixTimestamp,
44}
45
46impl ValidationContext {
47 #[must_use]
49 pub fn fork_is_active(self) -> bool {
50 match self.fork.activation {
51 ForkActivation::BlockOnly { activation_block } => self.block_number >= activation_block,
52 ForkActivation::BlockAndTimestamp {
53 activation_block,
54 activation_timestamp,
55 } => self.block_number >= activation_block && self.timestamp >= activation_timestamp,
56 }
57 }
58}
59
60#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62pub struct Decoded;
63
64#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub struct Canonical;
67
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub struct ForkValidated;
71
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
74pub struct SenderRecovered;
75
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78pub struct Transaction<State> {
79 _state: PhantomData<State>,
80}
81
82impl Transaction<Decoded> {
83 #[must_use]
85 pub const fn decoded() -> Self {
86 Self {
87 _state: PhantomData,
88 }
89 }
90
91 #[must_use]
93 pub const fn into_canonical(self) -> Transaction<Canonical> {
94 Transaction {
95 _state: PhantomData,
96 }
97 }
98}
99
100impl Transaction<Canonical> {
101 #[must_use]
103 pub const fn into_fork_validated(self) -> Transaction<ForkValidated> {
104 Transaction {
105 _state: PhantomData,
106 }
107 }
108}
109
110impl Transaction<ForkValidated> {
111 #[must_use]
113 pub const fn into_sender_recovered(self) -> Transaction<SenderRecovered> {
114 Transaction {
115 _state: PhantomData,
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn activation_requires_block_and_time() {
126 let context = ValidationContext {
127 fork: ForkSpec {
128 chain_id: ChainId::new(1),
129 activation: ForkActivation::BlockAndTimestamp {
130 activation_block: BlockNumber::new(10),
131 activation_timestamp: UnixTimestamp::new(20),
132 },
133 },
134 block_number: BlockNumber::new(10),
135 timestamp: UnixTimestamp::new(19),
136 };
137 assert!(!context.fork_is_active());
138 }
139
140 #[test]
141 fn block_only_activation_ignores_timestamp() {
142 let context = ValidationContext {
143 fork: ForkSpec {
144 chain_id: ChainId::new(1),
145 activation: ForkActivation::BlockOnly {
146 activation_block: BlockNumber::new(10),
147 },
148 },
149 block_number: BlockNumber::new(10),
150 timestamp: UnixTimestamp::new(0),
151 };
152 assert!(context.fork_is_active());
153 }
154
155 #[test]
156 fn transaction_typestate_advances_in_order() {
157 let transaction = Transaction::decoded()
158 .into_canonical()
159 .into_fork_validated()
160 .into_sender_recovered();
161 assert_eq!(
162 transaction,
163 Transaction::<SenderRecovered> {
164 _state: PhantomData
165 }
166 );
167 }
168}