@@ -98,6 +98,134 @@ impl CheapClone for EthereumAdapter {
98
98
}
99
99
100
100
impl EthereumAdapter {
101
+ // ------------------------------------------------------------------
102
+ // Constants and helper utilities used across eth_call handling
103
+ // ------------------------------------------------------------------
104
+
105
+ // Try to check if the call was reverted. The JSON-RPC response for reverts is
106
+ // not standardized, so we have ad-hoc checks for each Ethereum client.
107
+
108
+ // 0xfe is the "designated bad instruction" of the EVM, and Solidity uses it for
109
+ // asserts.
110
+ const PARITY_BAD_INSTRUCTION_FE : & str = "Bad instruction fe" ;
111
+
112
+ // 0xfd is REVERT, but on some contracts, and only on older blocks,
113
+ // this happens. Makes sense to consider it a revert as well.
114
+ const PARITY_BAD_INSTRUCTION_FD : & str = "Bad instruction fd" ;
115
+
116
+ const PARITY_BAD_JUMP_PREFIX : & str = "Bad jump" ;
117
+ const PARITY_STACK_LIMIT_PREFIX : & str = "Out of stack" ;
118
+
119
+ // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61.
120
+ const PARITY_OUT_OF_GAS : & str = "Out of gas" ;
121
+
122
+ // Also covers Nethermind reverts
123
+ const PARITY_VM_EXECUTION_ERROR : i64 = -32015 ;
124
+ const PARITY_REVERT_PREFIX : & str = "revert" ;
125
+
126
+ const XDAI_REVERT : & str = "revert" ;
127
+
128
+ // Deterministic Geth execution errors. We might need to expand this as
129
+ // subgraphs come across other errors. See
130
+ // https://github.com/ethereum/go-ethereum/blob/cd57d5cd38ef692de8fbedaa56598b4e9fbfbabc/core/vm/errors.go
131
+ const GETH_EXECUTION_ERRORS : & [ & str ] = & [
132
+ // The "revert" substring covers a few known error messages, including:
133
+ // Hardhat: "error: transaction reverted",
134
+ // Ganache and Moonbeam: "vm exception while processing transaction: revert",
135
+ // Geth: "execution reverted"
136
+ // And others.
137
+ "revert" ,
138
+ "invalid jump destination" ,
139
+ "invalid opcode" ,
140
+ // Ethereum says 1024 is the stack sizes limit, so this is deterministic.
141
+ "stack limit reached 1024" ,
142
+ // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61 for why the gas limit is considered deterministic.
143
+ "out of gas" ,
144
+ "stack underflow" ,
145
+ ] ;
146
+
147
+ /// Helper that checks if a geth style RPC error message corresponds to a revert.
148
+ fn is_geth_revert_message ( message : & str ) -> bool {
149
+ let env_geth_call_errors = ENV_VARS . geth_eth_call_errors . iter ( ) ;
150
+ let mut execution_errors = Self :: GETH_EXECUTION_ERRORS
151
+ . iter ( )
152
+ . copied ( )
153
+ . chain ( env_geth_call_errors. map ( |s| s. as_str ( ) ) ) ;
154
+ execution_errors. any ( |e| message. to_lowercase ( ) . contains ( e) )
155
+ }
156
+
157
+ /// Decode a Solidity revert(reason) payload, returning the reason string when possible.
158
+ fn as_solidity_revert_reason ( bytes : & [ u8 ] ) -> Option < String > {
159
+ let selector = & tiny_keccak:: keccak256 ( b"Error(string)" ) [ ..4 ] ;
160
+ if bytes. len ( ) >= 4 && & bytes[ ..4 ] == selector {
161
+ abi:: DynSolType :: String
162
+ . abi_decode ( & bytes[ 4 ..] )
163
+ . ok ( )
164
+ . and_then ( |val| val. clone ( ) . as_str ( ) . map ( ToOwned :: to_owned) )
165
+ } else {
166
+ None
167
+ }
168
+ }
169
+
170
+ /// Interpret the error returned by `eth_call`, distinguishing genuine failures from
171
+ /// EVM reverts. Returns `Ok(Null)` for reverts or a proper error otherwise.
172
+ fn interpret_eth_call_error (
173
+ logger : & Logger ,
174
+ err : web3:: Error ,
175
+ ) -> Result < call:: Retval , ContractCallError > {
176
+ fn reverted ( logger : & Logger , reason : & str ) -> Result < call:: Retval , ContractCallError > {
177
+ info ! ( logger, "Contract call reverted" ; "reason" => reason) ;
178
+ Ok ( call:: Retval :: Null )
179
+ }
180
+
181
+ if let web3:: Error :: Rpc ( rpc_error) = & err {
182
+ if Self :: is_geth_revert_message ( & rpc_error. message ) {
183
+ return reverted ( logger, & rpc_error. message ) ;
184
+ }
185
+ }
186
+
187
+ if let web3:: Error :: Rpc ( rpc_error) = & err {
188
+ let code = rpc_error. code . code ( ) ;
189
+ let data = rpc_error. data . as_ref ( ) . and_then ( |d| d. as_str ( ) ) ;
190
+
191
+ if code == Self :: PARITY_VM_EXECUTION_ERROR {
192
+ if let Some ( data) = data {
193
+ if Self :: is_parity_revert ( data) {
194
+ return reverted ( logger, & Self :: parity_revert_reason ( data) ) ;
195
+ }
196
+ }
197
+ }
198
+ }
199
+
200
+ Err ( ContractCallError :: Web3Error ( err) )
201
+ }
202
+
203
+ fn is_parity_revert ( data : & str ) -> bool {
204
+ data. to_lowercase ( ) . starts_with ( Self :: PARITY_REVERT_PREFIX )
205
+ || data. starts_with ( Self :: PARITY_BAD_JUMP_PREFIX )
206
+ || data. starts_with ( Self :: PARITY_STACK_LIMIT_PREFIX )
207
+ || data == Self :: PARITY_BAD_INSTRUCTION_FE
208
+ || data == Self :: PARITY_BAD_INSTRUCTION_FD
209
+ || data == Self :: PARITY_OUT_OF_GAS
210
+ || data == Self :: XDAI_REVERT
211
+ }
212
+
213
+ /// Checks if the given `web3::Error` corresponds to a Parity / Nethermind style EVM
214
+ /// revert and, if so, tries to extract a human-readable revert reason. Returns `Some`
215
+ /// with the reason when the error is identified as a revert, otherwise `None`.
216
+ fn parity_revert_reason ( data : & str ) -> String {
217
+ if data == Self :: PARITY_BAD_INSTRUCTION_FE {
218
+ return Self :: PARITY_BAD_INSTRUCTION_FE . to_owned ( ) ;
219
+ }
220
+
221
+ // Otherwise try to decode a Solidity revert reason payload.
222
+ let payload = data. trim_start_matches ( Self :: PARITY_REVERT_PREFIX ) ;
223
+ hex:: decode ( payload)
224
+ . ok ( )
225
+ . and_then ( |decoded| Self :: as_solidity_revert_reason ( & decoded) )
226
+ . unwrap_or_else ( || "no reason" . to_owned ( ) )
227
+ }
228
+
101
229
pub fn is_call_only ( & self ) -> bool {
102
230
self . call_only
103
231
}
@@ -567,11 +695,6 @@ impl EthereumAdapter {
567
695
block_ptr : BlockPtr ,
568
696
gas : Option < u32 > ,
569
697
) -> Result < call:: Retval , ContractCallError > {
570
- fn reverted ( logger : & Logger , reason : & str ) -> Result < call:: Retval , ContractCallError > {
571
- info ! ( logger, "Contract call reverted" ; "reason" => reason) ;
572
- Ok ( call:: Retval :: Null )
573
- }
574
-
575
698
let web3 = self . web3 . clone ( ) ;
576
699
let logger = Logger :: new ( & logger, o ! ( "provider" => self . provider. clone( ) ) ) ;
577
700
@@ -600,122 +723,14 @@ impl EthereumAdapter {
600
723
} ;
601
724
let result = web3. eth ( ) . call ( req, Some ( block_id) ) . boxed ( ) . await ;
602
725
603
- // Try to check if the call was reverted. The JSON-RPC response for reverts is
604
- // not standardized, so we have ad-hoc checks for each Ethereum client.
605
-
606
- // 0xfe is the "designated bad instruction" of the EVM, and Solidity uses it for
607
- // asserts.
608
- const PARITY_BAD_INSTRUCTION_FE : & str = "Bad instruction fe" ;
609
-
610
- // 0xfd is REVERT, but on some contracts, and only on older blocks,
611
- // this happens. Makes sense to consider it a revert as well.
612
- const PARITY_BAD_INSTRUCTION_FD : & str = "Bad instruction fd" ;
613
-
614
- const PARITY_BAD_JUMP_PREFIX : & str = "Bad jump" ;
615
- const PARITY_STACK_LIMIT_PREFIX : & str = "Out of stack" ;
616
-
617
- // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61.
618
- const PARITY_OUT_OF_GAS : & str = "Out of gas" ;
619
-
620
- // Also covers Nethermind reverts
621
- const PARITY_VM_EXECUTION_ERROR : i64 = -32015 ;
622
- const PARITY_REVERT_PREFIX : & str = "revert" ;
623
-
624
- const XDAI_REVERT : & str = "revert" ;
625
-
626
- // Deterministic Geth execution errors. We might need to expand this as
627
- // subgraphs come across other errors. See
628
- // https://github.com/ethereum/go-ethereum/blob/cd57d5cd38ef692de8fbedaa56598b4e9fbfbabc/core/vm/errors.go
629
- const GETH_EXECUTION_ERRORS : & [ & str ] = & [
630
- // The "revert" substring covers a few known error messages, including:
631
- // Hardhat: "error: transaction reverted",
632
- // Ganache and Moonbeam: "vm exception while processing transaction: revert",
633
- // Geth: "execution reverted"
634
- // And others.
635
- "revert" ,
636
- "invalid jump destination" ,
637
- "invalid opcode" ,
638
- // Ethereum says 1024 is the stack sizes limit, so this is deterministic.
639
- "stack limit reached 1024" ,
640
- // See f0af4ab0-6b7c-4b68-9141-5b79346a5f61 for why the gas limit is considered deterministic.
641
- "out of gas" ,
642
- "stack underflow" ,
643
- ] ;
644
-
645
- let env_geth_call_errors = ENV_VARS . geth_eth_call_errors . iter ( ) ;
646
- let mut geth_execution_errors = GETH_EXECUTION_ERRORS
647
- . iter ( )
648
- . copied ( )
649
- . chain ( env_geth_call_errors. map ( |s| s. as_str ( ) ) ) ;
650
-
651
- let as_solidity_revert_with_reason = |bytes : & [ u8 ] | {
652
- let solidity_revert_function_selector =
653
- & tiny_keccak:: keccak256 ( b"Error(string)" ) [ ..4 ] ;
654
-
655
- match bytes. len ( ) >= 4 && & bytes[ ..4 ] == solidity_revert_function_selector {
656
- false => None ,
657
- true => abi:: DynSolType :: String
658
- . abi_decode ( & bytes[ 4 ..] )
659
- . ok ( )
660
- . and_then ( |val| val. clone ( ) . as_str ( ) . map ( ToOwned :: to_owned) ) ,
661
- }
662
- } ;
663
-
664
726
match result {
665
- // A successful response.
666
727
Ok ( bytes) => Ok ( call:: Retval :: Value ( scalar:: Bytes :: from ( bytes) ) ) ,
667
-
668
- // Check for Geth revert.
669
- Err ( web3:: Error :: Rpc ( rpc_error) )
670
- if geth_execution_errors
671
- . any ( |e| rpc_error. message . to_lowercase ( ) . contains ( e) ) =>
672
- {
673
- reverted ( & logger, & rpc_error. message )
674
- }
675
-
676
- // Check for Parity revert.
677
- Err ( web3:: Error :: Rpc ( ref rpc_error) )
678
- if rpc_error. code . code ( ) == PARITY_VM_EXECUTION_ERROR =>
679
- {
680
- match rpc_error. data . as_ref ( ) . and_then ( |d| d. as_str ( ) ) {
681
- Some ( data)
682
- if data. to_lowercase ( ) . starts_with ( PARITY_REVERT_PREFIX )
683
- || data. starts_with ( PARITY_BAD_JUMP_PREFIX )
684
- || data. starts_with ( PARITY_STACK_LIMIT_PREFIX )
685
- || data == PARITY_BAD_INSTRUCTION_FE
686
- || data == PARITY_BAD_INSTRUCTION_FD
687
- || data == PARITY_OUT_OF_GAS
688
- || data == XDAI_REVERT =>
689
- {
690
- let reason = if data == PARITY_BAD_INSTRUCTION_FE {
691
- PARITY_BAD_INSTRUCTION_FE . to_owned ( )
692
- } else {
693
- let payload = data. trim_start_matches ( PARITY_REVERT_PREFIX ) ;
694
- hex:: decode ( payload)
695
- . ok ( )
696
- . and_then ( |payload| {
697
- as_solidity_revert_with_reason ( & payload)
698
- } )
699
- . unwrap_or ( "no reason" . to_owned ( ) )
700
- } ;
701
- reverted ( & logger, & reason)
702
- }
703
-
704
- // The VM execution error was not identified as a revert.
705
- _ => Err ( ContractCallError :: Web3Error ( web3:: Error :: Rpc (
706
- rpc_error. clone ( ) ,
707
- ) ) ) ,
708
- }
709
- }
710
-
711
- // The error was not identified as a revert.
712
- Err ( err) => Err ( ContractCallError :: Web3Error ( err) ) ,
728
+ Err ( err) => Self :: interpret_eth_call_error ( & logger, err) ,
713
729
}
714
730
}
715
731
} )
716
- . map_err ( |e| e. into_inner ( ) . unwrap_or ( ContractCallError :: Timeout ) )
717
- . boxed ( )
718
732
. await
733
+ . map_err ( |e| e. into_inner ( ) . unwrap_or ( ContractCallError :: Timeout ) )
719
734
}
720
735
721
736
async fn call_and_cache (
0 commit comments