[VPD 1241] Certik: Venus Labs - Core Feature Reaudit#315
Conversation
…ot upheld by the cache logic in the derivation bounder oracle
…ion, allowing silent cap-disable or price DoS
| function setSnapshot(uint256 _snapshotMaxExchangeRate, uint256 _snapshotTimestamp) external { | ||
| _checkAccessAllowed("setSnapshot(uint256,uint256)"); | ||
|
|
||
| if (snapshotInterval != 0) { |
There was a problem hiding this comment.
better to have same validation in setGrowthRate (against the stored snapshot) when interval goes 0 → non-zero,
There was a problem hiding this comment.
Yes, that would be better. However, we already have the final audit report, and in practice we wouldn't set the values to 0 anyway. So, I think we can include this change the next time the contract goes through an audit.
trumpgpt-bot
left a comment
There was a problem hiding this comment.
What changed
Core security fix (Certik DS2-87 + DS2-88) — CorrelatedTokenOracle.setSnapshot()
Two new input guards are added, active when snapshotInterval != 0 (capping is enabled):
- DS2-88 —
_snapshotMaxExchangeRate == 0is now rejected. Previously a zero value would causegetMaxAllowedExchangeRate()to return 0, andgetPrice()would fall through to the uncapped path, silently disabling the growth cap. - DS2-87 —
_snapshotTimestamp == 0 || > block.timestampis now rejected. A future or zero timestamp would makeblock.timestamp - snapshotTimestampunderflow (Solidity 0.8 reverts), DoS-ing everygetPrice()andgetMaxAllowedExchangeRate()call.
New InvalidSnapshotTimestamp custom error added. Tests updated to assert the new reverts and to confirm the bypass case (both args zero allowed when capping is disabled via setGrowthRate(0,0)).
Documentation-only changes (no logic):
ResilientOracle._updateAssetPrice— comment clarifies the emptycatch {}is intentional (swallows reverts from non-CorrelatedTokenOraclemain oracles)OneJumpOracle.INTERMEDIATE_ORACLE—@devclarifies the oracle must return a token-denominated rate, not USDDeviationBoundedOracleNatSpec — improved guidance on when to enable/disable the per-asset transient cache
Audit PDF — Certik reaudit report added.
What needs to be updated for mainnet
CorrelatedTokenOracle is abstract and all concrete implementations use a constructor + immutables (no UUPS proxy). All deployed oracle instances must be redeployed to get the new validation. The affected concrete types are the 15 inheritors: OneJumpOracle, ERC4626Oracle, WstETHOracleV2, WeETHOracle, WeETHAccountantOracle, EtherfiAccountantOracle, SlisBNBOracle, BNBxOracle, AsBNBOracle, AnkrBNBOracle, StkBNBOracle, WBETHOracle, SFraxOracle, PendleOracle, ZkETHOracle.
A VIP is then required to:
- Update each affected oracle slot in
ResilientOracleto point to the new deployed addresses - Grant ACM permissions for
setSnapshot(uint256,uint256),setGrowthRate(uint256,uint256), andsetSnapshotGap(uint256)on the new oracle addresses
ResilientOracle itself only had a comment change — no upgrade needed.
Constructor parity gap (cannot anchor inline — unchanged code)
setSnapshot() now blocks _snapshotTimestamp > block.timestamp when capping is active, but the constructor (line 96) only checks for zero in InvalidInitialSnapshot:
if ((_initialSnapshotMaxExchangeRate == 0 || _initialSnapshotTimestamp == 0) && _snapshotInterval > 0)
revert InvalidInitialSnapshot();A deployer who passes a future _initialSnapshotTimestamp with _snapshotInterval > 0 will deploy an oracle where getMaxAllowedExchangeRate() reverts until that timestamp passes, DoS-ing pricing — exactly the DS2-87 scenario. Suggest adding || _initialSnapshotTimestamp > block.timestamp to the constructor condition (or using the new InvalidSnapshotTimestamp error) for parity with the new setSnapshot guard.
|
|
||
| if (snapshotInterval != 0) { | ||
| if (_snapshotMaxExchangeRate == 0) revert InvalidSnapshotMaxExchangeRate(); | ||
| if (_snapshotTimestamp == 0 || _snapshotTimestamp > block.timestamp) revert InvalidSnapshotTimestamp(); |
There was a problem hiding this comment.
[minor] This guard correctly closes DS2-87 for setSnapshot, but the constructor (line 96) has no equivalent check for _initialSnapshotTimestamp > block.timestamp. A deployer can still deploy with a future initial timestamp and snapshotInterval > 0, which causes getMaxAllowedExchangeRate() (line 246: block.timestamp - snapshotTimestamp) to underflow-revert on every price call until the timestamp passes. Suggest extending the constructor's InvalidInitialSnapshot condition (or adding a separate InvalidSnapshotTimestamp revert) with || _initialSnapshotTimestamp > block.timestamp when _snapshotInterval > 0.
Description
Resolves #
Checklist