-
Notifications
You must be signed in to change notification settings - Fork 2
fix: 🛠️ Sherlock audit remediation #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…audit-issues-sherlock
* Replace broad exception handling with specific exceptions in price adapters * Use separate exception handlers with specific error messages for each exception type --------- Co-authored-by: Tim B <[email protected]>
* fix: 🐛 fix cowswap scaling * refactor: ♻️ Tidy code
* fix: 🔧 add timeouts added timeout to safe external api so it doesn't hang indefinitely * feat: 🩹 add cowswap timeout * increase timeout --------- Co-authored-by: timbrinded <[email protected]>
* Add Pyth API response validation with JSON and structure checks * Properly validate feed items are dicts instead of silently filtering * Refactor tests to reduce code duplication using helper functions * chore: fmt * Fix tests to use monkeypatch instead of mocker fixture * Apply ruff formatting * switch to pydantic model * fmt --------- Co-authored-by: timbrinded <[email protected]>
…ythAdapter (#107) Co-authored-by: matias-gonz <[email protected]>
* refactor: 🛠️ Change fetch_native_price return type to string to prevent float precision loss * add test * Add CowSwap API response validation with timeout and structure checks * chore: lint * Remove redundant numeric price and timeout tests * fix tests --------- Co-authored-by: timbrinded <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR addresses audit issues for the Sherlock oracle by improving robustness, error handling, and test coverage. The changes focus on fixing precision issues in price calculations, enhancing retry logic, adding comprehensive input validation, and removing unused code.
- Removes unused code (
units.py,safe_state.pyadapter) and adds test coverage for pricing, validation, and timeout behavior - Introduces global pipeline timeout, price validation retry logic with backoff, and HTTP request retry mechanisms
- Fixes decimal precision handling in price adapters (Pyth and CowSwap) to properly account for token decimals
Reviewed changes
Copilot reviewed 30 out of 31 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
uv.lock, pyproject.toml |
Adds pytest-mock dependency for improved testing |
tq-oracle.toml.example |
Adds configuration examples for new timeout and Pyth settings |
src/tq_oracle/settings.py |
Adds global timeout, price validation retry settings, and Pyth fail flags |
src/tq_oracle/main.py |
Adds CLI option for global timeout |
src/tq_oracle/pipeline/run.py |
Implements global timeout wrapper, removes unused address canonicalization |
src/tq_oracle/pipeline/pricing.py |
Adds retry logic with backoff for price validation |
src/tq_oracle/pipeline/assets.py |
Adds block_identifier to subvault queries |
src/tq_oracle/report/publisher.py |
Adds retry logic for Safe API calls |
src/tq_oracle/adapters/price_adapters/pyth.py |
Adds decimal-aware price calculations, input validation, and stale/missing feed tracking |
src/tq_oracle/adapters/price_adapters/cow_swap.py |
Removes unused get_token_decimals, improves error handling and precision |
src/tq_oracle/adapters/price_adapters/eth.py |
Removes redundant validate_prices call |
src/tq_oracle/adapters/price_validators/pyth.py |
Adds fail-on-stale and fail-on-missing price logic |
src/tq_oracle/adapters/asset_adapters/idle_balances.py |
Optimizes by caching supported assets query |
src/tq_oracle/processors/total_assets.py |
Refactors division to avoid precision loss |
src/tq_oracle/processors/oracle_helper.py |
Improves error handling for empty vaults |
src/tq_oracle/checks/price_validators.py |
Adds retry_recommended flag to PriceValidationError |
src/tq_oracle/adapters/check_adapters/timeout_check.py |
Adds check for suspicious reports allowing immediate replacement |
src/tq_oracle/adapters/check_adapters/safe_state.py |
Removes unused Safe state check adapter |
src/tq_oracle/adapters/check_adapters/__init__.py |
Removes SafeStateAdapter from check list |
src/tq_oracle/abi.py |
Updates get_oracle_address_from_vault to use settings object and block_identifier |
tests/test_units.py |
Removes tests for deleted scale_to_18 function |
tests/pipeline/test_run_timeout.py |
Adds tests for global timeout behavior |
tests/checks/test_price_validations.py |
Adds tests for price validation retry logic |
tests/adapters/price_validators/test_pyth.py |
Updates tests for decimal-aware pricing and adds stale/missing feed tests |
tests/adapters/price_adapters/test_pyth.py |
Adds comprehensive error handling tests |
tests/adapters/price_adapters/test_cow_swap.py |
Adds precision and error handling tests |
tests/adapters/asset_adapters/test_idle_balances.py |
Updates test to match supported_assets caching |
README.md |
Documents new global_timeout_seconds option |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| price_decimal = Decimal(str(price_value)) | ||
| except Exception as e: |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Catching bare Exception is too broad. Specify the expected exception types (e.g., ValueError, TypeError, decimal.InvalidOperation) to avoid masking unexpected errors.
| ) | ||
| raise asyncio.TimeoutError( | ||
| "Report exceeded global timeout " | ||
| f"{timeout_s}s (vault={vault_address})\n N.B. This can be changed via `global_timeout_seconds` " |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message contains a bare newline followed by 'N.B.' which may not display properly in all contexts. Consider using a space or reformatting to: f\"{timeout_s}s (vault={vault_address}). This can be changed via 'global_timeout_seconds' or CLI flag '--global-timeout-seconds'.\"
| f"{timeout_s}s (vault={vault_address})\n N.B. This can be changed via `global_timeout_seconds` " | |
| f"{timeout_s}s (vault={vault_address}). N.B. This can be changed via `global_timeout_seconds` " |
| assert validator.failure_tolerance == 1.0 | ||
|
|
||
|
|
||
| async def _usdc_decimals(self, _token_address: str) -> int: |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The helper function _usdc_decimals is defined at module level (outside any test function or class) but takes self as a parameter. This suggests it's meant to be a method, but it's not part of any class. Either remove the self parameter or move it inside the appropriate test class/fixture.
| async def _usdc_decimals(self, _token_address: str) -> int: | |
| async def _usdc_decimals(_token_address: str) -> int: |
| logger.error("OracleHelper contract call failed: %s", str(e)) | ||
|
|
||
| if config.ignore_empty_vault and total_assets == 0: |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error is logged before checking conditions, meaning the error will always be logged even when it's expected (empty vault with ignore flag). Consider moving the error logging inside the else block at line 81 where the exception is re-raised, or making it a warning when the vault is empty.
| decimals = await self.get_token_decimals(original_address) | ||
| price_per_base_unit_d18 = (asset_price_18 * 10**18) // (10**decimals) |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The get_token_decimals method is called for every asset inside a loop without caching. This could result in multiple RPC calls for the same token. Consider adding a decimals cache similar to what was removed from CowSwapAdapter, or calling this once per unique asset before the loop.
| # Oracle price: USDC is ~1/3000 ETH (since ETH is $3000 and USDC is $1) | ||
| monkeypatch.setattr( | ||
| "tq_oracle.adapters.price_adapters.pyth.PythAdapter.get_token_decimals", | ||
| _usdc_decimals, |
Copilot
AI
Nov 21, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The _usdc_decimals function is defined with a self parameter but is being used as a standalone function via monkeypatch.setattr. This will cause a TypeError when called because the function expects self but won't receive it in this patching context. Either remove self from the function signature or use a lambda/partial to properly bind it.
| _usdc_decimals, | |
| lambda *args, **kwargs: _usdc_decimals(), |
* fix dps * fix tests
* fix dps * fix tests * fix log
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 35 out of 36 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1,7 +1,10 @@ | |||
| from __future__ import annotations | |||
|
|
|||
| from _decimal import ROUND_DOWN | |||
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import ROUND_DOWN from decimal instead of _decimal. The _decimal module is an internal implementation detail. Use from decimal import Decimal, ROUND_DOWN for consistency with the rest of the codebase (see oracle_helper.py line 4).
| from _decimal import ROUND_DOWN | |
| from decimal import ROUND_DOWN |
| from _decimal import ROUND_DOWN | ||
|
|
||
| from ..adapters.price_adapters.base import PriceData | ||
| from ..processors.asset_aggregator import AggregatedAssets | ||
| from decimal import Decimal | ||
|
|
||
|
|
Copilot
AI
Nov 23, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Group imports from the same module together. Both Decimal and ROUND_DOWN should be imported on the same line: from decimal import Decimal, ROUND_DOWN. This matches the pattern used in oracle_helper.py (line 4).
| from _decimal import ROUND_DOWN | |
| from ..adapters.price_adapters.base import PriceData | |
| from ..processors.asset_aggregator import AggregatedAssets | |
| from decimal import Decimal | |
| from decimal import Decimal, ROUND_DOWN | |
| from ..adapters.price_adapters.base import PriceData | |
| from ..processors.asset_aggregator import AggregatedAssets |
* fix dps * fix tests * fix log * fix dp rounding
Warning
This should not be attempted to be merged until #130 updates this branch with the latest fixes.
Contains remediation for all of the issues raised by the Sherlock Auditor.