feat: op-reth (#4377)

Co-authored-by: Roberto Bayardo <bayardo@alum.mit.edu>
Co-authored-by: refcell.eth <abigger87@gmail.com>
Co-authored-by: Roman Krasiuk <rokrassyuk@gmail.com>
Co-authored-by: refcell <refcell@oplabs.co>
Co-authored-by: nicolas <48695862+merklefruit@users.noreply.github.com>
This commit is contained in:
clabby
2023-11-05 18:33:42 +01:00
committed by GitHub
parent 390abf3a44
commit 52670a8b24
105 changed files with 33415 additions and 408 deletions

View File

@ -77,9 +77,6 @@ triehash = "0.8"
plain_hasher = "0.2"
hash-db = "~0.15"
# value-256 is needed for the main_codec proptests to pass
reth-primitives = { path = ".", features = ["value-256"] }
# necessary so we don't hit a "undeclared 'std'":
# https://github.com/paradigmxyz/reth/pull/177#discussion_r1021172198
@ -92,10 +89,8 @@ default = ["c-kzg"]
arbitrary = ["revm-primitives/arbitrary", "reth-rpc-types/arbitrary", "dep:arbitrary", "dep:proptest", "dep:proptest-derive"]
c-kzg = ["revm-primitives/c-kzg", "dep:c-kzg"]
test-utils = ["dep:plain_hasher", "dep:hash-db", "dep:ethers-core"]
# value-256 controls whether transaction Value fields are DB-encoded as 256 bits instead of the
# default of 128 bits.
value-256 = ["reth-codecs/value-256"]
clap = ["dep:clap"]
optimism = ["reth-codecs/optimism"]
[[bench]]
name = "recover_ecdsa_crit"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -96,4 +96,70 @@ mod tests {
);
}
}
#[cfg(feature = "optimism")]
#[test]
fn calculate_optimism_base_fee_success() {
let base_fee = [
1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
1, 2,
];
let gas_used = [
10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
10000000,
];
let gas_limit = [
10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
18000000, 18000000,
];
let next_base_fee = [
1100000048, 1080000000, 1065714297, 1167067046, 1128881311, 1028254188, 1098203452, 1,
2, 3,
];
for i in 0..base_fee.len() {
assert_eq!(
next_base_fee[i],
calculate_next_block_base_fee(
gas_used[i],
gas_limit[i],
base_fee[i],
crate::BaseFeeParams::optimism(),
)
);
}
}
#[cfg(feature = "optimism")]
#[test]
fn calculate_optimism_goerli_base_fee_success() {
let base_fee = [
1000000000, 1000000000, 1000000000, 1072671875, 1059263476, 1049238967, 1049238967, 0,
1, 2,
];
let gas_used = [
10000000, 10000000, 10000000, 9000000, 10001000, 0, 10000000, 10000000, 10000000,
10000000,
];
let gas_limit = [
10000000, 12000000, 14000000, 10000000, 14000000, 2000000, 18000000, 18000000,
18000000, 18000000,
];
let next_base_fee = [
1180000000, 1146666666, 1122857142, 1244299375, 1189416692, 1028254188, 1144836295, 1,
2, 3,
];
for i in 0..base_fee.len() {
assert_eq!(
next_base_fee[i],
calculate_next_block_base_fee(
gas_used[i],
gas_limit[i],
base_fee[i],
crate::BaseFeeParams::optimism_goerli(),
)
);
}
}
}

View File

@ -17,6 +17,9 @@ pub use spec::{
ForkTimestamps, DEV, GOERLI, HOLESKY, MAINNET, SEPOLIA,
};
#[cfg(feature = "optimism")]
pub use spec::{BASE_GOERLI, BASE_MAINNET, OP_GOERLI};
// The chain info module.
mod info;
pub use info::ChainInfo;
@ -58,6 +61,9 @@ pub enum NamedChain {
OptimismKovan = 69,
OptimismGoerli = 420,
Base = 8453,
BaseGoerli = 84531,
Arbitrum = 42161,
ArbitrumTestnet = 421611,
ArbitrumGoerli = 421613,
@ -116,11 +122,53 @@ impl Chain {
Chain::Named(NamedChain::Holesky)
}
/// Returns the optimism goerli chain.
pub const fn optimism_goerli() -> Self {
Chain::Named(NamedChain::OptimismGoerli)
}
/// Returns the optimism mainnet chain.
pub const fn optimism_mainnet() -> Self {
Chain::Named(NamedChain::Optimism)
}
/// Returns the base goerli chain.
pub const fn base_goerli() -> Self {
Chain::Named(NamedChain::BaseGoerli)
}
/// Returns the base mainnet chain.
pub const fn base_mainnet() -> Self {
Chain::Named(NamedChain::Base)
}
/// Returns the dev chain.
pub const fn dev() -> Self {
Chain::Named(NamedChain::Dev)
}
/// Returns true if the chain contains Optimism configuration.
pub fn is_optimism(self) -> bool {
self.named().map_or(false, |c| {
matches!(
c,
NamedChain::Optimism |
NamedChain::OptimismGoerli |
NamedChain::OptimismKovan |
NamedChain::Base |
NamedChain::BaseGoerli
)
})
}
/// Attempts to convert the chain into a named chain.
pub fn named(&self) -> Option<NamedChain> {
match self {
Chain::Named(chain) => Some(*chain),
Chain::Id(id) => NamedChain::try_from(*id).ok(),
}
}
/// The id of the chain
pub fn id(&self) -> u64 {
match self {

View File

@ -153,6 +153,7 @@ pub static SEPOLIA: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
1273020,
b256!("649bbc62d0e31342afea4e5cd82d4049e7e1ee912fc0889aa790803be39038c5"),
)),
base_fee_params: BaseFeeParams::ethereum(),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
@ -241,6 +242,129 @@ pub static DEV: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
.into()
});
/// The Optimism Goerli spec
#[cfg(feature = "optimism")]
pub static OP_GOERLI: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
ChainSpec {
chain: Chain::optimism_goerli(),
genesis: serde_json::from_str(include_str!("../../res/genesis/goerli_op.json"))
.expect("Can't deserialize Optimism Goerli genesis json"),
genesis_hash: Some(b256!(
"c1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1"
)),
fork_timestamps: ForkTimestamps::default(),
paris_block_and_final_difficulty: Some((0, U256::from(0))),
hardforks: BTreeMap::from([
(Hardfork::Frontier, ForkCondition::Block(0)),
(Hardfork::Homestead, ForkCondition::Block(0)),
(Hardfork::Tangerine, ForkCondition::Block(0)),
(Hardfork::SpuriousDragon, ForkCondition::Block(0)),
(Hardfork::Byzantium, ForkCondition::Block(0)),
(Hardfork::Constantinople, ForkCondition::Block(0)),
(Hardfork::Petersburg, ForkCondition::Block(0)),
(Hardfork::Istanbul, ForkCondition::Block(0)),
(Hardfork::MuirGlacier, ForkCondition::Block(0)),
(Hardfork::Berlin, ForkCondition::Block(0)),
(Hardfork::London, ForkCondition::Block(0)),
(Hardfork::ArrowGlacier, ForkCondition::Block(0)),
(Hardfork::GrayGlacier, ForkCondition::Block(0)),
(
Hardfork::Paris,
ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) },
),
(Hardfork::Bedrock, ForkCondition::Block(4061224)),
(Hardfork::Regolith, ForkCondition::Timestamp(1679079600)),
]),
base_fee_params: BaseFeeParams::optimism(),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
..Default::default()
}
.into()
});
/// The Base Goerli spec
#[cfg(feature = "optimism")]
pub static BASE_GOERLI: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
ChainSpec {
chain: Chain::base_goerli(),
genesis: serde_json::from_str(include_str!("../../res/genesis/goerli_base.json"))
.expect("Can't deserialize Base Goerli genesis json"),
genesis_hash: Some(b256!(
"a3ab140f15ea7f7443a4702da64c10314eb04d488e72974e02e2d728096b4f76"
)),
fork_timestamps: ForkTimestamps::default(),
paris_block_and_final_difficulty: Some((0, U256::from(0))),
hardforks: BTreeMap::from([
(Hardfork::Frontier, ForkCondition::Block(0)),
(Hardfork::Homestead, ForkCondition::Block(0)),
(Hardfork::Tangerine, ForkCondition::Block(0)),
(Hardfork::SpuriousDragon, ForkCondition::Block(0)),
(Hardfork::Byzantium, ForkCondition::Block(0)),
(Hardfork::Constantinople, ForkCondition::Block(0)),
(Hardfork::Petersburg, ForkCondition::Block(0)),
(Hardfork::Istanbul, ForkCondition::Block(0)),
(Hardfork::MuirGlacier, ForkCondition::Block(0)),
(Hardfork::Berlin, ForkCondition::Block(0)),
(Hardfork::London, ForkCondition::Block(0)),
(Hardfork::ArrowGlacier, ForkCondition::Block(0)),
(Hardfork::GrayGlacier, ForkCondition::Block(0)),
(
Hardfork::Paris,
ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) },
),
(Hardfork::Bedrock, ForkCondition::Block(0)),
(Hardfork::Regolith, ForkCondition::Timestamp(1683219600)),
]),
base_fee_params: BaseFeeParams::optimism_goerli(),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
..Default::default()
}
.into()
});
/// The Base mainnet spec
#[cfg(feature = "optimism")]
pub static BASE_MAINNET: Lazy<Arc<ChainSpec>> = Lazy::new(|| {
ChainSpec {
chain: Chain::base_mainnet(),
genesis: serde_json::from_str(include_str!("../../res/genesis/base.json"))
.expect("Can't deserialize Base genesis json"),
genesis_hash: Some(b256!(
"f712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd"
)),
fork_timestamps: ForkTimestamps::default(),
paris_block_and_final_difficulty: Some((0, U256::from(0))),
hardforks: BTreeMap::from([
(Hardfork::Frontier, ForkCondition::Block(0)),
(Hardfork::Homestead, ForkCondition::Block(0)),
(Hardfork::Tangerine, ForkCondition::Block(0)),
(Hardfork::SpuriousDragon, ForkCondition::Block(0)),
(Hardfork::Byzantium, ForkCondition::Block(0)),
(Hardfork::Constantinople, ForkCondition::Block(0)),
(Hardfork::Petersburg, ForkCondition::Block(0)),
(Hardfork::Istanbul, ForkCondition::Block(0)),
(Hardfork::MuirGlacier, ForkCondition::Block(0)),
(Hardfork::Berlin, ForkCondition::Block(0)),
(Hardfork::London, ForkCondition::Block(0)),
(Hardfork::ArrowGlacier, ForkCondition::Block(0)),
(Hardfork::GrayGlacier, ForkCondition::Block(0)),
(
Hardfork::Paris,
ForkCondition::TTD { fork_block: Some(0), total_difficulty: U256::from(0) },
),
(Hardfork::Bedrock, ForkCondition::Block(0)),
(Hardfork::Regolith, ForkCondition::Timestamp(0)),
]),
base_fee_params: BaseFeeParams::optimism(),
prune_delete_limit: 1700,
snapshot_block_interval: 1_000_000,
..Default::default()
}
.into()
});
/// BaseFeeParams contains the config parameters that control block base fee computation
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub struct BaseFeeParams {
@ -258,6 +382,28 @@ impl BaseFeeParams {
elasticity_multiplier: EIP1559_DEFAULT_ELASTICITY_MULTIPLIER,
}
}
/// Get the base fee parameters for optimism goerli
#[cfg(feature = "optimism")]
pub const fn optimism_goerli() -> BaseFeeParams {
BaseFeeParams {
max_change_denominator:
crate::constants::OP_GOERLI_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR,
elasticity_multiplier:
crate::constants::OP_GOERLI_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER,
}
}
/// Get the base fee parameters for optimism mainnet
#[cfg(feature = "optimism")]
pub const fn optimism() -> BaseFeeParams {
BaseFeeParams {
max_change_denominator:
crate::constants::OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR,
elasticity_multiplier:
crate::constants::OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER,
}
}
}
/// An Ethereum chain specification.
@ -335,6 +481,11 @@ impl ChainSpec {
self.chain
}
/// Returns `true` if this chain contains Optimism configuration.
pub fn is_optimism(&self) -> bool {
self.chain.is_optimism()
}
/// Get the genesis block specification.
///
/// To get the header for the genesis block, use [`Self::genesis_header`] instead.
@ -873,6 +1024,24 @@ impl ChainSpecBuilder {
self
}
/// Enable Bedrock at genesis
#[cfg(feature = "optimism")]
pub fn bedrock_activated(mut self) -> Self {
self = self.paris_activated();
self.hardforks.insert(Hardfork::Bedrock, ForkCondition::Block(0));
self
}
/// Enable Regolith at the timestamp of activation.
/// For post-bedrock op-stack chains, this will be at genesis.
/// For pre-bedrock op-stack chains, this will be at the timestamp of regolith activation.
#[cfg(feature = "optimism")]
pub fn regolith_activated(mut self) -> Self {
self = self.bedrock_activated();
self.hardforks.insert(Hardfork::Regolith, ForkCondition::Timestamp(0));
self
}
/// Build the resulting [`ChainSpec`].
///
/// # Panics
@ -1197,6 +1366,9 @@ mod tests {
use bytes::BytesMut;
use std::{collections::HashMap, str::FromStr};
#[cfg(feature = "optimism")]
use crate::OP_GOERLI;
fn test_fork_ids(spec: &ChainSpec, cases: &[(Head, ForkId)]) {
for (block, expected_id) in cases {
let computed_id = spec.fork_id(block);
@ -1798,6 +1970,28 @@ Post-merge hard forks (timestamp based):
)
}
#[cfg(feature = "optimism")]
#[test]
fn optimism_goerli_forkids() {
test_fork_ids(
&OP_GOERLI,
&[
(
Head { number: 0, ..Default::default() },
ForkId { hash: ForkHash([0x6d, 0x63, 0x76, 0xbe]), next: 4061224 },
),
(
Head { number: 4061224, timestamp: 1679079599, ..Default::default() },
ForkId { hash: ForkHash([0x03, 0x47, 0x85, 0x69]), next: 1679079600 },
),
(
Head { number: 4061224, timestamp: 1679079600, ..Default::default() },
ForkId { hash: ForkHash([0x6d, 0x43, 0x1d, 0x6c]), next: 0 },
),
],
);
}
/// Checks that time-based forks work
///
/// This is based off of the test vectors here: https://github.com/ethereum/go-ethereum/blob/5c8cc10d1e05c23ff1108022f4150749e73c0ca1/core/forkid/forkid_test.go#L155-L188

View File

@ -60,6 +60,26 @@ pub const EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8;
/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559)
pub const EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 2;
/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
pub const OP_MAINNET_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 50;
/// Base fee max change denominator for Optimism Mainnet as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
pub const OP_MAINNET_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 6;
/// Base fee max change denominator for Optimism Goerli as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
pub const OP_GOERLI_EIP1559_DEFAULT_BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 50;
/// Base fee max change denominator for Optimism Goerli as defined in the Optimism
/// [transaction costs](https://community.optimism.io/docs/developers/build/differences/#transaction-costs) doc.
#[cfg(feature = "optimism")]
pub const OP_GOERLI_EIP1559_DEFAULT_ELASTICITY_MULTIPLIER: u64 = 10;
/// Multiplier for converting gwei to wei.
pub const GWEI_TO_WEI: u64 = 1_000_000_000;
@ -92,6 +112,14 @@ pub const HOLESKY_GENESIS_HASH: B256 =
pub const DEV_GENESIS_HASH: B256 =
b256!("2f980576711e3617a5e4d83dd539548ec0f7792007d505a3d2e9674833af2d7c");
/// Optimism goerli genesis hash.
pub const GOERLI_OP_GENESIS: B256 =
b256!("c1fc15cd51159b1f1e5cbc4b82e85c1447ddfa33c52cf1d98d14fba0d6354be1");
/// Base goerli genesis hash.
pub const GOERLI_BASE_GENESIS: B256 =
b256!("a3ab140f15ea7f7443a4702da64c10314eb04d488e72974e02e2d728096b4f76");
/// Keccak256 over empty array.
pub const KECCAK_EMPTY: B256 =
b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470");

View File

@ -41,6 +41,12 @@ pub enum Hardfork {
Shanghai,
/// Cancun.
Cancun,
/// Bedrock.
#[cfg(feature = "optimism")]
Bedrock,
/// Regolith
#[cfg(feature = "optimism")]
Regolith,
}
impl Hardfork {
@ -85,6 +91,10 @@ impl FromStr for Hardfork {
"paris" => Hardfork::Paris,
"shanghai" => Hardfork::Shanghai,
"cancun" => Hardfork::Cancun,
#[cfg(feature = "optimism")]
"bedrock" => Hardfork::Bedrock,
#[cfg(feature = "optimism")]
"regolith" => Hardfork::Regolith,
_ => return Err(format!("Unknown hardfork: {s}")),
};
Ok(hardfork)
@ -150,6 +160,18 @@ mod tests {
assert_eq!(hardforks, expected_hardforks);
}
#[test]
#[cfg(feature = "optimism")]
fn check_op_hardfork_from_str() {
let hardfork_str = ["beDrOck", "rEgOlITH"];
let expected_hardforks = [Hardfork::Bedrock, Hardfork::Regolith];
let hardforks: Vec<Hardfork> =
hardfork_str.iter().map(|h| Hardfork::from_str(h).unwrap()).collect();
assert_eq!(hardforks, expected_hardforks);
}
#[test]
fn check_nonexistent_hardfork_from_str() {
assert!(Hardfork::from_str("not a hardfork").is_err());

View File

@ -33,7 +33,6 @@ mod integer_list;
mod log;
mod net;
mod peer;
mod precaution;
pub mod proofs;
mod prune;
mod receipt;
@ -59,6 +58,8 @@ pub use chain::{
DisplayHardforks, ForkCondition, ForkTimestamps, NamedChain, DEV, GOERLI, HOLESKY, MAINNET,
SEPOLIA,
};
#[cfg(feature = "optimism")]
pub use chain::{BASE_GOERLI, BASE_MAINNET, OP_GOERLI};
pub use compression::*;
pub use constants::{
DEV_GENESIS_HASH, EMPTY_OMMER_ROOT_HASH, GOERLI_GENESIS_HASH, HOLESKY_GENESIS_HASH,
@ -101,6 +102,8 @@ pub use transaction::{
TxEip4844, TxHashOrNumber, TxLegacy, TxType, TxValue, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
};
#[cfg(feature = "optimism")]
pub use transaction::{TxDeposit, DEPOSIT_TX_TYPE_ID};
pub use withdrawal::Withdrawal;
// Re-exports

View File

@ -1,24 +0,0 @@
//! Helpers to ensure certain features are enabled or disabled.
//!
//! The motivation for this is to prevent that a binary is accidentally built with a feature that is
//! not intended to be used.
//!
//! Currently conflicting features are: `value-u256` which is required by optimism.
/// A macro to ensure that the crate's features are compatible with ethereum
#[macro_export]
macro_rules! ensure_ethereum {
() => {
#[cfg(feature = "value-256")]
compile_error!("The `value-256` feature is enabled but for `ethereum` it must be disabled: https://github.com/paradigmxyz/reth/issues/4891");
};
}
/// A macro to ensure that the crate's features are compatible with optimism
#[macro_export]
macro_rules! ensure_optimism {
() => {
#[cfg(not(feature = "value-256"))]
compile_error!("The `value-256` feature is disabled but for `optimism` it must be enabled: https://github.com/paradigmxyz/reth/issues/4891");
};
}

View File

@ -4,8 +4,8 @@ use crate::{
constants::EMPTY_OMMER_ROOT_HASH,
keccak256,
trie::{HashBuilder, Nibbles},
Address, GenesisAccount, Header, ReceiptWithBloom, ReceiptWithBloomRef, TransactionSigned,
Withdrawal, B256,
Address, GenesisAccount, Header, Receipt, ReceiptWithBloom, ReceiptWithBloomRef,
TransactionSigned, Withdrawal, B256,
};
use alloy_rlp::Encodable;
use bytes::{BufMut, BytesMut};
@ -70,18 +70,55 @@ pub fn calculate_withdrawals_root(withdrawals: &[Withdrawal]) -> B256 {
/// Calculates the receipt root for a header.
pub fn calculate_receipt_root(receipts: &[ReceiptWithBloom]) -> B256 {
#[cfg(feature = "optimism")]
{
// There is a minor bug in op-geth and op-erigon where in the Regolith hardfork,
// the receipt root calculation does not include the deposit nonce in the receipt
// encoding. This will be fixd in the next hardfork, however for now, we must strip
// the deposit nonce from the receipts before calculating the receipt root.
let receipts = receipts
.iter()
.cloned()
.map(|mut r| {
r.receipt.deposit_nonce = None;
r
})
.collect::<Vec<_>>();
ordered_trie_root_with_encoder(receipts.as_slice(), |r, buf| r.encode_inner(buf, false))
}
#[cfg(not(feature = "optimism"))]
ordered_trie_root_with_encoder(receipts, |r, buf| r.encode_inner(buf, false))
}
/// Calculates the receipt root for a header for the reference type of [ReceiptWithBloom].
/// Calculates the receipt root for a header for the reference type of [Receipt].
///
/// NOTE: Prefer [calculate_receipt_root] if you have log blooms memoized.
pub fn calculate_receipt_root_ref<T>(receipts: &[&T]) -> B256
where
for<'a> ReceiptWithBloomRef<'a>: From<&'a T>,
{
pub fn calculate_receipt_root_ref(receipts: &[&Receipt]) -> B256 {
#[cfg(feature = "optimism")]
{
// There is a minor bug in op-geth and op-erigon where in the Regolith hardfork,
// the receipt root calculation does not include the deposit nonce in the receipt
// encoding. This will be fixd in the next hardfork, however for now, we must strip
// the deposit nonce from the receipts before calculating the receipt root.
let receipts = receipts
.iter()
.map(|r| {
let mut r = (*r).clone();
r.deposit_nonce = None;
r
})
.collect::<Vec<_>>();
ordered_trie_root_with_encoder(&receipts, |r, buf| {
ReceiptWithBloomRef::from(r).encode_inner(buf, false)
})
}
#[cfg(not(feature = "optimism"))]
ordered_trie_root_with_encoder(receipts, |r, buf| {
ReceiptWithBloomRef::from(r).encode_inner(buf, false)
ReceiptWithBloomRef::from(*r).encode_inner(buf, false)
})
}
@ -146,9 +183,14 @@ pub mod triehash {
mod tests {
use super::*;
use crate::{
b256, bloom, constants::EMPTY_ROOT_HASH, hex, Block, Log, Receipt, TxType, B256, GOERLI,
bloom,
constants::EMPTY_ROOT_HASH,
hex_literal::hex,
proofs::{calculate_receipt_root, calculate_transaction_root, genesis_state_root},
Address, Block, GenesisAccount, Log, Receipt, ReceiptWithBloom, TxType, B256, GOERLI,
HOLESKY, MAINNET, SEPOLIA, U256,
};
use alloy_primitives::b256;
use alloy_rlp::Decodable;
#[test]
@ -161,6 +203,233 @@ mod tests {
assert_eq!(block.transactions_root, tx_root, "Must be the same");
}
/// Tests that the receipt root is computed correctly for the regolith block.
/// This was implemented due to a minor bug in op-geth and op-erigon where in
/// the Regolith hardfork, the receipt root calculation does not include the
/// deposit nonce in the receipt encoding.
/// To fix this an op-reth patch was applied to the receipt root calculation
/// to strip the deposit nonce from each receipt before calculating the root.
#[cfg(feature = "optimism")]
#[test]
fn check_optimism_receipt_root() {
use crate::{Bloom, Bytes};
let receipts = vec![
// 0xb0d6ee650637911394396d81172bd1c637d568ed1fbddab0daddfca399c58b53
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::DEPOSIT,
success: true,
cumulative_gas_used: 46913,
logs: vec![],
#[cfg(feature = "optimism")]
deposit_nonce: Some(4012991u64),
},
bloom: Bloom(hex!("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").into()),
},
// 0x2f433586bae30573c393adfa02bc81d2a1888a3d6c9869f473fb57245166bd9a
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::EIP1559,
success: true,
cumulative_gas_used: 118083,
logs: vec![
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001")),
},
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001")),
},
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("0eb774bb9698a73583fe07b6972cf2dcc08d1d97581a22861f45feb86b395820"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
b256!("000000000000000000000000c498902843af527e674846bb7edefa8ad62b8fb9"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000003")),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Bloom(hex!("00001000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000040000000000004000000000080000000000000000000000000000000000000000000000000000008000000000000080020000000000000000000000000002000000000000000000000000000080000010000").into()),
},
// 0x6c33676e8f6077f46a62eabab70bc6d1b1b18a624b0739086d77093a1ecf8266
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::EIP1559,
success: true,
cumulative_gas_used: 189253,
logs: vec![
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001")),
},
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("c3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001")),
},
Log {
address: hex!("ddb6dcce6b794415145eb5caa6cd335aeda9c272").into(),
topics: vec![
b256!("0eb774bb9698a73583fe07b6972cf2dcc08d1d97581a22861f45feb86b395820"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
b256!("0000000000000000000000009d521a04bee134ff8136d2ec957e5bc8c50394ec"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000003")),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Bloom(hex!("00000000000000000000200000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000002000000000020000000000000000000000000000000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000040000000000004000000000080000000000000000000000000000000000000000000000000000008000000000000080020000000000000000000000000002000000000000000000000000000080000000000").into()),
},
// 0x4d3ecbef04ba7ce7f5ab55be0c61978ca97c117d7da448ed9771d4ff0c720a3f
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::EIP1559,
success: true,
cumulative_gas_used: 346969,
logs: vec![
Log {
address: hex!("4200000000000000000000000000000000000006").into(),
topics: vec![
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
b256!("000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
b256!("0000000000000000000000002992607c1614484fe6d865088e5c048f0650afd4"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000018de76816d8000")),
},
Log {
address: hex!("cf8e7e6b26f407dee615fc4db18bf829e7aa8c09").into(),
topics: vec![
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
b256!("0000000000000000000000002992607c1614484fe6d865088e5c048f0650afd4"),
b256!("0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
],
data: Bytes::from_static(&hex!("000000000000000000000000000000000000000000000002d24d8e9ac1aa79e2")),
},
Log {
address: hex!("2992607c1614484fe6d865088e5c048f0650afd4").into(),
topics: vec![
b256!("1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"),
],
data: Bytes::from_static(&hex!("000000000000000000000000000000000000000000000009bd50642785c15736000000000000000000000000000000000000000000011bb7ac324f724a29bbbf")),
},
Log {
address: hex!("2992607c1614484fe6d865088e5c048f0650afd4").into(),
topics: vec![
b256!("d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822"),
b256!("00000000000000000000000029843613c7211d014f5dd5718cf32bcd314914cb"),
b256!("0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000018de76816d800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d24d8e9ac1aa79e2")),
},
Log {
address: hex!("6d0f8d488b669aa9ba2d0f0b7b75a88bf5051cd3").into(),
topics: vec![
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
b256!("0000000000000000000000008dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09"),
b256!("000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000014bc73062aea8093")),
},
Log {
address: hex!("8dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09").into(),
topics: vec![
b256!("1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000002f122cfadc1ca82a35000000000000000000000000000000000000000000000665879dc0609945d6d1")),
},
Log {
address: hex!("8dbffe4c8bf3caf5deae3a99b50cfcf3648cbc09").into(),
topics: vec![
b256!("d78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822"),
b256!("00000000000000000000000029843613c7211d014f5dd5718cf32bcd314914cb"),
b256!("000000000000000000000000c3feb4ef4c2a5af77add15c95bd98f6b43640cc8"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d24d8e9ac1aa79e200000000000000000000000000000000000000000000000014bc73062aea80930000000000000000000000000000000000000000000000000000000000000000")),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Bloom(hex!("00200000000000000000000080000000000000000000000000040000100004000000000000000000000000100000000000000000000000000000100000000000000000000000000002000008000000200000000200000000020000000000000040000000000000000400000200000000000000000000000000000010000000000400000000010400000000000000000000000000002000c80000004080002000000000000000400200000000800000000000000000000000000000000000000000000002000000000000000000000000000000000100001000000000000000000000002000000000000000000000010000000000000000000000800000800000").into()),
},
// 0xf738af5eb00ba23dbc1be2dbce41dbc0180f0085b7fb46646e90bf737af90351
ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::EIP1559,
success: true,
cumulative_gas_used: 623249,
logs: vec![
Log {
address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
topics: vec![
b256!("ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
b256!("0000000000000000000000000000000000000000000000000000000000000000"),
b256!("000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
b256!("000000000000000000000000000000000000000000000000000000000011a1d3"),
],
data: Default::default(),
},
Log {
address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
topics: vec![
b256!("9d89e36eadf856db0ad9ffb5a569e07f95634dddd9501141ecf04820484ad0dc"),
b256!("000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
b256!("000000000000000000000000000000000000000000000000000000000011a1d3"),
],
data: Bytes::from_static(&hex!("00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d515141646b33736538396b47716577395256567a316b68643548375562476d4d4a485a62566f386a6d346f4a2f30000000000000000000")),
},
Log {
address: hex!("ac6564f3718837caadd42eed742d75c12b90a052").into(),
topics: vec![
b256!("110d160a1bedeea919a88fbc4b2a9fb61b7e664084391b6ca2740db66fef80fe"),
b256!("00000000000000000000000084d47f6eea8f8d87910448325519d1bb45c2972a"),
b256!("000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e"),
b256!("000000000000000000000000000000000000000000000000000000000011a1d3"),
],
data: Bytes::from_static(&hex!("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000a4fa7f3fbf0677f254ebdb1646146864c305b76e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007717500762343034303661353035646234633961386163316433306335633332303265370000000000000000000000000000000000000000000000000000000000000037697066733a2f2f516d515141646b33736538396b47716577395256567a316b68643548375562476d4d4a485a62566f386a6d346f4a2f30000000000000000000")),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: Bloom(hex!("00000000000000000000000000000000400000000000000000000000000000000000004000000000000001000000000000000002000000000100000000000000000000000000000000000008000000000000000000000000000000000000000004000000020000000000000000000800000000000000000000000010200100200008000002000000000000000000800000000000000000000002000000000000000000000000000000080000000000000000000000004000000000000000000000000002000000000000000000000000000000000000200000000000000020002000000000000000002000000000000000000000000000000000000000000000").into()),
},
];
let root = calculate_receipt_root(&receipts);
assert_eq!(root, b256!("e255fed45eae7ede0556fe4fabc77b0d294d18781a5a581cab09127bc4cd9ffb"))
}
#[test]
fn check_receipt_root() {
let logs = vec![Log { address: Address::ZERO, topics: vec![], data: Default::default() }];
@ -171,6 +440,8 @@ mod tests {
success: true,
cumulative_gas_used: 102068,
logs,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom,
};

View File

@ -6,14 +6,18 @@ use crate::{
};
use alloy_rlp::{length_of_length, Decodable, Encodable};
use bytes::{Buf, BufMut, BytesMut};
use reth_codecs::{main_codec, Compact, CompactZstd};
use reth_codecs::{add_arbitrary_tests, main_codec, Compact, CompactZstd};
use std::{
cmp::Ordering,
ops::{Deref, DerefMut},
};
#[cfg(any(test, feature = "arbitrary"))]
use proptest::strategy::Strategy;
/// Receipt containing result of transaction execution.
#[main_codec(zstd)]
#[main_codec(no_arbitrary, zstd)]
#[add_arbitrary_tests]
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Receipt {
/// Receipt type.
@ -25,13 +29,10 @@ pub struct Receipt {
/// Gas used
pub cumulative_gas_used: u64,
/// Log send from contracts.
#[cfg_attr(
any(test, feature = "arbitrary"),
proptest(
strategy = "proptest::collection::vec(proptest::arbitrary::any::<Log>(), 0..=20)"
)
)]
pub logs: Vec<Log>,
/// Deposit nonce for Optimism deposited transactions
#[cfg(feature = "optimism")]
pub deposit_nonce: Option<u64>,
}
impl Receipt {
@ -181,6 +182,60 @@ impl ReceiptWithBloom {
}
}
#[cfg(any(test, feature = "arbitrary"))]
impl proptest::arbitrary::Arbitrary for Receipt {
type Parameters = ();
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
use proptest::prelude::{any, prop_compose};
prop_compose! {
fn arbitrary_receipt()(tx_type in any::<TxType>(),
success in any::<bool>(),
cumulative_gas_used in any::<u64>(),
logs in proptest::collection::vec(proptest::arbitrary::any::<Log>(), 0..=20),
_deposit_nonce in any::<Option<u64>>()) -> Receipt
{
Receipt { tx_type,
success,
cumulative_gas_used,
logs,
// Only receipts for deposit transactions may contain a deposit nonce
#[cfg(feature = "optimism")]
deposit_nonce: (tx_type == TxType::DEPOSIT).then_some(_deposit_nonce).flatten()
}
}
};
arbitrary_receipt().boxed()
}
type Strategy = proptest::strategy::BoxedStrategy<Receipt>;
}
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for Receipt {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let tx_type = TxType::arbitrary(u)?;
let success = bool::arbitrary(u)?;
let cumulative_gas_used = u64::arbitrary(u)?;
let logs = Vec::<Log>::arbitrary(u)?;
// Only receipts for deposit transactions may contain a deposit nonce
#[cfg(feature = "optimism")]
let deposit_nonce =
if tx_type == TxType::DEPOSIT { Option::<u64>::arbitrary(u)? } else { None };
Ok(Self {
tx_type,
success,
cumulative_gas_used,
logs,
#[cfg(feature = "optimism")]
deposit_nonce,
})
}
}
impl ReceiptWithBloom {
/// Encode receipt with or without the header data.
pub fn encode_inner(&self, out: &mut dyn BufMut, with_header: bool) {
@ -201,7 +256,27 @@ impl ReceiptWithBloom {
let bloom = Decodable::decode(b)?;
let logs = alloy_rlp::Decodable::decode(b)?;
let this = Self { receipt: Receipt { tx_type, success, cumulative_gas_used, logs }, bloom };
let receipt = match tx_type {
#[cfg(feature = "optimism")]
TxType::DEPOSIT => {
let consumed = started_len - b.len();
let has_nonce = rlp_head.payload_length - consumed > 0;
let deposit_nonce =
if has_nonce { Some(alloy_rlp::Decodable::decode(b)?) } else { None };
Receipt { tx_type, success, cumulative_gas_used, logs, deposit_nonce }
}
_ => Receipt {
tx_type,
success,
cumulative_gas_used,
logs,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
};
let this = Self { receipt, bloom };
let consumed = started_len - b.len();
if consumed != rlp_head.payload_length {
return Err(alloy_rlp::Error::ListLengthMismatch {
@ -239,17 +314,25 @@ impl Decodable for ReceiptWithBloom {
let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
"typed receipt cannot be decoded from an empty slice",
))?;
if receipt_type == 0x01 {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP2930)
} else if receipt_type == 0x02 {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP1559)
} else if receipt_type == 0x03 {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP4844)
} else {
Err(alloy_rlp::Error::Custom("invalid receipt type"))
match receipt_type {
0x01 => {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP2930)
}
0x02 => {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP1559)
}
0x03 => {
buf.advance(1);
Self::decode_receipt(buf, TxType::EIP4844)
}
#[cfg(feature = "optimism")]
0x7E => {
buf.advance(1);
Self::decode_receipt(buf, TxType::DEPOSIT)
}
_ => Err(alloy_rlp::Error::Custom("invalid receipt type")),
}
}
Ordering::Equal => {
@ -317,6 +400,13 @@ impl<'a> ReceiptWithBloomEncoder<'a> {
rlp_head.payload_length += self.bloom.length();
rlp_head.payload_length += self.receipt.logs.length();
#[cfg(feature = "optimism")]
if self.receipt.tx_type == TxType::DEPOSIT {
if let Some(deposit_nonce) = self.receipt.deposit_nonce {
rlp_head.payload_length += deposit_nonce.length();
}
}
rlp_head
}
@ -327,6 +417,12 @@ impl<'a> ReceiptWithBloomEncoder<'a> {
self.receipt.cumulative_gas_used.encode(out);
self.bloom.encode(out);
self.receipt.logs.encode(out);
#[cfg(feature = "optimism")]
if self.receipt.tx_type == TxType::DEPOSIT {
if let Some(deposit_nonce) = self.receipt.deposit_nonce {
deposit_nonce.encode(out)
}
}
}
/// Encode receipt with or without the header data.
@ -355,6 +451,10 @@ impl<'a> ReceiptWithBloomEncoder<'a> {
TxType::EIP4844 => {
out.put_u8(0x03);
}
#[cfg(feature = "optimism")]
TxType::DEPOSIT => {
out.put_u8(0x7E);
}
_ => unreachable!("legacy handled; qed."),
}
out.put_slice(payload.as_ref());
@ -374,7 +474,7 @@ impl<'a> Encodable for ReceiptWithBloomEncoder<'a> {
fn length(&self) -> usize {
let mut payload_len = self.receipt_length();
// account for eip-2718 type prefix and set the list
if matches!(self.receipt.tx_type, TxType::EIP1559 | TxType::EIP2930 | TxType::EIP4844) {
if !matches!(self.receipt.tx_type, TxType::Legacy) {
payload_len += 1;
// we include a string header for typed receipts, so include the length here
payload_len += length_of_length(payload_len);
@ -410,6 +510,8 @@ mod tests {
data: bytes!("0100ff"),
}],
success: false,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: [0; 256].into(),
};
@ -440,6 +542,8 @@ mod tests {
data: bytes!("0100ff"),
}],
success: false,
#[cfg(feature = "optimism")]
deposit_nonce: None,
},
bloom: [0; 256].into(),
};
@ -448,6 +552,31 @@ mod tests {
assert_eq!(receipt, expected);
}
#[cfg(feature = "optimism")]
#[test]
fn decode_deposit_receipt_regolith_roundtrip() {
let data = hex!("7ef9010c0182b741b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0833d3bbf");
// Deposit Receipt (post-regolith)
let expected = ReceiptWithBloom {
receipt: Receipt {
tx_type: TxType::DEPOSIT,
cumulative_gas_used: 46913,
logs: vec![],
success: true,
deposit_nonce: Some(4012991),
},
bloom: [0; 256].into(),
};
let receipt = ReceiptWithBloom::decode(&mut &data[..]).unwrap();
assert_eq!(receipt, expected);
let mut buf = BytesMut::default();
receipt.encode_inner(&mut buf, false);
assert_eq!(buf.freeze(), &data[..]);
}
#[test]
fn gigantic_receipt() {
let receipt = Receipt {
@ -470,6 +599,8 @@ mod tests {
data: Bytes::from(vec![1; 0xffffff]),
},
],
#[cfg(feature = "optimism")]
deposit_nonce: None,
};
let mut data = vec![];

View File

@ -8,6 +8,15 @@ pub fn revm_spec_by_timestamp_after_merge(
chain_spec: &ChainSpec,
timestamp: u64,
) -> revm_primitives::SpecId {
#[cfg(feature = "optimism")]
if chain_spec.is_optimism() {
if chain_spec.fork(Hardfork::Regolith).active_at_timestamp(timestamp) {
return revm_primitives::REGOLITH
} else {
return revm_primitives::BEDROCK
}
}
if chain_spec.is_cancun_active_at_timestamp(timestamp) {
revm_primitives::CANCUN
} else if chain_spec.is_shanghai_active_at_timestamp(timestamp) {
@ -19,6 +28,15 @@ pub fn revm_spec_by_timestamp_after_merge(
/// return revm_spec from spec configuration.
pub fn revm_spec(chain_spec: &ChainSpec, block: Head) -> revm_primitives::SpecId {
#[cfg(feature = "optimism")]
if chain_spec.is_optimism() {
if chain_spec.fork(Hardfork::Regolith).active_at_head(&block) {
return revm_primitives::REGOLITH
} else if chain_spec.fork(Hardfork::Bedrock).active_at_head(&block) {
return revm_primitives::BEDROCK
}
}
if chain_spec.fork(Hardfork::Cancun).active_at_head(&block) {
revm_primitives::CANCUN
} else if chain_spec.fork(Hardfork::Shanghai).active_at_head(&block) {
@ -112,6 +130,23 @@ mod tests {
revm_spec(&ChainSpecBuilder::mainnet().frontier_activated().build(), Head::default()),
revm_primitives::FRONTIER
);
#[cfg(feature = "optimism")]
{
#[inline(always)]
fn op_cs(f: impl FnOnce(ChainSpecBuilder) -> ChainSpecBuilder) -> ChainSpec {
let cs = ChainSpecBuilder::mainnet().chain(crate::Chain::Id(10));
f(cs).build()
}
assert_eq!(
revm_spec(&op_cs(|cs| cs.bedrock_activated()), Head::default()),
revm_primitives::BEDROCK
);
assert_eq!(
revm_spec(&op_cs(|cs| cs.regolith_activated()), Head::default()),
revm_primitives::REGOLITH
);
}
}
#[test]

View File

@ -4,9 +4,12 @@ use crate::{
revm::config::revm_spec,
revm_primitives::{AnalysisKind, BlockEnv, CfgEnv, Env, SpecId, TransactTo, TxEnv},
Address, Bytes, Chain, ChainSpec, Head, Header, Transaction, TransactionKind,
TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxLegacy, B256, U256,
TransactionSignedEcRecovered, B256, U256,
};
#[cfg(feature = "optimism")]
use revm_primitives::OptimismFields;
/// Convenience function to call both [fill_cfg_env] and [fill_block_env]
pub fn fill_cfg_and_block_env(
cfg: &mut CfgEnv,
@ -41,6 +44,11 @@ pub fn fill_cfg_env(
cfg_env.chain_id = chain_spec.chain().id();
cfg_env.spec_id = spec_id;
cfg_env.perf_analyse_created_bytecodes = AnalysisKind::Analyse;
#[cfg(feature = "optimism")]
{
cfg_env.optimism = chain_spec.is_optimism();
}
}
/// Fill block environment from Block.
@ -109,7 +117,17 @@ pub fn recover_header_signer(header: &Header) -> Option<Address> {
/// Returns a new [TxEnv] filled with the transaction's data.
pub fn tx_env_with_recovered(transaction: &TransactionSignedEcRecovered) -> TxEnv {
let mut tx_env = TxEnv::default();
#[cfg(not(feature = "optimism"))]
fill_tx_env(&mut tx_env, transaction.as_ref(), transaction.signer());
#[cfg(feature = "optimism")]
{
let mut envelope_buf = Vec::with_capacity(transaction.length_without_header());
transaction.encode_enveloped(&mut envelope_buf);
fill_tx_env(&mut tx_env, transaction.as_ref(), transaction.signer(), envelope_buf.into());
}
tx_env
}
@ -149,6 +167,13 @@ pub fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_b
// blob fields can be None for this tx
blob_hashes: Vec::new(),
max_fee_per_blob_gas: None,
#[cfg(feature = "optimism")]
optimism: OptimismFields {
source_hash: None,
mint: None,
is_system_transaction: Some(false),
enveloped_tx: None,
},
};
// ensure the block gas limit is >= the tx
@ -159,63 +184,65 @@ pub fn fill_tx_env_with_beacon_root_contract_call(env: &mut Env, parent_beacon_b
}
/// Fill transaction environment from [TransactionSignedEcRecovered].
#[cfg(not(feature = "optimism"))]
pub fn fill_tx_env_with_recovered(tx_env: &mut TxEnv, transaction: &TransactionSignedEcRecovered) {
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer())
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer());
}
/// Fill transaction environment from [TransactionSignedEcRecovered] and the given envelope.
#[cfg(feature = "optimism")]
pub fn fill_tx_env_with_recovered(
tx_env: &mut TxEnv,
transaction: &TransactionSignedEcRecovered,
envelope: Bytes,
) {
fill_tx_env(tx_env, transaction.as_ref(), transaction.signer(), envelope);
}
/// Fill transaction environment from a [Transaction] and the given sender address.
pub fn fill_tx_env<T>(tx_env: &mut TxEnv, transaction: T, sender: Address)
where
pub fn fill_tx_env<T>(
tx_env: &mut TxEnv,
transaction: T,
sender: Address,
#[cfg(feature = "optimism")] envelope: Bytes,
) where
T: AsRef<Transaction>,
{
tx_env.caller = sender;
match transaction.as_ref() {
Transaction::Legacy(TxLegacy {
nonce,
chain_id,
gas_price,
gas_limit,
to,
value,
input,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*gas_price);
Transaction::Legacy(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.gas_price);
tx_env.gas_priority_fee = None;
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
tx_env.transact_to = match tx.to {
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = *chain_id;
tx_env.nonce = Some(*nonce);
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = tx.chain_id;
tx_env.nonce = Some(tx.nonce);
tx_env.access_list.clear();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
#[cfg(feature = "optimism")]
fill_op_tx_env(tx_env, transaction, envelope);
}
Transaction::Eip2930(TxEip2930 {
nonce,
chain_id,
gas_price,
gas_limit,
to,
value,
input,
access_list,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*gas_price);
Transaction::Eip2930(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.gas_price);
tx_env.gas_priority_fee = None;
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
tx_env.transact_to = match tx.to {
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
tx_env.access_list = tx
.access_list
.0
.iter()
.map(|l| {
@ -224,30 +251,24 @@ where
.collect();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
#[cfg(feature = "optimism")]
fill_op_tx_env(tx_env, transaction, envelope);
}
Transaction::Eip1559(TxEip1559 {
nonce,
chain_id,
gas_limit,
max_fee_per_gas,
max_priority_fee_per_gas,
to,
value,
input,
access_list,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(*max_priority_fee_per_gas));
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
Transaction::Eip1559(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
tx_env.transact_to = match tx.to {
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
tx_env.access_list = tx
.access_list
.0
.iter()
.map(|l| {
@ -256,40 +277,74 @@ where
.collect();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
#[cfg(feature = "optimism")]
fill_op_tx_env(tx_env, transaction, envelope);
}
Transaction::Eip4844(TxEip4844 {
nonce,
chain_id,
gas_limit,
max_fee_per_gas,
max_priority_fee_per_gas,
to,
value,
access_list,
blob_versioned_hashes,
max_fee_per_blob_gas,
input,
}) => {
tx_env.gas_limit = *gas_limit;
tx_env.gas_price = U256::from(*max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(*max_priority_fee_per_gas));
tx_env.transact_to = match to {
TransactionKind::Call(to) => TransactTo::Call(*to),
Transaction::Eip4844(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
tx_env.transact_to = match tx.to {
TransactionKind::Call(to) => TransactTo::Call(to),
TransactionKind::Create => TransactTo::create(),
};
tx_env.value = (*value).into();
tx_env.data = input.clone();
tx_env.chain_id = Some(*chain_id);
tx_env.nonce = Some(*nonce);
tx_env.access_list = access_list
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
tx_env.access_list = tx
.access_list
.0
.iter()
.map(|l| {
(l.address, l.storage_keys.iter().map(|k| U256::from_be_bytes(k.0)).collect())
})
.collect();
tx_env.blob_hashes = blob_versioned_hashes.clone();
tx_env.max_fee_per_blob_gas = Some(U256::from(*max_fee_per_blob_gas));
tx_env.blob_hashes = tx.blob_versioned_hashes.clone();
tx_env.max_fee_per_blob_gas = Some(U256::from(tx.max_fee_per_blob_gas));
#[cfg(feature = "optimism")]
fill_op_tx_env(tx_env, transaction, envelope);
}
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::ZERO;
tx_env.gas_priority_fee = None;
match tx.to {
TransactionKind::Call(to) => tx_env.transact_to = TransactTo::Call(to),
TransactionKind::Create => tx_env.transact_to = TransactTo::create(),
}
tx_env.value = tx.value.into();
tx_env.data = tx.input.clone();
tx_env.chain_id = None;
tx_env.nonce = None;
fill_op_tx_env(tx_env, transaction, envelope);
}
}
}
#[cfg(feature = "optimism")]
#[inline(always)]
fn fill_op_tx_env<T: AsRef<Transaction>>(tx_env: &mut TxEnv, transaction: T, envelope: Bytes) {
match transaction.as_ref() {
Transaction::Deposit(tx) => {
tx_env.optimism = OptimismFields {
source_hash: Some(tx.source_hash),
mint: tx.mint,
is_system_transaction: Some(tx.is_system_transaction),
enveloped_tx: Some(envelope),
};
}
_ => {
tx_env.optimism = OptimismFields {
source_hash: None,
mint: None,
is_system_transaction: Some(false),
enveloped_tx: Some(envelope),
}
}
}
}

View File

@ -49,6 +49,15 @@ mod tx_value;
pub(crate) mod util;
mod variant;
#[cfg(feature = "optimism")]
mod optimism;
#[cfg(feature = "optimism")]
pub use optimism::TxDeposit;
#[cfg(feature = "optimism")]
use revm_primitives::U256;
#[cfg(feature = "optimism")]
pub use tx_type::DEPOSIT_TX_TYPE_ID;
// Expected number of transactions where we can expect a speed-up by recovering the senders in
// parallel.
pub(crate) static PARALLEL_SENDER_RECOVERY_THRESHOLD: Lazy<usize> =
@ -103,6 +112,9 @@ pub enum Transaction {
/// EIP-4844, also known as proto-danksharding, implements the framework and logic of
/// danksharding, introducing new transaction formats and verification rules.
Eip4844(TxEip4844),
/// Optimism deposit transaction.
#[cfg(feature = "optimism")]
Deposit(TxDeposit),
}
// === impl Transaction ===
@ -116,6 +128,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.signature_hash(),
Transaction::Eip1559(tx) => tx.signature_hash(),
Transaction::Eip4844(tx) => tx.signature_hash(),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => B256::ZERO,
}
}
@ -126,6 +140,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { chain_id, .. }) |
Transaction::Eip1559(TxEip1559 { chain_id, .. }) |
Transaction::Eip4844(TxEip4844 { chain_id, .. }) => Some(*chain_id),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
}
}
@ -136,6 +152,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { chain_id: ref mut c, .. }) |
Transaction::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) |
Transaction::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) => *c = chain_id,
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => { /* noop */ }
}
}
@ -147,6 +165,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { to, .. }) |
Transaction::Eip1559(TxEip1559 { to, .. }) |
Transaction::Eip4844(TxEip4844 { to, .. }) => to,
#[cfg(feature = "optimism")]
Transaction::Deposit(TxDeposit { to, .. }) => to,
}
}
@ -162,6 +182,8 @@ impl Transaction {
Transaction::Eip2930(access_list_tx) => access_list_tx.tx_type(),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.tx_type(),
Transaction::Eip4844(blob_tx) => blob_tx.tx_type(),
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.tx_type(),
}
}
@ -172,6 +194,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { value, .. }) |
Transaction::Eip1559(TxEip1559 { value, .. }) |
Transaction::Eip4844(TxEip4844 { value, .. }) => value,
#[cfg(feature = "optimism")]
Transaction::Deposit(TxDeposit { value, .. }) => value,
}
}
@ -182,6 +206,9 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { nonce, .. }) |
Transaction::Eip1559(TxEip1559 { nonce, .. }) |
Transaction::Eip4844(TxEip4844 { nonce, .. }) => *nonce,
// Deposit transactions do not have nonces.
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => 0,
}
}
@ -194,6 +221,8 @@ impl Transaction {
Transaction::Eip2930(tx) => Some(&tx.access_list),
Transaction::Eip1559(tx) => Some(&tx.access_list),
Transaction::Eip4844(tx) => Some(&tx.access_list),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
}
}
@ -204,6 +233,8 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { gas_limit, .. }) |
Transaction::Eip1559(TxEip1559 { gas_limit, .. }) |
Transaction::Eip4844(TxEip4844 { gas_limit, .. }) => *gas_limit,
#[cfg(feature = "optimism")]
Transaction::Deposit(TxDeposit { gas_limit, .. }) => *gas_limit,
}
}
@ -212,6 +243,8 @@ impl Transaction {
match self {
Transaction::Legacy(_) | Transaction::Eip2930(_) => false,
Transaction::Eip1559(_) | Transaction::Eip4844(_) => true,
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => false,
}
}
@ -224,6 +257,10 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price,
Transaction::Eip1559(TxEip1559 { max_fee_per_gas, .. }) |
Transaction::Eip4844(TxEip4844 { max_fee_per_gas, .. }) => *max_fee_per_gas,
// Deposit transactions buy their L2 gas on L1 and, as such, the L2 gas is not
// refundable.
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => 0,
}
}
@ -238,6 +275,8 @@ impl Transaction {
Transaction::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => {
Some(*max_priority_fee_per_gas)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
}
}
@ -251,6 +290,8 @@ impl Transaction {
Transaction::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => {
Some(blob_versioned_hashes.to_vec())
}
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => None,
}
}
@ -292,6 +333,8 @@ impl Transaction {
Transaction::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => {
*max_priority_fee_per_gas
}
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => 0,
}
}
@ -304,6 +347,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.gas_price,
Transaction::Eip1559(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
Transaction::Eip4844(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => 0,
}
}
@ -345,9 +390,47 @@ impl Transaction {
Transaction::Eip2930(TxEip2930 { input, .. }) |
Transaction::Eip1559(TxEip1559 { input, .. }) |
Transaction::Eip4844(TxEip4844 { input, .. }) => input,
#[cfg(feature = "optimism")]
Transaction::Deposit(TxDeposit { input, .. }) => input,
}
}
/// Returns the source hash of the transaction, which uniquely identifies its source.
/// If not a deposit transaction, this will always return `None`.
#[cfg(feature = "optimism")]
pub fn source_hash(&self) -> Option<B256> {
match self {
Transaction::Deposit(TxDeposit { source_hash, .. }) => Some(*source_hash),
_ => None,
}
}
/// Returns the amount of ETH locked up on L1 that will be minted on L2. If the transaction
/// is not a deposit transaction, this will always return `None`.
#[cfg(feature = "optimism")]
pub fn mint(&self) -> Option<u128> {
match self {
Transaction::Deposit(TxDeposit { mint, .. }) => *mint,
_ => None,
}
}
/// Returns whether or not the transaction is a system transaction. If the transaction
/// is not a deposit transaction, this will always return `false`.
#[cfg(feature = "optimism")]
pub fn is_system_transaction(&self) -> bool {
match self {
Transaction::Deposit(TxDeposit { is_system_transaction, .. }) => *is_system_transaction,
_ => false,
}
}
/// Returns whether or not the transaction is an Optimism Deposited transaction.
#[cfg(feature = "optimism")]
pub fn is_deposit(&self) -> bool {
matches!(self, Transaction::Deposit(_))
}
/// This encodes the transaction _without_ the signature, and is only suitable for creating a
/// hash intended for signing.
pub fn encode_without_signature(&self, out: &mut dyn bytes::BufMut) {
@ -376,6 +459,8 @@ impl Transaction {
Transaction::Eip4844(blob_tx) => {
blob_tx.encode_with_signature(signature, out, with_header)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.encode(out, with_header),
}
}
@ -386,6 +471,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.nonce = nonce,
Transaction::Eip1559(tx) => tx.nonce = nonce,
Transaction::Eip4844(tx) => tx.nonce = nonce,
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => { /* noop */ }
}
}
@ -396,6 +483,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.value = value,
Transaction::Eip1559(tx) => tx.value = value,
Transaction::Eip4844(tx) => tx.value = value,
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => tx.value = value,
}
}
@ -406,6 +495,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.input = input,
Transaction::Eip1559(tx) => tx.input = input,
Transaction::Eip4844(tx) => tx.input = input,
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => tx.input = input,
}
}
@ -417,6 +508,8 @@ impl Transaction {
Transaction::Eip2930(tx) => tx.size(),
Transaction::Eip1559(tx) => tx.size(),
Transaction::Eip4844(tx) => tx.size(),
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => tx.size(),
}
}
@ -502,31 +595,38 @@ impl From<TxEip4844> for Transaction {
}
impl Compact for Transaction {
// Serializes the TxType to the buffer if necessary, returning 2 bits of the type as an
// identifier instead of the length.
fn to_compact<B>(self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
let identifier = self.tx_type().to_compact(buf);
match self {
Transaction::Legacy(tx) => {
tx.to_compact(buf);
0
}
Transaction::Eip2930(tx) => {
tx.to_compact(buf);
1
}
Transaction::Eip1559(tx) => {
tx.to_compact(buf);
2
}
Transaction::Eip4844(tx) => {
tx.to_compact(buf);
3
}
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => {
tx.to_compact(buf);
}
}
identifier
}
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
// For backwards compatibility purposes, only 2 bits of the type are encoded in the identifier
// parameter. In the case of a 3, the full transaction type is read from the buffer as a
// single byte.
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
match identifier {
0 => {
let (tx, buf) = TxLegacy::from_compact(buf, buf.len());
@ -541,8 +641,24 @@ impl Compact for Transaction {
(Transaction::Eip1559(tx), buf)
}
3 => {
let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
(Transaction::Eip4844(tx), buf)
// An identifier of 3 indicates that the transaction type did not fit into
// the backwards compatible 2 bit identifier, their transaction types are
// larger than 2 bits (eg. 4844 and Deposit Transactions). In this case,
// we need to read the concrete transaction type from the buffer by
// reading the full 8 bits (single byte) and match on this transaction type.
let identifier = buf.get_u8() as usize;
match identifier {
3 => {
let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
(Transaction::Eip4844(tx), buf)
}
#[cfg(feature = "optimism")]
126 => {
let (tx, buf) = TxDeposit::from_compact(buf, buf.len());
(Transaction::Deposit(tx), buf)
}
_ => unreachable!("Junk data in database: unknown Transaction variant"),
}
}
_ => unreachable!("Junk data in database: unknown Transaction variant"),
}
@ -572,6 +688,10 @@ impl Encodable for Transaction {
Transaction::Eip4844(blob_tx) => {
blob_tx.encode_for_signing(out);
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => {
deposit_tx.encode(out, true);
}
}
}
@ -581,6 +701,8 @@ impl Encodable for Transaction {
Transaction::Eip2930(access_list_tx) => access_list_tx.payload_len_for_signature(),
Transaction::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.payload_len_for_signature(),
Transaction::Eip4844(blob_tx) => blob_tx.payload_len_for_signature(),
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len(),
}
}
}
@ -861,6 +983,12 @@ impl TransactionSigned {
///
/// Returns `None` if the transaction's signature is invalid, see also [Self::recover_signer].
pub fn recover_signer(&self) -> Option<Address> {
// Optimism's Deposit transaction does not have a signature. Directly return the
// `from` address.
#[cfg(feature = "optimism")]
if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction {
return Some(from)
}
let signature_hash = self.signature_hash();
self.signature.recover_signer(signature_hash)
}
@ -942,6 +1070,8 @@ impl TransactionSigned {
dynamic_fee_tx.payload_len_with_signature(&self.signature)
}
Transaction::Eip4844(blob_tx) => blob_tx.payload_len_with_signature(&self.signature),
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len(),
}
}
@ -1065,11 +1195,21 @@ impl TransactionSigned {
1 => Transaction::Eip2930(TxEip2930::decode_inner(data)?),
2 => Transaction::Eip1559(TxEip1559::decode_inner(data)?),
3 => Transaction::Eip4844(TxEip4844::decode_inner(data)?),
#[cfg(feature = "optimism")]
0x7E => Transaction::Deposit(TxDeposit::decode_inner(data)?),
_ => return Err(RlpError::Custom("unsupported typed transaction type")),
};
#[cfg(not(feature = "optimism"))]
let signature = Signature::decode(data)?;
#[cfg(feature = "optimism")]
let signature = if tx_type == DEPOSIT_TX_TYPE_ID {
Signature::default()
} else {
Signature::decode(data)?
};
let bytes_consumed = remaining_len - data.len();
if bytes_consumed != header.payload_length {
return Err(RlpError::UnexpectedLength)
@ -1127,6 +1267,8 @@ impl TransactionSigned {
Transaction::Eip4844(blob_tx) => {
blob_tx.payload_len_with_signature_without_header(&self.signature)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len_without_header(),
}
}
}
@ -1212,6 +1354,14 @@ impl proptest::arbitrary::Arbitrary for TransactionSigned {
// Otherwise we might overflow when calculating `v` on `recalculate_hash`
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
#[cfg(feature = "optimism")]
let sig = if transaction.is_deposit() {
Signature { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false }
} else {
sig
};
let mut tx =
TransactionSigned { hash: Default::default(), signature: sig, transaction };
tx.hash = tx.recalculate_hash();
@ -1232,14 +1382,16 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned {
transaction.set_chain_id(chain_id % (u64::MAX / 2 - 36));
}
let mut tx = TransactionSigned {
hash: Default::default(),
signature: Signature::arbitrary(u)?,
transaction,
};
tx.hash = tx.recalculate_hash();
let signature = Signature::arbitrary(u)?;
Ok(tx)
#[cfg(feature = "optimism")]
let signature = if transaction.is_deposit() {
Signature { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false }
} else {
signature
};
Ok(TransactionSigned::from_transaction_and_signature(transaction, signature))
}
}

View File

@ -0,0 +1,169 @@
use crate::{Address, Bytes, TransactionKind, TxType, TxValue, B256};
use alloy_rlp::{
length_of_length, Decodable, Encodable, Error as DecodeError, Header, EMPTY_STRING_CODE,
};
use bytes::Buf;
use reth_codecs::{main_codec, Compact};
use std::mem;
/// Deposit transactions, also known as deposits are initiated on L1, and executed on L2.
#[main_codec]
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
pub struct TxDeposit {
/// Hash that uniquely identifies the source of the deposit.
pub source_hash: B256,
/// The address of the sender account.
pub from: Address,
/// The address of the recipient account, or the null (zero-length) address if the deposited
/// transaction is a contract creation.
pub to: TransactionKind,
/// The ETH value to mint on L2.
pub mint: Option<u128>,
/// The ETH value to send to the recipient account.
pub value: TxValue,
/// The gas limit for the L2 transaction.
pub gas_limit: u64,
/// Field indicating if this transaction is exempt from the L2 gas limit.
pub is_system_transaction: bool,
/// Input has two uses depending if transaction is Create or Call (if `to` field is None or
/// Some).
pub input: Bytes,
}
impl TxDeposit {
/// Calculates a heuristic for the in-memory size of the [TxDeposit] transaction.
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<B256>() + // source_hash
mem::size_of::<Address>() + // from
self.to.size() + // to
mem::size_of::<Option<u128>>() + // mint
mem::size_of::<TxValue>() + // value
mem::size_of::<u64>() + // gas_limit
mem::size_of::<bool>() + // is_system_transaction
self.input.len() // input
}
/// Decodes the inner [TxDeposit] fields from RLP bytes.
///
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
/// RLP fields in the following order:
///
/// - `source_hash`
/// - `from`
/// - `to`
/// - `mint`
/// - `value`
/// - `gas_limit`
/// - `is_system_transaction`
/// - `input`
pub fn decode_inner(buf: &mut &[u8]) -> Result<Self, DecodeError> {
Ok(Self {
source_hash: Decodable::decode(buf)?,
from: Decodable::decode(buf)?,
to: Decodable::decode(buf)?,
mint: if *buf.first().ok_or(DecodeError::InputTooShort)? == EMPTY_STRING_CODE {
buf.advance(1);
None
} else {
Some(Decodable::decode(buf)?)
},
value: Decodable::decode(buf)?,
gas_limit: Decodable::decode(buf)?,
is_system_transaction: Decodable::decode(buf)?,
input: Decodable::decode(buf)?,
})
}
/// Outputs the length of the transaction's fields, without a RLP header or length of the
/// eip155 fields.
pub(crate) fn fields_len(&self) -> usize {
let mut len = 0;
len += self.source_hash.length();
len += self.from.length();
len += self.to.length();
len += self.mint.map_or(1, |mint| mint.length());
len += self.value.length();
len += self.gas_limit.length();
len += self.is_system_transaction.length();
len += self.input.0.length();
len
}
/// Encodes only the transaction's fields into the desired buffer, without a RLP header.
/// <https://github.com/ethereum-optimism/optimism/blob/develop/specs/deposits.md#the-deposited-transaction-type>
pub(crate) fn encode_fields(&self, out: &mut dyn bytes::BufMut) {
self.source_hash.encode(out);
self.from.encode(out);
self.to.encode(out);
if let Some(mint) = self.mint {
mint.encode(out);
} else {
out.put_u8(EMPTY_STRING_CODE);
}
self.value.encode(out);
self.gas_limit.encode(out);
self.is_system_transaction.encode(out);
self.input.encode(out);
}
/// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating
/// hash that for eip2718 does not require rlp header
pub(crate) fn encode(&self, out: &mut dyn bytes::BufMut, with_header: bool) {
let payload_length = self.fields_len();
if with_header {
Header {
list: false,
payload_length: 1 + length_of_length(payload_length) + payload_length,
}
.encode(out);
}
out.put_u8(self.tx_type() as u8);
let header = Header { list: true, payload_length };
header.encode(out);
self.encode_fields(out);
}
/// Output the length of the RLP signed transaction encoding. This encodes with a RLP header.
pub(crate) fn payload_len(&self) -> usize {
let payload_length = self.fields_len();
// 'tx type' + 'header length' + 'payload length'
let len = 1 + length_of_length(payload_length) + payload_length;
length_of_length(len) + len
}
pub(crate) fn payload_len_without_header(&self) -> usize {
let payload_length = self.fields_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
/// Get the transaction type
pub(crate) fn tx_type(&self) -> TxType {
TxType::DEPOSIT
}
}
#[cfg(test)]
mod tests {
use crate::{Bytes, TransactionSigned};
use alloy_rlp::Decodable;
use bytes::BytesMut;
use revm_primitives::hex_literal::hex;
#[test]
fn test_rlp_roundtrip() {
let bytes = Bytes::from_static(&hex!("7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"));
let tx_a = TransactionSigned::decode_enveloped(bytes.clone()).unwrap();
let tx_b = TransactionSigned::decode(&mut &bytes[..]).unwrap();
let mut buf_a = BytesMut::default();
tx_a.encode_enveloped(&mut buf_a);
assert_eq!(&buf_a[..], &bytes[..]);
let mut buf_b = BytesMut::default();
tx_b.encode_enveloped(&mut buf_b);
assert_eq!(&buf_b[..], &bytes[..]);
}
}

View File

@ -47,6 +47,16 @@ pub enum PooledTransactionsElement {
},
/// A blob transaction, which includes the transaction, blob data, commitments, and proofs.
BlobTransaction(BlobTransaction),
/// An Optimism deposit transaction
#[cfg(feature = "optimism")]
Deposit {
/// The inner transaction
transaction: crate::TxDeposit,
/// The signature
signature: Signature,
/// The hash of the transaction
hash: TxHash,
},
}
impl PooledTransactionsElement {
@ -69,6 +79,8 @@ impl PooledTransactionsElement {
Self::Eip2930 { transaction, .. } => transaction.signature_hash(),
Self::Eip1559 { transaction, .. } => transaction.signature_hash(),
Self::BlobTransaction(blob_tx) => blob_tx.transaction.signature_hash(),
#[cfg(feature = "optimism")]
Self::Deposit { .. } => B256::ZERO,
}
}
@ -79,6 +91,8 @@ impl PooledTransactionsElement {
PooledTransactionsElement::Eip2930 { hash, .. } => hash,
PooledTransactionsElement::Eip1559 { hash, .. } => hash,
PooledTransactionsElement::BlobTransaction(tx) => &tx.hash,
#[cfg(feature = "optimism")]
PooledTransactionsElement::Deposit { hash, .. } => hash,
}
}
@ -89,6 +103,10 @@ impl PooledTransactionsElement {
Self::Eip2930 { signature, .. } => signature,
Self::Eip1559 { signature, .. } => signature,
Self::BlobTransaction(blob_tx) => &blob_tx.signature,
#[cfg(feature = "optimism")]
Self::Deposit { .. } => {
panic!("Deposit transactions do not have a signature! This is a bug.")
}
}
}
@ -99,6 +117,8 @@ impl PooledTransactionsElement {
Self::Eip2930 { transaction, .. } => transaction.nonce,
Self::Eip1559 { transaction, .. } => transaction.nonce,
Self::BlobTransaction(blob_tx) => blob_tx.transaction.nonce,
#[cfg(feature = "optimism")]
Self::Deposit { .. } => 0,
}
}
@ -204,6 +224,12 @@ impl PooledTransactionsElement {
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => Ok(PooledTransactionsElement::Deposit {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
}
}
}
@ -232,6 +258,12 @@ impl PooledTransactionsElement {
hash,
},
Self::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
#[cfg(feature = "optimism")]
Self::Deposit { transaction, signature, hash } => TransactionSigned {
transaction: Transaction::Deposit(transaction),
signature,
hash,
},
}
}
@ -254,6 +286,8 @@ impl PooledTransactionsElement {
// the encoding does not use a header, so we set `with_header` to false
blob_tx.payload_len_with_type(false)
}
#[cfg(feature = "optimism")]
Self::Deposit { transaction, .. } => transaction.payload_len_without_header(),
}
}
}
@ -279,6 +313,10 @@ impl Encodable for PooledTransactionsElement {
// `rlp(tx_type || rlp([transaction_payload_body, blobs, commitments, proofs]))`
blob_tx.encode_with_type_inner(out, true);
}
#[cfg(feature = "optimism")]
Self::Deposit { transaction, .. } => {
transaction.encode(out, true);
}
}
}
@ -300,6 +338,11 @@ impl Encodable for PooledTransactionsElement {
// the encoding uses a header, so we set `with_header` to true
blob_tx.payload_len_with_type(true)
}
#[cfg(feature = "optimism")]
Self::Deposit { transaction, .. } => {
// method computes the payload len with a RLP header
transaction.payload_len()
}
}
}
}
@ -401,6 +444,12 @@ impl Decodable for PooledTransactionsElement {
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => Ok(PooledTransactionsElement::Deposit {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
}
}
}
@ -432,6 +481,10 @@ impl From<TransactionSigned> for PooledTransactionsElement {
sidecar: Default::default(),
})
}
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => {
PooledTransactionsElement::Deposit { transaction: tx, signature, hash }
}
}
}
}

View File

@ -62,6 +62,11 @@ impl Signature {
/// Output the `v` of the signature depends on chain_id
#[inline]
pub fn v(&self, chain_id: Option<u64>) -> u64 {
#[cfg(feature = "optimism")]
if self.r == U256::ZERO && self.s == U256::ZERO {
return 0
}
if let Some(chain_id) = chain_id {
// EIP-155: v = {0, 1} + CHAIN_ID * 2 + 35
self.odd_y_parity as u64 + chain_id * 2 + 35
@ -158,27 +163,50 @@ mod tests {
#[test]
fn test_payload_len_with_eip155_chain_id() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
// Select 1 as an arbitrary nonzero value for R and S, as v() always returns 0 for (0, 0).
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
assert_eq!(3, signature.payload_len_with_eip155_chain_id(None));
assert_eq!(3, signature.payload_len_with_eip155_chain_id(Some(1)));
assert_eq!(4, signature.payload_len_with_eip155_chain_id(Some(47)));
}
#[cfg(feature = "optimism")]
#[test]
fn test_zero_signature_payload_len_with_eip155_chain_id() {
let zero_signature = Signature { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false };
assert_eq!(3, zero_signature.payload_len_with_eip155_chain_id(None));
assert_eq!(3, zero_signature.payload_len_with_eip155_chain_id(Some(1)));
assert_eq!(3, zero_signature.payload_len_with_eip155_chain_id(Some(47)));
}
#[test]
fn test_v() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
// Select 1 as an arbitrary nonzero value for R and S, as v() always returns 0 for (0, 0).
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
assert_eq!(27, signature.v(None));
assert_eq!(37, signature.v(Some(1)));
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: true };
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: true };
assert_eq!(28, signature.v(None));
assert_eq!(38, signature.v(Some(1)));
}
#[cfg(feature = "optimism")]
#[test]
fn test_zero_signature_v() {
let signature = Signature { r: U256::ZERO, s: U256::ZERO, odd_y_parity: false };
assert_eq!(0, signature.v(None));
assert_eq!(0, signature.v(Some(1)));
assert_eq!(0, signature.v(Some(47)));
}
#[test]
fn test_encode_and_decode_with_eip155_chain_id() {
let signature = Signature { r: U256::default(), s: U256::default(), odd_y_parity: false };
// Select 1 as an arbitrary nonzero value for R and S, as v() always returns 0 for (0, 0).
let signature = Signature { r: U256::from(1), s: U256::from(1), odd_y_parity: false };
let mut encoded = BytesMut::new();
signature.encode_with_eip155_chain_id(&mut encoded, None);

View File

@ -1,4 +1,5 @@
use crate::U8;
use bytes::Buf;
use reth_codecs::{derive_arbitrary, Compact};
use serde::{Deserialize, Serialize};
@ -15,6 +16,10 @@ pub const EIP1559_TX_TYPE_ID: u8 = 2;
/// Identifier for [TxEip4844](crate::TxEip4844) transaction.
pub const EIP4844_TX_TYPE_ID: u8 = 3;
/// Identifier for [TxDeposit](crate::TxDeposit) transaction.
#[cfg(feature = "optimism")]
pub const DEPOSIT_TX_TYPE_ID: u8 = 126;
/// Transaction Type
///
/// Currently being used as 2-bit type when encoding it to [`Compact`] on
@ -34,6 +39,9 @@ pub enum TxType {
EIP1559 = 2_isize,
/// Shard Blob Transactions - EIP-4844
EIP4844 = 3_isize,
/// Optimism Deposit transaction.
#[cfg(feature = "optimism")]
DEPOSIT = 126_isize,
}
impl From<TxType> for u8 {
@ -43,6 +51,8 @@ impl From<TxType> for u8 {
TxType::EIP2930 => EIP2930_TX_TYPE_ID,
TxType::EIP1559 => EIP1559_TX_TYPE_ID,
TxType::EIP4844 => EIP4844_TX_TYPE_ID,
#[cfg(feature = "optimism")]
TxType::DEPOSIT => DEPOSIT_TX_TYPE_ID,
}
}
}
@ -54,7 +64,7 @@ impl From<TxType> for U8 {
}
impl Compact for TxType {
fn to_compact<B>(self, _: &mut B) -> usize
fn to_compact<B>(self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
@ -62,17 +72,30 @@ impl Compact for TxType {
TxType::Legacy => 0,
TxType::EIP2930 => 1,
TxType::EIP1559 => 2,
TxType::EIP4844 => 3,
_ => {
buf.put_u8(self as u8);
3
}
}
}
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
// For backwards compatibility purposes only 2 bits of the type are encoded in the identifier
// parameter. In the case of a 3, the full transaction type is read from the buffer as a
// single byte.
fn from_compact(mut buf: &[u8], identifier: usize) -> (Self, &[u8]) {
(
match identifier {
0 => TxType::Legacy,
1 => TxType::EIP2930,
2 => TxType::EIP1559,
_ => TxType::EIP4844,
_ => {
let identifier = buf.get_u8() as usize;
match identifier {
#[cfg(feature = "optimism")]
126 => TxType::DEPOSIT,
_ => TxType::EIP4844,
}
}
},
buf,
)

View File

@ -1,5 +1,5 @@
#[allow(unused_imports)]
// suppress warning for UIntTryTo, which is required only when value-256 feature is disabled
// suppress warning for UIntTryTo, which is required only when optimism feature is disabled
use crate::{
ruint::{ToUintError, UintTryFrom, UintTryTo},
U256,
@ -88,11 +88,11 @@ impl Compact for TxValue {
where
B: bytes::BufMut + AsMut<[u8]>,
{
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
{
self.0.to_compact(buf)
}
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
{
// SAFETY: For ethereum mainnet this is safe as the max value is
// 120000000000000000000000000 wei
@ -103,12 +103,12 @@ impl Compact for TxValue {
#[allow(unreachable_code)]
fn from_compact(buf: &[u8], identifier: usize) -> (Self, &[u8]) {
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
{
let (i, buf) = U256::from_compact(buf, identifier);
(TxValue(i), buf)
}
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
{
let (i, buf) = u128::from_compact(buf, identifier);
(TxValue::from(i), buf)
@ -119,12 +119,12 @@ impl Compact for TxValue {
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TxValue {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
{
Ok(Self(U256::arbitrary(u)?))
}
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
{
Ok(Self::try_from(u128::arbitrary(u)?).expect("to fit"))
}
@ -135,12 +135,12 @@ impl<'a> arbitrary::Arbitrary<'a> for TxValue {
impl proptest::arbitrary::Arbitrary for TxValue {
type Parameters = ParamsFor<()>;
fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
#[cfg(feature = "value-256")]
#[cfg(feature = "optimism")]
{
proptest::prelude::any::<U256>().prop_map(Self).boxed()
}
#[cfg(not(feature = "value-256"))]
#[cfg(not(feature = "optimism"))]
{
proptest::prelude::any::<u128>()
.prop_map(|num| Self::try_from(num).expect("to fit"))