@@ -115,6 +115,134 @@ impl CheapClone for EthereumAdapter {
115115}
116116
117117impl 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+
118246 pub fn is_call_only ( & self ) -> bool {
119247 self . call_only
120248 }
@@ -609,11 +737,6 @@ impl EthereumAdapter {
609737 block_ptr : BlockPtr ,
610738 gas : Option < u32 > ,
611739 ) -> Result < call:: Retval , ContractCallError > {
612- fn reverted ( logger : & Logger , reason : & str ) -> Result < call:: Retval , ContractCallError > {
613- info ! ( logger, "Contract call reverted" ; "reason" => reason) ;
614- Ok ( call:: Retval :: Null )
615- }
616-
617740 let web3 = self . web3 . clone ( ) ;
618741 let logger = Logger :: new ( & logger, o ! ( "provider" => self . provider. clone( ) ) ) ;
619742
@@ -642,122 +765,14 @@ impl EthereumAdapter {
642765 } ;
643766 let result = web3. eth ( ) . call ( req, Some ( block_id) ) . boxed ( ) . await ;
644767
645- // Try to check if the call was reverted. The JSON-RPC response for reverts is
646- // not standardized, so we have ad-hoc checks for each Ethereum client.
647-
648- // 0xfe is the "designated bad instruction" of the EVM, and Solidity uses it for
649- // asserts.
650- const PARITY_BAD_INSTRUCTION_FE : & str = "Bad instruction fe" ;
651-
652- // 0xfd is REVERT, but on some contracts, and only on older blocks,
653- // this happens. Makes sense to consider it a revert as well.
654- const PARITY_BAD_INSTRUCTION_FD : & str = "Bad instruction fd" ;
655-
656- const PARITY_BAD_JUMP_PREFIX : & str = "Bad jump" ;
657- const PARITY_STACK_LIMIT_PREFIX : & str = "Out of stack" ;
658-
659- // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61.
660- const PARITY_OUT_OF_GAS : & str = "Out of gas" ;
661-
662- // Also covers Nethermind reverts
663- const PARITY_VM_EXECUTION_ERROR : i64 = -32015 ;
664- const PARITY_REVERT_PREFIX : & str = "revert" ;
665-
666- const XDAI_REVERT : & str = "revert" ;
667-
668- // Deterministic Geth execution errors. We might need to expand this as
669- // subgraphs come across other errors. See
670- // https://github.com/ethereum/go-ethereum/blob/cd57d5cd38ef692de8fbedaa56598b4e9fbfbabc/core/vm/errors.go
671- const GETH_EXECUTION_ERRORS : & [ & str ] = & [
672- // The "revert" substring covers a few known error messages, including:
673- // Hardhat: "error: transaction reverted",
674- // Ganache and Moonbeam: "vm exception while processing transaction: revert",
675- // Geth: "execution reverted"
676- // And others.
677- "revert" ,
678- "invalid jump destination" ,
679- "invalid opcode" ,
680- // Ethereum says 1024 is the stack sizes limit, so this is deterministic.
681- "stack limit reached 1024" ,
682- // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61 for why the gas limit is considered deterministic.
683- "out of gas" ,
684- "stack underflow" ,
685- ] ;
686-
687- let env_geth_call_errors = ENV_VARS . geth_eth_call_errors . iter ( ) ;
688- let mut geth_execution_errors = GETH_EXECUTION_ERRORS
689- . iter ( )
690- . copied ( )
691- . chain ( env_geth_call_errors. map ( |s| s. as_str ( ) ) ) ;
692-
693- let as_solidity_revert_with_reason = |bytes : & [ u8 ] | {
694- let solidity_revert_function_selector =
695- & tiny_keccak:: keccak256 ( b"Error(string)" ) [ ..4 ] ;
696-
697- match bytes. len ( ) >= 4 && & bytes[ ..4 ] == solidity_revert_function_selector {
698- false => None ,
699- true => abi:: DynSolType :: String
700- . abi_decode ( & bytes[ 4 ..] )
701- . ok ( )
702- . and_then ( |val| val. clone ( ) . as_str ( ) . map ( ToOwned :: to_owned) ) ,
703- }
704- } ;
705-
706768 match result {
707- // A successful response.
708769 Ok ( bytes) => Ok ( call:: Retval :: Value ( scalar:: Bytes :: from ( bytes) ) ) ,
709-
710- // Check for Geth revert.
711- Err ( web3:: Error :: Rpc ( rpc_error) )
712- if geth_execution_errors
713- . any ( |e| rpc_error. message . to_lowercase ( ) . contains ( e) ) =>
714- {
715- reverted ( & logger, & rpc_error. message )
716- }
717-
718- // Check for Parity revert.
719- Err ( web3:: Error :: Rpc ( ref rpc_error) )
720- if rpc_error. code . code ( ) == PARITY_VM_EXECUTION_ERROR =>
721- {
722- match rpc_error. data . as_ref ( ) . and_then ( |d| d. as_str ( ) ) {
723- Some ( data)
724- if data. to_lowercase ( ) . starts_with ( PARITY_REVERT_PREFIX )
725- || data. starts_with ( PARITY_BAD_JUMP_PREFIX )
726- || data. starts_with ( PARITY_STACK_LIMIT_PREFIX )
727- || data == PARITY_BAD_INSTRUCTION_FE
728- || data == PARITY_BAD_INSTRUCTION_FD
729- || data == PARITY_OUT_OF_GAS
730- || data == XDAI_REVERT =>
731- {
732- let reason = if data == PARITY_BAD_INSTRUCTION_FE {
733- PARITY_BAD_INSTRUCTION_FE . to_owned ( )
734- } else {
735- let payload = data. trim_start_matches ( PARITY_REVERT_PREFIX ) ;
736- hex:: decode ( payload)
737- . ok ( )
738- . and_then ( |payload| {
739- as_solidity_revert_with_reason ( & payload)
740- } )
741- . unwrap_or ( "no reason" . to_owned ( ) )
742- } ;
743- reverted ( & logger, & reason)
744- }
745-
746- // The VM execution error was not identified as a revert.
747- _ => Err ( ContractCallError :: Web3Error ( web3:: Error :: Rpc (
748- rpc_error. clone ( ) ,
749- ) ) ) ,
750- }
751- }
752-
753- // The error was not identified as a revert.
754- Err ( err) => Err ( ContractCallError :: Web3Error ( err) ) ,
770+ Err ( err) => Self :: interpret_eth_call_error ( & logger, err) ,
755771 }
756772 }
757773 } )
758- . map_err ( |e| e. into_inner ( ) . unwrap_or ( ContractCallError :: Timeout ) )
759- . boxed ( )
760774 . await
775+ . map_err ( |e| e. into_inner ( ) . unwrap_or ( ContractCallError :: Timeout ) )
761776 }
762777
763778 async fn call_and_cache (
0 commit comments