Skip to content

Conversation

@timbrinded
Copy link
Collaborator

@timbrinded timbrinded commented Nov 21, 2025

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.

matias-gonz and others added 10 commits November 21, 2025 16:45
* 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]>
* 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]>
Copilot AI review requested due to automatic review settings November 21, 2025 19:12
Copilot finished reviewing on behalf of timbrinded November 21, 2025 19:14
Copy link
Contributor

Copilot AI left a 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.py adapter) 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.

Comment on lines +99 to +100
price_decimal = Decimal(str(price_value))
except Exception as e:
Copy link

Copilot AI Nov 21, 2025

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.

Copilot uses AI. Check for mistakes.
)
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` "
Copy link

Copilot AI Nov 21, 2025

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'.\"

Suggested change
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` "

Copilot uses AI. Check for mistakes.
assert validator.failure_tolerance == 1.0


async def _usdc_decimals(self, _token_address: str) -> int:
Copy link

Copilot AI Nov 21, 2025

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.

Suggested change
async def _usdc_decimals(self, _token_address: str) -> int:
async def _usdc_decimals(_token_address: str) -> int:

Copilot uses AI. Check for mistakes.
Comment on lines +74 to +76
logger.error("OracleHelper contract call failed: %s", str(e))

if config.ignore_empty_vault and total_assets == 0:
Copy link

Copilot AI Nov 21, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines 362 to 363
decimals = await self.get_token_decimals(original_address)
price_per_base_unit_d18 = (asset_price_18 * 10**18) // (10**decimals)
Copy link

Copilot AI Nov 21, 2025

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.

Copilot uses AI. Check for mistakes.
# 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,
Copy link

Copilot AI Nov 21, 2025

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.

Suggested change
_usdc_decimals,
lambda *args, **kwargs: _usdc_decimals(),

Copilot uses AI. Check for mistakes.
* fix dps

* fix tests
* fix dps

* fix tests

* fix log
Copilot AI review requested due to automatic review settings November 23, 2025 11:51
Copilot finished reviewing on behalf of timbrinded November 23, 2025 11:53
Copy link
Contributor

Copilot AI left a 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
Copy link

Copilot AI Nov 23, 2025

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).

Suggested change
from _decimal import ROUND_DOWN
from decimal import ROUND_DOWN

Copilot uses AI. Check for mistakes.
Comment on lines +3 to 9
from _decimal import ROUND_DOWN

from ..adapters.price_adapters.base import PriceData
from ..processors.asset_aggregator import AggregatedAssets
from decimal import Decimal


Copy link

Copilot AI Nov 23, 2025

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).

Suggested change
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

Copilot uses AI. Check for mistakes.
* fix dps

* fix tests

* fix log

* fix dp rounding
@timbrinded timbrinded added enhancement New feature or request FYEO FYEO auditor changes labels Dec 2, 2025
@timbrinded timbrinded closed this Dec 2, 2025
@timbrinded timbrinded reopened this Dec 2, 2025
@timbrinded timbrinded added sherlock Sherlock auditor changes audit and removed FYEO FYEO auditor changes enhancement New feature or request labels Dec 2, 2025
@timbrinded timbrinded changed the title Fix/audit issues sherlock fix: 🛠️ Sherlock audit remediation Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

audit sherlock Sherlock auditor changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants