Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Prague): Add EIP-7702 #1565

Merged
merged 14 commits into from
Jun 28, 2024
10 changes: 7 additions & 3 deletions crates/interpreter/src/gas/calc.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use revm_primitives::AccessListItem;

use super::constants::*;
use crate::{
num_words,
primitives::{SpecId, U256},
primitives::{AccessListItem, SpecId, U256},
SelfDestructResult,
};

Expand Down Expand Up @@ -359,6 +357,7 @@ pub fn validate_initial_tx_gas(
input: &[u8],
is_create: bool,
access_list: &[AccessListItem],
authorization_list_num: u64,
) -> u64 {
let mut initial_gas = 0;
let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
Expand Down Expand Up @@ -399,5 +398,10 @@ pub fn validate_initial_tx_gas(
initial_gas += initcode_cost(input.len() as u64)
}

// EIP-7702
if spec_id.is_enabled_in(SpecId::PRAGUE) {
initial_gas += authorization_list_num * PER_CONTRACT_CODE_BASE_COST;
}

initial_gas
}
3 changes: 3 additions & 0 deletions crates/interpreter/src/gas/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ pub const COLD_ACCOUNT_ACCESS_COST: u64 = 2600;
pub const WARM_STORAGE_READ_COST: u64 = 100;
pub const WARM_SSTORE_RESET: u64 = SSTORE_RESET - COLD_SLOAD_COST;

/// EIP-7702
pub const PER_CONTRACT_CODE_BASE_COST: u64 = 2400;

/// EIP-3860 : Limit and meter initcode
pub const INITCODE_WORD_COST: u64 = 2;

Expand Down
2 changes: 1 addition & 1 deletion crates/interpreter/src/instruction_result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub enum InstructionResult {
EOFFunctionStackOverflow,
/// Aux data overflow, new aux data is larger tha u16 max size.
EofAuxDataOverflow,
/// Aud data is smaller then already present data size.
/// Aux data is smaller then already present data size.
EofAuxDataTooSmall,
}

Expand Down
133 changes: 79 additions & 54 deletions crates/primitives/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,25 @@ impl Env {
/// Return initial spend gas (Gas needed to execute transaction).
#[inline]
pub fn validate_tx<SPEC: Spec>(&self) -> Result<(), InvalidTransaction> {
// Check if the transaction's chain id is correct
if let Some(tx_chain_id) = self.tx.chain_id {
if tx_chain_id != self.cfg.chain_id {
rakita marked this conversation as resolved.
Show resolved Hide resolved
return Err(InvalidTransaction::InvalidChainId);
}
}

// Check if gas_limit is more than block_gas_limit
if !self.cfg.is_block_gas_limit_disabled()
&& U256::from(self.tx.gas_limit) > self.block.gas_limit
{
return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
}

// Check that access list is empty for transactions before BERLIN
if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() {
return Err(InvalidTransaction::AccessListNotSupported);
}

// BASEFEE tx check
if SPEC::enabled(SpecId::LONDON) {
if let Some(priority_fee) = self.tx.gas_priority_fee {
Expand All @@ -109,13 +128,6 @@ impl Env {
}
}

// Check if gas_limit is more than block_gas_limit
if !self.cfg.is_block_gas_limit_disabled()
&& U256::from(self.tx.gas_limit) > self.block.gas_limit
{
return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
}

// EIP-3860: Limit and meter initcode
if SPEC::enabled(SpecId::SHANGHAI) && self.tx.transact_to.is_create() {
let max_initcode_size = self
Expand All @@ -128,65 +140,66 @@ impl Env {
}
}

// Check if the transaction's chain id is correct
if let Some(tx_chain_id) = self.tx.chain_id {
if tx_chain_id != self.cfg.chain_id {
return Err(InvalidTransaction::InvalidChainId);
}
}

// Check that access list is empty for transactions before BERLIN
if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() {
return Err(InvalidTransaction::AccessListNotSupported);
// - For before CANCUN, check that `blob_hashes` and `max_fee_per_blob_gas` are empty / not set
if !SPEC::enabled(SpecId::CANCUN)
&& (self.tx.max_fee_per_blob_gas.is_some() || !self.tx.blob_hashes.is_empty())
{
return Err(InvalidTransaction::BlobVersionedHashesNotSupported);
}

// - For CANCUN and later, check that the gas price is not more than the tx max
// - For before CANCUN, check that `blob_hashes` and `max_fee_per_blob_gas` are empty / not set
if SPEC::enabled(SpecId::CANCUN) {
// Presence of max_fee_per_blob_gas means that this is blob transaction.
if let Some(max) = self.tx.max_fee_per_blob_gas {
// ensure that the user was willing to at least pay the current blob gasprice
let price = self.block.get_blob_gasprice().expect("already checked");
if U256::from(price) > max {
return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
}
// Presence of max_fee_per_blob_gas means that this is blob transaction.
if let Some(max) = self.tx.max_fee_per_blob_gas {
// ensure that the user was willing to at least pay the current blob gasprice
let price = self.block.get_blob_gasprice().expect("already checked");
if U256::from(price) > max {
return Err(InvalidTransaction::BlobGasPriceGreaterThanMax);
}

// there must be at least one blob
if self.tx.blob_hashes.is_empty() {
return Err(InvalidTransaction::EmptyBlobs);
}
// there must be at least one blob
if self.tx.blob_hashes.is_empty() {
return Err(InvalidTransaction::EmptyBlobs);
}

// The field `to` deviates slightly from the semantics with the exception
// that it MUST NOT be nil and therefore must always represent
// a 20-byte address. This means that blob transactions cannot
// have the form of a create transaction.
if self.tx.transact_to.is_create() {
return Err(InvalidTransaction::BlobCreateTransaction);
}
// The field `to` deviates slightly from the semantics with the exception
// that it MUST NOT be nil and therefore must always represent
// a 20-byte address. This means that blob transactions cannot
// have the form of a create transaction.
if self.tx.transact_to.is_create() {
return Err(InvalidTransaction::BlobCreateTransaction);
}

// all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
for blob in self.tx.blob_hashes.iter() {
if blob[0] != VERSIONED_HASH_VERSION_KZG {
return Err(InvalidTransaction::BlobVersionNotSupported);
}
// all versioned blob hashes must start with VERSIONED_HASH_VERSION_KZG
for blob in self.tx.blob_hashes.iter() {
if blob[0] != VERSIONED_HASH_VERSION_KZG {
return Err(InvalidTransaction::BlobVersionNotSupported);
}
}

// ensure the total blob gas spent is at most equal to the limit
// assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
let num_blobs = self.tx.blob_hashes.len();
if num_blobs > MAX_BLOB_NUMBER_PER_BLOCK as usize {
return Err(InvalidTransaction::TooManyBlobs {
have: num_blobs,
max: MAX_BLOB_NUMBER_PER_BLOCK as usize,
});
}
// ensure the total blob gas spent is at most equal to the limit
// assert blob_gas_used <= MAX_BLOB_GAS_PER_BLOCK
let num_blobs = self.tx.blob_hashes.len();
if num_blobs > MAX_BLOB_NUMBER_PER_BLOCK as usize {
return Err(InvalidTransaction::TooManyBlobs {
have: num_blobs,
max: MAX_BLOB_NUMBER_PER_BLOCK as usize,
});
}
} else {
// if max_fee_per_blob_gas is not set, then blob_hashes must be empty
if !self.tx.blob_hashes.is_empty() {
return Err(InvalidTransaction::BlobVersionedHashesNotSupported);
}
if self.tx.max_fee_per_blob_gas.is_some() {
return Err(InvalidTransaction::MaxFeePerBlobGasNotSupported);
}

// check if EIP-7702 transaction is enabled.
if !SPEC::enabled(SpecId::PRAGUE) && self.tx.authorization_list.is_some() {
return Err(InvalidTransaction::AuthorizationListNotSupported);
}

if self.tx.authorization_list.is_some() {
// Check if other fields are unset.
if self.tx.max_fee_per_blob_gas.is_some() || !self.tx.blob_hashes.is_empty() {
return Err(InvalidTransaction::AuthorizationListInvalidFields);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this not be BlobVersionedHashesNotSupported? or something similar, this error is a bit vague since the error is that we have an auth list and blobs

Copy link
Member Author

@rakita rakita Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, EnvTx sucks rn, as we have all fields in one struck. This error marks AuthorizationList TX which has set some fields by mistake.

}
}

Expand Down Expand Up @@ -509,6 +522,7 @@ pub struct TxEnv {
pub value: U256,
/// The data of the transaction.
pub data: Bytes,

/// The nonce of the transaction.
///
/// Caution: If set to `None`, then nonce validation against the account's nonce is skipped: [InvalidTransaction::NonceTooHigh] and [InvalidTransaction::NonceTooLow]
Expand Down Expand Up @@ -550,6 +564,16 @@ pub struct TxEnv {
/// [EIP-4844]: https://eips.ethereum.org/EIPS/eip-4844
pub max_fee_per_blob_gas: Option<U256>,

/// List of authorizations, that contains the signature that authorizes this
/// caller to place the code to signer account.
///
/// Set EOA account code for one transaction
///
/// [EIP-Set EOA account code for one transaction](https://eips.ethereum.org/EIPS/eip-7702)
///
/// TODO: include from alloy/eips crate.
pub authorization_list: Option<Vec<(Address,Address)>>,

#[cfg_attr(feature = "serde", serde(flatten))]
#[cfg(feature = "optimism")]
/// Optimism fields.
Expand Down Expand Up @@ -594,6 +618,7 @@ impl Default for TxEnv {
access_list: Vec::new(),
blob_hashes: Vec::new(),
max_fee_per_blob_gas: None,
authorization_list: None,
#[cfg(feature = "optimism")]
optimism: OptimismFields::default(),
}
Expand Down
8 changes: 8 additions & 0 deletions crates/primitives/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,10 @@ pub enum InvalidTransaction {
BlobVersionNotSupported,
/// EOF crate should have `to` address
EofCrateShouldHaveToAddress,
/// EIP-7702 is not enabled.
AuthorizationListNotSupported,
/// EIP-7702 transaction has invalid fields set.
AuthorizationListInvalidFields,
/// System transactions are not supported post-regolith hardfork.
///
/// Before the Regolith hardfork, there was a special field in the `Deposit` transaction
Expand Down Expand Up @@ -343,6 +347,10 @@ impl fmt::Display for InvalidTransaction {
}
Self::BlobVersionNotSupported => write!(f, "blob version not supported"),
Self::EofCrateShouldHaveToAddress => write!(f, "EOF crate should have `to` address"),
Self::AuthorizationListNotSupported => write!(f, "authorization list not supported"),
Self::AuthorizationListInvalidFields => {
write!(f, "authorization list tx has invalid fields")
}
#[cfg(feature = "optimism")]
Self::DepositSystemTxPostRegolith => {
write!(
Expand Down
2 changes: 2 additions & 0 deletions crates/revm/src/context/evm_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ pub(crate) mod test_utils {
journaled_state: JournaledState::new(SpecId::CANCUN, HashSet::new()),
db,
error: Ok(()),
valid_authorizations: Vec::new(),
#[cfg(feature = "optimism")]
l1_block_info: None,
},
Expand All @@ -299,6 +300,7 @@ pub(crate) mod test_utils {
journaled_state: JournaledState::new(SpecId::CANCUN, HashSet::new()),
db,
error: Ok(()),
valid_authorizations: Default::default(),
#[cfg(feature = "optimism")]
l1_block_info: None,
},
Expand Down
6 changes: 6 additions & 0 deletions crates/revm/src/context/inner_evm_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ pub struct InnerEvmContext<DB: Database> {
pub db: DB,
/// Error that happened during execution.
pub error: Result<(), EVMError<DB::Error>>,
/// EIP-7702 Authorization list of accounts that needs to be cleared.
pub valid_authorizations: Vec<Address>,
/// Used as temporary value holder to store L1 block info.
#[cfg(feature = "optimism")]
pub l1_block_info: Option<crate::optimism::L1BlockInfo>,
Expand All @@ -44,6 +46,7 @@ where
journaled_state: self.journaled_state.clone(),
db: self.db.clone(),
error: self.error.clone(),
valid_authorizations: self.valid_authorizations.clone(),
#[cfg(feature = "optimism")]
l1_block_info: self.l1_block_info.clone(),
}
Expand All @@ -57,6 +60,7 @@ impl<DB: Database> InnerEvmContext<DB> {
journaled_state: JournaledState::new(SpecId::LATEST, HashSet::new()),
db,
error: Ok(()),
valid_authorizations: Default::default(),
#[cfg(feature = "optimism")]
l1_block_info: None,
}
Expand All @@ -70,6 +74,7 @@ impl<DB: Database> InnerEvmContext<DB> {
journaled_state: JournaledState::new(SpecId::LATEST, HashSet::new()),
db,
error: Ok(()),
valid_authorizations: Default::default(),
#[cfg(feature = "optimism")]
l1_block_info: None,
}
Expand All @@ -85,6 +90,7 @@ impl<DB: Database> InnerEvmContext<DB> {
journaled_state: self.journaled_state,
db,
error: Ok(()),
valid_authorizations: Default::default(),
#[cfg(feature = "optimism")]
l1_block_info: self.l1_block_info,
}
Expand Down
18 changes: 16 additions & 2 deletions crates/revm/src/handler/mainnet/post_execution.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use crate::{
interpreter::{Gas, SuccessOrHalt},
primitives::{
db::Database, EVMError, ExecutionResult, ResultAndState, Spec, SpecId::LONDON, U256,
db::Database, EVMError, ExecutionResult, ResultAndState, Spec, SpecId::LONDON,
KECCAK_EMPTY, U256,
},
Context, FrameResult,
};
Expand All @@ -21,6 +22,9 @@ pub fn clear<EXT, DB: Database>(context: &mut Context<EXT, DB>) {
// clear error and journaled state.
let _ = context.evm.take_error();
context.evm.inner.journaled_state.clear();
// Clear valid authorizations after each transaction.
// If transaction is valid they are consumed in `output` handler.
context.evm.inner.valid_authorizations.clear();
}

/// Reward beneficiary with gas fee.
Expand Down Expand Up @@ -92,7 +96,17 @@ pub fn output<EXT, DB: Database>(
let instruction_result = result.into_interpreter_result();

// reset journal and return present state.
let (state, logs) = context.evm.journaled_state.finalize();
let (mut state, logs) = context.evm.journaled_state.finalize();

// clear code of authorized accounts.
for authorized in core::mem::take(&mut context.evm.inner.valid_authorizations).into_iter() {
let account = state
.get_mut(&authorized)
.expect("Authorized account must exist");
account.info.code = None;
account.info.code_hash = KECCAK_EMPTY;
account.storage.clear();
}

let result = match instruction_result.result.into() {
SuccessOrHalt::Success(reason) => ExecutionResult::Success {
Expand Down
Loading
Loading