Skip to content

Commit

Permalink
feat: Eip 6110 (#8204)
Browse files Browse the repository at this point in the history
Co-authored-by: Oliver Nordbjerg <onbjerg@users.noreply.github.com>
Co-authored-by: Alexey Shekhirin <a.shekhirin@gmail.com>
  • Loading branch information
3 people committed May 13, 2024
1 parent da40116 commit 34e1312
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 8 deletions.
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.

4 changes: 4 additions & 0 deletions crates/ethereum/evm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ revm-precompile = { version = "7.0.0", features = ["std"], default-features = fa

# Alloy
alloy-consensus.workspace = true
alloy-eips.workspace = true
alloy-rlp.workspace = true
alloy-sol-types.workspace = true

# misc
tracing.workspace = true
serde_json.workspace = true

[dev-dependencies]
reth-interfaces = { workspace = true, features = ["test-utils"] }
Expand Down
114 changes: 114 additions & 0 deletions crates/ethereum/evm/src/eip6110.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//! EIP-6110 deposit requests parsing
use alloy_consensus::Request;
use alloy_eips::eip6110::{DepositRequest, MAINNET_DEPOSIT_CONTRACT_ADDRESS};
use alloy_sol_types::{sol, SolEvent};
use reth_interfaces::executor::BlockValidationError;
use reth_primitives::Receipt;
use revm_primitives::Log;

/// Parse [deposit contract](https://etherscan.io/address/0x00000000219ab540356cbb839cbe05303d7705fa) Deposits from receipts,
/// and returns them as a `Request`.
pub fn parse_deposits_from_receipts(
receipts: &[Receipt],
) -> Result<Vec<Request>, BlockValidationError> {
let res = receipts
.iter()
.flat_map(|receipt| receipt.logs.iter())
// No need to filter for topic because there's only one event and that's the Deposit event
// in the deposit contract.
.filter(|log| log.address == MAINNET_DEPOSIT_CONTRACT_ADDRESS)
.map(|log| {
let decoded_log = DepositEvent::decode_log(log, false)?;
let deposit = parse_deposit_from_log(&decoded_log);
Ok(Request::DepositRequest(deposit))
})
.collect::<Result<Vec<_>, _>>()
// todo: this is ugly, we should clean it up
.map_err(|err: alloy_sol_types::Error| {
BlockValidationError::DepositRequestDecode(err.to_string())
})?;
Ok(res)
}

sol! {
#[allow(missing_docs)]
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);
}

fn parse_deposit_from_log(log: &Log<DepositEvent>) -> DepositRequest {
// SAFETY: These `expect` https://github.com/ethereum/consensus-specs/blob/5f48840f4d768bf0e0a8156a3ed06ec333589007/solidity_deposit_contract/deposit_contract.sol#L107-L110
// are safe because the `DepositEvent` is the only event in the deposit contract and the length
// checks are done there.
DepositRequest {
pubkey: log
.pubkey
.as_ref()
.try_into()
.expect("pubkey length should be enforced in deposit contract"),
withdrawal_credentials: log
.withdrawal_credentials
.as_ref()
.try_into()
.expect("withdrawal_credentials length should be enforced in deposit contract"),
amount: u64::from_le_bytes(
log.amount
.as_ref()
.try_into()
.expect("amount length should be enforced in deposit contract"),
),
signature: log
.signature
.as_ref()
.try_into()
.expect("signature length should be enforced in deposit contract"),
index: u64::from_le_bytes(
log.index
.as_ref()
.try_into()
.expect("deposit index length should be enforced in deposit contract"),
),
}
}

#[cfg(test)]
mod tests {
use reth_primitives::TxType;

use super::*;

#[test]
fn test_parse_deposit_from_log() {
let receipts = vec![
// https://etherscan.io/tx/0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9
Receipt {
// these don't matter
tx_type: TxType::Legacy,
success: true,
cumulative_gas_used: 0,
logs: serde_json::from_str(
r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030998c8086669bf65e24581cda47d8537966e9f5066fc6ffdcba910a1bfb91eae7a4873fcce166a1c4ea217e6b1afd396200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002001000000000000000000000001c340fb72ed14d4eaa71f7633ee9e33b88d4f3900000000000000000000000000000000000000000000000000000000000000080040597307000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006098ddbffd700c1aac324cfdf0492ff289223661eb26718ce3651ba2469b22f480d56efab432ed91af05a006bde0c1ea68134e0acd8cacca0c13ad1f716db874b44abfcc966368019753174753bca3af2ea84bc569c46f76592a91e97f311eddec0000000000000000000000000000000000000000000000000000000000000008e474160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xa5239d4c542063d29022545835815b78b09f571f2bf1c8427f4765d6f5abbce9","transactionIndex":"0xc4","logIndex":"0x18f","removed":false}]"#
).unwrap(),
},
// https://etherscan.io/tx/0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338
Receipt {
// these don't matter
tx_type: TxType::Legacy,
success: true,
cumulative_gas_used: 0,
logs: serde_json::from_str(
r#"[{"address":"0x00000000219ab540356cbb839cbe05303d7705fa","topics":["0x649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"],"data":"0x00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030a1a2ba870a90e889aa594a0cc1c6feffb94c2d8f65646c937f1f456a315ef649533e25a4614d8f4f66ebdb06481b90af0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200100000000000000000000000a0f04a231efbc29e1db7d086300ff550211c2f6000000000000000000000000000000000000000000000000000000000000000800405973070000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060ad416d590e1a7f52baff770a12835b68904efad22cc9f8ba531e50cbbd26f32b9c7373cf6538a0577f501e4d3e3e63e208767bcccaae94e1e3720bfb734a286f9c017d17af46536545ccb7ca94d71f295e71f6d25bf978c09ada6f8d3f7ba0390000000000000000000000000000000000000000000000000000000000000008e374160000000000000000000000000000000000000000000000000000000000","blockHash":"0x8d1289c5a7e0965b1d1bb75cdc4c3f73dda82d4ebb94ff5b98d1389cebd53b56","blockNumber":"0x12f0d8d","transactionHash":"0xd9734d4e3953bcaa939fd1c1d80950ee54aeecc02eef6ae8179f47f5b7103338","transactionIndex":"0x7c","logIndex":"0xe2","removed":false}]"#,
).unwrap(),
},
];
let requests = parse_deposits_from_receipts(&receipts).unwrap();
assert_eq!(requests.len(), 2);
assert_eq!(requests[0].as_deposit_request().unwrap().amount, 32e9 as u64);
assert_eq!(requests[1].as_deposit_request().unwrap().amount, 32e9 as u64);
}
}
12 changes: 8 additions & 4 deletions crates/ethereum/evm/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ where
transaction_gas_limit: transaction.gas_limit(),
block_available_gas,
}
.into())
.into());
}

EvmConfig::fill_tx_env(evm.tx_mut(), transaction, *sender);
Expand Down Expand Up @@ -209,13 +209,17 @@ where
gas: GotExpected { got: cumulative_gas_used, expected: block.gas_used },
gas_spent_by_tx: receipts.gas_spent_by_tx()?,
}
.into())
.into());
}

// Collect all EIP-6110 deposits
let deposit_requests = crate::eip6110::parse_deposits_from_receipts(&receipts)?;

// Collect all EIP-7685 requests
let withdrawal_requests =
apply_withdrawal_requests_contract_call(&self.chain_spec, block.timestamp, &mut evm)?;
let requests = withdrawal_requests;
// Requests are ordered by Request Type ID.
let requests = [deposit_requests, withdrawal_requests].concat();

Ok(EthExecuteOutput { receipts, requests, gas_used: cumulative_gas_used })
}
Expand Down Expand Up @@ -311,7 +315,7 @@ where
receipts.iter(),
) {
debug!(target: "evm", %error, ?receipts, "receipts verification failed");
return Err(error)
return Err(error);
};
}

Expand Down
3 changes: 3 additions & 0 deletions crates/ethereum/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub mod verify;
/// Ethereum DAO hardfork state change data.
pub mod dao_fork;

/// [EIP-6110](https://eips.ethereum.org/EIPS/eip-6110) handling.
pub mod eip6110;

mod instructions;

/// Ethereum-related EVM configuration.
Expand Down
11 changes: 10 additions & 1 deletion crates/interfaces/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,21 @@ pub enum BlockValidationError {
/// The error message.
message: String,
},
/// EVM error during withdrawal requests contract call
/// EVM error during withdrawal requests contract call [EIP-7002]
///
/// [EIP-7002]: https://eips.ethereum.org/EIPS/eip-7002
#[error("failed to apply withdrawal requests contract call: {message}")]
WithdrawalRequestsContractCall {
/// The error message.
message: String,
},
/// Error when decoding deposit requests from receipts [EIP-6110]
///
/// [EIP-6110]: https://eips.ethereum.org/EIPS/eip-6110
// TODO(gakonst): This is an RLP decoding error but we don't import alloy_rlp here,
// do we want to?
#[error("could not decode deposit request: {0}")]
DepositRequestDecode(String),
}

/// BlockExecutor Errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,11 @@ impl BundleStateWithReceipts {
/// Transform block number to the index of block.
fn block_number_to_index(&self, block_number: BlockNumber) -> Option<usize> {
if self.first_block > block_number {
return None
return None;
}
let index = block_number - self.first_block;
if index >= self.receipts.len() as u64 {
return None
return None;
}
Some(index as usize)
}
Expand Down Expand Up @@ -269,7 +269,7 @@ impl BundleStateWithReceipts {
/// If the target block number is not included in the state block range.
pub fn split_at(self, at: BlockNumber) -> (Option<Self>, Self) {
if at == self.first_block {
return (None, self)
return (None, self);
}

let (mut lower_state, mut higher_state) = (self.clone(), self);
Expand Down

0 comments on commit 34e1312

Please sign in to comment.