Skip to content

Commit

Permalink
feat: use enum for OP receipt (#13334)
Browse files Browse the repository at this point in the history
  • Loading branch information
klkvr authored Dec 12, 2024
1 parent b65981e commit 379db1d
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 139 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion crates/optimism/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ reth-codecs = { workspace = true, features = ["test-utils", "op"] }
rstest.workspace = true
arbitrary.workspace = true
proptest.workspace = true
test-fuzz.workspace = true

[features]
default = ["std"]
Expand Down
292 changes: 173 additions & 119 deletions crates/optimism/primitives/src/receipt.rs
Original file line number Diff line number Diff line change
@@ -1,65 +1,72 @@
use alloc::vec::Vec;
use alloy_consensus::{
Eip2718EncodableReceipt, Eip658Value, ReceiptWithBloom, RlpDecodableReceipt,
Eip2718EncodableReceipt, Eip658Value, Receipt, ReceiptWithBloom, RlpDecodableReceipt,
RlpEncodableReceipt, TxReceipt, Typed2718,
};
use alloy_primitives::{Bloom, Log};
use alloy_rlp::{BufMut, Decodable, Encodable, Header};
use op_alloy_consensus::OpTxType;
use alloy_rlp::{BufMut, Decodable, Header};
use op_alloy_consensus::{OpDepositReceipt, OpTxType};
use reth_primitives_traits::InMemorySize;

/// Typed ethereum transaction receipt.
/// Receipt containing result of transaction execution.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "reth-codec", derive(reth_codecs::CompactZstd), reth_codecs::add_arbitrary_tests, reth_zstd(
compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR,
decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR
))]
pub struct OpReceipt {
/// Receipt type.
pub tx_type: OpTxType,
/// If transaction is executed successfully.
///
/// This is the `statusCode`
pub success: bool,
/// Gas used
pub cumulative_gas_used: u64,
/// Log send from contracts.
pub logs: Vec<Log>,
/// Deposit nonce for Optimism deposit transactions
pub deposit_nonce: Option<u64>,
/// Deposit receipt version for Optimism deposit transactions
///
///
/// The deposit receipt version was introduced in Canyon to indicate an update to how
/// receipt hashes should be computed when set. The state transition process
/// ensures this is only set for post-Canyon deposit transactions.
pub deposit_receipt_version: Option<u64>,
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum OpReceipt {
/// Legacy receipt
Legacy(Receipt),
/// EIP-2930 receipt
Eip2930(Receipt),
/// EIP-1559 receipt
Eip1559(Receipt),
/// EIP-7702 receipt
Eip7702(Receipt),
/// Deposit receipt
Deposit(OpDepositReceipt),
}

impl OpReceipt {
/// Returns [`OpTxType`] of the receipt.
pub const fn tx_type(&self) -> OpTxType {
match self {
Self::Legacy(_) => OpTxType::Legacy,
Self::Eip2930(_) => OpTxType::Eip2930,
Self::Eip1559(_) => OpTxType::Eip1559,
Self::Eip7702(_) => OpTxType::Eip7702,
Self::Deposit(_) => OpTxType::Deposit,
}
}

/// Returns inner [`Receipt`],
pub const fn as_receipt(&self) -> &Receipt {
match self {
Self::Legacy(receipt) |
Self::Eip2930(receipt) |
Self::Eip1559(receipt) |
Self::Eip7702(receipt) => receipt,
Self::Deposit(receipt) => &receipt.inner,
}
}

/// Returns length of RLP-encoded receipt fields with the given [`Bloom`] without an RLP header.
pub fn rlp_encoded_fields_length(&self, bloom: &Bloom) -> usize {
self.success.length() +
self.cumulative_gas_used.length() +
bloom.length() +
self.logs.length() +
self.deposit_nonce.map(|n| n.length()).unwrap_or(0) +
self.deposit_receipt_version.map(|v| v.length()).unwrap_or(0)
match self {
Self::Legacy(receipt) |
Self::Eip2930(receipt) |
Self::Eip1559(receipt) |
Self::Eip7702(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom),
Self::Deposit(receipt) => receipt.rlp_encoded_fields_length_with_bloom(bloom),
}
}

/// RLP-encodes receipt fields with the given [`Bloom`] without an RLP header.
pub fn rlp_encode_fields(&self, bloom: &Bloom, out: &mut dyn BufMut) {
self.success.encode(out);
self.cumulative_gas_used.encode(out);
bloom.encode(out);
self.logs.encode(out);
if let Some(nonce) = self.deposit_nonce {
nonce.encode(out);
}
if let Some(version) = self.deposit_receipt_version {
version.encode(out);
match self {
Self::Legacy(receipt) |
Self::Eip2930(receipt) |
Self::Eip1559(receipt) |
Self::Eip7702(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out),
Self::Deposit(receipt) => receipt.rlp_encode_fields_with_bloom(bloom, out),
}
}

Expand All @@ -74,55 +81,44 @@ impl OpReceipt {
buf: &mut &[u8],
tx_type: OpTxType,
) -> alloy_rlp::Result<ReceiptWithBloom<Self>> {
let header = Header::decode(buf)?;
if !header.list {
return Err(alloy_rlp::Error::UnexpectedString);
}

let remaining = buf.len();

let success = Decodable::decode(buf)?;
let cumulative_gas_used = Decodable::decode(buf)?;
let logs_bloom = Decodable::decode(buf)?;
let logs = Decodable::decode(buf)?;

let (deposit_nonce, deposit_receipt_version) = if tx_type == OpTxType::Deposit {
let remaining = || header.payload_length - (remaining - buf.len()) > 0;
let deposit_nonce = remaining().then(|| Decodable::decode(buf)).transpose()?;
let deposit_receipt_version =
remaining().then(|| Decodable::decode(buf)).transpose()?;

(deposit_nonce, deposit_receipt_version)
} else {
(None, None)
};

if buf.len() + header.payload_length != remaining {
return Err(alloy_rlp::Error::UnexpectedLength);
match tx_type {
OpTxType::Legacy => {
let ReceiptWithBloom { receipt, logs_bloom } =
RlpDecodableReceipt::rlp_decode_with_bloom(buf)?;
Ok(ReceiptWithBloom { receipt: Self::Legacy(receipt), logs_bloom })
}
OpTxType::Eip2930 => {
let ReceiptWithBloom { receipt, logs_bloom } =
RlpDecodableReceipt::rlp_decode_with_bloom(buf)?;
Ok(ReceiptWithBloom { receipt: Self::Eip2930(receipt), logs_bloom })
}
OpTxType::Eip1559 => {
let ReceiptWithBloom { receipt, logs_bloom } =
RlpDecodableReceipt::rlp_decode_with_bloom(buf)?;
Ok(ReceiptWithBloom { receipt: Self::Eip1559(receipt), logs_bloom })
}
OpTxType::Eip7702 => {
let ReceiptWithBloom { receipt, logs_bloom } =
RlpDecodableReceipt::rlp_decode_with_bloom(buf)?;
Ok(ReceiptWithBloom { receipt: Self::Eip7702(receipt), logs_bloom })
}
OpTxType::Deposit => {
let ReceiptWithBloom { receipt, logs_bloom } =
RlpDecodableReceipt::rlp_decode_with_bloom(buf)?;
Ok(ReceiptWithBloom { receipt: Self::Deposit(receipt), logs_bloom })
}
}

Ok(ReceiptWithBloom {
receipt: Self {
cumulative_gas_used,
tx_type,
success,
logs,
deposit_nonce,
deposit_receipt_version,
},
logs_bloom,
})
}
}

impl Eip2718EncodableReceipt for OpReceipt {
fn eip2718_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
!self.tx_type.is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
!self.tx_type().is_legacy() as usize + self.rlp_header_inner(bloom).length_with_payload()
}

fn eip2718_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
if !self.tx_type.is_legacy() {
out.put_u8(self.tx_type as u8);
if !self.tx_type().is_legacy() {
out.put_u8(self.tx_type() as u8);
}
self.rlp_header_inner(bloom).encode(out);
self.rlp_encode_fields(bloom, out);
Expand All @@ -132,7 +128,7 @@ impl Eip2718EncodableReceipt for OpReceipt {
impl RlpEncodableReceipt for OpReceipt {
fn rlp_encoded_length_with_bloom(&self, bloom: &Bloom) -> usize {
let mut len = self.eip2718_encoded_length_with_bloom(bloom);
if !self.tx_type.is_legacy() {
if !self.tx_type().is_legacy() {
len += Header {
list: false,
payload_length: self.eip2718_encoded_length_with_bloom(bloom),
Expand All @@ -144,7 +140,7 @@ impl RlpEncodableReceipt for OpReceipt {
}

fn rlp_encode_with_bloom(&self, bloom: &Bloom, out: &mut dyn BufMut) {
if !self.tx_type.is_legacy() {
if !self.tx_type().is_legacy() {
Header { list: false, payload_length: self.eip2718_encoded_length_with_bloom(bloom) }
.encode(out);
}
Expand Down Expand Up @@ -181,65 +177,123 @@ impl TxReceipt for OpReceipt {
type Log = Log;

fn status_or_post_state(&self) -> Eip658Value {
self.success.into()
self.as_receipt().status_or_post_state()
}

fn status(&self) -> bool {
self.success
self.as_receipt().status()
}

fn bloom(&self) -> Bloom {
alloy_primitives::logs_bloom(self.logs())
self.as_receipt().bloom()
}

fn cumulative_gas_used(&self) -> u128 {
self.cumulative_gas_used as u128
self.as_receipt().cumulative_gas_used()
}

fn logs(&self) -> &[Log] {
&self.logs
self.as_receipt().logs()
}
}

impl Typed2718 for OpReceipt {
fn ty(&self) -> u8 {
self.tx_type as u8
self.tx_type().into()
}
}

impl InMemorySize for OpReceipt {
fn size(&self) -> usize {
self.tx_type.size() +
core::mem::size_of::<bool>() +
core::mem::size_of::<u64>() +
self.logs.capacity() * core::mem::size_of::<Log>()
self.as_receipt().size()
}
}

impl reth_primitives_traits::Receipt for OpReceipt {}

#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for OpReceipt {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let tx_type = u.arbitrary()?;

let (deposit_nonce, deposit_receipt_version) = if tx_type == OpTxType::Deposit {
let deposit_nonce: Option<u64> = u.arbitrary()?;
let deposit_receipt_version =
(deposit_nonce.is_some()).then(|| u.arbitrary()).transpose()?;

(deposit_nonce, deposit_receipt_version)
} else {
(None, None)
};

Ok(Self {
logs: u.arbitrary()?,
success: u.arbitrary()?,
cumulative_gas_used: u.arbitrary()?,
tx_type,
deposit_nonce,
deposit_receipt_version,
})
#[cfg(feature = "reth-codec")]
mod compact {
use super::*;
use alloc::borrow::Cow;
use reth_codecs::Compact;

#[derive(reth_codecs::CompactZstd)]
#[reth_zstd(
compressor = reth_zstd_compressors::RECEIPT_COMPRESSOR,
decompressor = reth_zstd_compressors::RECEIPT_DECOMPRESSOR
)]
struct CompactOpReceipt<'a> {
tx_type: OpTxType,
success: bool,
cumulative_gas_used: u64,
logs: Cow<'a, Vec<Log>>,
deposit_nonce: Option<u64>,
deposit_receipt_version: Option<u64>,
}

impl<'a> From<&'a OpReceipt> for CompactOpReceipt<'a> {
fn from(receipt: &'a OpReceipt) -> Self {
Self {
tx_type: receipt.tx_type(),
success: receipt.status(),
cumulative_gas_used: receipt.cumulative_gas_used() as u64,
logs: Cow::Borrowed(&receipt.as_receipt().logs),
deposit_nonce: if let OpReceipt::Deposit(receipt) = receipt {
receipt.deposit_nonce
} else {
None
},
deposit_receipt_version: if let OpReceipt::Deposit(receipt) = receipt {
receipt.deposit_receipt_version
} else {
None
},
}
}
}

impl From<CompactOpReceipt<'_>> for OpReceipt {
fn from(receipt: CompactOpReceipt<'_>) -> Self {
let CompactOpReceipt {
tx_type,
success,
cumulative_gas_used,
logs,
deposit_nonce,
deposit_receipt_version,
} = receipt;

let inner = Receipt {
status: success.into(),
cumulative_gas_used: cumulative_gas_used as u128,
logs: logs.into_owned(),
};

match tx_type {
OpTxType::Legacy => Self::Legacy(inner),
OpTxType::Eip2930 => Self::Eip2930(inner),
OpTxType::Eip1559 => Self::Eip1559(inner),
OpTxType::Eip7702 => Self::Eip7702(inner),
OpTxType::Deposit => Self::Deposit(OpDepositReceipt {
inner,
deposit_nonce,
deposit_receipt_version,
}),
}
}
}

impl Compact for OpReceipt {
fn to_compact<B>(&self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
CompactOpReceipt::from(self).to_compact(buf)
}

fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
let (receipt, buf) = CompactOpReceipt::from_compact(buf, len);
(receipt.into(), buf)
}
}
}
Loading

0 comments on commit 379db1d

Please sign in to comment.