@@ -28,7 +28,7 @@ use graph::{
28
28
blockchain:: { block_stream:: BlockWithTriggers , BlockPtr , IngestorError } ,
29
29
prelude:: {
30
30
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,
32
32
web3:: {
33
33
self ,
34
34
types:: {
@@ -56,6 +56,7 @@ use std::time::Instant;
56
56
57
57
use crate :: adapter:: EthereumRpcError ;
58
58
use crate :: adapter:: ProviderStatus ;
59
+ use crate :: call_helper:: interpret_eth_call_error;
59
60
use crate :: chain:: BlockFinality ;
60
61
use crate :: trigger:: LogRef ;
61
62
use crate :: Chain ;
@@ -115,134 +116,6 @@ impl CheapClone for EthereumAdapter {
115
116
}
116
117
117
118
impl 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
-
246
119
pub fn is_call_only ( & self ) -> bool {
247
120
self . call_only
248
121
}
@@ -767,7 +640,7 @@ impl EthereumAdapter {
767
640
768
641
match result {
769
642
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) ,
771
644
}
772
645
}
773
646
} )
0 commit comments