use core::fmt;
use eth_valkyoth_primitives::{Address, Gas, Nonce};
use super::{SetCodeAuthorization, SetCodeTransactionDecodeError, UnvalidatedSetCodeTransaction};
use crate::{Hardfork, ValidationContext};
#[path = "validity_authorization.rs"]
mod authorization_validity;
use authorization_validity::validate_authorizations;
pub const EIP_7702_DELEGATION_INDICATOR_PREFIX: [u8; 3] = [0xef, 0x01, 0x00];
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetCodeTransactionValidationContext {
pub fork: ValidationContext,
pub minimum_gas_limit: Option<Gas>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetCodeAuthorizationAuthority {
pub authorization_index: usize,
pub authority: Address,
}
pub trait SetCodeAuthorizationAuthorityView {
fn authority_for(
&self,
authorization_index: usize,
authorization: SetCodeAuthorization,
) -> Option<Address>;
}
impl SetCodeAuthorizationAuthorityView for [SetCodeAuthorizationAuthority] {
fn authority_for(
&self,
authorization_index: usize,
_authorization: SetCodeAuthorization,
) -> Option<Address> {
self.iter()
.find(|entry| entry.authorization_index == authorization_index)
.map(|entry| entry.authority)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SetCodeAuthorityCode {
Empty,
Delegation {
target: Address,
},
Other,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetCodeAuthorityAccount {
pub authority: Address,
pub nonce: Nonce,
pub code: SetCodeAuthorityCode,
}
pub trait SetCodeAuthorityStateView {
fn authority_account(&self, authority: Address) -> Option<SetCodeAuthorityAccount>;
}
impl SetCodeAuthorityStateView for [SetCodeAuthorityAccount] {
fn authority_account(&self, authority: Address) -> Option<SetCodeAuthorityAccount> {
self.iter()
.find(|entry| entry.authority == authority)
.copied()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ValidSetCodeTransaction<'a> {
transaction: UnvalidatedSetCodeTransaction<'a>,
authorization_count: usize,
applied_authorization_count: usize,
skipped_authorization_count: usize,
}
impl<'a> ValidSetCodeTransaction<'a> {
const fn new(
transaction: UnvalidatedSetCodeTransaction<'a>,
authorization_count: usize,
applied_authorization_count: usize,
skipped_authorization_count: usize,
) -> Self {
Self {
transaction,
authorization_count,
applied_authorization_count,
skipped_authorization_count,
}
}
#[must_use]
pub const fn transaction(self) -> UnvalidatedSetCodeTransaction<'a> {
self.transaction
}
#[must_use]
pub const fn authorization_count(self) -> usize {
self.authorization_count
}
#[must_use]
pub const fn applied_authorization_count(self) -> usize {
self.applied_authorization_count
}
#[must_use]
pub const fn skipped_authorization_count(self) -> usize {
self.skipped_authorization_count
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SetCodeTransactionValidityError {
InactiveFork,
ForkBeforePrague,
WrongTransactionChain,
PriorityFeeExceedsMaxFee,
GasLimitTooLow,
EmptyAuthorizationList,
AuthorizationDecode(SetCodeTransactionDecodeError),
MissingAuthorizationAuthority {
authorization_index: usize,
},
WrongAuthorizationChain {
authorization_index: usize,
},
AuthorizationNonceTooHigh {
authorization_index: usize,
},
MissingAuthorityState {
authorization_index: usize,
},
AuthorityNonceMismatch {
authorization_index: usize,
},
InvalidAuthorityCode {
authorization_index: usize,
},
}
impl SetCodeTransactionValidityError {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::InactiveFork => "ETH_SET_CODE_VALIDITY_INACTIVE_FORK",
Self::ForkBeforePrague => "ETH_SET_CODE_VALIDITY_PRE_PRAGUE",
Self::WrongTransactionChain => "ETH_SET_CODE_VALIDITY_WRONG_TX_CHAIN",
Self::PriorityFeeExceedsMaxFee => "ETH_SET_CODE_VALIDITY_FEE_ORDER",
Self::GasLimitTooLow => "ETH_SET_CODE_VALIDITY_GAS_LIMIT",
Self::EmptyAuthorizationList => "ETH_SET_CODE_VALIDITY_EMPTY_AUTH_LIST",
Self::AuthorizationDecode(error) => error.code(),
Self::MissingAuthorizationAuthority { .. } => "ETH_SET_CODE_VALIDITY_MISSING_AUTHORITY",
Self::WrongAuthorizationChain { .. } => "ETH_SET_CODE_VALIDITY_WRONG_AUTH_CHAIN",
Self::AuthorizationNonceTooHigh { .. } => "ETH_SET_CODE_VALIDITY_AUTH_NONCE_TOO_HIGH",
Self::MissingAuthorityState { .. } => "ETH_SET_CODE_VALIDITY_MISSING_AUTHORITY_STATE",
Self::AuthorityNonceMismatch { .. } => "ETH_SET_CODE_VALIDITY_AUTHORITY_NONCE_MISMATCH",
Self::InvalidAuthorityCode { .. } => "ETH_SET_CODE_VALIDITY_INVALID_AUTHORITY_CODE",
}
}
#[must_use]
pub const fn message(self) -> &'static str {
match self {
Self::InactiveFork => "set-code transaction fork is not active",
Self::ForkBeforePrague => "set-code transaction requires Prague/Pectra or later",
Self::WrongTransactionChain => {
"set-code transaction chain id does not match validation context"
}
Self::PriorityFeeExceedsMaxFee => "set-code transaction priority fee exceeds max fee",
Self::GasLimitTooLow => "set-code transaction gas limit is below required minimum",
Self::EmptyAuthorizationList => {
"set-code transaction authorization list must not be empty"
}
Self::AuthorizationDecode(error) => error.message(),
Self::MissingAuthorizationAuthority { .. } => {
"set-code authorization has no verified recovered authority"
}
Self::WrongAuthorizationChain { .. } => {
"set-code authorization chain id is not valid for this chain"
}
Self::AuthorizationNonceTooHigh { .. } => {
"set-code authorization nonce is too high to increment"
}
Self::MissingAuthorityState { .. } => "set-code authority account state is unavailable",
Self::AuthorityNonceMismatch { .. } => {
"set-code authority nonce does not match authorization nonce"
}
Self::InvalidAuthorityCode { .. } => {
"set-code authority code is neither empty nor delegated"
}
}
}
#[must_use]
pub const fn category(self) -> SetCodeTransactionValidityErrorCategory {
match self {
Self::InactiveFork | Self::ForkBeforePrague | Self::WrongTransactionChain => {
SetCodeTransactionValidityErrorCategory::Fork
}
Self::PriorityFeeExceedsMaxFee => SetCodeTransactionValidityErrorCategory::Fee,
Self::GasLimitTooLow => SetCodeTransactionValidityErrorCategory::Gas,
Self::EmptyAuthorizationList
| Self::AuthorizationDecode(_)
| Self::MissingAuthorizationAuthority { .. }
| Self::WrongAuthorizationChain { .. }
| Self::AuthorizationNonceTooHigh { .. } => {
SetCodeTransactionValidityErrorCategory::Authorization
}
Self::MissingAuthorityState { .. }
| Self::AuthorityNonceMismatch { .. }
| Self::InvalidAuthorityCode { .. } => {
SetCodeTransactionValidityErrorCategory::AccountState
}
}
}
}
impl fmt::Display for SetCodeTransactionValidityError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.message())
}
}
#[cfg(feature = "std")]
impl std::error::Error for SetCodeTransactionValidityError {}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum SetCodeTransactionValidityErrorCategory {
Fork,
Fee,
Gas,
Authorization,
AccountState,
}
pub fn validate_set_code_transaction_context<'a, A, S>(
transaction: UnvalidatedSetCodeTransaction<'a>,
context: SetCodeTransactionValidationContext,
authorities: &A,
accounts: &S,
) -> Result<ValidSetCodeTransaction<'a>, SetCodeTransactionValidityError>
where
A: SetCodeAuthorizationAuthorityView + ?Sized,
S: SetCodeAuthorityStateView + ?Sized,
{
validate_outer_context(&transaction, context)?;
let summary = validate_authorizations(transaction, authorities, accounts);
Ok(ValidSetCodeTransaction::new(
transaction,
transaction.authorization_list.len(),
summary.applied,
summary.skipped,
))
}
fn validate_outer_context(
transaction: &UnvalidatedSetCodeTransaction<'_>,
context: SetCodeTransactionValidationContext,
) -> Result<(), SetCodeTransactionValidityError> {
if !context.fork.fork_is_active() {
return Err(SetCodeTransactionValidityError::InactiveFork);
}
if context.fork.fork.hardfork < Hardfork::Prague {
return Err(SetCodeTransactionValidityError::ForkBeforePrague);
}
if context.fork.fork.chain_id != transaction.chain_id {
return Err(SetCodeTransactionValidityError::WrongTransactionChain);
}
if wei_exceeds(
transaction.max_priority_fee_per_gas,
transaction.max_fee_per_gas,
) {
return Err(SetCodeTransactionValidityError::PriorityFeeExceedsMaxFee);
}
if let Some(minimum_gas_limit) = context.minimum_gas_limit
&& transaction.gas_limit < minimum_gas_limit
{
return Err(SetCodeTransactionValidityError::GasLimitTooLow);
}
if transaction.authorization_list.is_empty() {
return Err(SetCodeTransactionValidityError::EmptyAuthorizationList);
}
Ok(())
}
fn wei_exceeds(left: eth_valkyoth_primitives::Wei, right: eth_valkyoth_primitives::Wei) -> bool {
left.to_be_bytes() > right.to_be_bytes()
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub(super) struct AuthorizationValidationSummary {
pub(super) applied: usize,
pub(super) skipped: usize,
}
#[cfg(test)]
#[path = "validity_tests.rs"]
mod tests;