Skip to content

Commit 4f30205

Browse files
committed
ethereum: Fix adapter to filter proxy calls (#3219)
* ethereum: Fix adapter to filter proxy calls Without this the line that does `function_abi.decode_input(&call.input.0[4..])` was failing with the Err(InvalidData). For some reason some of the OpenSea Proxy contracts don't get recognized as DELEGATECALL, instead they are seen as CALLs, but you can identify them by the input having the wrong size since it's a fallback function call. A scenario to exemplify the problem, can be seen in this transaction: 0x4499dc75facb4cd27894196aac00bcb0ffbe3d64de87105878451d5637662f90 We would get the traces for the block 14043345 (0xD648D1) for the contract 0x7Be8076f4EA4A4AD08075C2508e481d6C946D12b. Then we would find two CALLs for the `atomicMatch_` function. However the first one would have the wrong size (fail at decoding) and the second one would be correct. That's because of the proxy and because of how fallbacks are compiled from Solidity (omitting the function signature, aka a feel bytes less). * ethereum: Apply code review suggestions for the adapter * ethereum: Elaborate/improve comment about call input size
1 parent 5d944c4 commit 4f30205

File tree

1 file changed

+35
-17
lines changed

1 file changed

+35
-17
lines changed

chain/ethereum/src/adapter.rs

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -304,23 +304,35 @@ impl EthereumCallFilter {
304304
}
305305

306306
// Ensure the call is to a contract the filter expressed an interest in
307-
match self.contract_addresses_function_signatures.get(&call.to) {
308-
None => false,
309-
Some(v) => {
310-
let signature = &v.1;
311-
312-
// If the call is to a contract with no specified functions, keep the call
313-
//
314-
// Allows the ability to genericly match on all calls to a contract.
315-
// Caveat is this catch all clause limits you from matching with a specific call
316-
// on the same address
317-
if signature.is_empty() {
318-
true
319-
} else {
320-
// Ensure the call is to run a function the filter expressed an interest in
321-
signature.contains(&call.input.0[..4])
322-
}
323-
}
307+
let signature = match self.contract_addresses_function_signatures.get(&call.to) {
308+
None => return false,
309+
Some(v) => &v.1,
310+
};
311+
312+
// If the call is to a contract with no specified functions, keep the call
313+
//
314+
// Allows the ability to genericly match on all calls to a contract.
315+
// Caveat is this catch all clause limits you from matching with a specific call
316+
// on the same address
317+
if signature.is_empty() {
318+
true
319+
} else {
320+
// Ensure the call is to run a function the filter expressed an interest in
321+
let correct_fn = signature.contains(&call.input.0[..4]);
322+
// Make sure the call input size is multiple of 32, otherwise we can't decode it.
323+
// This is due to the Ethereum ABI spec: https://docs.soliditylang.org/en/v0.8.11/abi-spec.html
324+
//
325+
// A scenario where the input could have the wrong size is when a `delegatecall` is
326+
// "disguised" as a `call`. For example for this couple of transactions/calls:
327+
// 1. https://etherscan.io/tx/0x8e992eeb40e18703dd8169a8031e2113311985f1c20e0723a2dc362df40bafb0
328+
// 2. https://etherscan.io/tx/0x7c391bcfa2007f84e123ad47ea72e2d6ffb2fbab81deff0990b0c499e0664a92
329+
//
330+
// The first one is acting as a proxy to the second one (an atomicMatch_).
331+
// If you try to decode the first call as it is/comes from a `traces` RPC request, it
332+
// will fail because it has a smaller size/length.
333+
let correct_input_size = (call.input.0.len() - 4) % 32 == 0;
334+
335+
correct_fn && correct_input_size
324336
}
325337
}
326338

@@ -714,6 +726,12 @@ mod tests {
714726
"call with correct address & signature should match"
715727
);
716728

729+
assert_eq!(
730+
false,
731+
filter.matches(&call(address(1), vec![1; 32])),
732+
"call with correct address & signature, but with incorrect input size should be ignored"
733+
);
734+
717735
assert_eq!(
718736
false,
719737
filter.matches(&call(address(1), vec![4u8; 36])),

0 commit comments

Comments
 (0)