hypersync_format/types/
mod.rs

1use arrayvec::ArrayVec;
2use serde::{Deserialize, Deserializer, Serialize};
3
4mod bloom_filter_wrapper;
5mod data;
6mod fixed_size_data;
7mod hex;
8mod quantity;
9mod transaction_status;
10mod transaction_type;
11pub mod uint;
12mod util;
13mod withdrawal;
14
15pub use bloom_filter_wrapper::FilterWrapper;
16pub use data::Data;
17pub use fixed_size_data::FixedSizeData;
18pub use hex::Hex;
19pub use quantity::Quantity;
20pub use transaction_status::TransactionStatus;
21pub use transaction_type::TransactionType;
22pub use withdrawal::Withdrawal;
23
24/// Evm block header object
25///
26/// See ethereum rpc spec for the meaning of fields
27#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
29#[serde(rename_all = "camelCase")]
30pub struct BlockHeader {
31    pub number: BlockNumber,
32    pub hash: Hash,
33    pub parent_hash: Hash,
34    pub nonce: Option<Nonce>,
35    #[serde(default)]
36    pub sha3_uncles: Hash,
37    pub logs_bloom: BloomFilter,
38    pub transactions_root: Hash,
39    pub state_root: Hash,
40    pub receipts_root: Hash,
41    pub miner: Address,
42    pub difficulty: Option<Quantity>,
43    pub total_difficulty: Option<Quantity>,
44    pub extra_data: Data,
45    pub size: Quantity,
46    pub gas_limit: Quantity,
47    pub gas_used: Quantity,
48    pub timestamp: Quantity,
49    pub uncles: Option<Vec<Hash>>,
50    pub base_fee_per_gas: Option<Quantity>,
51    pub blob_gas_used: Option<Quantity>,
52    pub excess_blob_gas: Option<Quantity>,
53    pub parent_beacon_block_root: Option<Hash>,
54    pub withdrawals_root: Option<Hash>,
55    pub withdrawals: Option<Vec<Withdrawal>>,
56    pub l1_block_number: Option<BlockNumber>,
57    pub send_count: Option<Quantity>,
58    pub send_root: Option<Hash>,
59    pub mix_hash: Option<Hash>,
60}
61
62/// Evm block object
63///
64/// A block will contain a header and either a list of full transaction objects or
65/// a list of only transaction hashes.
66#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
67#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
68#[serde(rename_all = "camelCase")]
69pub struct Block<Tx> {
70    #[serde(flatten)]
71    pub header: BlockHeader,
72    pub transactions: Vec<Tx>,
73}
74
75/// Evm transaction object
76///
77/// See ethereum rpc spec for the meaning of fields
78#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
79#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
80#[serde(rename_all = "camelCase")]
81pub struct Transaction {
82    pub block_hash: Hash,
83    pub block_number: BlockNumber,
84    pub from: Option<Address>,
85    pub gas: Quantity,
86    pub gas_price: Option<Quantity>,
87    pub hash: Hash,
88    pub input: Data,
89    pub nonce: Quantity,
90    pub to: Option<Address>,
91    pub transaction_index: TransactionIndex,
92    pub value: Quantity,
93    #[serde(rename = "type")]
94    pub type_: Option<TransactionType>,
95    pub v: Option<Quantity>,
96    pub r: Option<Quantity>,
97    pub s: Option<Quantity>,
98    pub y_parity: Option<Quantity>,
99    pub max_priority_fee_per_gas: Option<Quantity>,
100    pub max_fee_per_gas: Option<Quantity>,
101    pub chain_id: Option<Quantity>,
102    pub access_list: Option<Vec<AccessList>>,
103    pub authorization_list: Option<Vec<Authorization>>,
104    pub max_fee_per_blob_gas: Option<Quantity>,
105    pub blob_versioned_hashes: Option<Vec<Hash>>,
106    // OP stack fields
107    pub deposit_receipt_version: Option<Quantity>,
108    pub mint: Option<Quantity>,
109    pub source_hash: Option<Hash>,
110}
111
112/// Evm access list object
113///
114/// See ethereum rpc spec for the meaning of fields
115#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
116#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
117#[serde(rename_all = "camelCase")]
118pub struct AccessList {
119    pub address: Option<Address>,
120    pub storage_keys: Option<Vec<Hash>>,
121}
122
123/// Evm transaction authorization object
124///
125/// See ethereum rpc spec for the meaning of fields
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
128#[serde(rename_all = "camelCase")]
129pub struct Authorization {
130    pub chain_id: Quantity,
131    pub address: Address,
132    pub nonce: Quantity,
133    pub y_parity: Quantity,
134    pub r: Quantity,
135    pub s: Quantity,
136}
137
138/// Evm transaction receipt object
139///
140/// See ethereum rpc spec for the meaning of fields
141#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
142#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
143#[serde(rename_all = "camelCase")]
144pub struct TransactionReceipt {
145    pub transaction_hash: Hash,
146    pub transaction_index: TransactionIndex,
147    pub block_hash: Hash,
148    pub block_number: BlockNumber,
149    pub from: Address,
150    pub to: Option<Address>,
151    pub cumulative_gas_used: Quantity,
152    // Default null and undefined values
153    #[serde(default, deserialize_with = "nullable_default")]
154    pub effective_gas_price: Quantity,
155    pub gas_used: Quantity,
156    pub contract_address: Option<Address>,
157    pub logs: Vec<Log>,
158    pub logs_bloom: BloomFilter,
159    #[serde(rename = "type")]
160    pub type_: Option<TransactionType>,
161    pub root: Option<Hash>,
162    pub status: Option<TransactionStatus>,
163    pub l1_fee: Option<Quantity>,
164    pub l1_gas_price: Option<Quantity>,
165    pub l1_gas_used: Option<Quantity>,
166    // This is a float value printed as string, e.g. "0.69"
167    pub l1_fee_scalar: Option<String>,
168    pub gas_used_for_l1: Option<Quantity>,
169    pub blob_gas_price: Option<Quantity>,
170    // NOTE: These fields are needed for Optimism (not present on other chains)
171    pub deposit_nonce: Option<Quantity>,
172    pub deposit_receipt_version: Option<Quantity>,
173    pub blob_gas_used: Option<Quantity>,
174
175    // Optimism fields
176    pub l1_base_fee_scalar: Option<Quantity>,
177    pub l1_blob_base_fee: Option<Quantity>,
178    pub l1_blob_base_fee_scalar: Option<Quantity>,
179
180    // Arbitrum fields
181    pub l1_block_number: Option<Quantity>,
182}
183
184/// Deserialize with default for both null and undefined values
185fn nullable_default<'de, D, T>(deserializer: D) -> Result<T, D::Error>
186where
187    D: Deserializer<'de>,
188    T: Default + Deserialize<'de>,
189{
190    Ok(Option::<T>::deserialize(deserializer)?.unwrap_or_default())
191}
192
193/// Evm log object
194///
195/// See ethereum rpc spec for the meaning of fields
196#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct Log {
199    pub removed: Option<bool>,
200    pub log_index: LogIndex,
201    pub transaction_index: TransactionIndex,
202    pub transaction_hash: Hash,
203    pub block_hash: Hash,
204    pub block_number: BlockNumber,
205    pub address: Address,
206    pub data: Data,
207    pub topics: ArrayVec<LogArgument, 4>,
208    // // Many Modern RPCs return blockTimestamp, but it's not part of the official spec (yet).
209    // // EIP: https://ethereum-magicians.org/t/proposal-for-adding-blocktimestamp-to-logs-object-returned-by-eth-getlogs-and-related-requests/11183/7 - reth has already merged this: https://github.com/paradigmxyz/reth/pull/7606
210    pub block_timestamp: Option<Quantity>,
211}
212
213#[cfg(feature = "arbitrary")]
214impl<'input> arbitrary::Arbitrary<'input> for Log {
215    fn arbitrary(u: &mut arbitrary::Unstructured<'input>) -> arbitrary::Result<Self> {
216        let num_topics = u.arbitrary::<u8>()? % 4 + 1;
217        let mut topics = ArrayVec::<LogArgument, 4>::new();
218        for _ in 0..num_topics {
219            topics.push(u.arbitrary()?);
220        }
221
222        Ok(Self {
223            removed: u.arbitrary()?,
224            log_index: u.arbitrary()?,
225            transaction_index: u.arbitrary()?,
226            transaction_hash: u.arbitrary()?,
227            block_hash: u.arbitrary()?,
228            block_number: u.arbitrary()?,
229            address: u.arbitrary()?,
230            data: u.arbitrary()?,
231            topics,
232            block_timestamp: u.arbitrary()?,
233        })
234    }
235}
236
237/// Evm trace object (parity style, returned from trace_block request on RPC)
238///
239/// See trace_block documentation online for meaning of fields
240#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
241#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
242#[serde(rename_all = "camelCase")]
243pub struct Trace {
244    pub action: TraceAction,
245    pub block_hash: Hash,
246    pub block_number: u64,
247    pub result: Option<TraceResult>,
248    pub subtraces: Option<u64>,
249    pub trace_address: Option<Vec<u64>>,
250    pub transaction_hash: Option<Hash>,
251    pub transaction_position: Option<u64>,
252    #[serde(rename = "type")]
253    pub type_: Option<String>,
254    pub error: Option<String>,
255}
256
257/// Action object inside trace object (parity style, returned from trace_block request on RPC)
258///
259/// See trace_block documentation online for meaning of fields
260#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
261#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
262#[serde(rename_all = "camelCase")]
263pub struct TraceAction {
264    pub from: Option<Address>,
265    pub to: Option<Address>,
266    pub call_type: Option<String>,
267    pub gas: Option<Quantity>,
268    pub input: Option<Data>,
269    pub init: Option<Data>,
270    pub value: Option<Quantity>,
271    pub author: Option<Address>,
272    pub reward_type: Option<String>,
273    // For suicide traces
274    pub address: Option<Address>,
275    pub refund_address: Option<Address>,
276    pub balance: Option<Quantity>,
277}
278
279/// Result object inside trace object (parity style, returned from trace_block request on RPC)
280///
281/// See trace_block documentation online for meaning of fields
282#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
283#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
284#[serde(rename_all = "camelCase")]
285pub struct TraceResult {
286    pub address: Option<Address>,
287    pub code: Option<Data>,
288    pub gas_used: Option<Quantity>,
289    pub output: Option<Data>,
290}
291
292#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
293#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
294#[serde(rename_all = "camelCase")]
295pub struct DebugBlockTrace {
296    pub result: DebugTxTrace,
297    pub tx_hash: Hash,
298}
299
300#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
301#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
302#[serde(rename_all = "camelCase")]
303pub struct DebugTxTrace {
304    #[serde(rename = "type")]
305    pub type_: Option<String>,
306    pub from: Option<Address>,
307    pub to: Option<Address>,
308    pub value: Option<Quantity>,
309    pub gas: Option<Quantity>,
310    pub gas_used: Option<Quantity>,
311    pub input: Option<Data>,
312    pub output: Option<Data>,
313    pub error: Option<String>,
314    pub revert_reason: Option<String>,
315    #[serde(default)]
316    pub calls: Vec<DebugTxTrace>,
317}
318
319/// EVM hash is 32 bytes of data
320pub type Hash = FixedSizeData<32>;
321
322/// EVM log argument is 32 bytes of data
323pub type LogArgument = FixedSizeData<32>;
324
325/// EVM address is 20 bytes of data
326pub type Address = FixedSizeData<20>;
327
328/// EVM nonce is 8 bytes of data
329pub type Nonce = FixedSizeData<8>;
330
331pub type BloomFilter = Data;
332pub type BlockNumber = uint::UInt;
333pub type TransactionIndex = uint::UInt;
334pub type LogIndex = uint::UInt;
335
336#[cfg(test)]
337mod tests {
338    use serde_json::{json, Value};
339
340    use super::*;
341
342    #[test]
343    fn handle_zeta_null_effective_gas_price() {
344        // real world breaking example on zeta
345        let json = json!({
346          "transactionHash": "0xf19809f330bb78aa882976053ab40a7606797efcb6111f2e7112600e958a6e4c",
347          "transactionIndex": "0x22b8",
348          "blockHash": "0xaae719b56f61cb66cdc61ece1852cda22c936baff9a1dc6b0903be11073476b7",
349          "blockNumber": "0xa377b2",
350          "from": "0x735b14bb79463307aacbed86daf3322b1e6226ab",
351          "to": "0x91d18e54daf4f677cb28167158d6dd21f6ab3921",
352          "cumulativeGasUsed": "0x211ec2",
353          "effectiveGasPrice": null,
354          "contractAddress": null,
355          "gasUsed": "0x186a0",
356          "logs": [
357            {
358              "address": "0x91d18e54daf4f677cb28167158d6dd21f6ab3921",
359              "blockHash": "0xaae719b56f61cb66cdc61ece1852cda22c936baff9a1dc6b0903be11073476b7",
360              "blockNumber": "0xa377b2",
361              "data": "0x000000000000000000000000000000000000000000000000000000000000006900000000000000000000000000000000000000000000000000000000000001f4",
362              "logIndex": "0x0",
363              "removed": false,
364              "topics": [
365                "0x49f492222906ac486c3c1401fa545626df1f0c0e5a77a05597ea2ed66af9850d"
366              ],
367              "transactionHash": "0xf19809f330bb78aa882976053ab40a7606797efcb6111f2e7112600e958a6e4c",
368              "transactionIndex": "0x0"
369            }
370          ],
371          "logsBloom": "0x00000000000000000000000000000008000000000000000000000000000000000000000008000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000002000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
372          "status": "0x1",
373          "type": "0x58"
374        });
375
376        let _: TransactionReceipt =
377            serde_json::from_value(json.clone()).expect("should handle null effective gas price");
378
379        // Also check that it still handles undefined as before
380        let mut obj = json.as_object().unwrap().to_owned();
381        let _ = obj.remove("effectiveGasPrice");
382        let json = Value::Object(obj);
383        let _: TransactionReceipt =
384            serde_json::from_value(json).expect("should handle undefined effective gas price");
385    }
386}