Guardian's review of Convertible Deposits for Olympus, published September 2025. The report records 92 findings across 2 review rounds, including 1 critical and 13 high.
- Published
- Review window
- July 28 to August 18, 2025
- Rounds
- Main Review, Remediation Review
- Language
- Solidity
- Chains
- Ethereum, Arbitrum, Optimism, Base, Berachain
- Sector
- Tokens
- 1 Critical
- 13 High
- 13 Medium
- 39 Low
- 26 Informational
Scope
21 files in scope · 3,198 nSLOC
| File | nSLOC | Lines |
|---|---|---|
src/bases/BasePeriodicTaskManager.sol | 94 | 192 |
src/bases/BaseAssetManager.sol | 118 | 245 |
src/policies/ReserveWrapper.sol | 70 | 140 |
src/policies/Heart.sol | 116 | 218 |
src/policies/EmissionManager.sol | 326 | 564 |
src/libraries/Uint2Str.sol | 22 | 27 |
src/libraries/Timestamp.sol | 33 | 43 |
src/libraries/ERC6909Wrappable.sol | 125 | 221 |
src/libraries/DecimalString.sol | 51 | 87 |
src/libraries/CloneableReceiptToken.sol | 36 | 90 |
src/libraries/AddressStorageArray.sol | 23 | 47 |
src/external/clones/CloneERC20.sol | 64 | 125 |
src/modules/DEPOS/PositionTokenRenderer.sol | 146 | 184 |
src/modules/DEPOS/OlympusDepositPositionManager.sol | 207 | 432 |
src/modules/DEPOS/DEPOS.v1.sol | 11 | 37 |
src/policies/deposits/YieldDepositFacility.sol | 257 | 426 |
src/policies/deposits/DepositRedemptionVault.sol | 434 | 764 |
src/policies/deposits/DepositManager.sol | 295 | 548 |
src/policies/deposits/ConvertibleDepositFacility.sol | 212 | 379 |
src/policies/deposits/ConvertibleDepositAuctioneer.sol | 362 | 749 |
src/policies/deposits/BaseDepositFacility.sol | 196 | 337 |
Findings 92
Main Review
65 findings · July 28 to August 18, 2025-
C-01 Critical Incorrect Scaling Overmints OHM To User Logical Error Resolved
Description
In the conversion logic,
convertedTokenOut = FullMath.mulDiv(amount_, 10 ** IERC20(currentAsset).decimals(), position.conversionPrice)multipliesamount_(reserve token amount) by the reserve token's decimals (e.g., 18 for USDS). However,position.conversionPriceis calculated asdepositIn.mulDivUp(_ohmScale, ohmOut)where _ohmScale = 1e9 for OHM's 9 decimals.This introduces an extra factor of
10 ** reserveDecimals / 10 ** ohmDecimals(e.g., 10^9 for reserve=18, OHM=9), resulting inconvertedTokenOutbeing massively inflated.For reserve tokens with 18 decimals, users receive 1 billion times more OHM than intended which causes catastrophic OHM supply dilution and violates the principle that each OHM is backed appropriately by treasury assets.
Recommendation
Multiply by
_ohmScaleinstead of10 ** IERC20(currentAsset).decimals()in_previewConvert. -
H-01 High Reclaims Can Force Insolvent Deposit Managers Logical Error Resolved
Description
A user can call function
reclaimForto reclaim their entire deposit, but this does not update the user's DEPOS position, allowing users to claim yield on non-existent assets after reclaiming.This vector allows for the following scenario:
(1) Alice deposits 1e18 reserve tokens through
createPosition(2) Alice then reclaims her entire deposit, receiving a discounted amount. The DepositManager now holds 0 vault shares, but Alice's yield-earning position remains. (3) Bob then proceeds to deposit into the vault throughcreatePosition. (4) Yield accrues and a snapshot is taken. (5) Alice claims yield (claimYield) with her phantom position, receiving some reserve tokens. (6) Bob attempts to claim yield, but because Alice already withdrew some assets, Bob's claim yield attempt reverts withDepositManager_Insolvent. Bob cannot claim the yield he has earned with the assets he deposited into the vault and that are generating yield in the vault.Ultimately, Alice is able to prevent other users from claiming their yield by forcing insolvency through a reclaim, and also claim yield without having actively deposited assets in the vault.
Also note that the smaller the discount is configured (reclaim rate can be as high as 100%), the faster Alice will earn enough yield to overcome any immediate loss.
Recommendation
Adjust the remaining deposit of existing positions to reflect the withdrawal. Furthermore, ensure the reclaim rate is large enough to discourage malicious behavior.
-
H-02 High Yield Claim DoS'ed By Share Inconsistencies Logical Error Acknowledged
Description
Users are able to create a position in
YieldDepositFacilityin order to claim the yield from an ERC4626 vault strategy, without the ability to convert to OHM.Furthermore, users holding receipt tokens can start a redemption and borrow against it, withdrawing funds from the ERC4626 vault. The main issue arises when users borrow using the
YieldDepositFacility, as it reduces the active shares for the operator, while the facility’ user’slastShareswill still be using the full deposited value (sum oflastSharesof users is greater than the real active shares)This will impact the claimable yield calculation as users will try to claim more yield than what the max claimable yield allowed in the
DepositManagerfor theYieldDepositFacility, DoS'ing users when callingclaimYield.Although this issue describes only
YieldDepositFacilityusers, the same issue will apply ifConvertibleDepositFacilityusers redeem and borrow using theYieldDepositFacility.Recommendation
The solution requires a major refactor, as there is a discrepancy between the individual user shares calculation in
YieldDepositFacilityvs the actual shares of the facility in theDepositManager. When funds are borrowed out from the vault, these will not accrue yield, which should be reflected in the_previewClaimYieldcalculation. -
H-03 High Split Positions Cannot Claim Yield Logical Error Resolved
Description
When a position is created in YieldDepositFacility,
positionLastYieldConversionRateis set to ensure yield is claimed only from the deposit time onward.However, when splitting a position in
OlympusDepositPositionManager::split, the new position'spositionLastYieldConversionRateis not set, leaving it at 0. This causes a division-by-zero revert inclaimYieldwhen calculatinglastShares = mulDiv(remainingDeposit, decimals, lastSnapshotRate = 0), blocking yield claims for the new position.Recommendation
Stamp
positionLastYieldConversionRatewhen splitting for YDF positions. -
H-04 High Facilities Can Steal Yield From Each Other Logical Error Resolved
Description
Receipt tokens are fungible (receipt tokens created by one operator can be reclaimed through another). Therefore,
ConvertibleDepositFacilitydepositors can reclaim their tokens using theYieldDepositFacility.However, reclaiming receipt tokens reduce the amount of yield earned by the facility as it redeems shares from the operator. In extreme cases, the
YieldDepositFacilitycan be left with zero operator shares, preventing users to claim yield.On the other side, claiming yield from the
ConvertibleDepositFacilitywill be possible, but it is transferred to the treasury.Recommendation
Prevent users from the
ConvertibleDepositFacilityto be able to reclaim using a different facility (YieldDepositFacility). -
H-05 High Auctioneer Incompatible Logical Error Resolved
Description
The Auctioneer calculates the OHM output as
convertibleAmount = deposit_.mulDiv(_ohmScale, price_);This assumes price has deposit token decimal precision, but the price provided through
PRICE.getCurrentPrice()in the EmissionManager is 18 decimals. Non-18 decimal deposit tokens are incompatible with the Auctioneer since the resultingconvertibleAmountis no longer a 9 decimal OHM output amount. This will lead to the output amount to be larger than expected and absorb more capacity than expected, or truncate down to 0 when dealing with smaller decimal tokens such as USDC and revert.Recommendation
Scale the price to the deposit token's decimals.
-
H-06 High Heart Beat DoS'ed On Zero Emissions DoS Resolved
Description
The
heartbeat executes period tasks, including theEmissionManager.execute, where the auction parameters are set.In case of a low premium (or < 100%, which is the current case based on price and backing per ohm), the
emissionwill be zero. Therefore, bothtargetandtickSizeparams are zero duringsetAuctionParameters, values not allowed in the auctioneer.Two main issues here:
heart.beat()reverts, as periodic task execution reverts if one of the tasks fails.- In case there is a live auction and premium drops for the next execution, ticks won't be updated and auction results not stored.
Recommendation
Make sure periodic tasks do not revert during their execution, preventing complete DoS on the heart beat.
Additionally, due to the fact that emissions are zero, auctioneer should be disabled or paused to prevent more bids. Make sure day state and auction results are updated accordingly.
-
H-07 High User Can Game Rate Received From Vault Gaming Resolved
Description
In the
claimYieldfunction for expired positions, users can provide atimestampHint_to select a historical vault conversion rate fromvaultRateSnapshots. The only validation is that thehint ≤ expiry, with no check for proximity to expiry or against drops in rate. If a vault's rate peaks mid-period and then falls, users can hint to the peak, inflating yield calculations beyond the actual value at expiry. Consequently, more assets are withdrawn from the vault than expected, which effectively steals the gain of other users.Consider the following scenario:
(1) Alice creates a position, the vault accrues yield and a snapshot is taken at time X. (2) The strategy loses funds, the share value of the vault decreases, and a snapshot is taken at (X + 8) hours. (3) Bob then creates a position and a snapshot is taken at (X + 16) hours. (4) Time passes so Bob’s position is now past expiry. (5) Bob claims yield with a hint at time X, which is before the he even made a position, effectively stealing any yield Alice earned in her duration during the vault.
Recommendation
Enforce
timestampHint_is as close to the expiry as possible. -
H-08 High Insolvency Due To Fixed-Amount Withdrawals Logical Error Acknowledged
Description
Olympus intends to support vaults that can realize losses — in other words, vaults where the value of a user’s deposit may decrease after the time of deposit.
However, the current code is not equipped to handle such scenarios.
Example – Base Asset Manager
- A user deposits x amount into the vault. Olympus records the deposited asset amount and mints recipient tokens equivalent to that amount.
- When the user withdraws, Olympus allows them to redeem shares equivalent to their initial deposit amount.
- If the vault has incurred losses, this logic allows the user to withdraw more than their proportional share of the vault’s current value. This could lead to a bank run and insolvent positions.
Recommendation
We expect that there may be multiple areas in the code where loss-incurring vaults can cause similar issues. Addressing this in a single place may not be sufficient. A broader review and multiple code changes are likely required to ensure proper handling of loss scenarios.
-
M-01 Medium Griefing Receipt Token Conversion Access Control Resolved
Description
In order to convert a receipt token into OHM, users first need to approve the
DepositManagerto spend the tokens, enforced during_burn. This approval is also needed forwraporunwraptokens fromERC6909toERC20.However, malicious users can grief the
converttransaction by front running and wrapping/unwrapping receipt tokens. Theconvertwill now fail with insufficient allowance as it was spent by the malicious actor. This is possible due to the fact thatwrapandunwrapdo not have access control, and allow any user to perform the action on behalf of other, as long as the allowance was given. The most impact occurs when the attacker repeats this process, until the user's position expires,In case of a smart contract user, deployed with max approval to
DepositManagerto save gas, receipt tokens can be permanently locked if the ERC6909 tokens are suddenly wrapped into ERC20, without a way to handle these tokens or approval functionality.Recommendation
A combination of better documentation on approvals and stricter access control for wrap/unwrap functionality is recommended to mitigate this risk.
Alternatively, consider using
msg.senderinstead ofonBehalfOffor thewrapandunwrapfunctions. -
M-02 Medium Withdraw Rounding Prevents Yield Claims Logical Error Resolved
Description
When claiming yield, the assets are withdrawn from the vault through the deposit manager:
(, uint256 actualAmount) = _withdrawAsset(asset_, recipient_, amount_);ERC4626
withdrawends up withdrawing the exact number of assets requested, but it also rounds up the number of shares that are burnt. Because more shares are burnt,depositedSharesInAssetsis decreased slightly more than expected, making it more likely that the vault is insolvent:_assetLiabilities[assetLiabilitiesKey] > depositedSharesInAssets + borrowedAmount. Ultimately a user attempts to claim yield but is prevented by as little as 1 wei of insolvency due to the extra drop indepositedSharesInAssets.This issue also occurs with
borrowAgainstRedemptionas it triggerswithdrawas well, and the deficit will continue to compound as more users submit borrows.Recommendation
Consider adjusting the withdrawal mechanism such that the user’s requested amount to withdraw is rounded down with existing ERC4626 functionality. The user may now receive less than than requested for redemption and this must be appropriately documented (redemptions are not exactly 1:1 anymore).
-
M-03 Medium Emissions Not Adjusted Logical Error Acknowledged
Description
The EmissionManager calculates emissions based on supply (
getSupply() = gohm.totalSupply() * gohm.index() / 10 ** _gohmDecimals) and premium, assuming a single reserve token. If multiple EmissionManagers are deployed for different reserves (e.g. USDC through separate auctioneers), each independently computes and mints OHM emissions without aggregating across all managers. This duplicates emissions, asgetNextEmission()in one manager doesn't account for emissions from others, over-minting OHM and diluting supply. For example 2 EmissionManagers/Auctioneers can double the expected OHM emission.Recommendation
Consider accounting for multiple managers within
getNextEmission. Otherwise, utilize only one EmissionManager and Auctioneer at a time. -
M-04 Medium Keepers Not Incentivized To Quickly Beat Warning Acknowledged
Description
Within the Heart, the
currentReward()function pays 0 at the exact beat boundary (current time == lastBeat + frequency()), then ramps linearly tomaxRewardovermin(auctionDuration, frequency()). Sincebeat()reverts before the boundary and is valid at the boundary with 0 reward, rational keepers are incentivized to wait past the boundary to earn a positive payout. Especially if transaction gas fees are higher, it only makes sense for keepers to wait to get a positive reward that exceeds the gas fee. This leads to execution delay, delayed price moving average updates, etc.Recommendation
Consider a base reward at the valid time boundary or run protocol keepers.
-
M-05 Medium Yield Lost For Expired Positions Unexpected Behavior Acknowledged
Description
Creating positions in
YieldDepositFacilitygives user the ability to claim yield from the vault, until the position expires. Once expired, users will only be able to claim up to the expiring timestamp.These expired positions will still have assets deposited in the vault, accruing yield. However, the yield generated is not distributed to existing users, even after expired positions redeem or reclaim (exit).
This pending yield will be left in the
DepositManagereven after all facility positions exit, with no way to claim it.YieldDepositFacilitywill end up in an invalid state:- operator liabilites = 0
- shares in assets > 0
- max claim yield > 0
Recommendation
Consider adding an admin function to claim yield during emergencies or when operator liabilities becomes 0 (no user deposits and available yield to claim).
-
M-06 Medium Converted Reserves Do Not Increase Backing Logical Error Acknowledged
Description
Teller will make a
callBacktoEmissionManagerin order to update the backing price based on new supply and reserves added.On the other side, the
ConvertibleDepositFacility.convertfunction will mint OHM to user, after withdrawing reserves from the vault into the treasury. These reserves will later be deposited into the vault during a periodic task execution inReserveWrapper.However, this convert flow does not update the backing price before depositing the received reserves and minting the output amount of OHM, compared to teller purchases during
callbackexecution.Recommendation
Consider updating backing OHM price when converting notes in the
ConvertibleDepositFacility. -
M-07 Medium First Depositor Attack Affects Redemptions Logical Error Acknowledged
Description
Users interacting with a facility are potentially vulnerable to the first depositor inflation attack depending on the underlying vault.
Consider a vault that allows for donations (using
balanceOf):- Bob deposits 1 wei USDS into CDF and gets 1 wei rUSDS
- Alice deposits 20,000 USDS into CDF, expecting 20,000 rUSDS
- Bob frontruns and transfers 10,000 USDS directly to the vault
- Alice is minted 15,000 rUSDS but only 1 wei of shares is minted for the DepositManager for the CDF's operator shares
- CDF operator shares is only 2 wei now
- Alice redeems 15,000 rUSDS and withdraws 15,000 USDS, and shares for the CDF operator goes from 2 wei to 1 wei.
- Bob redeems his 1 wei USDS and shares is now 0, but the Vault contains 15,000 USDS.
- Bob deposits 15,000 USDS again, receiving 30,000 rUSDS due to the vault’s sitting 15,000 USDS
- Bob redeems 30,000 rUSDS, withdrawing 30,000 USDS, recovering the vault’s leftover assets.
- Bob invested 1 wei + 10,000 USDS (donation) + 15,000 USDS (final deposit) and walked away with 30,000 USDS, making 5,000 USDS in profit while Alice lost $5,000 USDS
- Consequently, the
actualAmountreceipt token setup does not prevent the first depositor inflation attack but adds extra steps necessary to execute it.
Note that the severity is of course subject to the length of redemption periods and whether the underlying vault is susceptible to the typical first depositor inflation attack.
Recommendation
Ensure the underlying vault uses the OZ virtual offset or dead shares are minted initially.
-
M-08 Medium Capacity Can Grow Incorrectly Unexpected Behavior Resolved
Description
The
_getCurrentTickfunction computes thecapacityToAddamount accounting for the number of enabled deposit periods such that the configured aggregate auction target is met per day in capacity additions.However in the
enableDepositPeriodanddisableDepositPeriodfunctions there is no update to ensure that the latest capacity changes have been reflected up to the current timestamp.This allows for technically incorrect capacity changes to occur around the enabling and disabling of deposit period.
For example, consider the following scenario:
- There are 2 deposit periods, A & B, enabled for the auctioneer
- The daily target for all auctions in aggregate is 100 tokens
- it has been 3 hours since the last capacity update
- 3 * 100 / 24 = 12.5 total capacity which should be added to auctions A & B through an increase of 6.25 capacity to both
- A third deposit period is enabled, C
- A deposit occurs for deposit period A right after the deposit period C is enabled
- Now the 12.5 total capacity which has accrued is split amongst deposit periods A, B, and C — giving A only a 4.167 token allocation when it should have received 6.25
Recommendation
In the
enableDepositPeriodanddisableDepositPeriodfunctions consider updating the capacities for the existing deposit periods with the_updateTicksfunction before a new period is added or removed to reflect the state of the capacities of each deposit period at that time. -
M-09 Medium Capacity Calculation Incorrect Logical Error Resolved
Description
uint256 capacityToAdd = (_auctionParameters.target * timePassed) / 1 days / _depositPeriodsCount;This formula assumes all deposit periods are active from the beginning. If a deposit period is enabled or disabled mid-auction:
- Enabling a new period causes under-selling (less OHM minted).
- Disabling a period causes overselling (more OHM minted).
Recommendation
Restrict enabling/disabling deposit periods to parameter updates (
setAuctionParams) before tick recalculations. Only allow emergency toggling in exceptional cases. -
M-10 Medium Target Update Without LastUpdate Adjustment Logical Error Resolved
Description
When
setAuctionParamsis called to set a new target,lastUpdateis not updated. The new target applies retroactively from the last update, affecting both past and future periods, which may result in artificially discounted or inflated prices.Recommendation
Update
lastUpdatewithinsetAuctionParamsby settingsetLastUpdate_ = true. -
M-11 Medium Inconsistent Global VS Per-Period Auction Parameters Logical Error Resolved
Description
The auction design maintains global variables for:
tickSizetargetdayState
But maintains per-period state for:
depositPeriodPreviousTicks[depositPeriod](price, capacity, lastUpdate).
This creates asymmetry:
- When tick size is reduced globally (after hitting the daily limit), all deposit periods inherit the smaller tick size.
- However, each deposit period’s
previousTick(price, capacity, lastUpdate) is only updated when a bid occurs in that specific period.
If a new bid occurs in a different deposit period right after tick size reduction, the global tick size reduction applies instantly, but the local
previousTickstate for that period is stale. This leads to:getCurrentTickrunning for significantly more iterations (capacity ÷ smaller tick size, plus longertimePassed), pushing the price downward excessively.previewBidalso using the reduced tick size, so while the final loop ramps the price upward, the first tick capacity is sold at a direct discount, and later ones start going up from the outdated starting price, resulting in total discount.
Example Scenario
- Two deposit periods (A and B).
- Period A hits its daily limit → tick size halved globally.
- Period B has not been updated since earlier in the day (large capacity, stale price, older lastUpdate).
- A user bids in Period B:
- Loop runs longer (capacity ÷ smaller tick size).
- Starting price is based on the outdated
previousTick, which is lower. - Price steps down further with each iteration, creating unintended discounts.
Net effect: the bidder in Period B acquires OHM cheaper than intended.
Recommendation
- Revisit the architecture:
- Either make all auction parameters (tick size, targets, day state) per-period, OR
- Keep them global but synchronize state updates across all deposit periods whenever tick size is reduced.
-
L-01 Low Risk Of Underflow With Multiple Operators Warning Resolved
Description
Because function
handleBorrowdoes not decrement the appropriate_assetOperatorCommittedDeposits, if there are multiple authorized operators for a facility a malicious operator can prevent another operator from withdrawing entirely.Consider the following scenario:
Operator A commits 50 tokens and Operator B commits 50 tokens, total tokens committed is 100
-> _assetOperatorCommittedDeposits[A] reads 50 -> _assetOperatorCommittedDeposits[B] reads 50 -> _assetCommittedDeposits reads 100
Operator A borrows 50 tokens (
handleBorrow)-> _assetOperatorCommittedDeposits[A] still reads 50 -> _assetOperatorCommittedDeposits[B] reads 50 -> _assetCommittedDeposits reads 50
Operator A can now handleCommitWithdraw
-> _assetOperatorCommittedDeposits[A] reads 0 -> _assetOperatorCommittedDeposits[B] reads 50 -> _assetCommittedDeposits reads 0
Operator B cannot withdraw anything since
_assetCommittedDepositsis 0 and underflow reverts on any operation.Note that for this attack vector to be performed there have to be multiple operators, however only the
DepositRedemptionVaultappears to be be a valid operator currently.Recommendation
Update
assetOperatorCommittedDepositsaccordingly when borrowing. -
L-02 Low Insolvency When Large Yield Logical Error Resolved
Description
In
DepositManager.claimYield, withdrawing a large yield relative to a small deposit burns all operator shares viavault.withdraw, settingdepositedSharesInAssetsto 0 while liabilities remain. This triggersDepositManager_Insolvent, blocking subsequent yield claims.Consider the following scenario:
- Deposit: 100 assets, 100 shares, 100 receipts.
- Yield: +1e6 assets, vault = 1000100 assets, 100 shares, rate = 10001.0.
- Claim: Withdraws 999999 assets, burns all 100 shares.
- Post-claim: borrowed = 0, shares = 0, depositedSharesInAssets = 0, liabilities = 100 hence 100 > 0 + 0 which triggers DepositManager_Insolvent on claim.
Recommendation
Consider enforcing a minimum deposit amount as this scenario is more prevalent for small deposits.
-
L-03 Low Protocol Risk When Interest Exceeds Buffer Warning Acknowledged
Description
In DepositRedemptionVault, the loan interest is not capped to ensure it remains below the buffer
(redemption.amount - loan.initialPrincipal). Governance can set_assetAnnualInterestRatesand _assetMaxBorrowPercentagessuch that interest exceeds the buffer, incentivizing users to default rather than repay or extend loans.Consider the following scenario:
- start redemption 100
- borrow 90 (principal:90 interest:45)
- if you repay => pay 45, claim 100, net = 100 - 45 = 55
- if you default => do not pay anything, lose everything, but walk away with 90
Recommendation
Clearly document and ensure the interest rate and max borrow is set appropriately so that the amount committed is greater.
-
L-04 Low User Can Delay Reclaims Censoring Acknowledged
Description
When a user starts a redemption their assets are committed and the available deposits (
getAvailableDeposits) is reduced by that amount.The smaller the available deposits, the less users can
reclaimFordue to_validateAvailableDepositsvalidation:if (amount_ > availableDeposits) revert DepositFacility_InsufficientDeposits(amount_, availableDeposits);Because there is no penalty for starting a redemption and then cancelling (
cancelRedemption) to receive their committed tokens back, malicious users can continuously commit their receipt tokens to delay user reclaims.Consider the following example:
- User A has 100 receipt tokens, deposited 100 tokens
- User B has 100 receipt tokens, deposited 100 tokens
- User A commits 100 to start redemption
- getAvailableDeposits = 100 (shares in assets assume 1:0.5 lossy vault) - 100 = 0
- User B cannot reclaim until User B cancels their redemption
Recommendation
Consider adding a fee for cancellation.
-
L-05 Low Default VS Reclaim Gaming Warning Resolved
Description
Depending on the configured parameters, it may be more favorable for a user to borrow, wait the loan's term, and self-default rather than reclaim. Consequently, the protocol treasury's earning may potentially be negatively impacted with the user incentives.
Recommendation
Document and take this into consideration when setting protocol parameters.
-
L-06 Low Delayed Heartbeat Affects Auction Tracking Warning Resolved
Description
Everyday the auction stores the convertible OHM in the
_dayState, and this is recorded within the_auctionResults. The expectation is that the EmissionManager will trigger the auction every 24 hours, and every 24 hours there will be an accurate representation of convertible amount to decide whether a bond market needs to be created.The Heart is not required to beat every 8 hours, so waiting for 3 beats may take longer than 24 hours if there are outages, block congestion, etc. In this case, more convertible amount may be attributed to a day than was actually sold in that day, preventing the creation of the bond market even if there was underselling.
Recommendation
Consider documenting this behavior.
-
L-07 Low Price Decays Faster Than Expected Unexpected Behavior Resolved
Description
In the
ConvertibleDepositAuctioneercontract the size of each tick decays as the daily expected volume of purchases is surpassed by an incremental multiple.This allows the price of the auction to move up faster as more convertible deposits are purchased.
However it also allows the price to decay more rapidly due to the logic inside of
_getCurrentTickwhich decays the price in correspondence with the number of ticks which would be crossed by thecapacityToAddwhich is added over time.The outcome of this behavior is that the auction simply allows users to purchase more tokens at a lower average price within a day than may be expected when there is a large volume purchased, which pushes the amount to a multiple of the daily allotment, at the beginning of the day.
Recommendation
Be aware of this unexpected increase in the rate at which the auction price can drop after a large volume has been purchased.
To negate this effect, use the initial tick size rather than the
_currentTickSizein the decaywhileloop in the_getCurrentTickfunction. -
L-08 Low Deposit Period Enabled Before Policy Warning Resolved
Description
Enabling a deposit period before the Auctioneer policy is initialized through
_enablesets the previous tick's price and capacity to 0, as_auctionParameters.minPriceand_auctionParameters.tickSizeare uninitialized. This invalid state persists until re-enabled or updated.Recommendation
Add a check in
enableDepositPeriodto revert if policy is not enabled or clearly document this behavior. -
L-09 Low Re-enabling Loses Tick Data Warning Resolved
Description
Disabling and re-enabling a deposit period with functions
disableDepositPeriodandenableDepositPeriodresets the previous tick to the auction'sminPriceandtickSize, losing prior capacity and price data. New capacity from time passed during disablement is not added, leading to lost OHM conversion potential.Recommendation
Be aware and clearly document this behavior.
-
L-10 Low Setting Tracking Period Wipes Existing Results Warning Resolved
Description
Calling
setAuctionTrackingPeriodresets_auctionResultsto a new array of lengthdays_, wiping prior results and setting_auctionResultsNextIndex=0. This loses historical data, potentially misinforming EmissionManager on underselling and triggering unnecessary bond markets. Note that the number of days can even be set to the same amount, and the historical data will be wiped.Recommendation
Be aware and clearly document this behavior.
-
L-11 Low Failed Bond Market Creation Derails Auction Warning Resolved
Description
The EmissionManager tightly couples auction parameter updates with bond market creation. If
bondAuctioneer.createMarketfails (e.g.allowNewMarkets = false), the entire emission execution reverts. The auction will continue to operate with stale parameters -- minimum auction price, current tick size will not be adjusted to reflect latest emission rate and OHM price. Furthermore, the auction results will not be stored for the day.Recommendation
Consider having governance safety mechanisms in-place to set auction parameters and data in the case of failure.
-
L-12 Low Disabling Policy Loses Auction Results Warning Acknowledged
Description
Disabling the Auctioneer before the 3rd Heartbeat (24 hours) skips
_storeAuctionResultsinsetAuctionParameters, losing the daily convertible data. Re-enabling the policy starts the tracking from scratch, potentially triggering unnecessary bond markets due to perceived underselling.Recommendation
Be aware and clearly document this behavior.
-
L-13 Low Allowance Decrease Frontrunning Warning Acknowledged
Description
The
approvefunction simply resets the allowance to the new value:allowance[msg.sender][spender] = amount;This allows a spender to utilize the prior approval before the new one is set. For example, if the spender has a previous allowance of 100e18 and the owner wants to decrease this to 10e18, the spender can frontrun this call and consume the full 100e18 allowance, while getting an additional allowance of 10e18 granted.
Recommendation
Clearly document this risk with the
approvefunction. -
L-14 Low Task Execution Succeeds Warning Resolved
Description
A low-level call is used to trigger a custom selector on a periodic task address. If the address does not have contract code, the call will return with
success = trueeven if no task was truly executed.Recommendation
Ensure only valid contract addresses are added for tasks.
-
L-15 Low Max Yield Cannot Be Claimed Warning Resolved
Description
The
maxClaimYieldfunction aims to "return the maximum yield that can be claimed for an asset and operator pair", but it does not represent the true amount that can be claimed from within the YieldDepositFacility. A portion of the yield will be unclaimable through the YDF because the last snapshot rate for a position is adjusted by 1 wei, as well as precision loss when calculating a position's current value. Therefore, the calculated yield across multiple positions and users will not sum up to the maxYield returns for the YDF operator. This may be unexpected for integrators and off-chain systems relying onmaxClaimYield.Recommendation
Clearly document that
maxClaimYieldis the theoretical max and may not be reached by all users claiming their current yield. -
L-16 Low Cross-Contract Reentrancy Risk Warning Resolved
Description
Throughout the contracts,
safeTransferFromis not performed at the very top of the function. This opens up attack vectors for ERC777 deposit tokens to trigger a cross-contract reentrancy since state updates are performed before the tokens are received.Recommendation
Move
safeTransferFromto the top of functions and take into consideration which tokens are allowed within the protocol. -
L-17 Low Parameters Should Be Configurable Warning Resolved
Description
Parameters such as
_assetMaxBorrowPercentagesare dependent solely on the deposit token, and are the same across all facilities. However, some facilities may have different risk profiles, hence the borrow percentages, reclaim rates, etc. should be adjustable according to the facility as well.Recommendation
Consider allowing for more granularity in parameter adjustments by also accounting for the facility.
-
L-18 Low Lack Of Validation On Withdrawal Warning Resolved
Description
There are multiple entrypoints in the DepositManager that access function
_withdrawAssetsuch asclaimYield,borrowingWithdraw, andwithdraw.Currently only
claimYieldvalidates that the DepositManager is solvent, but it would be advisable that_withdrawAssetitself would have the validation as a chokepoint pattern to ensure all withdrawals leave the DepositManager solvent and if there are any issues the Olympus team is swiftly aware.Recommendation
Consider adding solvency validation to
_withdrawAsset. -
L-19 Low Missing Validation For
previewReclaimValidation ResolvedDescription
During reclaims, the amount being withdrawn is compared to the available deposits, accounting for commitments, to verify if there are enough assets to reclaim.
However, deposit manager will only allow to withdraw up to the asset liabilities of the given facility, which only increase during deposits. Given the fact that YDF users can reclaim using CDF, withdrawing funds may underflow as the amount was not validated against the facility liabilities:
_assetLiabilities[_getAssetLiabilitiesKey(params_.asset, msg.sender)] -= params_.amount;Recommendation
During
previewReclaim, verify that theamountparam is lower or equal to the facility liabilities in theDepositManager, not the available deposits (which include yield). -
L-20 Low Initial Auction Parameters Not Validated Validation Resolved
Description
The
EmissionManageris responsible for periodically tuning the auction parameters, which are calculated based on the emissions and current OHM price. ThetickSize_is calculated as a % of thetarget_.However, during the
ConvertibleDepositAuctioneerpolicy enable flow, there is no validation ontickSize_ < target_. If the tick size is greater than the target , it will open unexpected behaviors, as the auctioneer will decrease the tick size faster due to higher values of the multiplier.Recommendation
Consider not only validating non zero values for the auction params, but ensuring
tickSize_ < target_, in line with theEmissionManager. This can be done in the_enableas the issue only appears during policy enabling. -
L-21 Low Risk-Free Bids Warning Acknowledged
Description
Based on the current Auction status, users can bid for convertible notes and ideally purchase a position with the lowest conversion price to make a profit from longing OHM.
However, even if price does decrease, users can still start a redemption and receive their entire deposit back 1:1. Consequently, users have no direct asset-risk for utilizing convertible notes. Olympus should confirm this is intended mechanics for convertible notes, especially since most auctions will begin at an OHM price below market, effectively creating a risk-free money printer for some.
Recommendation
Consider if this is intended behavior. If so, consider extra fees upon redemptions or bids. Furthermore, consider implementing the possibility for a time delay before conversion.
-
L-22 Low Asymmetric Yield Across Multiple Claims Gaming Resolved
Description
Function
claimYieldupdates the position's last snapshot rate after each yield claim, setting thelastSnapshotRateto a higher value for subsequent calculations as yield is earned. This increases the denominator in thelastSharescomputation for remaining growth periods, resulting in fewer effective shares (lastShares) and lower total yield when claiming multiple times compared to a single claim over the same period and end rate. Ultimately, users who claim yield multiple times during a position’s period will receive less total yield than those who claim once.Recommendation
Clearly document this behavior to users.
-
L-23 Low ERC-4626 Deposit VS PreviewRedeem Invariant Superfluous Code Acknowledged
Description
In yield deposit facilities, Olympus first deposits tokens into the vault and then calls
previewRedeemto determine the owed assets, minting equivalent receipt tokens.However, there is no guarantee that,
previewRedeem(deposit(amount x)) ≈ xwould always holds true for all kinds of vaults.This may deviate if the vault charges withdrawal fees or considers available liquidity when calculating redemptions.
Recommendation
Before onboarding any vault, review its behavior to ensure this invariant holds. Specifically, confirm that
previewRedeem(deposit(x))≈xfor the chosen vault, otherwise deposits may miscalculate user entitlements. -
L-24 Low Vault Validation Warnings Validation Acknowledged
Description
Each asset has a specified deposit cap (
assetConfiguration.depositCap) which should not be exceeded when depositing assets through the DepositManager. Each asset also has an arbitrarily configured ERC4626 vault which was set during theaddAssetfunction call by a manager or admin.Because the vault can require a minimum deposit amount which is not currently enforced directly in the DepositManager, there is potential for the minimum deposit to be greater than the amount of capacity leftover before exceeding the
assetConfiguration.depositCapand users will be unable to deposit even when there is available space.Another potential asymmetry is if the vault has its own deposit cap, and users may attempt deposits through the DepositManager that will simply fail when the deposit into the vault is attempted and the vault’s cap is exceeded, although there is still available space from the DepositManager’s perspective.
Recommendation
Try to maintain alignment between the vault’s configuration and DepositManager’s configuration to avoid unexpected behavior and/or document this risk.
-
L-25 Low Asymmetric Remaining Deposit Warning Resolved
Description
Function
YieldDepositFacility::createPositionusesremainingDeposit: params_.amountbut this may slightly differ from theactualAmountreturned by the deposit due to ERC4626 rounding. ThisremainingDepositvalue is asymmetric withConvertibleDepositFacility::createPositionwhich sets it asremainingDeposit: actualAmount. Consequently, thecurrentValueduring yield calculations within the YieldDepositFacility may be just slightly larger than intended.Recommendation
Consider using
remainingDeposit: actualAmount. -
L-26 Low Lack Of Slippage Protection On Bids MEV Resolved
Description
As users call function
bid()to purchase convertible deposit tokens, the tick prices will continue to increase as demand grows. However, there is no slippage protection mechanism in the function, such that a user may receive a much loweroutput.ohmOutthan expected.Recommendation
Consider adding slippage-protection on the output amount in function
bid(). -
L-27 Low Split Griefing Increases Gas Cost Gas Griefing Resolved
Description
The
splitfunction in OlympusDepositPositionManager allows a position owner to split a position into smaller positions (e.g., 1 wei) and assign them to an arbitrary receiver, appending new position IDs to the receiver's_userPositionsarray. A malicious user can repeatedly split tiny amounts to a recipient, bloating their_userPositionsarray. This increases gas costs for the receiver intransferFrom, which loops over_userPositionsto remove the transferred ID. While testing it was found an attacker would expend more gas than the recipient would lose with the bloated list of positions, making DoS scenarios unlikely.Recommendation
Consider enforcing a minimum amount for the
splitfunction and/or clearly document this risk. -
L-28 Low Potential Superior Loans Configuration Resolved
Description
In the
DepositRedemptionVault, depending on the interest rate and_claimDefaultRewardPercentagea borrower may be able to borrow and pay a lower total amount than compared to the interest rate by defaulting themselves.For example:
- Consider an interest rate of 12% annually
- A
_assetMaxBorrowPercentagesof 80% - A
_claimDefaultRewardPercentageof 50% - The interest for a 12 month period deposit of 100 tokens is 12 tokens
- The principal for the 12 month period deposit is 80 tokens, leaving 20 as a buffer
- If the user pays off their loan before the end of the 12 month period, they will pay back the 80 token principal + the 12 token interest
- If the user instead waits until the end of their loan period and defaults themselves, they will lose the 20 token buffer, but gain half of that as a default reward
In the case where the user defaults themselves they keep the 80 token borrowed amount and only lose the 10 token half of the buffer, rather than paying the 12 tokens in interest.
Recommendation
Be aware of this superior loan strategy and consider this when configuring the
_assetMaxBorrowPercentages,_claimDefaultRewardPercentage, and interest rate. -
L-29 Low Handle Loan Functions Validation Resolved
Description
The
handleBorrowandhandleLoanRepayfunctions neglect to update the_assetOperatorCommittedDepositsmapping values, while they do update the_assetCommittedDepositsfor the asset, allowing the total committed deposits for an asset to disagree with the sum of all operator committed deposits. Furthermore, this allows one operator to “steal” committed deposits from another.Consider,
ConvertibleDepositFacilitywith Operators A and BOperator A commits 50 tokens and Operator B commits 50 tokens, total tokens committed is 100
-> _assetOperatorCommittedDeposits[A] reads 50 -> _assetOperatorCommittedDeposits[B] reads 50 -> _assetCommittedDeposits reads 100
Operator A borrows 50 tokens
-> _assetOperatorCommittedDeposits[A] still reads 50 -> _assetOperatorCommittedDeposits[B] reads 50 -> _assetCommittedDeposits reads 50
Operator A can now handleCommitWithdraw
-> _assetOperatorCommittedDeposits[A] reads 0 -> _assetOperatorCommittedDeposits[B] reads 50 -> _assetCommittedDeposits reads 0
Operator B cannot withdraw anything since
_assetCommittedDepositsis 0 and underflow reverts on any operation.Currently there is no issue related to this as there is only one planned operator, the
DepositRedemptonVault, and the logic within theDepositRedemptonVaultdoes not allow for this to be exploited.Recommendation
Consider updating the
_assetOperatorCommittedDepositsto correspond with the_assetCommittedDepositsupdate in thehandleBorrowandhandleLoanRepayfunctions -
L-30 Low Borrowing More Is Incentivized For Defaults Warning Acknowledged
Description
Upon loan default, the
retainedCollateralwhich serves as the buffer between user collateral value and borrow amount is witheld from the user:uint256 retainedCollateral = redemption.amount - loan.initialPrincipal.Borrowers are incentivized to borrow more if defaulting since the
retainedCollateralis smaller when theinitialPrincipalis larger. This may encourage users who do not have intention of paying the interest to maximally borrow funds and reduce available deposits, which may affect features such as reclaims which require available deposits.This creates a counterintuitive outcome where borrowers taking larger loans are penalized less, incentivizing higher borrowing. Assets with lower borrowing capacity could result in disproportionate losses to users.
Recommendation
Consider aligning with the standard liquidation practices:
- Keeper receives a cut proportional to borrowed amount.
- Protocol receives a liquidation penalty proportional to borrowed amount.
- Remainder is returned to the position holder.
-
I-01 Informational YDF conversionPrice Should Use Constant Best Practices Resolved
Description
In
YieldDepositFacility::createPosition, theconversionPricefor a yield-only position is hardcoded totype(uint256).max.The DEPOS module already defines a
constant NON_CONVERSION_PRICE = type(uint256).maxfor non-convertible positions. Hardcoding risks misalignment if the module is updated with a differentNON_CONVERSION_PRICEin the future, even if this is an unlikely scenario.Recommendation
Consider using the existing
NON_CONVERSION_PRICEconstant inYieldDepositFacility::createPosition. -
I-02 Informational Empty Position Id List Validation Missing Best Practices Resolved
Description
The
convertfunction in ConvertibleDepositFacility does not explicitly check ifpositionIds_.length > 0, allowing calls with empty arrays. While this results inreceiptTokenInandconvertedTokenOutbeing 0, the transaction reverts safely duringMINTR.mintOhm(which disallows zero mints). However, this lacks an early revert, potentially wasting gas and reducing user clarity.Recommendation
Consider reverting
if (positionIds_.length == 0)to be symmetrical with YieldDepositFacility validations. -
I-03 Informational depositAmount Calculation Rounds Rounding Resolved
Description
In the
_previewBidfunction thedepositAmount = convertibleAmount.mulDiv(output.tickPrice, _ohmScale)calculation which occurs when thedepositAmountmust be derived from the total available capacity at the current tick uses round down division.Therefore the resulting depositAmount is rounded down from what the user has to put into the conversion from their total deposit. This however rounds against the favor of the protocol and in favor of the user.
Recommendation
Out of an abundance of caution, to follow best practices of rounding this calculation should round against the user and estimate a larger depositAmount that should be deducted from the user’s total deposits using
mulDivUp.Notice that this may result in cases where the
remainingDepositwould be subtracted such that it underflows and causes a panic revert due to additional wei being removed. In this case theremainingDepositvalue should be minimized to zero. -
I-04 Informational Unwrapped Receipt Necessary For Redemption Documentation Resolved
Description
Only unwrapped receipt tokens are supported to start redemptions due to
_pullReceiptTokenrequiring the ERC6909 function. Furthermore, functionreclaimForrequires unwrapped tokens due toisWrapped: false. This is safe from a security perspective, but should be clearly documented that users must unwrap before beginning redemptions/reclaiming.Recommendation
Clearly document this behavior.
-
I-05 Informational Actual Amount Ignored During Bids Informational Resolved
Description
The
YieldDepositFacility.createPositionreturns theactualAmountwhich represents the amount of receipt tokens minted.Even though the
ConvertibleDepositFacility.createPositionreturns this same parameter, this function is only callable by theROLE_AUCTIONEERwhich is granted to theConvertibleDepositAuctioneer.The
ConvertibleDepositAuctioneer.biddoes not return theactualAmount. Therefore, integrations will need to rely on the receipt token balance delta to calculate this amount.Recommendation
Return the
actualAmountparameter during bids -
I-06 Informational Redundant Boolean Check Best Practices Resolved
Description
Within function
claimDefaultedLoan, the expressionloan.isDefaulted == trueis not necessary and can be shorted toloan.isDefaulted.Recommendation
Consider changing the expression to
loan.isDefaulted. -
I-07 Informational Using Magic Numbers Best Practices Resolved
Description
The codebase uses magic numbers to represent certain values. For example:
100e2instead ofONE_HUNDRED_PERCENT, used in _previewBorrowAgainstRedemption, setMaxBorrowPercentage, setAnnualInterestRate, setClaimDefaultRewardPercentage12instead of creatingTWELVE_MONTHSconstant30 daysinstead of creatingONE_MONTHconstant
Recommendation
Consider using constants instead of magic numbers, to avoid mistakes.
-
I-08 Informational Superfluous Permission Request Superfluous Code Resolved
Description
The
ConvertibleDepositFacilitypolicy has a requests permission for theMINTR.decreaseMintApproval, but the contract never calls this function.Recommendation
Remove superfluous permission.
-
I-09 Informational Borrows Pay Full Interest Regardless Of Payback Documentation Resolved
Description
If a user borrows deposit tokens for a short period of time (e.g. a single block) and proceeds fully repay their loan, the user still owes the entire interest as if they had an active loan for the entire deposit period. This may be unexpected for some users, especially who are accustomed to time based interest mechanisms.
Recommendation
Clearly document this behavior to users.
-
I-10 Informational Newly Minted OHM Not Counted In Supply Warning Acknowledged
Description
Function
getSupply()return supply as(gohm.totalSupply() * gohm.index()) / 10 ** _gohmDecimals;, but this does not consider any newly minted OHM. Consequently, the emission may be lower than intended.Recommendation
Clarify if this is intended behavior.
-
I-11 Informational Convertible Deposit Facility (CDF) Logical Error Acknowledged
Description
Within the Convertible Deposit Facility, both redemptions and conversions can occur against the same position. The user just needs to acquire more receipt tokens through a
depositorcreatePositionto have both be in parallel. While no immediate issue was identified, this is a feature/inconsistency protocol team should be aware of.Recommendation
Be aware of this duality during future modifications.
-
I-12 Informational Unnecessary Day State Storage Read Gas Optimization Resolved
Description
The
_previewBidwill loop untilremainingDeposit == 0, increasing the tick price and size.If there is not enough capacity in the current tick, the size is calculated based on
_getNewTickSizewhich uses_dayState.convertibleto account for daily ohm bids. However, this daily value is constant throughout the while loop, creating unnecessary storage reads.Recommendation
Consider caching the
_dayState.convertiblebefore the while loop, and using the memory variable instead. -
I-13 Informational Redundant Setting Of Asset And Period Logical Error Acknowledged
Description
Asset and deposit period values are expected to remain consistent across all entries of a position. Currently, these values are set repeatedly within the loop for each iteration, which is redundant.
Recommendation
Optimize by setting
assetandperiodMonthsonce for the first position and reuse them, rather than resetting on each loop iteration. -
I-14 Informational Unnecessary While Loop Gas Optimization Acknowledged
Description
The
_getCurrentTickfunction uses awhile (newCapacity > _currentTickSize)loop to iteratively reduce capacity and decay the tick price until capacity fits within the tick size or hitsminPrice. This is unnecessary, as the number of decay steps and final price can be computed directly without looping.Recommendation
Consider replacing the loop with a direct calculation.
-
I-15 Informational Lack Of SafeTransfer Best Practices Resolved
Description
In the
YieldDepositFacilitycontract, thetransferfunction is used when claiming yield, but best practices is to usesafeTransferto handle the return values of the various assets that can be configured.Recommendation
Consider using
safeTransferinstead fromSafeERC20.
Remediation Review
27 findings-
H-01 High Reclaim DoS When Actual Less Than Discounted Rounding Resolved
Description
In function
reclaim, a discount is calculated on theamount_the user requests, and theamount_is attempted to be withdrawn from the DepositManager to cover the discounted amount as well as the treasury amount.Because function
DEPOSIT_MANAGER.withdrawno longer withdraws exactly theamount_requested, it is now possible for theactualAmountwithdrawn to be a couple wei below what was requested.Consequently, it is now possible for
actualAmountto be less thandiscountedAssetsOut, which would either cause:ERC20(address(depositToken_)).safeTransfer(msg.sender, discountedAssetsOut);to revert in the case thatdiscountedAssetsOutis less than theactualAmountdirectly.- If the above transfer succeeds, then the next transfer would panic underflow with
actualAmount - discountedAssetsOut.
Ultimately, the
reclaimfunctionality is DoS'd due to the latestDEPOSIT_MANAGER.withdrawmechanics because of the rounding changes.Recommendation
Consider enforcing that the
discountedAssetsOutis no more than theactualAmountor apply a lower discount rate. -
H-02 High Borrowing Insolvency Prevents Borrows Rounding Resolved
Description
When users borrow using
DepositRedemptionVault, assets are withdrawn from the vault. Due to the rounding issues when redeeming, the actual amount withdrawn is 1 wei less than the amount requested.The
_borrowedAmountsmapping is decreased by this actual amount, instead of theparams_.amount. Therefore, the remaining shares in assets plus this new borrowed value will be lower than operator liabilities, creating an insolvent case.Recommendation
Consider reducing the
_borrowedAmountsmapping by the requested amount instead of theactualAmount. Additionally, user principal should still be equal to the requested amount so the borrowedAmounts mapping is not populated with leftover wei after a repay. -
H-03 High Reclaims Can Force Insolvent Deposit Managers Logical Error Acknowledged
Description
A user can call function
reclaimto reclaim their entire deposit, but this does not update the user's DEPOS position, allowing users to claim yield on non-existent assets after reclaiming.This vector allows for the following scenario:
(1) Alice deposits 1e18 reserve tokens through
createPosition(2) Alice then reclaims her entire deposit, receiving a discounted amount. The DepositManager now holds 0 vault shares, but Alice's yield-earning position remains. (3) Bob then proceeds to deposit into the vault throughcreatePosition. (4) Yield accrues and a snapshot is taken. (5) Alice claims yield (claimYield) with her phantom position, receiving some reserve tokens. (6) Bob attempts to claim yield, but because Alice already withdrew some assets, Bob's claim yield attempt reverts withDepositManager_Insolvent. Bob cannot claim the yield he has earned with the assets he deposited into the vault and that are generating yield in the vault.Ultimately, Alice is able to prevent other users from claiming their yield by forcing insolvency through a reclaim, and also claim yield without having actively deposited assets in the vault.
Also note that the smaller the discount is configured (reclaim rate can be as high as 100%), the faster Alice will earn enough yield to overcome any immediate loss.
Recommendation
Adjust the remaining deposit of existing positions to reflect the withdrawal. Furthermore, ensure the reclaim rate is large enough to discourage malicious behavior.
-
H-04 High Gas Intensive YDF Prevents Yield Claims DoS Acknowledged
Description
To remedy H-07, a Timestamp Linked List was introduced to manage snapshot history and always be able to give a user the latest snapshotted rate since expiry. Notably, a snapshot is taken every time a user creates a position which increases the length of the list and necessary gas cost when the next user creates a position.
Furthermore, when the position is past expiry, many timestamps may be added to the list post-expiry, such that more iterations are needed to find the position's latest expiry with function
findLastBefore.Although there is a malicious case where attackers create many positions to stuff the list, it is also expected that naturally thousands of positions will be created to the point where: (1)
createPositionbecomes too expensive and users do not want to create positions nor use the YDF entirely. (2) Existing positions do not claim their yield as the gas costs exceed the yield they gained during their deposit period.While testing with 10,000 rate timestamp snapshots taken since a position's expiry, a user had to pay ~5,000,000 gas units to create a position and ~3,000,000 gas units to claim yield. With gas prices 4.5 gwei and ETH at $4,600, gas costs will be $60 USD -- high enough to overcome the yield from any smaller position.
Recommendation
Consider using a Bitmap instead of a Linked List. This would allow for O(1) insertion as well as O(1) search given the proper hint is provided, and that hint that can be stored off-chain.
-
H-05 High Auctions DoS'd By Increasing Price DoS Resolved
Description
As reported in H-01, in case of a low premium (or < 100%, which is the current case based on price and backing per ohm), the emission will be zero. When the emission is 0, the tick size is now 1, which is intended to "disable" the auction as prices will drastically increase and quickly with each user bid.
Another key point is that convertible positions are effectively risk-free. If the conversion price is not suitable, a user will just begin a redemption and retrieve 100% of their deposited funds over some time.
This allows for the following, likely attack vector:
(1) Emission is 0, hence auction is created with tick size of 1 wei
(2) Alice begins to bid and drastically increase the price
(3) Auction "re-enabled" with new emission target being non-zero
(4) Because the price is so high, any genuine bids would output 0 OHM and revert with
ConvertibleDepositAuctioneer_ConvertedAmountZero(5) The Auction is now DoS'd for a prolonged period of time because lots of new capacity is necessary to drop the price to a reasonable level.
Recommendation
Instead of arbitrarily setting a small size for 0 emissions, pause the auction process and consider requiring a minimum size for an auction.
-
M-01 Medium Claim Yield Revert With High Yield Fee Logical Error Acknowledged
Description
The
_yieldFeewithin the YDF can be set to near 100%. In such cases, theyieldFeecan exceed theactualAmountleading to a ERC20 inufficient balance revert when tranferring theyieldFeeto the TRSRY.In the most extreme
_yieldFeeof 100%, theactualYieldMinusFeemay be zero, andERC20(address(asset)).safeTransfer(msg.sender, actualYieldMinusFee);may revert as some tokens do not allow zero amount transfers.Ultimately, user may not be able to claim yield when the
yieldFeeis high.Recommendation
Consider having the
yieldFeebe based on theactualAmount. Otherwise, ensure the_yieldFeeis low enough to prevent blocked claims. Furthermore, do not performERC20(address(asset)).safeTransfer(msg.sender, actualYieldMinusFee);unlessactualYieldMinusFee > 0. -
M-02 Medium Split To Bypass Minimum Deposit Logical Error Resolved
Description
A minimum deposit was introduced to prevent malicious deposits and small, likely malicious positions.
However, a user can split their existing position to make the new position's remaining deposit below the minimum deposit.
Recommendation
Validate minimum deposit on split on the old and new position.
-
L-01 Low Period Enabled in Auctioneer Is Disabled In DM Validation Acknowledged
Description
In the auctioneer it is possible to enable a deposit period with
enableDepositPeriodthat is disabled through the DepositManager and vice versa. Consequently, it is also possible for a user to initiallybidand create a position when the period is enabled in both, and afterwards when disabled in the DepositManager the user is unable toconvert.Recommendation
Try to maintain symmetry between enabled deposit periods in the Auctioneer and the DepositManager.
-
L-02 Low Lack Of Access Control On Create Token Access Control Acknowledged
Description
Function
ReceiptManager.createTokendoes not have any access control. An arbitrary user can callcreateTokenand bloat space with receipt tokens that have no connection to the DepositManager.Recommendation
Make
createTokena permissioned function. -
L-03 Low Minimum Deposit and Caps Not Granular Best Practices Acknowledged
Description
Currently the
minimumDepositanddepositCapare configured directly on the asset in theassetConfigurationmapping, meaning all periods/facilities/etc. share the same minimum deposit and deposit cap.It may be prudent to allow more granular parameters to account for different risks across facilities rather than on the asset entirely.
Recommendation
Consider having the
minimumDepositanddepositCapbe configurable by facility as well. -
L-04 Low Rates Can Change Across Block Warning Acknowledged
Description
Function
_takeSnapshotoverwrites vault rates keyed only byblock.timestamp. MultipleclaimYield()calls in the same block can record different rates under the same key. Consequently, earlier claims may be underpaid relative to claimants after yield is accumulated in the vault within the block.Recommendation
Clearly document this risk.
-
L-05 Low Zero Actual Amount Not Verified Warning Resolved
Description
Function
handleCommitWithdrawchecks that the requestedamount_is non-zero:if (amount_ == 0) revert DepositFacility_ZeroAmount();However, it does not validate that the
actualAmountis non-zero. If that returnedactualAmountwere to then be used in a token transfer or some other integration, it may lead to unexpected reverts.Recommendation
Consider validating that the
actualAmountis non-zero. -
L-06 Low Min Price Straddle Warning Acknowledged
Description
The auction mechanism enforces a daily emission throttle using
_dayState.convertibleand the tick size adjustment logic. As more OHM is sold within a day, tick sizes shrink and prices ramp more aggressively.However, _dayState and _currentTickSize are hard-reset during
setAuctionParametersThis reset clears the global convertible counter and restores tick size to the standard value, regardless of how much OHM was sold just before the reset.An attacker can exploit this by straddling the reset:
(1) Place a large bid just before the reset, consuming OHM up to the current capacity at the tick
(2) Immediately after reset, bid again while the system treats the new day as if no OHM has been sold, with tick size restored.
This allows a single buyer to split a large purchase across two “days,” obtaining more OHM at cheaper average prices than if the same purchase had occurred continuously in one day. The intended daily emission throttle is therefore weakened around reset boundaries.
Recommendation
Clearly document this behavior.
-
L-07 Low Asymmetry Between Preview And Borrow Amounts Rounding Acknowledged
Description
The
previewBorrowAgainstRedemptionwill calculate principal amount based on redemption details. However, the mainborrowAgainstRedemptionwill withdraw assets from vault, incurring in rounding issues when redeeming. This will create asymmetry between the previewed principal amount and the actual tokens received.Recommendation
Clearly document this assymmetry,
-
L-08 Low Queue Not Cleared When Policy Disabled Warning Acknowledged
Description
When the
ConvertibleDepositAuctioneerpolicy is disabled, the queue is not cleared. Once the policy is re-enabled, deposit periods which are no longer intended are still processed and affect the deposit count.Recommendation
Consider adding a function to allow governance the ability to adjust the queue as necessary to allow greater fine-tuning of what is in/(ex)cluded. Furthermore, consider if the queue should be cleared when the policy is disabled.
-
L-09 Low No Single Source Of Truth For Deposit Periods Logical Error Resolved
Description
The
ConvertibleDepositAuctioneerdoes not have a single source of truth for deposit periods. In fact, it relies on the 3 different state variables:_depositPeriodsEnabled_depositPeriods_depositPeriodsCount
However,
_depositPeriodswill only contain enabled periods and the count can be easily obtained from the length of the set. Not only this is prone to errors if there is any misalignment, but also consumes more gas.Recommendation
Consider having a single source, using the enumerable set.
-
I-01 Informational DepositManager_Insolvent Revert Needs More Data Best Practices Resolved
Description
Currently the
DepositManager_Insolventonly contains theoperatorLiabilitiesbut it would also be useful to view thedepositedSharesInAssetsandborrowedAmount.Recommendation
Consider adding more data to the
DepositManager_Insolventrevert -
I-02 Informational Deposit Periods Can Be Alternated Warning Acknowledged
Description
In the newly introduced queue, it is possible to alternate deposit period enables/disables continuously to bypass
_validateNoDuplicatePendingChangechecks.Recommendation
Be aware and document this behavior.
-
I-03 Informational Receipt Token Symbol Not Prefixed With "r" Informational Acknowledged
Description
Previously, the receipt token symbol was prefixed with the letter "r" (i.e. rUSDS3m). Now, the "r" was removed and symbol will just start with the operator name.
Recommendation
Add the missing prefix "r" to the receipt token symbol.
-
I-04 Informational Pending Queue Internal Best Practices Resolved
Description
The
_pendingDepositPeriodChangesqueue is internal but it may be useful for off-chain systems to query which deposit period changes have been queued and will take effect.Recommendation
Consider making the queue visibility
public. -
I-05 Informational Bond Markets Created When Auctioneer Disabled Warning Resolved
Description
When the Auctioneer policy is disabled, the EmissionManager will continue to
setAuctionParametersand auction results will be tracked. Consequently, bond markets will continue to be created even though the auctioneer is disabled which may be unexpected.Recommendation
Consider if this is expected behavior according to spec.
-
I-06 Informational Superfluous Enumerable Set Validation Gas Optimization Resolved
Description
The
EnumerableSet.addwill first check if the set contains the value. Therefore,_enableDepositPerioddoes not require to validate_depositPeriods.contains(depositPeriod_)before callingadd. This will also save some gas and avoid duplicatedcontainscall.Recommendation
Use the
addfunction directly without callingcontains -
I-07 Informational Shadowed State Variable Best Practices Resolved
Description
The
ConvertibleDepositAuctioneerdeclares some mapping values and memory variables using the nameisEnabled. However, this shadows the existingPolicyEnabler.isEnabledstate variable, potentially causing unexpected issues.Recommendation
Consider renaming
isEnabledused in theConvertibleDepositAuctioneeror prefix a_to differentiate it. -
I-08 Informational Periods Should Be Enabled Before Policy Configuration Resolved
Description
When auctioneer policy is enabled, it processes pending deposit period changes. If periods are not added to the queue before this policy is enabled, then they will need to wait a full day until
EmissionManagersets the auction params and process this queue.Recommendation
Document this behavior and make sure periods are added to the queue before enabling the policy.
-
I-09 Informational Backing/premium jumps when Treasury temporarily holds raw reserves Warning Acknowledged
Description
After this PR, there are windows where the Treasury keeps the raw reserve (e.g., USDS) before periodically depositing into
sReserve(vault). The bond market’s pricing/backing usesgetReserves, which currently only valuessReserve. During those windows, idle reserves are excluded, causing stepwise jumps in backing and bond premium, effecting next emissions.Recommendation
Beware of this consideration, if not intended, consider include reserve balance inside getReserves as well.
-
I-10 Informational Missing min-shares/slippage check on periodic vault deposits Warning Acknowledged
Description
Reserve wrapper periodically deposits the entire idle reserve into the vault but doesn’t enforce a minimum shares received. This is okay for monotonic, yield-accruing vaults like
sUSDS/USDS, but would be unsafe for volatile vaults (price can move against the deposit between quote and execution).Recommendation
Beware of this consideration, if not intended, consider adding a minShares (or minOut) parameter to the deposit path and revert on shortfall.
-
I-11 Informational ERC-6909 supply is not authoritative for receipt tokens Warning Acknowledged
Description
Users can wrap/unwrap at will, so the
totalSuppliesmapping for the ERC-6909 receipt token won’t always reflect true backing or valuation at any instant. Relying ontotalSupplyfor economics/accounting can misprice or misreport reserves.Recommendation
Do not use ERC-6909
totalSupplyas a source of truth for valuation/backing.
No findings match.
More from Olympus
Put your code through the same review.
This review started with a conversation about scope. Tell us what you are building and we will plan yours with you.
