axiom_circuit/subquery/
tx.rs

1use anyhow::{bail, Result};
2use axiom_codec::{
3    special_values::{
4        TX_BLOCK_NUMBER_FIELD_IDX, TX_CALLDATA_HASH_FIELD_IDX, TX_CALLDATA_IDX_OFFSET,
5        TX_CONTRACT_DATA_IDX_OFFSET, TX_CONTRACT_DEPLOY_SELECTOR_VALUE, TX_DATA_LENGTH_FIELD_IDX,
6        TX_FUNCTION_SELECTOR_FIELD_IDX, TX_NO_CALLDATA_SELECTOR_VALUE, TX_TX_INDEX_FIELD_IDX,
7        TX_TX_TYPE_FIELD_IDX,
8    },
9    types::native::{AnySubquery, TxSubquery},
10};
11use axiom_query::axiom_eth::{halo2_base::AssignedValue, Field};
12use ethers::{
13    providers::{JsonRpcClient, Middleware, Provider},
14    types::{BigEndianHash, BlockId, H256, U64},
15    utils::keccak256,
16};
17use num_derive::FromPrimitive;
18use num_traits::FromPrimitive;
19use tokio::runtime::Runtime;
20
21use super::{caller::FetchSubquery, types::AssignedTxSubquery, utils::pad_to_bytes32};
22use crate::impl_fr_from;
23
24#[derive(FromPrimitive)]
25pub enum TxField {
26    ChainId,
27    Nonce,
28    MaxPriorityFeePerGas,
29    MaxFeePerGas,
30    GasLimit,
31    To,
32    Value,
33    Data,
34    GasPrice,
35    V,
36    R,
37    S,
38    TxType = TX_TX_TYPE_FIELD_IDX as isize,
39    BlockNumber = TX_BLOCK_NUMBER_FIELD_IDX as isize,
40    TxIndex = TX_TX_INDEX_FIELD_IDX as isize,
41    FunctionSelector = TX_FUNCTION_SELECTOR_FIELD_IDX as isize,
42    CalldataHash = TX_CALLDATA_HASH_FIELD_IDX as isize,
43    DataLength = TX_DATA_LENGTH_FIELD_IDX as isize,
44}
45impl_fr_from!(TxField);
46
47pub async fn get_tx_field_value<P: JsonRpcClient>(
48    provider: &Provider<P>,
49    query: TxSubquery,
50) -> Result<H256> {
51    let block_id = BlockId::from(query.block_number as u64);
52    let tx = provider
53        .get_transaction_by_block_and_index(block_id, U64::from(query.tx_idx))
54        .await;
55    if tx.is_err() {
56        bail!("Provider Error: Couldn't Fetch Tx")
57    }
58    let tx = tx.unwrap();
59    if tx.is_none() {
60        bail!("Transaction does not exist")
61    }
62    let tx = tx.unwrap();
63    //todo: validate tx size
64
65    if query.field_or_calldata_idx < TX_CALLDATA_IDX_OFFSET.try_into().unwrap() {
66        let tx_field_idx =
67            TxField::from_u32(query.field_or_calldata_idx).expect("Invalid field index");
68
69        let val = match tx_field_idx {
70            TxField::ChainId => H256::from_uint(&tx.chain_id.unwrap()),
71            TxField::Nonce => H256::from_uint(&tx.nonce),
72            TxField::MaxPriorityFeePerGas => H256::from_uint(&tx.max_priority_fee_per_gas.unwrap()),
73            TxField::MaxFeePerGas => H256::from_uint(&tx.max_fee_per_gas.unwrap()),
74            TxField::GasLimit => H256::from_uint(&tx.gas),
75            TxField::To => H256::from(tx.to.unwrap()),
76            TxField::Value => H256::from_uint(&tx.value),
77            TxField::Data => {
78                let padded = pad_to_bytes32(&tx.input);
79                H256::from(padded)
80            }
81            TxField::GasPrice => {
82                if tx.transaction_type.unwrap() == 2.into() {
83                    bail!("Gas Price not available for EIP-1559 transactions")
84                }
85                let gas_price = tx.gas_price.unwrap();
86                H256::from_uint(&gas_price)
87            }
88            TxField::V => H256::from_low_u64_be(tx.v.as_u64()),
89            TxField::R => H256::from_uint(&tx.r),
90            TxField::S => H256::from_uint(&tx.s),
91            TxField::TxType => H256::from_low_u64_be(tx.transaction_type.unwrap().as_u64()),
92            TxField::BlockNumber => H256::from_low_u64_be(tx.block_number.unwrap().as_u64()),
93            TxField::TxIndex => H256::from_low_u64_be(tx.transaction_index.unwrap().as_u64()),
94            TxField::FunctionSelector => {
95                let calldata = tx.input;
96                let to = tx.to;
97
98                if calldata.len() == 0 {
99                    H256::from_low_u64_be(TX_NO_CALLDATA_SELECTOR_VALUE as u64)
100                } else if calldata.len() > 0 && to.is_none() {
101                    H256::from_low_u64_be(TX_CONTRACT_DEPLOY_SELECTOR_VALUE as u64)
102                } else {
103                    if calldata.len() < 4 {
104                        bail!("Invalid calldata")
105                    }
106                    let selector = &calldata[0..4];
107                    H256::from(pad_to_bytes32(selector))
108                }
109            }
110            TxField::CalldataHash => {
111                let calldata = tx.input;
112                let hash = keccak256(&calldata);
113                H256::from(hash)
114            }
115            TxField::DataLength => H256::from_low_u64_be(tx.input.len() as u64),
116        };
117        return Ok(val);
118    }
119
120    if query.field_or_calldata_idx < TX_CONTRACT_DATA_IDX_OFFSET.try_into().unwrap() {
121        let calldata = tx.input;
122
123        let calldata_idx = (query.field_or_calldata_idx as usize) - TX_CALLDATA_IDX_OFFSET;
124        if calldata_idx >= (calldata.len() - 4) / 32 {
125            bail!("Invalid calldata index")
126        }
127        let calldata_bytes = &calldata[4 + calldata_idx * 32..4 + (calldata_idx + 1) * 32];
128        Ok(H256::from_slice(calldata_bytes))
129    } else {
130        let contract_data = tx.input;
131        let contract_data_idx =
132            (query.field_or_calldata_idx as usize) - TX_CONTRACT_DATA_IDX_OFFSET;
133        let num_slots = usize::div_ceil(contract_data.len(), 32);
134        if contract_data_idx == num_slots - 1 {
135            let contract_data_bytes = &contract_data[contract_data_idx * 32..];
136            let padded = pad_to_bytes32(contract_data_bytes);
137            return Ok(H256::from(padded));
138        }
139        let contract_data_bytes =
140            &contract_data[contract_data_idx * 32..(contract_data_idx + 1) * 32];
141        Ok(H256::from_slice(contract_data_bytes))
142    }
143}
144
145impl<F: Field> FetchSubquery<F> for AssignedTxSubquery<F> {
146    fn fetch<P: JsonRpcClient>(&self, p: &Provider<P>) -> Result<H256> {
147        let rt = Runtime::new()?;
148        let val = rt.block_on(get_tx_field_value(p, (*self).into()))?;
149        Ok(val)
150    }
151
152    fn any_subquery(&self) -> AnySubquery {
153        AnySubquery::Transaction((*self).into())
154    }
155
156    fn flatten(&self) -> Vec<AssignedValue<F>> {
157        vec![self.block_number, self.tx_idx, self.field_or_calldata_idx]
158    }
159}