diff --git a/crates/core/src/types/constants.rs b/crates/core/src/types/constants.rs new file mode 100644 index 00000000000..3034eed7a0d --- /dev/null +++ b/crates/core/src/types/constants.rs @@ -0,0 +1,9 @@ +// Transaction costs +pub const TX_BASE_COST: u64 = 21000; +pub const TX_DATA_COST_PER_NON_ZERO: u64 = 16; +pub const TX_DATA_COST_PER_ZERO: u64 = 4; +pub const TX_CREATE_COST: u64 = 32000; +pub const TX_ACCESS_LIST_ADDRESS_COST: u64 = 2400; +pub const TX_ACCESS_LIST_STORAGE_KEY_COST: u64 = 1900; +pub const MAX_CODE_SIZE: usize = 0x6000; +pub const GAS_INIT_CODE_WORD_COST: u64 = 2; diff --git a/crates/core/src/types/mod.rs b/crates/core/src/types/mod.rs index b571123cf74..a0b71852996 100644 --- a/crates/core/src/types/mod.rs +++ b/crates/core/src/types/mod.rs @@ -1,13 +1,17 @@ mod account; mod block; +mod constants; mod engine; mod genesis; mod receipt; mod transaction; +mod utils; pub use account::*; pub use block::*; +pub use constants::*; pub use engine::*; pub use genesis::*; pub use receipt::*; pub use transaction::*; +pub use utils::*; diff --git a/crates/core/src/types/transaction.rs b/crates/core/src/types/transaction.rs index fde0deb6f8f..3a814714aac 100644 --- a/crates/core/src/types/transaction.rs +++ b/crates/core/src/types/transaction.rs @@ -11,6 +11,11 @@ use crate::rlp::{ structs::{Decoder, Encoder}, }; +use super::{ + ceil32, GAS_INIT_CODE_WORD_COST, MAX_CODE_SIZE, TX_ACCESS_LIST_ADDRESS_COST, TX_BASE_COST, + TX_CREATE_COST, TX_DATA_COST_PER_NON_ZERO, TX_DATA_COST_PER_ZERO, +}; + #[derive(Clone, Debug, PartialEq, Eq)] pub enum Transaction { LegacyTransaction(LegacyTransaction), @@ -442,6 +447,37 @@ fn recover_address( Address::from_slice(&hash[12..]) } +/// Verifies a transaction. +// reference: https://github.com/ethereum/execution-specs/blob/c854868f4abf2ab0c3e8790d4c40607e0d251147/src/ethereum/shanghai/fork.py#L678 +pub fn validate_transaction(tx: &Transaction) -> bool { + calculate_intrinsic_cost(tx) <= tx.gas_limit() + && !(matches!(tx.to(), TxKind::Create) && tx.data().len() > 2 * MAX_CODE_SIZE) +} + +/// Calculates the gas that is charged before execution is started. +fn calculate_intrinsic_cost(tx: &Transaction) -> u64 { + let data_cost = tx.data().iter().fold(0, |acc, byte| { + acc + if *byte == 0 { + TX_DATA_COST_PER_ZERO + } else { + TX_DATA_COST_PER_NON_ZERO + } + }); + let create_cost = match tx.to() { + TxKind::Call(_) => 0, + TxKind::Create => TX_CREATE_COST + init_code_cost(tx.data().len() as u64), + }; + let access_list_cost = tx.access_list().iter().fold(0, |acc, (_, keys)| { + acc + TX_ACCESS_LIST_ADDRESS_COST + keys.len() as u64 * TX_ACCESS_LIST_ADDRESS_COST + }); + TX_BASE_COST + data_cost + create_cost + access_list_cost +} + +/// Calculates the gas to be charged for the init code in a create transaction. +fn init_code_cost(init_code_length: u64) -> u64 { + GAS_INIT_CODE_WORD_COST * ceil32(init_code_length) / 32 +} + #[cfg(test)] mod tests { use crate::types::{compute_receipts_root, BlockBody, Receipt}; @@ -552,4 +588,12 @@ mod tests { }; assert_eq!(tx, expected_tx); } + + #[test] + fn validate_legacy_tx() { + let legacy_tx = LegacyTransaction::decode(&hex!("f86d80843baa0c4082f618946177843db3138ae69679a54b95cf345ed759450d870aa87bee538000808360306ba0151ccc02146b9b11adf516e6787b59acae3e76544fdcd75e77e67c6b598ce65da064c5dd5aae2fbb535830ebbdad0234975cd7ece3562013b63ea18cc0df6c97d4")).unwrap(); + assert!(validate_transaction(&Transaction::LegacyTransaction( + legacy_tx + ))) + } } diff --git a/crates/core/src/types/utils.rs b/crates/core/src/types/utils.rs new file mode 100644 index 00000000000..d1de43db59c --- /dev/null +++ b/crates/core/src/types/utils.rs @@ -0,0 +1,9 @@ +/// Converts a unsigned integer to the next closest multiple of 32 +pub fn ceil32(n: u64) -> u64 { + let rem = n % 32; + if rem == 0 { + n + } else { + n + 32 - rem + } +}