Skip to content

Commit

Permalink
feat: Add OP receipt fields (#41)
Browse files Browse the repository at this point in the history
* feat: Add `TxDeposit` type

* feat: Add OP fields to `Receipt`
  • Loading branch information
clabby authored Feb 24, 2024
1 parent 00a715e commit d9c45d0
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 16 deletions.
1 change: 1 addition & 0 deletions crates/derive/src/types/eips/eip2718.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const TX_TYPE_BYTE_MAX: u8 = 0x7f;
/// [EIP-2718] decoding errors.
///
/// [EIP-2718]: https://eips.ethereum.org/EIPS/eip-2718
#[derive(Debug)]
pub enum Eip2718Error {
/// Rlp error from [`alloy_rlp`].
RlpError(alloy_rlp::Error),
Expand Down
165 changes: 150 additions & 15 deletions crates/derive/src/types/receipt.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
//! This module contains the receipt types used within the derivation pipeline.

use core::cmp::Ordering;

use crate::types::transaction::TxType;
use alloc::vec::Vec;
use alloy_primitives::{Bloom, Log};
use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable};
use alloy_rlp::{length_of_length, Buf, BufMut, BytesMut, Decodable, Encodable};

/// Receipt containing result of transaction execution.
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Receipt {
/// The transaction type of the receipt.
pub tx_type: TxType,
/// If transaction is executed successfully.
///
/// This is the `statusCode`
Expand All @@ -15,6 +20,15 @@ pub struct Receipt {
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>,
}

impl Receipt {
Expand Down Expand Up @@ -69,10 +83,19 @@ impl ReceiptWithBloom {
}

fn payload_len(&self) -> usize {
self.receipt.success.length()
let mut payload_len = self.receipt.success.length()
+ self.receipt.cumulative_gas_used.length()
+ self.bloom.length()
+ self.receipt.logs.len()
+ self.receipt.logs.len();
if self.receipt.tx_type == TxType::Deposit {
if let Some(deposit_nonce) = self.receipt.deposit_nonce {
payload_len += deposit_nonce.length();
}
if let Some(deposit_receipt_version) = self.receipt.deposit_receipt_version {
payload_len += deposit_receipt_version.length();
}
}
payload_len
}

/// Returns the rlp header for the receipt payload.
Expand All @@ -90,10 +113,56 @@ impl ReceiptWithBloom {
self.receipt.cumulative_gas_used.encode(out);
self.bloom.encode(out);
self.receipt.logs.encode(out);

if self.receipt.tx_type == TxType::Deposit {
if let Some(deposit_nonce) = self.receipt.deposit_nonce {
deposit_nonce.encode(out)
}
if let Some(deposit_receipt_version) = self.receipt.deposit_receipt_version {
deposit_receipt_version.encode(out)
}
}
}

fn encode_inner(&self, out: &mut dyn BufMut, with_header: bool) {
if matches!(self.receipt.tx_type, TxType::Legacy) {
self.encode_fields(out);
return;
}

let mut payload = BytesMut::new();
self.encode_fields(&mut payload);

if with_header {
let payload_length = payload.len() + 1;
let header = alloy_rlp::Header {
list: false,
payload_length,
};
header.encode(out);
}

match self.receipt.tx_type {
TxType::Legacy => unreachable!("legacy already handled"),

TxType::Eip2930 => {
out.put_u8(0x01);
}
TxType::Eip1559 => {
out.put_u8(0x02);
}
TxType::Eip4844 => {
out.put_u8(0x03);
}
TxType::Deposit => {
out.put_u8(0x7E);
}
}
out.put_slice(payload.as_ref());
}

/// Decodes the receipt payload
fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
fn decode_receipt(buf: &mut &[u8], tx_type: TxType) -> alloy_rlp::Result<Self> {
let b: &mut &[u8] = &mut &**buf;
let rlp_head = alloy_rlp::Header::decode(b)?;
if !rlp_head.list {
Expand All @@ -106,10 +175,33 @@ impl ReceiptWithBloom {
let bloom = Decodable::decode(b)?;
let logs = Decodable::decode(b)?;

let receipt = Receipt {
success,
cumulative_gas_used,
logs,
let receipt = match tx_type {
TxType::Deposit => {
let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0;
let deposit_nonce = remaining(b)
.then(|| alloy_rlp::Decodable::decode(b))
.transpose()?;
let deposit_receipt_version = remaining(b)
.then(|| alloy_rlp::Decodable::decode(b))
.transpose()?;

Receipt {
tx_type,
success,
cumulative_gas_used,
logs,
deposit_nonce,
deposit_receipt_version,
}
}
_ => Receipt {
tx_type,
success,
cumulative_gas_used,
logs,
deposit_nonce: None,
deposit_receipt_version: None,
},
};

let this = Self { receipt, bloom };
Expand All @@ -127,20 +219,63 @@ impl ReceiptWithBloom {

impl alloy_rlp::Encodable for ReceiptWithBloom {
fn encode(&self, out: &mut dyn BufMut) {
self.encode_fields(out);
self.encode_inner(out, true)
}

fn length(&self) -> usize {
let payload_length = self.receipt.success.length()
+ self.receipt.cumulative_gas_used.length()
+ self.bloom.length()
+ self.receipt.logs.length();
payload_length + length_of_length(payload_length)
let rlp_head = self.receipt_rlp_header();
let mut payload_len = length_of_length(rlp_head.payload_length) + rlp_head.payload_length;
// account for eip-2718 type prefix and set the list
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);
}

payload_len
}
}

impl alloy_rlp::Decodable for ReceiptWithBloom {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Self::decode_receipt(buf)
// a receipt is either encoded as a string (non legacy) or a list (legacy).
// We should not consume the buffer if we are decoding a legacy receipt, so let's
// check if the first byte is between 0x80 and 0xbf.
let rlp_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
"cannot decode a receipt from empty bytes",
))?;

match rlp_type.cmp(&alloy_rlp::EMPTY_LIST_CODE) {
Ordering::Less => {
// strip out the string header
let _header = alloy_rlp::Header::decode(buf)?;
let receipt_type = *buf.first().ok_or(alloy_rlp::Error::Custom(
"typed receipt cannot be decoded from an empty slice",
))?;
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)
}
0x7E => {
buf.advance(1);
Self::decode_receipt(buf, TxType::Deposit)
}
_ => Err(alloy_rlp::Error::Custom("invalid receipt type")),
}
}
Ordering::Equal => Err(alloy_rlp::Error::Custom(
"an empty list is not a valid receipt encoding",
)),
Ordering::Greater => Self::decode_receipt(buf, TxType::Legacy),
}
}
}
3 changes: 2 additions & 1 deletion crates/derive/src/types/transaction/envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ use alloy_rlp::{length_of_length, Decodable, Encodable};
/// [2930]: https://eips.ethereum.org/EIPS/eip-2930
/// [4844]: https://eips.ethereum.org/EIPS/eip-4844
#[repr(u8)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Default)]
pub enum TxType {
/// Wrapped legacy transaction type.
#[default]
Legacy = 0,
/// EIP-2930 transaction type.
Eip2930 = 1,
Expand Down

0 comments on commit d9c45d0

Please sign in to comment.