eth_valkyoth_protocol/transaction/set_code/
validity.rs1use core::fmt;
2
3use eth_valkyoth_primitives::{Address, Gas, Nonce};
4
5use super::{SetCodeAuthorization, SetCodeTransactionDecodeError, UnvalidatedSetCodeTransaction};
6use crate::{Hardfork, ValidationContext};
7
8#[path = "validity_authorization.rs"]
9mod authorization_validity;
10
11use authorization_validity::validate_authorizations;
12
13pub const EIP_7702_DELEGATION_INDICATOR_PREFIX: [u8; 3] = [0xef, 0x01, 0x00];
15
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
18pub struct SetCodeTransactionValidationContext {
19 pub fork: ValidationContext,
21 pub minimum_gas_limit: Option<Gas>,
27}
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub struct SetCodeAuthorizationAuthority {
35 pub authorization_index: usize,
37 pub authority: Address,
39}
40
41pub trait SetCodeAuthorizationAuthorityView {
47 fn authority_for(
50 &self,
51 authorization_index: usize,
52 authorization: SetCodeAuthorization,
53 ) -> Option<Address>;
54}
55
56impl SetCodeAuthorizationAuthorityView for [SetCodeAuthorizationAuthority] {
57 fn authority_for(
58 &self,
59 authorization_index: usize,
60 _authorization: SetCodeAuthorization,
61 ) -> Option<Address> {
62 self.iter()
63 .find(|entry| entry.authorization_index == authorization_index)
64 .map(|entry| entry.authority)
65 }
66}
67
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
70pub enum SetCodeAuthorityCode {
71 Empty,
73 Delegation {
79 target: Address,
81 },
82 Other,
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
88pub struct SetCodeAuthorityAccount {
89 pub authority: Address,
91 pub nonce: Nonce,
93 pub code: SetCodeAuthorityCode,
95}
96
97pub trait SetCodeAuthorityStateView {
108 fn authority_account(&self, authority: Address) -> Option<SetCodeAuthorityAccount>;
110}
111
112impl SetCodeAuthorityStateView for [SetCodeAuthorityAccount] {
113 fn authority_account(&self, authority: Address) -> Option<SetCodeAuthorityAccount> {
114 self.iter()
115 .find(|entry| entry.authority == authority)
116 .copied()
117 }
118}
119
120#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122pub struct ValidSetCodeTransaction<'a> {
123 transaction: UnvalidatedSetCodeTransaction<'a>,
124 authorization_count: usize,
125 applied_authorization_count: usize,
126 skipped_authorization_count: usize,
127}
128
129impl<'a> ValidSetCodeTransaction<'a> {
130 const fn new(
131 transaction: UnvalidatedSetCodeTransaction<'a>,
132 authorization_count: usize,
133 applied_authorization_count: usize,
134 skipped_authorization_count: usize,
135 ) -> Self {
136 Self {
137 transaction,
138 authorization_count,
139 applied_authorization_count,
140 skipped_authorization_count,
141 }
142 }
143
144 #[must_use]
146 pub const fn transaction(self) -> UnvalidatedSetCodeTransaction<'a> {
147 self.transaction
148 }
149
150 #[must_use]
152 pub const fn authorization_count(self) -> usize {
153 self.authorization_count
154 }
155
156 #[must_use]
158 pub const fn applied_authorization_count(self) -> usize {
159 self.applied_authorization_count
160 }
161
162 #[must_use]
164 pub const fn skipped_authorization_count(self) -> usize {
165 self.skipped_authorization_count
166 }
167}
168
169#[non_exhaustive]
175#[derive(Clone, Copy, Debug, Eq, PartialEq)]
176pub enum SetCodeTransactionValidityError {
177 InactiveFork,
179 ForkBeforePrague,
181 WrongTransactionChain,
183 PriorityFeeExceedsMaxFee,
185 GasLimitTooLow,
187 EmptyAuthorizationList,
189 AuthorizationDecode(SetCodeTransactionDecodeError),
191 MissingAuthorizationAuthority {
193 authorization_index: usize,
195 },
196 WrongAuthorizationChain {
198 authorization_index: usize,
200 },
201 AuthorizationNonceTooHigh {
203 authorization_index: usize,
205 },
206 MissingAuthorityState {
208 authorization_index: usize,
210 },
211 AuthorityNonceMismatch {
213 authorization_index: usize,
215 },
216 InvalidAuthorityCode {
218 authorization_index: usize,
220 },
221}
222
223impl SetCodeTransactionValidityError {
224 #[must_use]
226 pub const fn code(self) -> &'static str {
227 match self {
228 Self::InactiveFork => "ETH_SET_CODE_VALIDITY_INACTIVE_FORK",
229 Self::ForkBeforePrague => "ETH_SET_CODE_VALIDITY_PRE_PRAGUE",
230 Self::WrongTransactionChain => "ETH_SET_CODE_VALIDITY_WRONG_TX_CHAIN",
231 Self::PriorityFeeExceedsMaxFee => "ETH_SET_CODE_VALIDITY_FEE_ORDER",
232 Self::GasLimitTooLow => "ETH_SET_CODE_VALIDITY_GAS_LIMIT",
233 Self::EmptyAuthorizationList => "ETH_SET_CODE_VALIDITY_EMPTY_AUTH_LIST",
234 Self::AuthorizationDecode(error) => error.code(),
235 Self::MissingAuthorizationAuthority { .. } => "ETH_SET_CODE_VALIDITY_MISSING_AUTHORITY",
236 Self::WrongAuthorizationChain { .. } => "ETH_SET_CODE_VALIDITY_WRONG_AUTH_CHAIN",
237 Self::AuthorizationNonceTooHigh { .. } => "ETH_SET_CODE_VALIDITY_AUTH_NONCE_TOO_HIGH",
238 Self::MissingAuthorityState { .. } => "ETH_SET_CODE_VALIDITY_MISSING_AUTHORITY_STATE",
239 Self::AuthorityNonceMismatch { .. } => "ETH_SET_CODE_VALIDITY_AUTHORITY_NONCE_MISMATCH",
240 Self::InvalidAuthorityCode { .. } => "ETH_SET_CODE_VALIDITY_INVALID_AUTHORITY_CODE",
241 }
242 }
243
244 #[must_use]
246 pub const fn message(self) -> &'static str {
247 match self {
248 Self::InactiveFork => "set-code transaction fork is not active",
249 Self::ForkBeforePrague => "set-code transaction requires Prague/Pectra or later",
250 Self::WrongTransactionChain => {
251 "set-code transaction chain id does not match validation context"
252 }
253 Self::PriorityFeeExceedsMaxFee => "set-code transaction priority fee exceeds max fee",
254 Self::GasLimitTooLow => "set-code transaction gas limit is below required minimum",
255 Self::EmptyAuthorizationList => {
256 "set-code transaction authorization list must not be empty"
257 }
258 Self::AuthorizationDecode(error) => error.message(),
259 Self::MissingAuthorizationAuthority { .. } => {
260 "set-code authorization has no verified recovered authority"
261 }
262 Self::WrongAuthorizationChain { .. } => {
263 "set-code authorization chain id is not valid for this chain"
264 }
265 Self::AuthorizationNonceTooHigh { .. } => {
266 "set-code authorization nonce is too high to increment"
267 }
268 Self::MissingAuthorityState { .. } => "set-code authority account state is unavailable",
269 Self::AuthorityNonceMismatch { .. } => {
270 "set-code authority nonce does not match authorization nonce"
271 }
272 Self::InvalidAuthorityCode { .. } => {
273 "set-code authority code is neither empty nor delegated"
274 }
275 }
276 }
277
278 #[must_use]
280 pub const fn category(self) -> SetCodeTransactionValidityErrorCategory {
281 match self {
282 Self::InactiveFork | Self::ForkBeforePrague | Self::WrongTransactionChain => {
283 SetCodeTransactionValidityErrorCategory::Fork
284 }
285 Self::PriorityFeeExceedsMaxFee => SetCodeTransactionValidityErrorCategory::Fee,
286 Self::GasLimitTooLow => SetCodeTransactionValidityErrorCategory::Gas,
287 Self::EmptyAuthorizationList
288 | Self::AuthorizationDecode(_)
289 | Self::MissingAuthorizationAuthority { .. }
290 | Self::WrongAuthorizationChain { .. }
291 | Self::AuthorizationNonceTooHigh { .. } => {
292 SetCodeTransactionValidityErrorCategory::Authorization
293 }
294 Self::MissingAuthorityState { .. }
295 | Self::AuthorityNonceMismatch { .. }
296 | Self::InvalidAuthorityCode { .. } => {
297 SetCodeTransactionValidityErrorCategory::AccountState
298 }
299 }
300 }
301}
302
303impl fmt::Display for SetCodeTransactionValidityError {
304 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
305 formatter.write_str(self.message())
306 }
307}
308
309#[cfg(feature = "std")]
310impl std::error::Error for SetCodeTransactionValidityError {}
311
312#[non_exhaustive]
314#[derive(Clone, Copy, Debug, Eq, PartialEq)]
315pub enum SetCodeTransactionValidityErrorCategory {
316 Fork,
318 Fee,
320 Gas,
322 Authorization,
324 AccountState,
326}
327
328pub fn validate_set_code_transaction_context<'a, A, S>(
336 transaction: UnvalidatedSetCodeTransaction<'a>,
337 context: SetCodeTransactionValidationContext,
338 authorities: &A,
339 accounts: &S,
340) -> Result<ValidSetCodeTransaction<'a>, SetCodeTransactionValidityError>
341where
342 A: SetCodeAuthorizationAuthorityView + ?Sized,
343 S: SetCodeAuthorityStateView + ?Sized,
344{
345 validate_outer_context(&transaction, context)?;
346 let summary = validate_authorizations(transaction, authorities, accounts);
347 Ok(ValidSetCodeTransaction::new(
348 transaction,
349 transaction.authorization_list.len(),
350 summary.applied,
351 summary.skipped,
352 ))
353}
354
355fn validate_outer_context(
356 transaction: &UnvalidatedSetCodeTransaction<'_>,
357 context: SetCodeTransactionValidationContext,
358) -> Result<(), SetCodeTransactionValidityError> {
359 if !context.fork.fork_is_active() {
360 return Err(SetCodeTransactionValidityError::InactiveFork);
361 }
362 if context.fork.fork.hardfork < Hardfork::Prague {
363 return Err(SetCodeTransactionValidityError::ForkBeforePrague);
364 }
365 if context.fork.fork.chain_id != transaction.chain_id {
366 return Err(SetCodeTransactionValidityError::WrongTransactionChain);
367 }
368 if wei_exceeds(
369 transaction.max_priority_fee_per_gas,
370 transaction.max_fee_per_gas,
371 ) {
372 return Err(SetCodeTransactionValidityError::PriorityFeeExceedsMaxFee);
373 }
374 if let Some(minimum_gas_limit) = context.minimum_gas_limit
375 && transaction.gas_limit < minimum_gas_limit
376 {
377 return Err(SetCodeTransactionValidityError::GasLimitTooLow);
378 }
379 if transaction.authorization_list.is_empty() {
380 return Err(SetCodeTransactionValidityError::EmptyAuthorizationList);
381 }
382 Ok(())
383}
384
385fn wei_exceeds(left: eth_valkyoth_primitives::Wei, right: eth_valkyoth_primitives::Wei) -> bool {
386 left.to_be_bytes() > right.to_be_bytes()
387}
388
389#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
390pub(super) struct AuthorizationValidationSummary {
391 pub(super) applied: usize,
392 pub(super) skipped: usize,
393}
394
395#[cfg(test)]
396#[path = "validity_tests.rs"]
397mod tests;