axiom_circuit/subquery/
receipt.rs

1use anyhow::{bail, Result};
2use axiom_codec::{
3    special_values::{
4        RECEIPT_ADDRESS_IDX, RECEIPT_BLOCK_NUMBER_FIELD_IDX, RECEIPT_DATA_IDX_OFFSET,
5        RECEIPT_LOGS_BLOOM_IDX_OFFSET, RECEIPT_LOG_IDX_OFFSET, RECEIPT_TX_INDEX_FIELD_IDX,
6        RECEIPT_TX_TYPE_FIELD_IDX,
7    },
8    types::native::{AnySubquery, ReceiptSubquery},
9};
10use axiom_query::axiom_eth::{halo2_base::AssignedValue, Field};
11use ethers::{
12    providers::{JsonRpcClient, Middleware, Provider},
13    types::{BigEndianHash, BlockId, H256},
14};
15use num_derive::FromPrimitive;
16use num_traits::FromPrimitive;
17use tokio::runtime::Runtime;
18
19use super::{caller::FetchSubquery, types::AssignedReceiptSubquery, utils::pad_to_bytes32};
20use crate::impl_fr_from;
21
22#[derive(FromPrimitive)]
23pub enum ReceiptField {
24    Status,    // status for post EIP-658
25    PostState, // postState for pre EIP-658
26    CumulativeGas,
27    LogsBloom,
28    Logs,
29    TxType = RECEIPT_TX_TYPE_FIELD_IDX as isize,
30    BlockNumber = RECEIPT_BLOCK_NUMBER_FIELD_IDX as isize,
31    TxIndex = RECEIPT_TX_INDEX_FIELD_IDX as isize,
32}
33impl_fr_from!(ReceiptField);
34
35pub async fn get_receipt_field_value<P: JsonRpcClient>(
36    provider: &Provider<P>,
37    query: ReceiptSubquery,
38) -> Result<H256> {
39    let block_id = BlockId::from(query.block_number as u64);
40    let tx = provider
41        .get_transaction_by_block_and_index(block_id, query.tx_idx.into())
42        .await?;
43    let tx_hash = tx.unwrap().hash;
44    let receipt = provider.get_transaction_receipt(tx_hash).await?.unwrap();
45    //todo: check receipt size
46    let field_or_log_idx = query.field_or_log_idx as usize;
47    if (RECEIPT_LOGS_BLOOM_IDX_OFFSET..RECEIPT_LOGS_BLOOM_IDX_OFFSET + 8)
48        .contains(&field_or_log_idx)
49    {
50        let bloom = receipt.logs_bloom.to_fixed_bytes();
51        let log_idx = (field_or_log_idx - RECEIPT_LOGS_BLOOM_IDX_OFFSET) * 32;
52        return Ok(H256::from_slice(&bloom[log_idx..log_idx + 32]));
53    }
54
55    if field_or_log_idx >= RECEIPT_LOG_IDX_OFFSET {
56        let log_idx = field_or_log_idx - RECEIPT_LOG_IDX_OFFSET;
57        if log_idx >= receipt.logs.len() {
58            bail!("Log does not exist")
59        }
60        let log = receipt.logs[log_idx].clone();
61        let topics = log.topics;
62        if query.event_schema != H256::zero() && query.event_schema != topics[0] {
63            bail!("Log does not match event schema")
64        }
65
66        let topic_or_data_or_address_idx = query.topic_or_data_or_address_idx as usize;
67
68        if topic_or_data_or_address_idx == RECEIPT_ADDRESS_IDX {
69            return Ok(log.address.into());
70        } else if topic_or_data_or_address_idx < RECEIPT_DATA_IDX_OFFSET {
71            if topic_or_data_or_address_idx > topics.len() {
72                bail!("Topic does not exist")
73            }
74
75            if topic_or_data_or_address_idx < topics.len() {
76                return Ok(topics[topic_or_data_or_address_idx]);
77            }
78
79            if topic_or_data_or_address_idx == topics.len() {
80                return Ok(log.address.into());
81            }
82        } else {
83            let data_idx = topic_or_data_or_address_idx - RECEIPT_DATA_IDX_OFFSET;
84            if data_idx >= log.data.len() / 32 {
85                bail!("Data does not exist")
86            }
87            let data_bytes = &log.data[data_idx * 32..(data_idx + 1) * 32];
88            return Ok(H256::from_slice(data_bytes));
89        }
90    }
91
92    let receipt_field_idx =
93        ReceiptField::from_usize(field_or_log_idx).expect("Invalid field index");
94    let val = match receipt_field_idx {
95        ReceiptField::Status => H256::from_low_u64_be(receipt.status.unwrap().as_u64()),
96        ReceiptField::PostState => receipt.root.unwrap(),
97        ReceiptField::CumulativeGas => H256::from_uint(&receipt.cumulative_gas_used),
98        ReceiptField::LogsBloom => {
99            let logs_bloom = receipt.logs_bloom;
100            H256::from(pad_to_bytes32(logs_bloom.as_fixed_bytes()))
101        }
102        ReceiptField::Logs => {
103            bail!("Use log idx instead of logs field")
104        }
105        ReceiptField::TxType => H256::from_low_u64_be(receipt.transaction_type.unwrap().as_u64()),
106        ReceiptField::BlockNumber => H256::from_low_u64_be(receipt.block_number.unwrap().as_u64()),
107        ReceiptField::TxIndex => H256::from_low_u64_be(receipt.transaction_index.as_u64()),
108    };
109
110    Ok(val)
111}
112
113impl<F: Field> FetchSubquery<F> for AssignedReceiptSubquery<F> {
114    fn fetch<P: JsonRpcClient>(&self, p: &Provider<P>) -> Result<H256> {
115        let rt = Runtime::new()?;
116        let val = rt.block_on(get_receipt_field_value(p, (*self).into()))?;
117        Ok(val)
118    }
119
120    fn any_subquery(&self) -> AnySubquery {
121        AnySubquery::Receipt((*self).into())
122    }
123
124    fn flatten(&self) -> Vec<AssignedValue<F>> {
125        vec![
126            self.block_number,
127            self.tx_idx,
128            self.field_or_log_idx,
129            self.topic_or_data_or_address_idx,
130            self.event_schema.hi(),
131            self.event_schema.lo(),
132        ]
133    }
134}