use alloc::vec::Vec;
use alloy_consensus::{Eip658Value, Receipt, ReceiptWithBloom, TxReceipt};
use alloy_eips::{
eip2718::{Decodable2718, Eip2718Error, Eip2718Result, Encodable2718},
Typed2718,
};
use alloy_primitives::{bytes::BufMut, Bloom, Log};
use alloy_rlp::{Decodable, Encodable};
use core::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[doc(alias = "AnyTransactionReceiptEnvelope", alias = "AnyTxReceiptEnvelope")]
pub struct AnyReceiptEnvelope<T = Log> {
#[cfg_attr(feature = "serde", serde(flatten))]
pub inner: ReceiptWithBloom<Receipt<T>>,
#[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
pub r#type: u8,
}
impl<T> AnyReceiptEnvelope<T> {
pub const fn is_legacy(&self) -> bool {
self.r#type == 0
}
}
impl<T: Encodable> AnyReceiptEnvelope<T> {
pub fn rlp_payload_length(&self) -> usize {
let length = self.inner.length();
if self.is_legacy() {
length
} else {
length + 1
}
}
}
impl<T> AnyReceiptEnvelope<T> {
pub const fn is_success(&self) -> bool {
self.status()
}
pub const fn status(&self) -> bool {
self.inner.receipt.status.coerce_status()
}
pub const fn bloom(&self) -> Bloom {
self.inner.logs_bloom
}
pub const fn bloom_ref(&self) -> &Bloom {
&self.inner.logs_bloom
}
pub const fn cumulative_gas_used(&self) -> u64 {
self.inner.receipt.cumulative_gas_used
}
pub fn logs(&self) -> &[T] {
&self.inner.receipt.logs
}
}
impl<T> TxReceipt for AnyReceiptEnvelope<T>
where
T: Clone + fmt::Debug + PartialEq + Eq + Send + Sync,
{
type Log = T;
fn status_or_post_state(&self) -> Eip658Value {
self.inner.receipt.status
}
fn status(&self) -> bool {
self.status()
}
fn bloom(&self) -> Bloom {
self.bloom()
}
fn cumulative_gas_used(&self) -> u64 {
self.cumulative_gas_used()
}
fn logs(&self) -> &[T] {
Self::logs(self)
}
fn into_logs(self) -> Vec<Self::Log> {
self.inner.receipt.logs
}
}
impl Typed2718 for AnyReceiptEnvelope {
fn ty(&self) -> u8 {
self.r#type
}
}
impl Encodable2718 for AnyReceiptEnvelope {
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),
}
self.inner.encode(out);
}
}
impl Decodable2718 for AnyReceiptEnvelope {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Eip2718Result<Self> {
if ty == 0 {
return Err(Eip2718Error::UnexpectedType(ty));
}
let receipt = Decodable::decode(buf)?;
Ok(Self { inner: receipt, r#type: ty })
}
fn fallback_decode(buf: &mut &[u8]) -> Eip2718Result<Self> {
let receipt = Decodable::decode(buf)?;
Ok(Self { inner: receipt, r#type: 0 })
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use alloy_rlp::Header;
fn envelope(ty: u8) -> AnyReceiptEnvelope {
AnyReceiptEnvelope { inner: Default::default(), r#type: ty }
}
fn network_framed(payload: &[u8]) -> Vec<u8> {
let mut out = Vec::new();
Header { list: false, payload_length: payload.len() }.encode(&mut out);
out.extend_from_slice(payload);
out
}
#[test]
fn legacy_receipt_roundtrips_untagged() {
let envelope = envelope(0);
let encoded = envelope.encoded_2718();
assert!(encoded[0] >= 0xc0, "sanity: legacy receipts are encoded as a bare RLP list");
let mut network = Vec::new();
envelope.network_encode(&mut network);
assert_eq!(network, encoded);
assert_eq!(AnyReceiptEnvelope::decode_2718_exact(&encoded).unwrap(), envelope);
assert_eq!(AnyReceiptEnvelope::network_decode(&mut encoded.as_slice()).unwrap(), envelope);
}
#[test]
fn typed_receipt_roundtrips() {
let envelope = envelope(0x7e);
let encoded = envelope.encoded_2718();
assert_eq!(encoded[0], 0x7e, "sanity: typed receipts are prefixed with their type byte");
let mut network = Vec::new();
envelope.network_encode(&mut network);
assert_eq!(network, network_framed(&encoded));
assert_eq!(AnyReceiptEnvelope::decode_2718_exact(&encoded).unwrap(), envelope);
assert_eq!(AnyReceiptEnvelope::network_decode(&mut network.as_slice()).unwrap(), envelope);
}
#[test]
fn tagged_legacy_receipt_is_rejected() {
let mut tagged = vec![0x00];
tagged.extend_from_slice(&envelope(0).encoded_2718());
let tagged_network = network_framed(&tagged);
assert!(matches!(
AnyReceiptEnvelope::decode_2718_exact(&tagged),
Err(Eip2718Error::UnexpectedType(0))
));
assert!(matches!(
AnyReceiptEnvelope::network_decode(&mut tagged_network.as_slice()),
Err(Eip2718Error::UnexpectedType(0))
));
}
}