use ethrex_common::{
Address, Bloom, Bytes, H256,
constants::GAS_PER_BLOB,
evm::calculate_create_address,
serde_utils,
types::{
BlockHash, BlockHeader, BlockNumber, Log, Receipt, Transaction, TxKind, TxType,
bloom_from_logs,
},
};
use ethrex_crypto::NativeCrypto;
use serde::{Deserialize, Serialize};
use crate::utils::RpcErr;
#[derive(Debug, Serialize, Deserialize)]
pub struct RpcReceipt {
#[serde(flatten)]
pub receipt: RpcReceiptInfo,
pub logs: Vec<RpcLog>,
#[serde(flatten)]
pub tx_info: RpcReceiptTxInfo,
#[serde(flatten)]
pub block_info: RpcReceiptBlockInfo,
}
impl RpcReceipt {
pub fn new(
receipt: Receipt,
tx_info: RpcReceiptTxInfo,
block_info: RpcReceiptBlockInfo,
init_log_index: u64,
) -> Self {
let mut logs = vec![];
let mut log_index = init_log_index;
for log in receipt.logs.clone() {
logs.push(RpcLog::new(log, log_index, &tx_info, &block_info));
log_index += 1;
}
Self {
receipt: receipt.into(),
logs,
tx_info,
block_info,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcReceiptInfo {
#[serde(rename = "type")]
pub tx_type: TxType,
#[serde(with = "serde_utils::bool")]
pub status: bool,
#[serde(with = "serde_utils::u64::hex_str")]
pub cumulative_gas_used: u64,
pub logs_bloom: Bloom,
}
impl From<Receipt> for RpcReceiptInfo {
fn from(receipt: Receipt) -> Self {
Self {
tx_type: receipt.tx_type,
status: receipt.succeeded,
cumulative_gas_used: receipt.cumulative_gas_used,
logs_bloom: bloom_from_logs(&receipt.logs, ðrex_crypto::NativeCrypto),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RpcLog {
#[serde(flatten)]
pub log: RpcLogInfo,
#[serde(with = "serde_utils::u64::hex_str")]
pub log_index: u64,
pub removed: bool,
pub transaction_hash: H256,
#[serde(with = "serde_utils::u64::hex_str")]
pub transaction_index: u64,
pub block_hash: BlockHash,
#[serde(with = "serde_utils::u64::hex_str")]
pub block_number: BlockNumber,
}
impl RpcLog {
pub fn new(
log: Log,
log_index: u64,
tx_info: &RpcReceiptTxInfo,
block_info: &RpcReceiptBlockInfo,
) -> RpcLog {
Self {
log: log.into(),
log_index,
removed: false,
transaction_hash: tx_info.transaction_hash,
transaction_index: tx_info.transaction_index,
block_hash: block_info.block_hash,
block_number: block_info.block_number,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct RpcLogInfo {
pub address: Address,
pub topics: Vec<H256>,
#[serde(with = "serde_utils::bytes")]
pub data: Bytes,
}
impl From<Log> for RpcLogInfo {
fn from(log: Log) -> Self {
Self {
address: log.address,
topics: log.topics,
data: log.data,
}
}
}
#[derive(Debug, Serialize, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcReceiptBlockInfo {
pub block_hash: BlockHash,
#[serde(with = "serde_utils::u64::hex_str")]
pub block_number: BlockNumber,
}
impl RpcReceiptBlockInfo {
pub fn from_block_header(block_header: BlockHeader) -> Self {
RpcReceiptBlockInfo {
block_hash: block_header.hash(),
block_number: block_header.number,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RpcReceiptTxInfo {
pub transaction_hash: H256,
#[serde(with = "ethrex_common::serde_utils::u64::hex_str")]
pub transaction_index: u64,
pub from: Address,
pub to: Option<Address>,
pub contract_address: Option<Address>,
#[serde(with = "ethrex_common::serde_utils::u64::hex_str")]
pub gas_used: u64,
#[serde(with = "ethrex_common::serde_utils::u64::hex_str")]
pub effective_gas_price: u64,
#[serde(
skip_serializing_if = "Option::is_none",
with = "serde_utils::u64::hex_str_opt",
default = "Option::default"
)]
pub blob_gas_price: Option<u64>,
#[serde(
skip_serializing_if = "Option::is_none",
with = "serde_utils::u64::hex_str_opt",
default = "Option::default"
)]
pub blob_gas_used: Option<u64>,
}
impl RpcReceiptTxInfo {
pub fn from_transaction(
transaction: Transaction,
index: u64,
gas_used: u64,
block_blob_gas_price: u64,
base_fee_per_gas: Option<u64>,
) -> Result<Self, RpcErr> {
let nonce = transaction.nonce();
let from = transaction.sender(&NativeCrypto)?;
let transaction_hash = transaction.hash();
let effective_gas_price =
u64::try_from(transaction.effective_gas_price(base_fee_per_gas).ok_or(
RpcErr::Internal("Could not get effective gas price from tx".into()),
)?)
.map_err(|_| RpcErr::Internal("effective gas price overflows u64".into()))?;
let transaction_index = index;
let (blob_gas_price, blob_gas_used) = match &transaction {
Transaction::EIP4844Transaction(tx) => (
Some(block_blob_gas_price),
Some(tx.blob_versioned_hashes.len() as u64 * GAS_PER_BLOB as u64),
),
_ => (None, None),
};
let (contract_address, to) = match transaction.to() {
TxKind::Create => (Some(calculate_create_address(from, nonce)), None),
TxKind::Call(addr) => (None, Some(addr)),
};
Ok(Self {
transaction_hash,
transaction_index,
from,
to,
contract_address,
gas_used,
effective_gas_price,
blob_gas_price,
blob_gas_used,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use ethrex_common::{
Bytes,
types::{Log, TxType},
};
use hex_literal::hex;
#[test]
fn serialize_receipt() {
let receipt = RpcReceipt::new(
Receipt {
tx_type: TxType::EIP4844,
succeeded: true,
cumulative_gas_used: 147,
logs: vec![Log {
address: Address::zero(),
topics: vec![],
data: Bytes::from_static(b"strawberry"),
}],
},
RpcReceiptTxInfo {
transaction_hash: H256::zero(),
transaction_index: 1,
from: Address::zero(),
to: Some(Address::from(hex!(
"7435ed30a8b4aeb0877cef0c6e8cffe834eb865f"
))),
contract_address: None,
gas_used: 147,
effective_gas_price: 157,
blob_gas_price: None,
blob_gas_used: None,
},
RpcReceiptBlockInfo {
block_hash: BlockHash::zero(),
block_number: 3,
},
0,
);
let expected = r#"{"type":"0x3","status":"0x1","cumulativeGasUsed":"0x93","logsBloom":"0x00000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","logs":[{"address":"0x0000000000000000000000000000000000000000","topics":[],"data":"0x73747261776265727279","logIndex":"0x0","removed":false,"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x1","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x3"}],"transactionHash":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionIndex":"0x1","from":"0x0000000000000000000000000000000000000000","to":"0x7435ed30a8b4aeb0877cef0c6e8cffe834eb865f","contractAddress":null,"gasUsed":"0x93","effectiveGasPrice":"0x9d","blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","blockNumber":"0x3"}"#;
assert_eq!(serde_json::to_string(&receipt).unwrap(), expected);
}
}