axiom_circuit/subquery/
tx.rs1use 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 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}