@@ -28,7 +28,7 @@ use graph::{
2828 blockchain:: { block_stream:: BlockWithTriggers , BlockPtr , IngestorError } ,
2929 prelude:: {
3030 anyhow:: { self , anyhow, bail, ensure, Context } ,
31- async_trait, debug, error, hex, info, retry, serde_json as json, tiny_keccak , trace, warn,
31+ async_trait, debug, error, hex, info, retry, serde_json as json, trace, warn,
3232 web3:: {
3333 self ,
3434 types:: {
@@ -56,6 +56,7 @@ use std::time::Instant;
5656
5757use crate :: adapter:: EthereumRpcError ;
5858use crate :: adapter:: ProviderStatus ;
59+ use crate :: call_helper:: interpret_eth_call_error;
5960use crate :: chain:: BlockFinality ;
6061use crate :: trigger:: LogRef ;
6162use crate :: Chain ;
@@ -115,134 +116,6 @@ impl CheapClone for EthereumAdapter {
115116}
116117
117118impl EthereumAdapter {
118- // ------------------------------------------------------------------
119- // Constants and helper utilities used across eth_call handling
120- // ------------------------------------------------------------------
121-
122- // Try to check if the call was reverted. The JSON-RPC response for reverts is
123- // not standardized, so we have ad-hoc checks for each Ethereum client.
124-
125- // 0xfe is the "designated bad instruction" of the EVM, and Solidity uses it for
126- // asserts.
127- const PARITY_BAD_INSTRUCTION_FE : & str = "Bad instruction fe" ;
128-
129- // 0xfd is REVERT, but on some contracts, and only on older blocks,
130- // this happens. Makes sense to consider it a revert as well.
131- const PARITY_BAD_INSTRUCTION_FD : & str = "Bad instruction fd" ;
132-
133- const PARITY_BAD_JUMP_PREFIX : & str = "Bad jump" ;
134- const PARITY_STACK_LIMIT_PREFIX : & str = "Out of stack" ;
135-
136- // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61.
137- const PARITY_OUT_OF_GAS : & str = "Out of gas" ;
138-
139- // Also covers Nethermind reverts
140- const PARITY_VM_EXECUTION_ERROR : i64 = -32015 ;
141- const PARITY_REVERT_PREFIX : & str = "revert" ;
142-
143- const XDAI_REVERT : & str = "revert" ;
144-
145- // Deterministic Geth execution errors. We might need to expand this as
146- // subgraphs come across other errors. See
147- // https://github.com/ethereum/go-ethereum/blob/cd57d5cd38ef692de8fbedaa56598b4e9fbfbabc/core/vm/errors.go
148- const GETH_EXECUTION_ERRORS : & [ & str ] = & [
149- // The "revert" substring covers a few known error messages, including:
150- // Hardhat: "error: transaction reverted",
151- // Ganache and Moonbeam: "vm exception while processing transaction: revert",
152- // Geth: "execution reverted"
153- // And others.
154- "revert" ,
155- "invalid jump destination" ,
156- "invalid opcode" ,
157- // Ethereum says 1024 is the stack sizes limit, so this is deterministic.
158- "stack limit reached 1024" ,
159- // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61 for why the gas limit is considered deterministic.
160- "out of gas" ,
161- "stack underflow" ,
162- ] ;
163-
164- /// Helper that checks if a geth style RPC error message corresponds to a revert.
165- fn is_geth_revert_message ( message : & str ) -> bool {
166- let env_geth_call_errors = ENV_VARS . geth_eth_call_errors . iter ( ) ;
167- let mut execution_errors = Self :: GETH_EXECUTION_ERRORS
168- . iter ( )
169- . copied ( )
170- . chain ( env_geth_call_errors. map ( |s| s. as_str ( ) ) ) ;
171- execution_errors. any ( |e| message. to_lowercase ( ) . contains ( e) )
172- }
173-
174- /// Decode a Solidity revert(reason) payload, returning the reason string when possible.
175- fn as_solidity_revert_reason ( bytes : & [ u8 ] ) -> Option < String > {
176- let selector = & tiny_keccak:: keccak256 ( b"Error(string)" ) [ ..4 ] ;
177- if bytes. len ( ) >= 4 && & bytes[ ..4 ] == selector {
178- abi:: DynSolType :: String
179- . abi_decode ( & bytes[ 4 ..] )
180- . ok ( )
181- . and_then ( |val| val. clone ( ) . as_str ( ) . map ( ToOwned :: to_owned) )
182- } else {
183- None
184- }
185- }
186-
187- /// Interpret the error returned by `eth_call`, distinguishing genuine failures from
188- /// EVM reverts. Returns `Ok(Null)` for reverts or a proper error otherwise.
189- fn interpret_eth_call_error (
190- logger : & Logger ,
191- err : web3:: Error ,
192- ) -> Result < call:: Retval , ContractCallError > {
193- fn reverted ( logger : & Logger , reason : & str ) -> Result < call:: Retval , ContractCallError > {
194- info ! ( logger, "Contract call reverted" ; "reason" => reason) ;
195- Ok ( call:: Retval :: Null )
196- }
197-
198- if let web3:: Error :: Rpc ( rpc_error) = & err {
199- if Self :: is_geth_revert_message ( & rpc_error. message ) {
200- return reverted ( logger, & rpc_error. message ) ;
201- }
202- }
203-
204- if let web3:: Error :: Rpc ( rpc_error) = & err {
205- let code = rpc_error. code . code ( ) ;
206- let data = rpc_error. data . as_ref ( ) . and_then ( |d| d. as_str ( ) ) ;
207-
208- if code == Self :: PARITY_VM_EXECUTION_ERROR {
209- if let Some ( data) = data {
210- if Self :: is_parity_revert ( data) {
211- return reverted ( logger, & Self :: parity_revert_reason ( data) ) ;
212- }
213- }
214- }
215- }
216-
217- Err ( ContractCallError :: Web3Error ( err) )
218- }
219-
220- fn is_parity_revert ( data : & str ) -> bool {
221- data. to_lowercase ( ) . starts_with ( Self :: PARITY_REVERT_PREFIX )
222- || data. starts_with ( Self :: PARITY_BAD_JUMP_PREFIX )
223- || data. starts_with ( Self :: PARITY_STACK_LIMIT_PREFIX )
224- || data == Self :: PARITY_BAD_INSTRUCTION_FE
225- || data == Self :: PARITY_BAD_INSTRUCTION_FD
226- || data == Self :: PARITY_OUT_OF_GAS
227- || data == Self :: XDAI_REVERT
228- }
229-
230- /// Checks if the given `web3::Error` corresponds to a Parity / Nethermind style EVM
231- /// revert and, if so, tries to extract a human-readable revert reason. Returns `Some`
232- /// with the reason when the error is identified as a revert, otherwise `None`.
233- fn parity_revert_reason ( data : & str ) -> String {
234- if data == Self :: PARITY_BAD_INSTRUCTION_FE {
235- return Self :: PARITY_BAD_INSTRUCTION_FE . to_owned ( ) ;
236- }
237-
238- // Otherwise try to decode a Solidity revert reason payload.
239- let payload = data. trim_start_matches ( Self :: PARITY_REVERT_PREFIX ) ;
240- hex:: decode ( payload)
241- . ok ( )
242- . and_then ( |decoded| Self :: as_solidity_revert_reason ( & decoded) )
243- . unwrap_or_else ( || "no reason" . to_owned ( ) )
244- }
245-
246119 pub fn is_call_only ( & self ) -> bool {
247120 self . call_only
248121 }
@@ -767,7 +640,7 @@ impl EthereumAdapter {
767640
768641 match result {
769642 Ok ( bytes) => Ok ( call:: Retval :: Value ( scalar:: Bytes :: from ( bytes) ) ) ,
770- Err ( err) => Self :: interpret_eth_call_error ( & logger, err) ,
643+ Err ( err) => interpret_eth_call_error ( & logger, err) ,
771644 }
772645 }
773646 } )
0 commit comments