op_alloy_consensus/receipt/
envelope.rsuse crate::{OpDepositReceipt, OpDepositReceiptWithBloom, OpTxType};
use alloc::vec::Vec;
use alloy_consensus::{Eip658Value, Receipt, ReceiptWithBloom, TxReceipt};
use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718};
use alloy_primitives::{logs_bloom, Bloom, Log};
use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(tag = "type"))]
#[non_exhaustive]
pub enum OpReceiptEnvelope<T = Log> {
#[cfg_attr(feature = "serde", serde(rename = "0x0", alias = "0x00"))]
Legacy(ReceiptWithBloom<T>),
#[cfg_attr(feature = "serde", serde(rename = "0x1", alias = "0x01"))]
Eip2930(ReceiptWithBloom<T>),
#[cfg_attr(feature = "serde", serde(rename = "0x2", alias = "0x02"))]
Eip1559(ReceiptWithBloom<T>),
#[cfg_attr(feature = "serde", serde(rename = "0x4", alias = "0x04"))]
Eip7702(ReceiptWithBloom<T>),
#[cfg_attr(feature = "serde", serde(rename = "0x7e", alias = "0x7E"))]
Deposit(OpDepositReceiptWithBloom<T>),
}
impl OpReceiptEnvelope<Log> {
pub fn from_parts<'a>(
status: bool,
cumulative_gas_used: u128,
logs: impl IntoIterator<Item = &'a Log>,
tx_type: OpTxType,
deposit_nonce: Option<u64>,
deposit_receipt_version: Option<u64>,
) -> Self {
let logs = logs.into_iter().cloned().collect::<Vec<_>>();
let logs_bloom = logs_bloom(&logs);
let inner_receipt =
Receipt { status: Eip658Value::Eip658(status), cumulative_gas_used, logs };
match tx_type {
OpTxType::Legacy => {
Self::Legacy(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
}
OpTxType::Eip2930 => {
Self::Eip2930(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
}
OpTxType::Eip1559 => {
Self::Eip1559(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
}
OpTxType::Eip7702 => {
Self::Eip7702(ReceiptWithBloom { receipt: inner_receipt, logs_bloom })
}
OpTxType::Deposit => {
let inner = OpDepositReceiptWithBloom {
receipt: OpDepositReceipt {
inner: inner_receipt,
deposit_nonce,
deposit_receipt_version,
},
logs_bloom,
};
Self::Deposit(inner)
}
}
}
}
impl<T> OpReceiptEnvelope<T> {
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,
}
}
pub fn is_success(&self) -> bool {
self.status()
}
pub fn status(&self) -> bool {
self.as_receipt().unwrap().status.coerce_status()
}
pub fn cumulative_gas_used(&self) -> u128 {
self.as_receipt().unwrap().cumulative_gas_used
}
pub fn logs(&self) -> &[T] {
&self.as_receipt().unwrap().logs
}
pub const fn logs_bloom(&self) -> &Bloom {
match self {
Self::Legacy(t) => &t.logs_bloom,
Self::Eip2930(t) => &t.logs_bloom,
Self::Eip1559(t) => &t.logs_bloom,
Self::Eip7702(t) => &t.logs_bloom,
Self::Deposit(t) => &t.logs_bloom,
}
}
pub fn deposit_nonce(&self) -> Option<u64> {
self.as_deposit_receipt().and_then(|r| r.deposit_nonce)
}
pub fn deposit_receipt_version(&self) -> Option<u64> {
self.as_deposit_receipt().and_then(|r| r.deposit_receipt_version)
}
pub const fn as_deposit_receipt_with_bloom(&self) -> Option<&OpDepositReceiptWithBloom<T>> {
match self {
Self::Deposit(t) => Some(t),
_ => None,
}
}
pub const fn as_deposit_receipt(&self) -> Option<&OpDepositReceipt<T>> {
match self {
Self::Deposit(t) => Some(&t.receipt),
_ => None,
}
}
pub const fn as_receipt(&self) -> Option<&Receipt<T>> {
match self {
Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip7702(t) => {
Some(&t.receipt)
}
Self::Deposit(t) => Some(&t.receipt.inner),
}
}
}
impl OpReceiptEnvelope {
pub fn inner_length(&self) -> usize {
match self {
Self::Legacy(t) => t.length(),
Self::Eip2930(t) => t.length(),
Self::Eip1559(t) => t.length(),
Self::Eip7702(t) => t.length(),
Self::Deposit(t) => t.length(),
}
}
pub fn rlp_payload_length(&self) -> usize {
let length = self.inner_length();
match self {
Self::Legacy(_) => length,
_ => length + 1,
}
}
}
impl<T> TxReceipt<T> for OpReceiptEnvelope<T>
where
T: Clone + core::fmt::Debug + PartialEq + Eq + Send + Sync,
{
fn status_or_post_state(&self) -> Eip658Value {
self.as_receipt().unwrap().status
}
fn status(&self) -> bool {
self.as_receipt().unwrap().status.coerce_status()
}
fn bloom(&self) -> Bloom {
*self.logs_bloom()
}
fn bloom_cheap(&self) -> Option<Bloom> {
Some(self.bloom())
}
fn cumulative_gas_used(&self) -> u128 {
self.as_receipt().unwrap().cumulative_gas_used
}
fn logs(&self) -> &[T] {
&self.as_receipt().unwrap().logs
}
}
impl Encodable for OpReceiptEnvelope {
fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
self.network_encode(out)
}
fn length(&self) -> usize {
let mut payload_length = self.rlp_payload_length();
if !self.is_legacy() {
payload_length += length_of_length(payload_length);
}
payload_length
}
}
impl Decodable for OpReceiptEnvelope {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Self::network_decode(buf)
.map_or_else(|_| Err(alloy_rlp::Error::Custom("Unexpected type")), Ok)
}
}
impl Encodable2718 for OpReceiptEnvelope {
fn type_flag(&self) -> Option<u8> {
match self {
Self::Legacy(_) => None,
Self::Eip2930(_) => Some(OpTxType::Eip2930 as u8),
Self::Eip1559(_) => Some(OpTxType::Eip1559 as u8),
Self::Eip7702(_) => Some(OpTxType::Eip7702 as u8),
Self::Deposit(_) => Some(OpTxType::Deposit as u8),
}
}
fn encode_2718_len(&self) -> usize {
self.inner_length() + !self.is_legacy() as usize
}
fn encode_2718(&self, out: &mut dyn BufMut) {
match self.type_flag() {
None => {}
Some(ty) => out.put_u8(ty),
}
match self {
Self::Deposit(t) => t.encode(out),
Self::Legacy(t) | Self::Eip2930(t) | Self::Eip1559(t) | Self::Eip7702(t) => {
t.encode(out)
}
}
}
}
impl Decodable2718 for OpReceiptEnvelope {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
match ty.try_into().map_err(|_| Eip2718Error::UnexpectedType(ty))? {
OpTxType::Legacy => {
Err(alloy_rlp::Error::Custom("type-0 eip2718 transactions are not supported")
.into())
}
OpTxType::Eip1559 => Ok(Self::Eip1559(Decodable::decode(buf)?)),
OpTxType::Eip7702 => Ok(Self::Eip7702(Decodable::decode(buf)?)),
OpTxType::Eip2930 => Ok(Self::Eip2930(Decodable::decode(buf)?)),
OpTxType::Deposit => Ok(Self::Deposit(Decodable::decode(buf)?)),
}
}
fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
Ok(Self::Legacy(Decodable::decode(buf)?))
}
}
#[cfg(all(test, feature = "arbitrary"))]
impl<'a, T> arbitrary::Arbitrary<'a> for OpReceiptEnvelope<T>
where
T: arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
match u.int_in_range(0..=4)? {
0 => Ok(Self::Legacy(ReceiptWithBloom::<T>::arbitrary(u)?)),
1 => Ok(Self::Eip2930(ReceiptWithBloom::<T>::arbitrary(u)?)),
2 => Ok(Self::Eip1559(ReceiptWithBloom::<T>::arbitrary(u)?)),
_ => Ok(Self::Deposit(OpDepositReceiptWithBloom::<T>::arbitrary(u)?)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_consensus::{Receipt, ReceiptWithBloom};
use alloy_eips::eip2718::Encodable2718;
use alloy_primitives::{address, b256, bytes, hex, Log, LogData};
use alloy_rlp::Encodable;
#[cfg(not(feature = "std"))]
use alloc::{vec, vec::Vec};
#[test]
fn encode_legacy_receipt() {
let expected = hex!("f901668001b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f85ff85d940000000000000000000000000000000000000011f842a0000000000000000000000000000000000000000000000000000000000000deada0000000000000000000000000000000000000000000000000000000000000beef830100ff");
let mut data = vec![];
let receipt =
OpReceiptEnvelope::Legacy(ReceiptWithBloom {
receipt: Receipt {
status: false.into(),
cumulative_gas_used: 0x1u128,
logs: vec![Log {
address: address!("0000000000000000000000000000000000000011"),
data: LogData::new_unchecked(
vec![
b256!("000000000000000000000000000000000000000000000000000000000000dead"),
b256!("000000000000000000000000000000000000000000000000000000000000beef"),
],
bytes!("0100ff"),
),
}],
},
logs_bloom: [0; 256].into(),
});
receipt.network_encode(&mut data);
assert_eq!(receipt.length(), expected.len());
assert_eq!(data, expected);
}
#[test]
fn legacy_receipt_from_parts() {
let receipt =
OpReceiptEnvelope::from_parts(true, 100, vec![], OpTxType::Legacy, None, None);
assert!(receipt.status());
assert_eq!(receipt.cumulative_gas_used(), 100);
assert_eq!(receipt.logs().len(), 0);
assert_eq!(receipt.tx_type(), OpTxType::Legacy);
}
#[test]
fn deposit_receipt_from_parts() {
let receipt =
OpReceiptEnvelope::from_parts(true, 100, vec![], OpTxType::Deposit, Some(1), Some(2));
assert!(receipt.status());
assert_eq!(receipt.cumulative_gas_used(), 100);
assert_eq!(receipt.logs().len(), 0);
assert_eq!(receipt.tx_type(), OpTxType::Deposit);
assert_eq!(receipt.deposit_nonce(), Some(1));
assert_eq!(receipt.deposit_receipt_version(), Some(2));
}
}