Fix EIP-4626 contract revert classification#4360
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the is_contract_revert method to the ContractErrorExt trait to specifically handle contract-level reverts, including those with empty data, and updates the EIP-4626 price estimator to utilize this check. Review feedback identifies a potential regression where DecodingError is no longer classified as a non-vault condition and highlights that transport errors should be explicitly mapped to ProtocolInternal to ensure the failover mechanism functions correctly.
| || err.code == 3 | ||
| || err.message.to_lowercase().contains("revert") | ||
| } | ||
| ContractError::ZeroData(..) => true, |
There was a problem hiding this comment.
The implementation of is_contract_revert introduces a regression compared to the previous is_contract_error check. Specifically, it returns false for DecodingError, which occurs when a contract implements the expected selector but returns data that doesn't match the ABI (e.g., a non-vault contract that happens to have an asset() function returning a string). Previously, these were correctly classified as non-vaults in the EIP-4626 estimator. To maintain robustness against interface mismatches, DecodingError should be included.
| ContractError::ZeroData(..) => true, | |
| ContractError::ZeroData(..) | ContractError::DecodingError(..) => true, |
There was a problem hiding this comment.
The suggested error is not a thing. UnknownFunction and UnknownSelector are, and they fit this use case
| // Contract-level revert on `asset()` + working `decimals()` = | ||
| // plain ERC-20. Transient transport failures propagate so they | ||
| // retry instead of pinning the token as non-vault. | ||
| Err(err) if err.is_contract_revert() && decimals_result.is_ok() => Ok(None), |
There was a problem hiding this comment.
The error handling here does not correctly distinguish between contract-level reverts and node/transport errors. When asset() fails due to a transient transport issue (e.g., timeout or connection error), is_contract_revert() returns false, and the error falls through to be wrapped in EstimatorInternal. This prevents the FallbackNativePriceEstimator from triggering a failover to a healthy node, as it only reacts to ProtocolInternal errors. Transport errors should be explicitly caught and propagated as ProtocolInternal.
| Err(err) if err.is_contract_revert() && decimals_result.is_ok() => Ok(None), | |
| Err(err) if err.is_contract_revert() && decimals_result.is_ok() => Ok(None), | |
| Err(err) if err.is_node_error() => Err(PriceEstimationError::ProtocolInternal(err.into())), |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
|
@claude review |
|
Claude encountered an error —— View job Code review
SummaryThe core fix is sound. The root cause was that empty-data reverts (e.g. calling a missing
|
MartinquaXD
left a comment
There was a problem hiding this comment.
Change looks reasonable to me. 👌
| /// Regression test: USDC-like terminal tokens revert their non-existent | ||
| /// `asset()` selector with *empty* revert data. The classification must | ||
| /// treat that as "non-vault" (loop terminates cleanly), not as a transport | ||
| /// failure that aborts the unwrap chain. | ||
| #[tokio::test] | ||
| async fn terminal_token_reverting_without_data_classified_as_non_vault() { |
There was a problem hiding this comment.
Wouldn't it be nicer to make this a forked test and test with the real USDC token?
That way, if we encounter another token that behaves weirdly we could just plug that one in and see what happens.
Description
Testing the Euler vault tokens I found that the revert condition is a bit too strict for the Eip4626 purposes and it would think that the underlying asset was not valid because
asset()failed; even though that's the expected behavior.This PR adds a new condition for a revert caused by a lack of selector, specifically for this case.
Tested in staging, results can be seen in — https://victorialogs.dev.cow.fi/goto/ffjuyxx1tk6psc?orgId=1
native_price requests were done for
0x53afe3343f322c4189ab69e0d048efd154259419Changes
How to test
Call the native_price endpoint with a Euler vault address (ensure that staging has them enabled)