use crate::account::Signature;
use crate::client::evm::tx::{AccessItem, EIP1559, Eip1559};
use crate::client::evm::u256::{B256, U256};
use crate::utils::{Address, Figure};
use serde::{Deserialize, Deserializer, de};
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Receipt {
pub transaction_hash: B256,
#[serde(default, deserialize_with = "opt_quantity")]
pub transaction_index: Option<u64>,
#[serde(default)]
pub block_hash: Option<B256>,
#[serde(default, deserialize_with = "opt_quantity")]
pub block_number: Option<u64>,
pub from: Address,
#[serde(default)]
pub to: Option<Address>,
#[serde(default)]
pub contract_address: Option<Address>,
#[serde(deserialize_with = "quantity")]
pub gas_used: u64,
#[serde(deserialize_with = "quantity")]
pub cumulative_gas_used: u64,
#[serde(default)]
pub effective_gas_price: Option<U256>,
#[serde(default, deserialize_with = "opt_status")]
pub status: Option<bool>,
pub logs: Vec<Log>,
#[serde(default, rename = "type", deserialize_with = "opt_byte")]
pub kind: Option<u8>,
}
impl Receipt {
#[must_use]
pub fn succeeded(&self) -> bool {
self.status == Some(true)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
pub hash: B256,
#[serde(default, rename = "type", deserialize_with = "byte")]
pub kind: u8,
#[serde(default, deserialize_with = "opt_quantity")]
pub chain_id: Option<u64>,
#[serde(deserialize_with = "quantity")]
pub nonce: u64,
#[serde(default)]
pub block_hash: Option<B256>,
#[serde(default, deserialize_with = "opt_quantity")]
pub block_number: Option<u64>,
#[serde(default, deserialize_with = "opt_quantity")]
pub transaction_index: Option<u64>,
pub from: Address,
#[serde(default)]
pub to: Option<Address>,
pub value: U256,
#[serde(deserialize_with = "quantity")]
pub gas: u64,
#[serde(default)]
pub gas_price: Option<U256>,
#[serde(default)]
pub max_fee_per_gas: Option<U256>,
#[serde(default)]
pub max_priority_fee_per_gas: Option<U256>,
#[serde(default)]
pub access_list: Vec<AccessItem>,
#[serde(deserialize_with = "bytes")]
pub input: Vec<u8>,
#[serde(default)]
pub r: Option<U256>,
#[serde(default)]
pub s: Option<U256>,
#[serde(default, deserialize_with = "opt_quantity")]
pub y_parity: Option<u64>,
}
impl Transaction {
#[must_use]
pub fn to_eip1559(&self) -> Option<Eip1559> {
if self.kind != EIP1559 {
return None;
}
Some(Eip1559 {
chain_id: self.chain_id?,
nonce: self.nonce,
max_priority_fee: self.max_priority_fee_per_gas?,
max_fee: self.max_fee_per_gas?,
gas: self.gas,
to: self.to,
value: self.value,
data: self.input.clone(),
access_list: self.access_list.clone(),
})
}
#[must_use]
pub fn signature(&self) -> Option<Signature> {
let parity = u8::try_from(self.y_parity?).ok().filter(|parity| *parity <= 1)?;
Some(Signature::from_parts(self.r?.to_be_bytes(), self.s?.to_be_bytes(), 27 + parity))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Block {
#[serde(deserialize_with = "quantity")]
pub number: u64,
pub hash: B256,
pub parent_hash: B256,
#[serde(deserialize_with = "quantity")]
pub timestamp: u64,
#[serde(deserialize_with = "quantity")]
pub gas_limit: u64,
#[serde(deserialize_with = "quantity")]
pub gas_used: u64,
#[serde(default)]
pub base_fee_per_gas: Option<U256>,
pub miner: Address,
pub transactions: BlockTransactions,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(untagged)]
pub enum BlockTransactions {
Hashes(Vec<B256>),
Full(Vec<Transaction>),
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Log {
pub address: Address,
pub topics: Vec<B256>,
#[serde(deserialize_with = "bytes")]
pub data: Vec<u8>,
#[serde(default, deserialize_with = "opt_quantity")]
pub block_number: Option<u64>,
#[serde(default)]
pub block_hash: Option<B256>,
#[serde(default)]
pub transaction_hash: Option<B256>,
#[serde(default, deserialize_with = "opt_quantity")]
pub transaction_index: Option<u64>,
#[serde(default, deserialize_with = "opt_quantity")]
pub log_index: Option<u64>,
#[serde(default)]
pub removed: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FeeHistory {
#[serde(deserialize_with = "quantity")]
pub oldest_block: u64,
pub base_fee_per_gas: Vec<U256>,
pub gas_used_ratio: Vec<Figure>,
#[serde(default)]
pub reward: Vec<Vec<U256>>,
}
impl FeeHistory {
#[must_use]
pub fn next_base_fee(&self) -> Option<U256> {
self.base_fee_per_gas.last().copied()
}
}
pub(crate) fn quantity<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u64, D::Error> {
let value = U256::deserialize(deserializer)?;
value.to_u64().ok_or_else(|| de::Error::custom(format!("{value} does not fit 64 bits")))
}
fn opt_quantity<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<u64>, D::Error> {
match Option::<U256>::deserialize(deserializer)? {
Some(value) => {
value.to_u64().map(Some).ok_or_else(|| de::Error::custom(format!("{value} does not fit 64 bits")))
}
None => Ok(None),
}
}
fn byte<'de, D: Deserializer<'de>>(deserializer: D) -> Result<u8, D::Error> {
let value = quantity(deserializer)?;
u8::try_from(value).map_err(|_| de::Error::custom(format!("{value} is not a transaction type")))
}
fn opt_byte<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<u8>, D::Error> {
match opt_quantity(deserializer)? {
Some(value) => {
u8::try_from(value).map(Some).map_err(|_| de::Error::custom(format!("{value} is not a transaction type")))
}
None => Ok(None),
}
}
fn opt_status<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<bool>, D::Error> {
match opt_quantity(deserializer)? {
Some(0) => Ok(Some(false)),
Some(1) => Ok(Some(true)),
Some(other) => Err(de::Error::custom(format!("{other} is not a receipt status"))),
None => Ok(None),
}
}
pub(crate) fn bytes<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
let text = <std::borrow::Cow<'de, str>>::deserialize(deserializer)?;
let digits = text.strip_prefix("0x").ok_or_else(|| de::Error::custom(format!("`{text}` is not 0x and hex")))?;
crate::utils::hex::decode(digits).map_err(de::Error::custom)
}
#[cfg(test)]
#[path = "tests/records.rs"]
mod tests;