use bytes::Bytes;
use ethereum_types::{Address, Bloom, BloomInput, H256};
use ethrex_crypto::Crypto;
use ethrex_rlp::{
decode::{RLPDecode, get_rlp_bytes_item_payload, is_encoded_as_bytes},
encode::RLPEncode,
error::RLPDecodeError,
structs::{Decoder, Encoder},
};
use serde::{Deserialize, Serialize};
use crate::types::TxType;
pub type Index = u64;
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct Receipt {
pub tx_type: TxType,
pub succeeded: bool,
pub cumulative_gas_used: u64,
pub logs: Vec<Log>,
}
impl Receipt {
pub fn new(tx_type: TxType, succeeded: bool, cumulative_gas_used: u64, logs: Vec<Log>) -> Self {
Self {
tx_type,
succeeded,
cumulative_gas_used,
logs,
}
}
pub fn encode_inner(&self) -> Vec<u8> {
let mut encoded_data = vec![];
let tx_type: u8 = self.tx_type as u8;
Encoder::new(&mut encoded_data)
.encode_field(&tx_type)
.encode_field(&self.succeeded)
.encode_field(&self.cumulative_gas_used)
.encode_field(&self.logs)
.finish();
encoded_data
}
pub fn encode_inner_with_bloom(&self, crypto: &dyn Crypto) -> Vec<u8> {
self.encode_inner_with_precomputed_bloom(bloom_from_logs(&self.logs, crypto))
}
pub fn encode_inner_with_precomputed_bloom(&self, bloom: Bloom) -> Vec<u8> {
let mut encode_buf = Vec::with_capacity(512);
if self.tx_type != TxType::Legacy {
encode_buf.push(self.tx_type as u8);
}
Encoder::new(&mut encode_buf)
.encode_field(&self.succeeded)
.encode_field(&self.cumulative_gas_used)
.encode_field(&bloom)
.encode_field(&self.logs)
.finish();
encode_buf
}
}
pub fn bloom_from_logs(logs: &[Log], crypto: &dyn Crypto) -> Bloom {
let mut bloom = Bloom::zero();
for log in logs {
let address_hash = crypto.keccak256(log.address.as_bytes());
bloom.accrue(BloomInput::Hash(&address_hash));
for topic in log.topics.iter() {
let topic_hash = crypto.keccak256(topic.as_bytes());
bloom.accrue(BloomInput::Hash(&topic_hash));
}
}
bloom
}
impl RLPEncode for Receipt {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
let encoded_inner = self.encode_inner();
buf.put_slice(&encoded_inner);
}
}
impl RLPDecode for Receipt {
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
let decoder = Decoder::new(rlp)?;
let (tx_type, decoder): (u8, _) = decoder.decode_field("tx-type")?;
let (succeeded, decoder) = decoder.decode_field("succeeded")?;
let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
let (logs, decoder) = decoder.decode_field("logs")?;
let Some(tx_type) = TxType::from_u8(tx_type) else {
return Err(RLPDecodeError::Custom(
"Invalid transaction type".to_string(),
));
};
Ok((
Receipt {
tx_type,
succeeded,
cumulative_gas_used,
logs,
},
decoder.finish()?,
))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub struct ReceiptWithBloom {
pub tx_type: TxType,
pub succeeded: bool,
pub cumulative_gas_used: u64,
pub bloom: Bloom,
pub logs: Vec<Log>,
}
impl ReceiptWithBloom {
pub fn new(tx_type: TxType, succeeded: bool, cumulative_gas_used: u64, logs: Vec<Log>) -> Self {
Self {
tx_type,
succeeded,
cumulative_gas_used,
bloom: bloom_from_logs(&logs, ðrex_crypto::NativeCrypto),
logs,
}
}
pub fn encode_inner(&self) -> Vec<u8> {
let mut encode_buff = match self.tx_type {
TxType::Legacy => {
vec![]
}
_ => {
vec![self.tx_type as u8]
}
};
Encoder::new(&mut encode_buff)
.encode_field(&self.succeeded)
.encode_field(&self.cumulative_gas_used)
.encode_field(&self.bloom)
.encode_field(&self.logs)
.finish();
encode_buff
}
pub fn decode_inner(rlp: &[u8]) -> Result<Self, RLPDecodeError> {
let (tx_type, rlp) = match rlp.first() {
Some(tx_type) if *tx_type < 0x7f => {
let tx_type = match tx_type {
0x0 => TxType::Legacy,
0x1 => TxType::EIP2930,
0x2 => TxType::EIP1559,
0x3 => TxType::EIP4844,
0x4 => TxType::EIP7702,
0x7d => TxType::FeeToken,
0x7e => TxType::Privileged,
ty => {
return Err(RLPDecodeError::Custom(format!(
"Invalid transaction type: {ty}"
)));
}
};
(tx_type, &rlp[1..])
}
_ => (TxType::Legacy, rlp),
};
let decoder = Decoder::new(rlp)?;
let (succeeded, decoder) = decoder.decode_field("succeeded")?;
let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
let (bloom, decoder) = decoder.decode_field("bloom")?;
let (logs, decoder) = decoder.decode_field("logs")?;
decoder.finish()?;
Ok(Self {
tx_type,
succeeded,
cumulative_gas_used,
bloom,
logs,
})
}
}
impl RLPEncode for ReceiptWithBloom {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
match self.tx_type {
TxType::Legacy => {
let legacy_encoded = self.encode_inner();
buf.put_slice(&legacy_encoded);
}
_ => {
let typed_recepipt_encoded = self.encode_inner();
let bytes = Bytes::from(typed_recepipt_encoded);
bytes.encode(buf);
}
};
}
}
impl RLPDecode for ReceiptWithBloom {
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
let (tx_type, rlp) = if is_encoded_as_bytes(rlp)? {
let payload = get_rlp_bytes_item_payload(rlp)?;
let tx_type = match payload.first().ok_or(RLPDecodeError::InvalidLength)? {
0x0 => TxType::Legacy,
0x1 => TxType::EIP2930,
0x2 => TxType::EIP1559,
0x3 => TxType::EIP4844,
0x4 => TxType::EIP7702,
0x7d => TxType::FeeToken,
0x7e => TxType::Privileged,
ty => {
return Err(RLPDecodeError::Custom(format!(
"Invalid transaction type: {ty}"
)));
}
};
(tx_type, &payload[1..])
} else {
(TxType::Legacy, rlp)
};
let decoder = Decoder::new(rlp)?;
let (succeeded, decoder) = decoder.decode_field("succeeded")?;
let (cumulative_gas_used, decoder) = decoder.decode_field("cumulative_gas_used")?;
let (bloom, decoder) = decoder.decode_field("bloom")?;
let (logs, decoder) = decoder.decode_field("logs")?;
Ok((
ReceiptWithBloom {
tx_type,
succeeded,
cumulative_gas_used,
bloom,
logs,
},
decoder.finish()?,
))
}
}
impl From<&Receipt> for ReceiptWithBloom {
fn from(receipt: &Receipt) -> Self {
Self {
tx_type: receipt.tx_type,
succeeded: receipt.succeeded,
cumulative_gas_used: receipt.cumulative_gas_used,
bloom: bloom_from_logs(&receipt.logs, ðrex_crypto::NativeCrypto),
logs: receipt.logs.clone(),
}
}
}
impl From<&ReceiptWithBloom> for Receipt {
fn from(receipt: &ReceiptWithBloom) -> Self {
Self {
tx_type: receipt.tx_type,
succeeded: receipt.succeeded,
cumulative_gas_used: receipt.cumulative_gas_used,
logs: receipt.logs.clone(),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Log {
pub address: Address,
pub topics: Vec<H256>,
pub data: Bytes,
}
impl RLPEncode for Log {
fn encode(&self, buf: &mut dyn bytes::BufMut) {
Encoder::new(buf)
.encode_field(&self.address)
.encode_field(&self.topics)
.encode_field(&self.data)
.finish();
}
}
impl RLPDecode for Log {
fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), RLPDecodeError> {
let decoder = Decoder::new(rlp)?;
let (address, decoder) = decoder.decode_field("address")?;
let (topics, decoder) = decoder.decode_field("topics")?;
let (data, decoder) = decoder.decode_field("data")?;
let log = Log {
address,
topics,
data,
};
Ok((log, decoder.finish()?))
}
}
#[cfg(test)]
mod test {
use super::*;
fn h256_from_hex(s: &str) -> H256 {
H256::from_slice(&hex::decode(s).unwrap())
}
#[test]
fn test_encode_decode_receipt_legacy() {
let receipt = Receipt {
tx_type: TxType::Legacy,
succeeded: true,
cumulative_gas_used: 1200,
logs: vec![Log {
address: Address::random(),
topics: vec![],
data: Bytes::from_static(b"foo"),
}],
};
let encoded_receipt = receipt.encode_to_vec();
assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
}
#[test]
fn test_encode_decode_receipt_non_legacy() {
let receipt = Receipt {
tx_type: TxType::EIP4844,
succeeded: true,
cumulative_gas_used: 1500,
logs: vec![Log {
address: Address::random(),
topics: vec![],
data: Bytes::from_static(b"bar"),
}],
};
let encoded_receipt = receipt.encode_to_vec();
assert_eq!(receipt, Receipt::decode(&encoded_receipt).unwrap())
}
#[test]
fn test_encode_decode_inner_receipt_legacy() {
let receipt = ReceiptWithBloom {
tx_type: TxType::Legacy,
succeeded: true,
cumulative_gas_used: 1200,
bloom: Bloom::random(),
logs: vec![Log {
address: Address::random(),
topics: vec![],
data: Bytes::from_static(b"foo"),
}],
};
let encoded_receipt = receipt.encode_inner();
assert_eq!(
receipt,
ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap()
)
}
#[test]
fn test_encode_decode_receipt_inner_non_legacy() {
let receipt = ReceiptWithBloom {
tx_type: TxType::EIP4844,
succeeded: true,
cumulative_gas_used: 1500,
bloom: Bloom::random(),
logs: vec![Log {
address: Address::random(),
topics: vec![],
data: Bytes::from_static(b"bar"),
}],
};
let encoded_receipt = receipt.encode_inner();
assert_eq!(
receipt,
ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap()
)
}
#[test]
fn test_encode_receipt_with_bloom() {
let receipt = Receipt {
tx_type: TxType::EIP1559,
succeeded: true,
cumulative_gas_used: 1500,
logs: vec![Log {
address: Address::random(),
topics: vec![
h256_from_hex(
"e70c0d1060ffbafc84e0e18d028245de3deeb0f41ecbade6562fa657d85ae945",
),
h256_from_hex(
"e7e9cd61c8c6cb313324d785aa130fe50a7b9885e4d1d7700a327c5e9ae4e183",
),
h256_from_hex(
"666d827b9db958c08f7186f127e3d9ea6a97288bcc4b527951ce493f6e2b76c4",
),
h256_from_hex(
"28b4366544dccafad7b61138e9ada51706e85bb217a20cfa1c86e2648f8f369a",
),
h256_from_hex(
"85cf9717f65c70d71cc6175f653512c13ce7b6a9bc5d9c2b9c49b2d2d6cb9536",
),
],
data: Bytes::from_static(b"bar"),
}],
};
let encoded_receipt = receipt.encode_inner_with_bloom(ðrex_crypto::NativeCrypto);
let correct_bloom = {
let mut bloom = Bloom::zero();
for log in receipt.logs {
bloom.accrue(BloomInput::Raw(log.address.as_ref()));
for topic in log.topics.iter() {
bloom.accrue(BloomInput::Raw(topic.as_ref()));
}
}
bloom
};
let receipt_with_bloom = ReceiptWithBloom::decode_inner(&encoded_receipt).unwrap();
assert_eq!(receipt_with_bloom.bloom, correct_bloom);
}
}