Olympus DAO
Cooler V2

Covered Cooler V2's perpetual position model, unified user positions, governed LTV growth, and collateral vote delegation.
report id
0410
category
DeFi & Yield
language
Solidity
final report
April 4, 2025
lines of code
1851
Content

Download audit report

1

Executive Summary

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 TypeSecurity Review
Final ReportApril 4, 2025
Repositoryolympus-v3
Commitff0ad35b6b
Final Commita2ba2de9f1
DocumentationDocs
Documentation AssessmentHigh
Test Suite AssessmentHigh
Total findings
5
Fixed
4
Acknowledged
1
Mitigated
0
Unresolved
0

Findings by severity

  • Critical0
  • High0
  • Medium0
  • Low1
  • Informational4
  • Best Practices0

4

System Overview

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
  • CoolerTreasuryBorrower
  • CoolerLtvOracle
  • Delegation System
Diagram from the audit report

4.1 MonoCooler

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:

  • addCollateral(...): Users can add gOHM as collateral through this function. Users can also change the delegations on their collateral, including the new added value. Any user can add collateral on behalf of another user; however, if delegations are changed, the user must be authorized.
  • withdrawCollateral(...): Users can remove their collateral through this function. Only the user or authorized users can withdraw collateral from an account. Changing the delegation of the remaining collateral through this function is also possible. The remaining collateral for an account must be enough to keep their debt at or below the maxOriginationLtv. Users can only withdraw undelegated collateral, so they need to adjust delegations in order to withdraw their gOHM.
  • borrow(...): Users can borrow the debt token through this function. Currently, the debt token is USDS. Only the user or authorized users can borrow on behalf of an account. Users can borrow until their debt reaches the current maxOriginationLtv.
  • repay(...): Users pay debt tokens back to reduce their debt through this function. Any user can do a repayment on behalf of any other user.
  • applyDelegations(...): Users can call this function to modify the delegations of their collateral. Only the user or authorized users can modify the delegations.
  • applyUnhealthyDelegations(...): This function allows any user to undelegate for other accounts that are in unhealthy positions (can be liquidated). This function exits to execute partial undelegations if an account has delegated to too many addresses.
  • batchLiquidate(...): This function is used to liquidate positions that are in an unhealthy state. An account will be unhealthy if its current debt is bigger than its collateral multiplied by the Liquidation LTV, account's debt will increase over time due to the borrowing interest applied. When an account is liquidated, its debt is wiped and its collateral is burned. The liquidator will receive gOHM as an incentive; the greater the user's debt, the greater the incentive the liquidator will receive. The incentive will be taken from the position's collateral before burning.

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.

4.2 CoolerTreasuryBorrower

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:

  • borrow(...): This function will be called by MonoCooler to get funds from the Treasury. MonoCooler will specify the amount in WAD, and this contract will convert into the needed amount and get the funds from the Treasury.
  • repay(...): The function will be called by MonoCooler to repay its debt with the Treasury contract. This contract will take its current balance of debt token send the funds back to the Treasury, the debt in Treasury will be updated accordingly.

4.3 CoolerLtvOracle

CoolerLtvOracle controls how much USDS can be borrowed against gOHM. Rather than using external market prices, it uses two key thresholds:

  • Origination LTV: Maximum borrow amount for new loans.
  • Liquidation LTV: Higher threshold amount that triggers liquidations.

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.

4.4 Delegation system

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.

5

Risk Rating Methodology

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:

  1. High: the issue is trivial to exploit and has no specific conditions that need to be met;
  2. Medium: the issue is moderately complex and may have some conditions that need to be met;
  3. Low: the issue is very complex and requires very specific conditions to be met.

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:

  1. High: the issue can cause significant damage, such as loss of funds or the protocol entering an unrecoverable state;
  2. Medium: the issue can cause moderate damage, such as impacts that only affect a small group of users or only a particular part of the protocol;
  3. Low: the issue can cause little to no damage, such as bugs that are easily recoverable or cause unexpected interactions that cause minor inconveniences.

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 / LikelihoodLowMediumHigh
HighMediumHighCritical
MediumLowMediumHigh
LowInfo / Best PracticesLowMedium
UndeterminedUndeterminedUndeterminedUndetermined

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.

  1. Informational findings do not pose any risk to the application, but they carry some information that the audit team intends to pass to the client formally;
  2. Best Practice findings are used when some piece of code does not conform with smart contract development best practices;
  3. Undetermined findings are used when we cannot predict the impact or likelihood of the issue.

6

Issues

6.1 Users may receive slightly more tokens than intended due to rounding in borrow

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.

6.2 Borrowing doesn't always increase debt

FileFix commitTypeStatus
src/policies/cooler/MonoCooler.sole54b3f1InfoFixed

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.

6.3 Debt with Treasury is not updated on liquidation

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.

6.4 Liquidation can be delayed by front running call to applyUnhealthyDelegations(...)

FileFix commitTypeStatus
src/policies/cooler/MonoCooler.sol91d0c69InfoFixed

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.

6.5 Olympus Treasury may lose value due to rounding in SUSDS.deposit(...)

FileFix commitTypeStatus
src/policies/cooler/CoolerTreasuryBorrower.sol—InfoAcknowledged

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.

7

Documentation Evaluation

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:

  • Technical whitepaper: A technical whitepaper is a comprehensive document describing the smart contract’s design and technical details. It includes information about the purpose of the contract, its architecture, its components, and how they interact with each other;
  • User manual: A user manual is a document that provides information about how to use the smart contract. It includes step-by-step instructions on how to perform various tasks and explains the different features and functionalities of the contract;
  • Code documentation: Code documentation is a document that provides details about the code of the smart contract. It includes information about the functions, variables, and classes used in the code, as well as explanations of how they work;
  • API documentation: API documentation is a document that provides information about the API (Application Programming Interface) of the smart contract. It includes details about the methods, parameters, and responses that can be used to interact with the contract;
  • Testing documentation: Testing documentation is a document that provides information about how the smart contract was tested. It includes details about the test cases that were used, the results of the tests, and any issues that were identified during testing;
  • Audit documentation: Audit documentation includes reports, notes, and other materials related to the security audit of the smart contract. This type of documentation is critical in ensuring that the smart contract is secure and free from vulnerabilities.

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.

8

Remarks & Test Suite

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.