Download audit report
This document presents the security review performed by Nethermind Security for Mellow Protocol smart contracts. The scope of this audit was specifically focused on the newly developed OracleSubmitter contract. The OracleSubmitter contract is a specialized adapter designed to serve as an on-chain, Chainlink-compatible price feed for the Mellow Protocol. Its primary function is to act as an interface by submitting price reports to the main Oracle contract (which validates them), reading back the validated report (including its suspicion status), caching it, and exposing the latest non-suspicious price for the system's baseAsset.
To enforce this architecture, the OracleSubmitter contract's address is designed to be the sole holder of SUBMIT_REPORTS_ROLE and ACCEPT_REPORT_ROLE in the main protocol's vault.
The audited code comprises 76 lines of code written in the Solidity language. The audit included the OracleSubmitter contract.
The audit was performed using (a) manual analysis of the codebase and (b) automated analysis tools.
Findings by severity
| # | Contract | LoC | Comments | Ratio | Blank | Total |
| 1 | src/oracles/OracleSubmitter.sol | 76 | 4 | 5.3% | 18 | 98 |
| Total | 76 | 4 | 5.3% | 18 | 98 |
| # | Finding | Severity | Update |
| 1 | Calculation of latestAnswer introduces minor precision loss due to double rounding | Info | Acknowledged |
| 2 | The latestRoundData() function returns misleading timestamps | Info | Acknowledged |
For a comprehensive understanding of the entire Mellow Protocol system, please refer to the official audit report: NM0587-FINAL_Mellow. This section focuses exclusively on the newly added OracleSubmitter contract.
The OracleSubmitter contract is a specialized adapter designed to serve as an on-chain, Chainlink-compatible price feed for the Mellow Protocol. Its primary function is to act as a complete interface by submitting price reports to the main IOracle contract (which validates them), reading back the validated report—including its suspicion status—caching it, and exposing the latest non-suspicious price of the system's baseAsset.
To enforce this architecture, the OracleSubmitter contract's address is designed to be the sole holder of the SUBMIT_REPORTS_ROLE and ACCEPT_REPORT_ROLE on the main protocol's vault. This design ensures it acts as a mandatory proxy for all price submissions and acceptances. This allows external protocols (e.g., Gearbox) to consume Mellow's baseAsset price using the standard latestRoundData() interface.
The primary flow for updating the price feed begins when an authorized Submitter (an address granted the SUBMIT_REPORTS_ROLE on the OracleSubmitter contract) calls submitReports(...).
Submitter calls submitReports(...) with an array of reports. The protocol enforces that the first report in the array must be for the baseAsset.OracleSubmitter contract (acting as the sole role-holder) then forwards the raw reports to the main IOracle contract by calling oracle.submitReports(...). The main IOracle contains the logic to validate the price, check for deviations, and flag a report as suspicious.OracleSubmitter immediately queries the main IOracle using oracle.getReport(...) to retrieve the detailed, validated report (which includes the isSuspicious flag). This detailed report is then cached locally in the _reports mapping.baseAsset and is not flagged as suspicious (!report.isSuspicious), the OracleSubmitter contract updates its internal latestAnswer by calling _updateLatestAnswer(...). If a baseAsset report is suspicious, it is cached, but latestAnswer is not updated.If the main IOracle flags a baseAsset report as suspicious (e.g., due to significant price deviation), the latestAnswer is not updated, and the price feed remains stale. This situation requires manual intervention from an authorized Accepter (an address granted the ACCEPT_REPORT_ROLE on the OracleSubmitter contract).
acceptReports(...) function, providing the asset, price, and timestamp of the suspicious report.OracleSubmitter contract (acting as the sole role-holder) to the main IOracle via oracle.acceptReport(...), which clears the isSuspicious flag in the main oracle.acceptReports(...) is called, the OracleSubmitter checks if the accepted report is for the baseAsset. If it is, the contract immediately updates its latestAnswer with the now-accepted price. This ensures the feed becomes "unstuck" and reflects the manually verified price.acceptedAt mapping.The primary purpose of the OracleSubmitter is to be consumed by other protocols. It achieves this by exposing a standard Chainlink AggregatorV3Interface.
latestRoundData(): This function provides the standard interface for price feed consumption. It returns the latestAnswer (the price) and updatedAt (the timestamp of the last update). As Mellow's oracle does not use round IDs, roundId and answeredInRound are hardcoded to 0.latestAnswer is not stored as a simple price. The main IOracle provides priceD18, which represents shares per asset, scaled by 1e18. To be useful as a price feed, this value must be inverted to assets per share. The _updateLatestAnswer(...) function calculates this as int256(1e36 / uint256(priceD18)).baseAsset is USDC (6 decimals) and the IOracle reports a priceD18 of 1e30 (1e18 shares for 1e-12 USDC), the latestAnswer becomes 1e36 / 1e30 = 1e6. This 1e6 correctly represents the price of one share in terms of the asset's smallest unit (1 USDC = 1e6).The risk rating methodology used by Nethermind Security follows the principles established by the OWASP Foundation. The severity of each finding is determined by two factors: Likelihood and Impact.
Likelihood measures how likely the finding is to be uncovered and exploited by an attacker. This factor will be one of the following values:
When defining the likelihood of a finding, other factors are also considered. These can include but are not limited to motive, opportunity, exploit accessibility, ease of discovery, and ease of exploit.
Impact is a measure of the damage that may be caused if an attacker exploits the finding. This factor will be one of the following values:
When defining the impact of a finding, other factors are also considered. These can include but are not limited to data and state integrity, loss of availability, financial loss, and reputation damage. After defining the likelihood and impact of an issue, the severity is determined according to the table below.
| Impact / Likelihood | Low | Medium | High |
| High | Medium | High | Critical |
| Medium | Low | Medium | High |
| Low | Info / Best Practices | Low | Medium |
| Undetermined | Undetermined | Undetermined | Undetermined |
To address issues that do not fit a High, Medium or Low severity, Nethermind Security also uses three more finding severities: Informational, Best Practices, and Undetermined.
latestAnswer introduces minor precision loss due to double rounding| File | Fix commit | Type | Status |
src/oracles/OracleSubmitter.sol | — | Info | Acknowledged |
The OracleSubmitter contract is designed to function as a Chainlink-compatible price feed adapter. It exposes functions like latestRoundData(...) which return the latestAnswer state variable, representing the price of the base asset per share (assets/shares).
This latestAnswer is calculated and set in the _updateLatestAnswer(...) function by inverting the priceD18 value (which represents shares/assets). The issue is that this process involves a double-rounding error.
priceD18 value is typically calculated externally (e.g., in OracleHelper) using Math.mulDiv(...). This division calculates (shares * 1e18) / assets, rounding the result _down_ (floor);_updateLatestAnswer(...) function then takes this already rounded-down priceD18 and uses it as a denominator in another division: 1e36 / priceD18; function _updateLatestAnswer(uint224 priceD18) internal {
// @audit The priceD18 value is the result of a previous division that rounded down.
// @audit-issue Using this rounded-down value as a denominator in a new division
// causes the final result (latestAnswer) to be rounded up, amplifying the inaccuracy.
latestAnswer = int256(1e36 / uint256(priceD18));
updatedAt = uint32(block.timestamp);
}Because the denominator ( priceD18) is slightly smaller than its true value (due to the first rounding-down), the final latestAnswer (which also rounds down) will be slightly _larger_ than the true asset/share price. This introduces a minor but persistent upward bias in the reported price. While this value is understood to be an _effective_ price, reflecting vault state (including continuous fees and queued actions), this specific rounding behavior is an artifact of the calculation method.
Recommendation
Consider documenting this calculation behavior. Since the protocol's price already represents an effective rate rather than a "spot" price, this minor precision artifact may be acceptable. However, any integrator relying on this oracle as a Chainlink-compatible feed should be made aware that the latestAnswer is subject to a small, persistent upward bias due to this double-rounding.
latestRoundData() function returns misleading timestamps| File | Fix commit | Type | Status |
src/oracles/OracleSubmitter.sol | — | Info | Acknowledged |
The OracleSubmitter contract provides a Chainlink-compatible latestRoundData() interface. This function is expected to return five values, including an answer, a startedAt timestamp (when the price round began), and an updatedAt timestamp (when the answer was last updated).
The issue is that the latestRoundData() function implementation uses the contract's internal updatedAt state variable for _both_ the startedAt and updatedAt return values.
// src/oracles/OracleSubmitter.sol
function latestRoundData() public view returns (uint80, int256, uint256, uint256, uint80) {
uint32 timestamp = updatedAt;
// @audit-issue The same `updatedAt` value is returned for both `startedAt` and `updatedAt`.
return (0, latestAnswer, timestamp, timestamp, 0);
}This internal updatedAt variable is set in the _updateLatestAnswer(...) helper function. This helper is called in two scenarios:
submitReports(...), _only if_ the submitted report is not suspicious;acceptReports(...), when a (previously suspicious) report is manually accepted;// src/oracles/OracleSubmitter.sol
function _updateLatestAnswer(uint224 priceD18) internal {
// ...
// @audit `updatedAt` is set to the current timestamp of finalization.
updatedAt = uint32(block.timestamp);
}
function submitReports(...) external onlyRole(oracle.SUBMIT_REPORTS_ROLE()) {
// ...
// @audit This is only called if the report is *not* suspicious.
if (!report.isSuspicious && asset == baseAsset) {
_updateLatestAnswer(report.priceD18);
}
// ...
}The problem arises when a report for the baseAsset is submitted and flagged as suspicious. The submitReports(...) function will _not_ update updatedAt. Only later, when acceptReports(...) is called, will updatedAt be set to the _acceptance_ timestamp.
This means that for a suspicious report, latestRoundData() will return the _acceptance time_ as both the startedAt and updatedAt time. This is misleading for consumers of the oracle, as it hides the true submission time (when the "round" effectively started) and the latency between submission and acceptance.
Recommendation
Consider revisiting the logic for how timestamps are stored and returned in latestRoundData(). A more accurate implementation might distinguish between the initial submission time of a price report (which could be considered startedAt) and the time it was finalized and accepted (the current updatedAt).
Alternatively, if this behavior is intended, consider adding clear documentation to the latestRoundData() function explaining that both startedAt and updatedAt will reflect the finalization timestamp of the latest valid price, especially in cases where a suspicious report is manually accepted.
Software documentation refers to the written or visual information that describes the functionality, architecture, design, and implementation of software. It provides a comprehensive overview of the software system and helps users, developers, and stakeholders understand how the software works, how to use it, and how to maintain it. Software documentation can take different forms, such as user manuals, system manuals, technical specifications, requirements documents, design documents, and code comments. Software documentation is critical in software development, enabling effective communication between developers, testers, users, and other stakeholders. It helps to ensure that everyone involved in the development process has a shared understanding of the software system and its functionality. Moreover, software documentation can improve software maintenance by providing a clear and complete understanding of the software system, making it easier for developers to maintain, modify, and update the software over time. Smart contracts can use various types of software documentation. Some of the most common types include:
These types of documentation are essential for smart contract development and maintenance. They help ensure that the contract is properly designed, implemented, and tested, and they provide a reference for developers who need to modify or maintain the contract in the future.
No documentation for the OracleSubmitter contract was provided, but Mellow's team addressed all the questions and concerns raised by the Nethermind Security team, providing valuable insights and a comprehensive understanding of the project's technical aspects.
No tests were provided for the OracleSubmitter contract specifically. The current test suite focuses on the Oracle and OracleHelper contracts. The Nethermind Security team recommends creating a comprehensive test suite for the OracleSubmitter contract as well.
As auditors, we recommend that any changes or updates made to the audited codebase undergo a re-audit or security review to address potential vulnerabilities or risks introduced by the modifications. By conducting a re-audit or security review of the modified codebase, you can significantly enhance the overall security of your system and reduce the likelihood of exploitation. However, we do not possess the authority or right to impose obligations or restrictions on our clients regarding codebase updates, modifications, or subsequent audits. Accordingly, the decision to seek a re-audit or security review lies solely with you.
This report is based on the scope of materials and documentation provided by you to Nethermind in order that Nethermind could conduct the security review outlined in 1. Executive Summary and 2. Audited Files. The results set out in this report may not be complete nor inclusive of all vulnerabilities. Nethermind has provided the review and this report on an as-is, where-is, and as-available basis. You agree that your access and/or use, including but not limited to any associated services, products, protocols, platforms, content, and materials, will be at your sole risk. Blockchain technology remains under development and is subject to unknown risks and flaws. The review does not extend to the compiler layer, or any other areas beyond the programming language, or other programming aspects that could present security risks. This report does not indicate the endorsement of any particular project or team, nor guarantee its security. No third party should rely on this report in any way, including for the purpose of making any decisions to buy or sell a product, service or any other asset. To the fullest extent permitted by law, Nethermind disclaims any liability in connection with this report, its content, and any related services and products and your use thereof, including, without limitation, the implied warranties of merchantability, fitness for a particular purpose, and non-infringement. Nethermind does not warrant, endorse, guarantee, or assume responsibility for any product or service advertised or offered by a third party through the product, any open source or third-party software, code, libraries, materials, or information linked to, called by, referenced by or accessible through the report, its content, and the related services and products, any hyperlinked websites, any websites or mobile applications appearing on any advertising, and Nethermind will not be a party to or in any way be responsible for monitoring any transaction between you and any third-party providers of products or services. As with the purchase or use of a product or service through any medium or in any environment, you should use your best judgment and exercise caution where appropriate. FOR AVOIDANCE OF DOUBT, THE REPORT, ITS CONTENT, ACCESS, AND/OR USAGE THEREOF, INCLUDING ANY ASSOCIATED SERVICES OR MATERIALS, SHALL NOT BE CONSIDERED OR RELIED UPON AS ANY FORM OF FINANCIAL, INVESTMENT, TAX, LEGAL, REGULATORY, OR OTHER ADVICE.
The Leading Engineers of Blockchain Infrastructure