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: implement EIP-7002 #8485

Merged
merged 11 commits into from
May 30, 2024
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/ethereum/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ reth-ethereum-consensus.workspace = true
revm-primitives.workspace = true

[dev-dependencies]
reth-testing-utils.workspace = true
reth-revm = { workspace = true, features = ["test-utils"] }
alloy-eips.workspace = true

secp256k1.workspace = true
142 changes: 130 additions & 12 deletions crates/ethereum/evm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ use reth_primitives::{
use reth_revm::{
batch::{BlockBatchRecord, BlockExecutorStats},
db::states::bundle_state::BundleRetention,
state_change::{apply_beacon_root_contract_call, post_block_balance_increments},
state_change::{
apply_beacon_root_contract_call, apply_withdrawal_requests_contract_call,
post_block_balance_increments,
},
Evm, State,
};
use revm_primitives::{
Expand Down Expand Up @@ -119,13 +122,16 @@ impl<EvmConfig> EthEvmExecutor<EvmConfig>
where
EvmConfig: ConfigureEvm,
{
/// Executes the transactions in the block and returns the receipts.
/// Executes the transactions in the block and returns the receipts of the transactions in the
/// block, the total gas used and the list of EIP-7685 [requests](Request).
///
/// This applies the pre-execution changes, and executes the transactions.
/// This applies the pre-execution and post-execution changes that require an [EVM](Evm), and
/// executes the transactions.
///
/// # Note
///
/// It does __not__ apply post-execution changes.
/// It does __not__ apply post-execution changes that do not require an [EVM](Evm), for that see
/// [EthBlockExecutor::post_execution].
fn execute_state_transitions<Ext, DB>(
&self,
block: &BlockWithSenders,
Expand Down Expand Up @@ -188,9 +194,13 @@ where
},
);
}
drop(evm);

Ok(EthExecuteOutput { receipts, requests: vec![], gas_used: cumulative_gas_used })
// Collect all EIP-7685 requests
let withdrawal_requests =
apply_withdrawal_requests_contract_call(&self.chain_spec, block.timestamp, &mut evm)?;
let requests = withdrawal_requests;

Ok(EthExecuteOutput { receipts, requests, gas_used: cumulative_gas_used })
}
}

Expand Down Expand Up @@ -251,7 +261,8 @@ where

/// Execute a single block and apply the state changes to the internal state.
///
/// Returns the receipts of the transactions in the block and the total gas used.
/// Returns the receipts of the transactions in the block, the total gas used and the list of
/// EIP-7685 [requests](Request).
///
/// Returns an error if execution fails.
fn execute_without_verification(
Expand All @@ -264,7 +275,6 @@ where

// 2. configure the evm and execute
let env = self.evm_env_for_block(&block.header, total_difficulty);

let output = {
let evm = self.executor.evm_config.evm_with_env(&mut self.state, env);
self.executor.execute_state_transitions(block, evm)
Expand All @@ -283,8 +293,8 @@ where
self.state.set_state_clear_flag(state_clear_flag);
}

/// Apply post execution state changes, including block rewards, withdrawals, and irregular DAO
/// hardfork state change.
/// Apply post execution state changes that do not require an [EVM](Evm), such as: block
/// rewards, withdrawals, and irregular DAO hardfork state change
pub fn post_execution(
&mut self,
block: &BlockWithSenders,
Expand Down Expand Up @@ -429,11 +439,20 @@ where
#[cfg(test)]
mod tests {
use super::*;
use alloy_eips::eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS};
use reth_primitives::{keccak256, Account, Block, ChainSpecBuilder, ForkCondition, B256};
use alloy_eips::{
eip4788::{BEACON_ROOTS_ADDRESS, BEACON_ROOTS_CODE, SYSTEM_ADDRESS},
eip7002::{WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS, WITHDRAWAL_REQUEST_PREDEPLOY_CODE},
};
use reth_primitives::{
constants::ETH_TO_WEI, keccak256, public_key_to_address, Account, Block, ChainSpecBuilder,
ForkCondition, Transaction, TxKind, TxLegacy, B256,
};
use reth_revm::{
database::StateProviderDatabase, test_utils::StateProviderTest, TransitionState,
};
use reth_testing_utils::generators::{self, sign_tx_with_key_pair};
use revm_primitives::{b256, fixed_bytes, Bytes};
use secp256k1::{Keypair, Secp256k1};
use std::collections::HashMap;

fn create_state_provider_with_beacon_root_contract() -> StateProviderTest {
Expand All @@ -455,6 +474,25 @@ mod tests {
db
}

fn create_state_provider_with_withdrawal_requests_contract() -> StateProviderTest {
let mut db = StateProviderTest::default();

let withdrawal_requests_contract_account = Account {
nonce: 1,
balance: U256::ZERO,
bytecode_hash: Some(keccak256(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone())),
};

db.insert_account(
WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS,
withdrawal_requests_contract_account,
Some(WITHDRAWAL_REQUEST_PREDEPLOY_CODE.clone()),
HashMap::new(),
);

db
}

fn executor_provider(chain_spec: Arc<ChainSpec>) -> EthExecutorProvider<EthEvmConfig> {
EthExecutorProvider { chain_spec, evm_config: Default::default() }
}
Expand Down Expand Up @@ -801,4 +839,84 @@ mod tests {
.unwrap();
assert_eq!(parent_beacon_block_root_storage, U256::from(0x69));
}

#[test]
fn eip_7002() {
let chain_spec = Arc::new(
ChainSpecBuilder::from(&*MAINNET)
.shanghai_activated()
.with_fork(Hardfork::Prague, ForkCondition::Timestamp(0))
.build(),
);

let mut db = create_state_provider_with_withdrawal_requests_contract();

let secp = Secp256k1::new();
let sender_key_pair = Keypair::new(&secp, &mut generators::rng());
let sender_address = public_key_to_address(sender_key_pair.public_key());

db.insert_account(
sender_address,
Account { nonce: 1, balance: U256::from(ETH_TO_WEI), bytecode_hash: None },
None,
HashMap::new(),
);

// https://github.com/lightclient/7002asm/blob/e0d68e04d15f25057af7b6d180423d94b6b3bdb3/test/Contract.t.sol.in#L49-L64
let validator_public_key = fixed_bytes!("111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111");
let withdrawal_amount = fixed_bytes!("2222222222222222");
let input: Bytes = [&validator_public_key[..], &withdrawal_amount[..]].concat().into();
assert_eq!(input.len(), 56);

let mut header = chain_spec.genesis_header();
header.gas_limit = 1_500_000;
header.gas_used = 134_807;
header.receipts_root =
b256!("b31a3e47b902e9211c4d349af4e4c5604ce388471e79ca008907ae4616bb0ed3");

let tx = sign_tx_with_key_pair(
sender_key_pair,
Transaction::Legacy(TxLegacy {
chain_id: Some(chain_spec.chain.id()),
nonce: 1,
gas_price: header.base_fee_per_gas.unwrap().into(),
gas_limit: 134_807,
to: TxKind::Call(WITHDRAWAL_REQUEST_PREDEPLOY_ADDRESS),
// `MIN_WITHDRAWAL_REQUEST_FEE`
value: U256::from(1),
input,
}),
);

let provider = executor_provider(chain_spec);

let executor = provider.executor(StateProviderDatabase::new(&db));

let BlockExecutionOutput { receipts, requests, .. } = executor
.execute(
(
&Block {
header,
body: vec![tx],
ommers: vec![],
withdrawals: None,
requests: None,
}
.with_recovered_senders()
.unwrap(),
U256::ZERO,
)
.into(),
)
.unwrap();

let receipt = receipts.first().unwrap();
assert!(receipt.success);

let request = requests.first().unwrap();
let withdrawal_request = request.as_withdrawal_request().unwrap();
assert_eq!(withdrawal_request.source_address, sender_address);
assert_eq!(withdrawal_request.validator_public_key, validator_public_key);
assert_eq!(withdrawal_request.amount, u64::from_be_bytes(withdrawal_amount.into()));
}
}
6 changes: 6 additions & 0 deletions crates/evm/execution-errors/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ pub enum BlockValidationError {
/// The error message.
message: String,
},
/// EVM error during withdrawal requests contract call
#[error("failed to apply withdrawal requests contract call: {message}")]
WithdrawalRequestsContractCall {
/// The error message.
message: String,
},
}

/// BlockExecutor Errors
Expand Down
38 changes: 36 additions & 2 deletions crates/payload/basic/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ use reth_payload_builder::{
};
use reth_primitives::{
constants::{EMPTY_WITHDRAWALS, RETH_CLIENT_VERSION, SLOT_DURATION},
proofs, BlockNumberOrTag, Bytes, ChainSpec, SealedBlock, Withdrawals, B256, U256,
proofs, BlockNumberOrTag, Bytes, ChainSpec, Request, SealedBlock, Withdrawals, B256, U256,
};
use reth_provider::{
BlockReaderIdExt, BlockSource, CanonStateNotification, ProviderError, StateProviderFactory,
};
use reth_revm::state_change::{
apply_beacon_root_contract_call, post_block_withdrawals_balance_increments,
apply_beacon_root_contract_call, apply_withdrawal_requests_contract_call,
post_block_withdrawals_balance_increments,
};
use reth_tasks::TaskSpawner;
use reth_transaction_pool::TransactionPool;
Expand Down Expand Up @@ -888,6 +889,39 @@ where
.map_err(|err| PayloadBuilderError::Internal(err.into()))
}

/// Apply the [EIP-7002](https://eips.ethereum.org/EIPS/eip-7002) post block contract call.
///
/// This constructs a new [Evm] with the given DB, and environment
/// ([CfgEnvWithHandlerCfg] and [BlockEnv]) to execute the post block contract call.
///
/// This uses [apply_withdrawal_requests_contract_call] to ultimately calculate the
/// [requests](Request).
pub fn post_block_withdrawal_requests_contract_call<DB: Database + DatabaseCommit, Attributes>(
db: &mut DB,
chain_spec: &ChainSpec,
initialized_cfg: &CfgEnvWithHandlerCfg,
initialized_block_env: &BlockEnv,
attributes: &Attributes,
) -> Result<Vec<Request>, PayloadBuilderError>
where
DB::Error: std::fmt::Display,
Attributes: PayloadBuilderAttributes,
{
// apply post-block EIP-7002 contract call
let mut evm_post_block = Evm::builder()
.with_db(db)
.with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env(
initialized_cfg.clone(),
initialized_block_env.clone(),
Default::default(),
))
.build();

// initialize a block from the env, because the post block call needs the block itself
apply_withdrawal_requests_contract_call(chain_spec, attributes.timestamp(), &mut evm_post_block)
.map_err(|err| PayloadBuilderError::Internal(err.into()))
}

/// Checks if the new payload is better than the current best.
///
/// This compares the total fees of the blocks, higher is better.
Expand Down
Loading
Loading