Download audit report
This document presents the security review performed by Nethermind Security for Cooler V2 contracts. Cooler V2 is a lending system that allows users to borrow USDS against gOHM collateral without losing the voting capabilities from gOHM.
Cooler V2 main features include: a) perpetual positions, b) single unified position per user, c) governance-controlled LTV growth through a drip system, and d) delegation of the deposited collateral's voting power.
The audit comprises 1851 lines of solidity code. The audit was performed using (a) manual analysis of the codebase, (b) automated analysis tools, and (c) creation of test cases.
| Audit assessment | |
| Audit Type | Security Review |
| Final Report | April 4, 2025 |
| Repository | olympus-v3 |
| Commit | ff0ad35b6b |
| Final Commit | a2ba2de9f1 |
| Documentation | Docs |
| Documentation Assessment | High |
| Test Suite Assessment | High |
Findings by severity
| # | Contract | LoC | Comments | Ratio | Blank | Total |
| 1 | src/libraries/CompoundedInterest.sol | 17 | 6 | 35.3% | 4 | 27 |
| 2 | src/libraries/SafeCast.sol | 16 | 1 | 6.2% | 3 | 20 |
| 3 | src/policies/utils/PolicyEnabler.sol | 29 | 63 | 217.2% | 19 | 111 |
| 4 | src/policies/utils/RoleDefinitions.sol | 4 | 4 | 100.0% | 1 | 9 |
| 5 | src/policies/utils/PolicyAdmin.sol | 25 | 12 | 48.0% | 7 | 44 |
| 6 | src/policies/cooler/MonoCooler.sol | 683 | 235 | 34.4% | 167 | 1085 |
| 7 | src/policies/cooler/CoolerTreasuryBorrower.sol | 88 | 32 | 36.4% | 23 | 143 |
| 8 | src/policies/cooler/CoolerLtvOracle.sol | 159 | 54 | 34.0% | 40 | 253 |
| 9 | src/policies/interfaces/cooler/ICoolerLtvOracle.sol | 53 | 46 | 86.8% | 21 | 120 |
| 10 | src/policies/interfaces/cooler/ICoolerTreasuryBorrower.sol | 16 | 25 | 156.2% | 8 | 49 |
| 11 | src/policies/interfaces/cooler/IMonoCooler.sol | 192 | 241 | 125.5% | 56 | 489 |
| 12 | src/external/cooler/DelegateEscrow.sol | 55 | 25 | 45.5% | 18 | 98 |
| 13 | src/external/cooler/DelegateEscrowFactory.sol | 49 | 20 | 40.8% | 14 | 83 |
| 14 | src/modules/DLGTE/IDLGTE.v1.sol | 64 | 72 | 112.5% | 23 | 159 |
| 15 | src/modules/DLGTE/OlympusGovDelegation.sol | 339 | 86 | 25.4% | 65 | 490 |
| 16 | src/modules/DLGTE/DLGTE.v1.sol | 62 | 25 | 40.3% | 17 | 104 |
| Total | 1851 | 947 | 51.2% | 486 | 3284 |
The Cooler V2 is a lending system that allows users to borrow USDS against gOHM collateral without losing gOHM's voting power. It allows gOHM holders to maximize their earnings on gOHM by applying their own strategies. The system is composed by four main components:

MonoCooler is the main lending contract that manages user positions. This contract handles borrows, repayments, and liquidations. It keeps track of the user's collateral and debt. The main actions users can execute through this contract include:
It is relevant to note that all the amounts for the debt tokens are specified in WAD for this contract. Those amounts are properly scaled through the CoolerTreasuryBorrower contract.
CoolerTreasuryBorrower connects MonoCooler to the Treasury's sUSDS reserves. It handles debt management and conversion between USDS and sUSDS. The conversion is needed because when Treasury holds funds it keeps them in sUSDS for yield, but loans are provided in USDS.
The contract tracks total debt to Treasury and ensures all USDS operations are properly converted when interacting with Treasury's sUSDS holdings.
The contract has two main functions:
CoolerLtvOracle controls how much USDS can be borrowed against gOHM. Rather than using external market prices, it uses two key thresholds:
The Origination LTV can only increase over time through a linear "drip" system - this means each increase must specify a target value and the time period to reach it. The Liquidation LTV is set as a premium above the Origination LTV. Reducing the current Origination LTV is not possible.
The Origination LTV value is expected to be set conservatively, increasing together with the value of gOHM but not surpassing it.
DLGTE manages governance power of gOHM while it's being used as collateral in MonoCooler. Each account's gOHM can be split between multiple delegates through individual escrow contracts, allowing governance participation even while the gOHM is locked.
The module tracks balances per policy (like MonoCooler) and per user, ensuring one policy can't withdraw gOHM deposited by another. Users can delegate to up to ten addresses by default (governance can whitelist a contract to delegate to more than this), with each delegate getting their own escrow contract.
Users cannot interact directly with the DLGTE module; they need to do it through the different policies, like the applyDelegations(...) function in MonoCooler.
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.
| File | Fix commit | Type | Status |
src/policies/cooler/CoolerTreasuryBorrower.sol | 66acf65 | Low | Fixed |
When a user borrows USDS, the MonoCooler contract invokes the borrow(...) function in CoolerTreasuryBorrower. To facilitate the transfer of the requested USDS amount, the contract first calls SUSDS.previewWithdraw(amountInWad) to calculate the equivalent amount of sUSDS to withdraw from the treasury's reserves. This value is rounded up by previewWithdraw(...) to ensure that at least the requested USDS amount is covered.
After withdrawing the calculated sUSDS amount, the contract calls SUSDS.redeem(...), which transfers the corresponding USDS to the user:
function borrow(
uint256 amountInWad,
address recipient
) external override onlyEnabled onlyRole(COOLER_ROLE) {
// ...
// Since TRSRY holds sUSDS, a conversion must be done before funding.
// Withdraw that sUSDS amount locally and then redeem to USDS sending to the recipient
uint256 susdsAmount = SUSDS.previewWithdraw(amountInWad);
TRSRY.increaseWithdrawApproval(address(this), SUSDS, susdsAmount);
TRSRY.withdrawReserves(address(this), SUSDS, susdsAmount);
SUSDS.redeem(susdsAmount, recipient, address(this));
}The issue arises because redeem(...) returns all USDS corresponding to the rounded-up sUSDS amount, potentially resulting in the user receiving slightly more than the intended amount of USDS. This excess is not accounted for in MonoCooler's internal records, introducing minor discrepancies.
While the dust amount is negligible under normal conditions (as the sUSDS price closely tracks USDS at 1:1), the discrepancy could become more significant if the sUSDS price deviates from USDS.
Recommendation
Consider using sUSDS.withdraw(...) instead of sUSDS.redeem(...) which will transfer the exact amount of USDS back to the user.
| File | Fix commit | Type | Status |
src/policies/cooler/MonoCooler.sol | e54b3f1 | Info | Fixed |
Calculation of debt is always done by rounding up except when borrowed. This results in the scenario where someone can borrow 1 wei without increasing their debt.
function borrow(
uint128 borrowAmount,
address onBehalfOf,
address recipient
) external override returns (uint128 amountBorrowed) {
// ...
// Sync global debt state when borrowing
...
// @audit debt is calculated by rounding down
// don't round up the debt when borrowing.
uint128 currentDebt = _currentAccountDebt(
_accountDebtCheckpoint,
aState.interestAccumulatorRay,
gStateCache.interestAccumulatorRay,
false
);
// ...
}Recommendation
Consider rounding up when calculating current debt in the borrow(...) function.
| File | Fix commit | Type | Status |
src/policies/cooler/CoolerTreasuryBorrower.sol | a2ba2de | Info | Fixed |
The MonoCooler contract facilitates lending of assets sourced from the Olympus Treasury. The CoolerTreasuryBorrower contract interacts with the treasury to withdraw assets during borrowing and return them during repayment. In both the borrow and repay operations, the CoolerTreasuryBorrower updates its recorded debt with the treasury accordingly.
However, when a liquidation occurs, the user’s debt is cleared within MonoCooler, but the corresponding debt recorded in the treasury remains unchanged. This creates an inconsistency between the actual state of loans and the debt values tracked by the treasury.
Such inconsistencies may lead to accounting discrepancies and could affect any processes or policies that rely on the accuracy of debt data maintained by the treasury.
Recommendation
Consider implementing a mechanism to update the treasury's debt records when a liquidation occurs.
applyUnhealthyDelegations(...)| File | Fix commit | Type | Status |
src/policies/cooler/MonoCooler.sol | 91d0c69 | Info | Fixed |
To liquidate an unhealthy position, the undelegated assets should be enough to cover the collateral to liquidate. The batchLiquidate(...) calls DLGTE.withdrawUndelegatedGohm(...) with autoRescindDelegations as true which will loop through the delegations and undelegate assets if required. Although the default value for the maximum number of delegations that can be done at a time is 10, admin can allow someone to have more than that. This means that if number of delegations is large enough such that looping through all the delegations and undelegating required assets can't be done in a single transaction, the applyUnhealthyDelegations(...) can be used to do this manually in multiple calls before calling batchLiquidate(...). The issue is that if the user front runs the call to applyUnhealthyDelegations(...) by changing their delegations then the call to applyUnhealthyDelegations(...) would revert. This allows to delay liquidating the position.
Recommendation
Consider adding a mechanism to not allow users with unhealthy positions to change their delegations such that to avoid getting liquidated.
SUSDS.deposit(...)| File | Fix commit | Type | Status |
src/policies/cooler/CoolerTreasuryBorrower.sol | — | Info | Acknowledged |
When a user repays their debt, the CoolerTreasuryBorrower contract deposits the repaid USDS into the SUSDS vault and sends the resulting SUSDS tokens to the treasury.
function repay() external override onlyEnabled onlyRole(COOLER_ROLE) {
uint256 debtTokenAmount = _USDS.balanceOf(address(this));
if (debtTokenAmount == 0) revert ExpectedNonZero();
// This policy is allowed to overpay TRSRY, in which case it's debt is set to zero
// and any future repayments are just deposited. There are no 'credits' for overpaying
uint256 outstandingDebt = TRSRY.reserveDebt(_USDS, address(this));
uint256 delta;
if (outstandingDebt > debtTokenAmount) {
unchecked {
delta = outstandingDebt - debtTokenAmount;
}
}
TRSRY.setDebt({
debtor_: address(this),
token_: _USDS,
amount_: delta
});
_USDS.safeApprove(address(SUSDS), debtTokenAmount);
// @audit - This function will round down the amount of `SUSDS` to deposit
SUSDS.deposit(debtTokenAmount, address(TRSRY));
}The SUSDS.deposit(...) function calculates the number of shares based on the deposited amount but rounds down when issuing shares. This can result in a small loss of value for the receiver (the treasury) when the deposited amount is not perfectly divisible by the share price.
A malicious user could exploit this behavior by borrowing from MonoCooler and repaying their debt in multiple small transactions, each causing the treasury to lose up to 1 wei worth of SUSDS per repayment due to rounding. Over a large number of repayments, this could accumulate to a measurable value loss for the treasury.
However, the attacker does not directly benefit from this behavior. Borrowers must be gOHM holders, and diminishing the treasury's assets could negatively affect the value of gOHM, harming the attacker’s own position. Additionally, the treasury’s yield-generating strategies are likely to offset such minor losses.
Consider implementing monitoring or rate-limiting mechanisms on MonoCooler repayments to detect or mitigate repeated small repayments that could exploit rounding behavior.
Recommendation
Consider implementing monitoring mechanisms on MonoCooler repayments to detect repeated small repayments that could exploit rounding behavior.
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.
Extensive and thorough documentation was provided for the mechanisms of Cooler V2. In addition to documentation, the code was properly commented and explained. The Cooler V2 team was available to explain any doubt or question raised by the Nethermind Security team.
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