1use crate::{
18 client::{SubstrateBlock, SubstrateBlockNumber},
19 subxt_client::{
20 self,
21 revive::{calls::types::EthTransact, events::ContractEmitted},
22 system::events::ExtrinsicSuccess,
23 transaction_payment::events::TransactionFeePaid,
24 SrcChainConfig,
25 },
26 ClientError, LOG_TARGET,
27};
28use futures::{stream, StreamExt};
29use pallet_revive::{
30 create1,
31 evm::{GenericTransaction, Log, ReceiptInfo, TransactionSigned, H256, U256},
32};
33use sp_core::keccak_256;
34use std::{future::Future, pin::Pin, sync::Arc};
35use subxt::OnlineClient;
36
37type FetchGasPriceFn = Arc<
38 dyn Fn(H256) -> Pin<Box<dyn Future<Output = Result<U256, ClientError>> + Send>> + Send + Sync,
39>;
40#[derive(Clone)]
42pub struct ReceiptExtractor {
43 fetch_gas_price: FetchGasPriceFn,
45
46 native_to_eth_ratio: u32,
48
49 earliest_receipt_block: Option<SubstrateBlockNumber>,
51}
52
53async fn native_to_eth_ratio(api: &OnlineClient<SrcChainConfig>) -> Result<u32, ClientError> {
55 let query = subxt_client::constants().revive().native_to_eth_ratio();
56 api.constants().at(&query).map_err(|err| err.into())
57}
58
59impl ReceiptExtractor {
60 pub fn is_before_earliest_block(&self, block_number: SubstrateBlockNumber) -> bool {
62 block_number < self.earliest_receipt_block.unwrap_or_default()
63 }
64
65 pub async fn new(
67 api: OnlineClient<SrcChainConfig>,
68 earliest_receipt_block: Option<SubstrateBlockNumber>,
69 ) -> Result<Self, ClientError> {
70 let native_to_eth_ratio = native_to_eth_ratio(&api).await?;
71
72 let fetch_gas_price = Arc::new(move |block_hash| {
73 let api_clone = api.clone();
74 let fut = async move {
75 let runtime_api = api_clone.runtime_api().at(block_hash);
76 let payload = subxt_client::apis().revive_api().gas_price();
77 let base_gas_price = runtime_api.call(payload).await?;
78 Ok(*base_gas_price)
79 };
80 Box::pin(fut) as Pin<Box<_>>
81 });
82
83 Ok(Self { native_to_eth_ratio, fetch_gas_price, earliest_receipt_block })
84 }
85
86 #[cfg(test)]
87 pub fn new_mock() -> Self {
88 let fetch_gas_price =
89 Arc::new(|_| Box::pin(std::future::ready(Ok(U256::from(1000)))) as Pin<Box<_>>);
90
91 Self { native_to_eth_ratio: 1_000_000, fetch_gas_price, earliest_receipt_block: None }
92 }
93
94 async fn extract_from_extrinsic(
96 &self,
97 block: &SubstrateBlock,
98 ext: subxt::blocks::ExtrinsicDetails<SrcChainConfig, subxt::OnlineClient<SrcChainConfig>>,
99 call: EthTransact,
100 ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> {
101 let transaction_index = ext.index();
102 let block_number = U256::from(block.number());
103 let block_hash = block.hash();
104 let events = ext.events().await?;
105
106 let success = events.has::<ExtrinsicSuccess>().inspect_err(|err| {
107 log::debug!(target: LOG_TARGET, "Failed to lookup for ExtrinsicSuccess event in block {block_number}: {err:?}")
108 })?;
109 let tx_fees = events
110 .find_first::<TransactionFeePaid>()?
111 .ok_or(ClientError::TxFeeNotFound)
112 .inspect_err(
113 |err| log::debug!(target: LOG_TARGET, "TransactionFeePaid not found in events for block {block_number}\n{err:?}")
114 )?;
115 let transaction_hash = H256(keccak_256(&call.payload));
116
117 let signed_tx =
118 TransactionSigned::decode(&call.payload).map_err(|_| ClientError::TxDecodingFailed)?;
119 let from = signed_tx.recover_eth_address().map_err(|_| {
120 log::error!(target: LOG_TARGET, "Failed to recover eth address from signed tx");
121 ClientError::RecoverEthAddressFailed
122 })?;
123
124 let base_gas_price = (self.fetch_gas_price)(block_hash).await?;
125 let tx_info =
126 GenericTransaction::from_signed(signed_tx.clone(), base_gas_price, Some(from));
127 let gas_price = tx_info.gas_price.unwrap_or_default();
128 let gas_used = U256::from(tx_fees.tip.saturating_add(tx_fees.actual_fee))
129 .saturating_mul(self.native_to_eth_ratio.into())
130 .checked_div(gas_price)
131 .unwrap_or_default();
132
133 let logs = events
135 .iter()
136 .filter_map(|event_details| {
137 let event_details = event_details.ok()?;
138 let event = event_details.as_event::<ContractEmitted>().ok()??;
139
140 Some(Log {
141 address: event.contract,
142 topics: event.topics,
143 data: Some(event.data.into()),
144 block_number,
145 transaction_hash,
146 transaction_index: transaction_index.into(),
147 block_hash,
148 log_index: event_details.index().into(),
149 ..Default::default()
150 })
151 })
152 .collect();
153
154 let contract_address = if tx_info.to.is_none() {
155 Some(create1(
156 &from,
157 tx_info
158 .nonce
159 .unwrap_or_default()
160 .try_into()
161 .map_err(|_| ClientError::ConversionFailed)?,
162 ))
163 } else {
164 None
165 };
166
167 let receipt = ReceiptInfo::new(
168 block_hash,
169 block_number,
170 contract_address,
171 from,
172 logs,
173 tx_info.to,
174 gas_price,
175 gas_used,
176 success,
177 transaction_hash,
178 transaction_index.into(),
179 tx_info.r#type.unwrap_or_default(),
180 );
181 Ok((signed_tx, receipt))
182 }
183
184 pub async fn extract_from_block(
186 &self,
187 block: &SubstrateBlock,
188 ) -> Result<Vec<(TransactionSigned, ReceiptInfo)>, ClientError> {
189 if self.is_before_earliest_block(block.number()) {
190 return Ok(vec![]);
191 }
192
193 let extrinsics = block.extrinsics().await.inspect_err(|err| {
195 log::debug!(target: LOG_TARGET, "Error fetching for #{:?} extrinsics: {err:?}", block.number());
196 })?;
197
198 let extrinsics = extrinsics.iter().flat_map(|ext| {
199 let call = ext.as_extrinsic::<EthTransact>().ok()??;
200 Some((ext, call))
201 });
202
203 stream::iter(extrinsics)
204 .map(|(ext, call)| async move {
205 self.extract_from_extrinsic(block, ext, call).await.inspect_err(|err| {
206 log::warn!(target: LOG_TARGET, "Error extracting extrinsic: {err:?}");
207 })
208 })
209 .buffer_unordered(10)
210 .collect::<Vec<Result<_, _>>>()
211 .await
212 .into_iter()
213 .collect::<Result<Vec<_>, _>>()
214 }
215
216 pub async fn extract_from_transaction(
218 &self,
219 block: &SubstrateBlock,
220 transaction_index: usize,
221 ) -> Result<(TransactionSigned, ReceiptInfo), ClientError> {
222 let extrinsics = block.extrinsics().await?;
223 let ext = extrinsics
224 .iter()
225 .nth(transaction_index)
226 .ok_or(ClientError::EthExtrinsicNotFound)?;
227
228 let call = ext
229 .as_extrinsic::<EthTransact>()?
230 .ok_or_else(|| ClientError::EthExtrinsicNotFound)?;
231 self.extract_from_extrinsic(block, ext, call).await
232 }
233}