Ethena engaged Guardian to review the security of their Ethena's Onchain Minter. From the 6th of October to the 15th of October, a team of 4 auditors reviewed the source code in scope.
- Published
- Review window
- October 6 to 15, 2025
- Rounds
- Main Review, Remediation Review
- Language
- Solidity
- Chains
- Ethereum
- Sector
- Stablecoins
- 0 Critical
- 0 High
- 1 Medium
- 19 Low
- 52 Informational
Scope
Overview
Ethena engaged Guardian to review the security of their Ethena's Onchain Minter. From the 6th of October to the 15th of October, a team of 4 auditors reviewed the source code in scope.
Findings 72
Main Review
57 findings-
L-01 Low Future Prices Allowed For Chainlink Validation Resolved
Description
In the
ChainlinkAggregatorFeedcontract the same future price handling exists as in thePythOracleFeedcontract.However because Chainlink is an aggregator oracle where the updated at time is assigned based on the
block.timestampof the update transaction.Therefore, it is not possible for a future dated price timestamp to be reported.
Recommendation
Consider removing the future price timestamp handling and require that a price is strictly from the current timestamp or in the past up to the staleness threshold.
Resolution
Ethena Team: Resolved.
-
L-02 Low burnFrom Discrepancy In xUSD Implementations Compatibility Resolved
Description
In the non-OFT xUSD implementation the
burnFromfunction is permissioned such that only callers who are whitelisted minters may invoke it.However the
ERC20BurnableUpgradeable.burnFromfunction includes an allowance validation mechanism, and for this reason the sameburnFromfunction in thexUSDOFTUpgradeablecontract is not permissioned to only minter addresses.Recommendation
Consider if both the normal xUSD and
xUSDOFTUpgradeableimplementations should standardize on the same permissions for theburnFromfunction, whether that be only for minters or callable for the public.If the intent is that only minters may call the
burnFromfunction, then consider if the approval validation is necessary and whether minters should have the ability to burn without being limited by the approval amount, as they are also trusted to mint a nearly unlimited amount of the token.Resolution
Ethena Team: Resolved.
-
L-03 Low Misleading RateLimiterUpgradeableStorageLocation Unexpected Behavior Resolved
Description
In the
RateLimiterUpgradeablecontract the storage location for theRateLimiterUpgradeableStorageLocationvalue does not agree with what the comment proposes derives it.The proposed
keccak256(abi.encode(uint256(keccak256("RateLimiterUpgradeable")) - 1)) &~bytes32(uint256(0xff))result is:0xf40fb8ff74ec76132acbfc7d3a15179466240c00e91ec9e6b83495e3a3e51600.However the actual storage location value used is:
0x8327c26dd5e5ceb4f0d1337dcb7f7536980a6eda04f5f7fb6577c3c861cded00.Recommendation
Consider using the actual result of the proposed:
keccak256(abi.encode(uint256(keccak256("RateLimiterUpgradeable")) - 1)) &~bytes32(uint256(0xff))value,or update the comment to indicate what the current value represents.
Resolution
Ethena Team: Resolved.
-
L-04 Low Rate Limit Consumed With No Input Unexpected Behavior Acknowledged
Description
Proof of concept: PoC
xUSDOFTUpgradeableinherits from theRateLimiterUpgradeablecontract and implements a rate limiting - for a given period of time users can send only limited amount of tokens to other chains.The
_debit()function is run on each crosschain call to reduce the balance of the sender. The function is overridden to check the rate limit and update the amount that's sent.Notice that the call to
_checkAndUpdateRateLimit()passes_amountLDas amount. Inside that function,amountInFlightwill be increased by the value of this parameter.After the rate limit logic is executed, only the amount returned by
_debitView()will be burned from the user.The
debitView()will return amount equivalent to the return value of_removeDust().Since
localDecimals = 18andsharedDecimals = 6,decimalConversionRate = 12. Therefore, the last 12 digits of theamountLDwill be cut off.If a user passes
decimalConversionRate - 1asamountLD, the burned amount will be0, but the actualamountLDwill be used from the available limit. This can be performed even by users that don't have any tokens.However, to use up the whole limit is not that easy because of gas cost. Still, for smaller limits or almost used ones, this attack may prevent crosschain communication when not expected.
Recommendation
Use the actual
amountSentLDwhen updating the rate limit parameters.Resolution
Ethena Team: Acknowledged.
-
L-05 Low Incorrect RL State Preservation During Updates Unexpected Behavior Acknowledged
Description
Proof of concept: PoC
RateLimiterUpgradeablestores information about each eid in the following struct:struct RateLimit { uint256 amountInFlight; uint256 lastUpdated; uint256 limit; uint256 window; }The limit/window represents the rate of tokens that can be sent to the destination chain per second. The system uses
amountInFlightto track the debited assets over the window. As time passes, theamountInFlightexperiences decay which allows more tokens to be sent.As described in Rate Limit State Preservation During Updates, when
_setRateLimitsis invoked to change the limits, the in flight amount is not reset to 0 because if it was, users would have been able to immediately consume the new limit, even if the old one was already used up.This behavior creates a new issue that results in users not being able to send tokens for more than the configured window. Let's look at the example in the README.
Like step 3 says, user locked out until decay reduces it below 10. However, the decay rate is now 10/hour, which means 90 hours must pass before the in flight amount reaches 0. It may be expected that the if branch in
_amountCanBeSent()will reset the in flight amount to 0 once the window is over, but nothing stops users from sending 0 amount requests to reset the_lastUpdatedvalue and never enter the conditional's body.In result, after lowering the limit, users have to wait a longer, unexpected period of time than the expected configured window.
This also doesn’t align with what’s stated in the README > Natural Decay: The time-based decay will naturally resolve the situation over the configured window
Recommendation
When updating the limit to a lower value than the current one via a call to
_setRateLimits(), consider:- setting the current
amountInFlighttomin(amountInFlight, newLimit) - setting
lastUpdatedtoblock.timestamp
This approach will ensure users are subject to the new limit rate and at the same time are unable to immediately start spending it.
NOTE: If this fix is implemented, the owner will be able to pass limit = 0, to reset the in flight value.
Resolution
Ethena Team: Acknowledged.
- setting the current
-
L-06 Low Timestamp Preservation Is In Favor Of The Users Unexpected Behavior Acknowledged
Description
It's stated in the
READMEthat the_setRateLimit()"function intentionally does NOT resetamountInFlightorlastUpdatedvalues for existing rate limits". It's said that the approach "reflects conservative rate limiting principles".But if we take a look at the
decay, we can see that the larger thetimeSinceLastDeposit, the larger the decay itself.uint256 decay = (_limit * timeSinceLastDeposit) / _window;The bigger the decay is, the more assets can be sent to the destination chain, which is the opposite of the expected conservative behavior defined in the
README. This also grants users the ability to send more tokens than the previous limit if the new rate is larger.For example:
RL= 10 000 tokens / 10 hours- User A sends 10 000 tokens
- 2 hours pass without activity
- New
RLis set to 5000 tokens / 2 hours - Users are now able to immediately send 5000 tokens
Recommendation
Consider resetting the
timestamponRLmodification, or at least when the newlimit / windowis greater than the previous ratio.Resolution
Ethena Team: Acknowledged.
-
L-07 Low transferToCustody() Doesn't Check The Recipient Validation Resolved
Description
OnChainMinting.transferToCustody()allows theCOLLATERAL_MANAGER_ROLEto send any collateral tokens from the minting contract to the collateral custodian.The problem with this function is that it doesn't check if
custodianAddressisaddress(0). This can lead to loss of funds if the function is misused.For example, if
removeCollateral()is executed before that, the collateral config, including the custodian, will be deleted and the funds will be just sent toaddress(0).Recommendation
Revert in
transferToCustody()if the recipient isaddress(0).Resolution
Ethena Team: Resolved.
-
L-08 Low Period Limits Can Be Set Lower Than Epoch Limits DoS Acknowledged
Description
The protocol has no validation ensuring that period limits are greater than or equal to epoch limits.
Since an epoch is a subset of time within a period, it's logically impossible to mint/redeem more in a single epoch than in the entire period.
However, the admin can set
maxMintPerEpoch > maxMintPerPeriod, creating an impossible constraint that breaks the system.The same issue exists for:
- Collateral-specific limits
- Benefactor-specific limits
- Default benefactor limits
In the
READMEit’s mentioned that this is expected, but the code comments and max durations for epochs and periods don’t align with that/// @notice Minimum allowed epoch duration (10 seconds) /// @dev Epochs are intended to track shorter time periods, such as 30 seconds, while periods track longer durations uint256 private constant MIN_EPOCH_DURATION = 10; /// @notice Maximum allowed epoch duration (24 hours) uint256 private constant MAX_EPOCH_DURATION = 24 hours; /// @notice Minimum allowed period duration (10 seconds) /// @dev Periods are intended to track longer time periods, such as 24 hours, complementing the shorter epoch-based limits uint256 private constant MIN_PERIOD_DURATION = 10; /// @notice Maximum allowed period duration (30 days) uint256 private constant MAX_PERIOD_DURATION = 30 days;Recommendation
Add validation during the initial setup to avoid the period limit being less than the epoch limit. Consider adding validation for when the admin updates the period and epoch limits through the
setGlobalPeriodLimits()function such that these limits do not contradict each other.And finally, consider validating that a period is longer than an epoch in the
setEpochDurationandsetPeriodDurationfunctions.Resolution
Ethena Team: Acknowledged.
-
L-09 Low Oracle Bounds May Bias The Oracle Unexpected Behavior Resolved
Description
In the
AggregateOracleFeedcontract if an oracle’s reported price is less than the minimum or greater than the maximum then it is skipped from the end result.In some exceptional scenarios this may lead to an unintended bias of the
AggregateOracleFeedresult and allow for a small mispricing.Consider the following example:
minNumberOfOraclesis 2- There are 4 configured oracles
- The
minOraclePriceis 0.98 and themaxOraclePriceis 1.02 - The stablecoin in question deepens slightly to a market average price of $0.981
- Oracles 1 & 2 report 0.982 and 0.984 respectively
- Oracles 3 & 4 report 0.979 and 0.978 respectively
- The average considering all oracle results is (0.982 + 0.984 + 0.979 + 0.978) / 4 = 0.981
- However oracles 3 & 4 are removed from the average result since they happen to lie below the
minOraclePrice
- The actual result from the
AggregateOracleFeedis (0.982 + 0.984) / 2 = 0.983 - This produces a less accurate price and biases the oracle upwards in this case
Recommendation
Consider if this bias in such edge case scenarios is acceptable. This edge case may inform the configuration of the
minOraclePriceandmaxOraclePrice, it should only validate against extreme values that are highly likely to be aberrations.Otherwise consider removing the need to exclude oracles based on individual price results that are above an arbitrary min/max and instead use the median price reported by an oracle.
Resolution
Ethena Team: Resolved.
-
L-10 Low Oracle Specific Staleness Thresholds Hardcoded Validation Resolved
Description
In the
ChainlinkOracleFeedcontract the staleness threshold is hardcoded to be 1 day and 1 minute long, however some feeds have a different heartbeat than this such as the ETH-BTC feed on Ethereum mainnet as an example.This means the
ChainlinkOracleFeedcontract is incompatible with such feeds, as it will either not give enough time for an oracle update to occur if the heartbeat is longer than 24 hours, or it will give too much time for an oracle update to occur if it is less than 24 hours.Recommendation
Consider allowing the
ORACLE_STALENESS_THRESHOLDvalue to instead be assigned on deployment and perhaps even configurable afterwards.Resolution
Ethena Team: Resolved.
-
L-11 Low Aggregator Updates Are Sandwichable Gaming Acknowledged
Description
When the market price moves past an aggregator’s deviation tolerance an update is triggered onchain.
For aggregators with a larger deviation threshold, this could create small arbitrage opportunities when deviation triggered updates occur.
Consider the following scenario:
- The system is deployed on a network with a public mempool, e.g. Ethereum
- The reported price for USDT from an aggregator is $1.00 (assume the
AggregateOracleFeedreports this
price directly)
- The deviation threshold for the aggregator is 1%
- The market price for USDT moves to $0.99
- User A observes a deviation triggered update in the public mempool and chooses to front-run it by
depositing 100 USDT into the
OnChainMintingcontract- The USDT is still valued at the yet-to-be-updated aggregator price of $1
- (Ignore fees) User A receives
oracleAmountOut = 100e6 * 1e18 * 1e18 / (1e18 * 1e6) = 100e18 xUSD - The aggregator update occurs and the reported price of USDT is now $0.99
- User A immediately back runs this and withdraws their xUSD
- (Ignore fees) User A receives
oracleAmountOut = 100e18 * 1e18 * 1e6 / (0.99e18 * 1e18) = 101.01010101
USDTAlice increased her USDT holdings by arbitraging the aggregator update and the way that the
OnChainMintingsystem relies on it.Recommendation
The effect of this is reduced using an aggregated oracle because a single deviation update is dampened by other oracles which are unlikely to also be updated at the same time. Be aware of this arbitrage vector when choosing the feeds which are to be supported by the Aggregator oracle, ideally those with small deviation thresholds are prioritized.
Furthermore, consider the deviation thresholds of the underlying oracles when assigning the fee rate as any fee rate that matches or exceeds the deviation threshold renders this arbitrage unprofitable.
Resolution
Ethena Team: Acknowledged.
-
L-12 Low Limits Ineffectively Prevent Concentration Validation Acknowledged
Description
In the
OnChainMintingsystem the epoch and period limits are meant to rate limit the amount of minting and redemptions that can occur in the system as well as limit the concentration of certain collateral types that can build up.However the
_validateCollateralEpochLimitsand_validateCollateralPeriodLimitsvalidations work based on theamountOutfor mints andorder.amountInfor burns, which are both xUSD asset amounts.Because of the varying fee rates applied, and in the case of any depeg scenario, this ineffectively limits the amount of collateral actually deposited into the system, negating the objective of avoiding collateral concentration.
For example:
- Consider a scenario where USDC has depegged to 90% (for explicative purposes)
- The
benefactorMaxMintPerPeriodis 100 - Assume no fees
- For a stablecoin with no fees and no depeg, only 100 tokens can be deposited
- For USDC in this case, 100/90 ~= 111.11 USDC can be deposited
- This allows for a higher concentration of USDC to be accumulated, especially when USDC repegs
Consider another case:
- Assume a 5% fee on deposits specifically for USDC (for explicative purposes)
- The
benefactorMaxMintPerPeriodis 100 - For a stablecoin with no fees and no depeg, only 100 tokens can be deposited
- For USDC in this case, 100/95 ~= 1.053 USDC can be deposited
- This allows for a higher concentration of USDC to be accumulated
Recommendation
For collaterals, consider also implementing a collateral amount based validation per period and epoch to effectively limit the concentration of certain tokens. Otherwise, consider adding a simple max collateral amount validation in the system.
Resolution
Ethena Team: Acknowledged.
-
L-13 Low Shared Nonce Breaks Integrations Compatibility Resolved
Description
One benefactor can have multiple approved
delegatedSignersassociated with it, however theorderNonceInvalidatoris tied to the benefactor’s state and is thus shared amongst all of thedelegatedSignersas well as the benefactor themselves.This may cause issues for complex integrations which use multiple different services to submit orders through various
delegatedSigners.Since each service does not have its own
orderNonceInvalidatorthen there may arise race conditions that cause actions to unintentionally fail, rather than simply providing the intended benefit of preventing the same order from being submitted twice.Recommendation
Consider keying the
orderNonceInvalidatormapping based upon thedelegatedSigner/benefactor who is initiating the execution.Resolution
Ethena Team: Resolved.
-
L-14 Low Redstone Max Staleness Is 30 Hours Unexpected Behavior Resolved
Description
In the
MultiFeedAdapterWithoutRoundsbase contract which is used forRedStoneaggregator oracles, the on-chain validated SLA for staleness is the MAX_DATA_STALENESS.Which is a constant set at 30 hours, rather than the purported 24 hours. Thus, a
RedStonefeed may technically be updated past the expected 1441 minutes threshold and still be considered a valid update by theRedStonecontracts.Recommendation
Be aware of this 30 hour staleness validation on the
RedStoneside, and consider having a per-feed staleness threshold as a part of the remediation to H-01.Resolution
Ethena Team: Resolved.
-
L-15 Low Peg Arb Affected By Fees Unexpected Behavior Acknowledged
Description
The xUSD protocol hopes to maintain the
pegPricewhile maintaining overcollateralization based on the fees charged on mint/redeem.One of the main mechanisms for maintaining the peg is a mint/redeem arbitrage loop which would ideally keep the spot price of xUSD tied to the configured
pegPrice.For example, if the spot price of xUSD were currently $0.98 on a Uniswap pool then it could be bought from Uniswap at $0.98 and redeemed for $1 of collateral for a risk free profit. This trade would be executed until there is no marginal profit on the spot AMM and the spot price is $1.
However, due to the fees applied on minting and redeeming, the arbitrage opportunity is stifled.
Consider a 1% fee, if the price of xUSD on the spot market is $0.98, then arbitrageurs will be able to buy up xUSD at $0.98 and redeem it for $0.99 worth of collateral due to a peg price of $1 and a fee of $0.01.
However, when the spot price reaches $0.99 then there is no marginal profit to gain by executing the arbitrage loop, and thus arbitrageurs will not execute the trade.
As a result there will be limited demand for xUSD on the spot market above (
pegPrice - redeemFee), which may allow the token to actively trade at less than the peg price.Recommendation
Be aware of these dynamics, and consider if this is acceptable for the purpose of the protocol.
Resolution
Ethena Team: Acknowledged.
-
L-16 Low Fee Is Applied Inconsistently Unexpected Behavior Acknowledged
Description
The
_getQuote()function applies a fee during mints and redeems. A more conservative approach is taken whereamountOutis the lesser value betweenoneToOneAmountOutandoracleAmountOut. The main difference between the two isoneToOneAmountOutprices the collateral asset at exactly $1, whileoracleAmountOutuses the actual price returned by the oracle. The fee is applied only to theoneToOneAmountOutcalculation. So the real fee paid ismax(oracleAmountOut - oneToOneAmountOut, 0).The idea is that for mints, if the price is <$1, the user already loses some value and because of that, a smaller fee is charged, or even 0 if the depeg difference exceeds the fee. However, after the mint, the user will receive the same USD value as they have put into the protocol, besides the fees. This leads to the protocol granting free mints.
For example:
- Fee = 1%, USDC = $0.99
- User A mints for 1000 USDC ($990)
oneToOneAmountOut = oracleAmountOut = 990xUSD- User A received 990xUSD ($990), which is the same amount they used for minting = no fee applied
Notice that if the price was $1.1 instead:
- User A mints for 1000 USDC ($1100)
oneToOneAmountOut = 990xUSD ($990)oracleAmountOut = 1100xUSD ($1100)fee paid = oracleAmountOut - oneToOneAmountOut = $110
So the user ended up paying both the fee and the depeg difference.
The same behavior is observed in the redeem flow. This even allows entering and exiting the system without paying any fees if one of the assets is < $1 and the other is > $1.
- Mint and redeem fees = 1%; USDC = $0.99; USDT =$ 1.01
- User A mints 1000 USDC for 990xUSD
- User A redeems 990xUSD for 980 USDT
- 980 USDT = 989.8 $USD
In result the user paid almost 0 fees.
Recommendation
Consider using the
netAmountInwhich accounts for deducted fees in theoracleAmountOutcalculation. This way even when a stablecoin depegs the overcollateralization ratio by the fee percentage is maintained upon mint/redeem actions.Furthermore, when the stablecoin going in as collateral is depegged upwards during a mint or when the stablecoin coming out as redeemed assets is depegged downwards during a redeem, the user is hurt additionally by the fees above and beyond the desired collateralization ratio of the protocol. This may be acceptable to conservatively price the stablecoins received, however it should be considered whether this is intended or not.
Resolution
Ethena Team: Acknowledged.
-
L-17 Low getBenefactorFeesForCollateral Skips Defaults Unexpected Behavior Resolved
Description
OnChainMinting.getBenefactorFeesForCollateral()returns themintFeeByCollateralandredeemFeeByCollateralfor a given pair of user and collateral or 0 if the user is exempted from fees for that collateral.When the user is not exempt and their fee is 0,
_getQuote()will use the:_collateralConfig.defaultMintFeeor_collateralConfig.defaultRedeemFee.Because
getBenefactorFeesForCollateral()doesn't use these defaults, there will be a discrepancy between the returned value from the function and the actual paid fee.Recommendation
Consider if the default fees should be included in the
getBenefactorFeesForCollateralfunction.Resolution
Ethena Team: Resolved.
-
I-01 Informational Oracle Manipulation With Inappropriate Staleness Gaming Resolved
Description
The
AggregateOracleFeedcontract performs amaxStalenessvalidation against all of the configured oracles. Some of these oracles are pull based (PythOracleFeed) while others are push based (ChainlinkOracleFeed).The staleness validation cannot be the same across these two types of oracles because push based aggregator feeds are updated on a less frequent basis, operating on strict SLAs for price deviation thresholds that are maintained by the oracle provider, while pull based oracles have no deviation SLA and are as fresh as the prices users choose to submit to them upon usage.
It is worth noting that some Pyth feeds are considered “sponsored feeds” and are updated regularly within a deviation SLA, however these feeds are not widely supported and we are considering non-sponsored Pyth feeds in this finding. Notice that there is no USDT sponsored feed on Ethereum for example.
A Chainlink aggregator feed that has a heartbeat window of 4 hours may have a price update that is 3 hours old but still perfectly acceptable given that the oracle SLA is e.g. 0.50% of deviation. For that reason, the maximum staleness of a push based oracle must be a longer time duration which typically matches the heartbeat duration.
However, applying that same maximum staleness to a pull based oracle such as the
PythOracleFeedallows users to use prices that are potentially extremely different than the current market price because there is no deviation threshold SLA with these oracles.Consider the following scenario:
- Market price at hour 100 is $1.00
- Current market price at hour 103 is $1.02
- The heartbeat of push based Oracle A is 4 hours, with a deviation threshold of 50 BIPs
- The
maxStalenessof the aggregate oracle is assigned as 4 hours, as this is the heartbeat of the push based oracle - Push based Oracle A was updated at the 102.5 hour mark given that the market price has moved more than 50 BIPs recently
- Pull based Oracle B has not been updated since hour 98 simply due to lacking usage
- A user may provide an update to Oracle B with the price data from hour 100 and this will be considered non-stale since it is within the
aggregate oracle threshold of 4 hours
- The average price is ($1.02 (current from the push based oracle A) + $1.00 (stale price from the pull based oracle B)) / 2 = $1.01
- Thus the actor has effectively manipulated the oracle system to use an inaccurate price and can then leverage this to arbitrage
the
OnChainMintingsystem.Recommendation
Both of the
PythOracleFeedandChainlinkOracleFeedoracle contracts do have their individual staleness checks, however currently these are both hardcoded at 1 day and 1 minute.Enforce strict staleness validations on a per-oracle level instead of general staleness validations at the aggregate oracle level, and require that pull based feeds such as the
PythOracleFeedhave a very short staleness tolerance of at the very maximum a few minutes to ensure up to date pricing.Notice that this will require users to provide a Pyth update using the hermes API, which is the expected interaction flow for the non-sponsored feeds.
Resolution
Ethena Team: Resolved.
-
I-02 Informational Ethena Minter Used To Exit Depegged Collateral Warning Acknowledged
Description
Proof of concept: PoC
In the event that one collateral asset in the backing basket of assets depegs, the Ethena Minter contract may be used to exit this collateral asset to the detriment of other depositors.
Consider the following scenario:
- Assume fees are excluded for the two actors
- The minter is backed by two collaterals, USDT and USDC.
- USDC depegs to 90 cents
pegPricefor the synthetic stablecoin is 1e18- Alice deposits 100 USDT and receives 100e18 synthetic stablecoins
- Bob deposits 100 USDC and receives 90 synthetic stablecoins
- Bob redeems 90 synthetic stablecoins for 90 USDT
- Alice redeems 10 synthetic stablecoins for 10 USDT
- Alice redeems 90 synthetic stablecoins for 90 USDC
- Alice received (90 * $0.90) + (10 *$ 1) = $91 from her redemption, even though she had originally deposited USDT
- Bob does not benefit in this scenario
- 9 USDC is left in the system with no corresponding synthetic stablecoins to redeem it.
In this scenario, where a user deposits a stablecoin that has already depegged, there is no gain for that user due to the defensive selection of the worse exchange rate for the user in the
_getQuotefunction.However in scenarios where many users have already deposited a variety of collaterals at the time when one collateral depegs, there may be a rush to withdraw the non-depegged collateral assets to avoid being negative impacted.
Recommendation
This risk of depeg is known to the Ethena team. It may be accepted that a depeg scenario will require manual intervention, however be aware of the current behavior where there may be a "bank run" and the final redeemers will be affected the most rather than the depeg affecting all xUSD holders at a fraction of the impact.
Such an underlying collateral depeg may result in a depeg of xUSD itself given that the arbitrage loop breaks when only depegged collateral assets remain.
Resolution
Ethena Team: Acknowledged.
-
I-03 Informational Unnecessary Zero Configuration Handling Gas Optimization Resolved
Description
In the
_validateBenefactorEpochLimitsfunction the validation is only performed if the providedmaxvalue is greater than zero.This is likely because the benefactor’s individual epoch limit configuration is optional. However in the case where the benefactor’s individual epoch limit configuration is unconfigured and left as zero, the
defaultBenefactorMaxMintPerEpochis used in its place.Therefore, unless the
defaultBenefactorMaxMintPerEpochis configured as zero, the_validateBenefactorEpochLimitsfunction can never receive amaxvalue of zero.And there already exist validations that ensure that the
defaultBenefactorMaxMintPerEpochcannot be configured to zero.The same applies to the
_validateBenefactorPeriodLimitsfunction.Recommendation
Consider removing the zero case handling from the
_validateBenefactorEpochLimitsand_validateBenefactorPeriodLimitsfunctions.Resolution
Ethena Team: Resolved.
-
I-04 Informational Duplicated Functions Unexpected Behavior Resolved
Description
The
getCurrentEpochEndandgetEpochEndTimestampfunctions as well as thegetCurrentPeriodEndandgetPeriodEndTimestampfunctions, have the same implementation.Recommendation
Consider deduplicating these functions.
Resolution
Ethena Team: Resolved.
-
I-05 Informational defaultBenefactorMaxRedeemPerPeriod Typo Typo Resolved
Description
In the documentation for the
defaultBenefactorMaxRedeemPerPeriodanddefaultBenefactorMaxRedeemPerEpochfunctions, theNatSpecprovides that these functions return thedefaultMaxRedeemPerPeriodanddefaultMaxRedeemPerEpochrespectively.However this is not the same format that is used to describe the return value for the
defaultBenefactorMaxMintPerPeriodanddefaultBenefactorMaxMintPerEpochfunctions.Recommendation
Instead to follow the existing format, the return value should be stated as
defaultBenefactorMaxRedeemPerPeriodanddefaultBenefactorMaxRedeemPerEpochin theNatSpec.Resolution
Ethena Team: Resolved.
-
I-06 Informational DEFAULT_ADMIN_ROLE Restriction Bypass Access Control Acknowledged
Description
The
enableBenefactor()andenableCollateral()functions inOnChainMintingset theisActiveflag of a benefactor or collateral totrue. The functions can only be called by addresses with theDEFAULT_ADMIN_ROLE.However, the
addBenefactor()andaddCollateral()functions callable by theBENEFACTOR_MANAGER_ROLEandCOLLATERAL_MANAGER_ROLEcan be used to activate the benefactor or the collateral, bypassing theDEFAULT_ADMIN_ROLErestriction.Recommendation
Consider checking if a benefactor or collateral have already been added and revert if they have.
Resolution
Ethena Team: Acknowledged.
-
I-07 Informational OFT's Minters() Function Returns Minter Status Best Practices Resolved
Description
The
xUSDOFTUpgradeable.minters(address)function's name implies the returned value will be the minters of the token, however the function returns whether the given account is a minter or not.The same is true for
blacklist()andblacklister()Recommendation
Consider renaming the functions to
isMinter(),isBlacklisted(),isBlacklister().Resolution
Ethena Team: Resolved.
-
I-08 Informational Inconsistency Between Tokens Mint Functions Best Practices Resolved
Description
The
xUSDOFTUpgradeable.mint()function reverts if the minted amount is 0.if (amount == 0) revert OFTErrors.InvalidAmount();On the other hand,
xUSD.mint()doesn't perform such a validation and instead calls the internal_mint()with the given amount.function mint(address to, uint256 amount) external { xUSDStorage storage $ = _getxUSDStorage(); if (!$.minters[msg.sender]) revert OnlyMinter(); _mint(to, amount); emit Minted(to, amount, msg.sender); }Recommendation
Consider performing the check in
xUSDas wellResolution
Ethena Team: Resolved.
-
I-09 Informational Burned Events Emitted Only For burnFrom() Events Resolved
Description
OFTEvents.Burned()andIxUSD.Burned()are emitted only when theburnFrom()function is called, but users can also useburn().Recommendation
Double check if the events should be emitted in the
burn()functions. If yes, add it. If no, consider changing theNatSpecbecause it currently saysEmitted when tokens are burned.Resolution
Ethena Team: Resolved.
-
I-10 Informational 0xdead Shouldn't Be Blacklisted Informational Acknowledged
Description
If
address(0xdead)is blacklisted in any of thexUSDOFTUpgradeablecontracts, all crosschain transfers toaddress(0)will be failing. That's becauseOFTUpgradeable._credit()redirectsaddress(0)transfers toaddress(0xdead).if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0) _mint(_to, _amountLD);Then
_mint()will invoke_update()and if0xdeadis blacklisted, the transaction will revert.Recommendation
Keep that in mind when modifying the blacklist
Resolution
Ethena Team: Acknowledged.
-
I-11 Informational AggregateOracleFeed Always Rounds Down Best Practices Acknowledged
Description
In the
getPricefunction theaveragePriceresult always uses round down division no matter what the resulting oracle price is to be used for.When depositing collateral, it is more advantageous to the user that the oracle price reports a higher price, so in this case rounding down is appropriate.
However when withdrawing collateral, it is more advantageous to the user that the oracle prices the output collateral lower, so in this case rounding up would be appropriate.
Furthermore, within the oracle contracts that make up the
AggregateOracleFeed, thePythOracleFeedand theChainlinkOracleFeed, round down division is used to convert the decimals of the resulting price to 18.In these cases the same rounding logic ought to apply to round against the user and in the favor of the protocol whenever possible.
Recommendation
Consider adding a boolean parameter to indicate which direction the
AggregateOracleFeedand the subfeeds within it ought to round.Resolution
Ethena Team: Acknowledged.
-
I-12 Informational USDT Has A Fee On Transfer Feature Unexpected Behavior Acknowledged
Description
USDT, one of the supported collateral tokens by the minter, has a fee on transfer feature that is currently disabled.If it's ever enabled, the accounting of the minter will behave incorrectly because the contract assumes the whole amount is being transferred to it.
Recommendation
Consider making the contract work correctly with fee on transfer tokens.
Resolution
Ethena Team: Acknowledged.
-
I-13 Informational Lack Of Admin Validation In PythOracleFeed Validation Resolved
Description
The constructor in
PythOracleFeed.soldoes not validate_admin != address(0)before grantingDEFAULT_ADMIN_ROLE.If deployed with a zero admin, admin-only functions like
setMaxConfidencebecome permanently inaccessible, preventing parameter updates and incident response.Recommendation
Add a zero-address check for
_adminin the constructor and revert if provided asaddress(0).Resolution
Ethena Team: Resolved.
-
I-14 Informational A Single Oracle Can Cause OOG Revert Informational Acknowledged
Description
The
AggregatorOracleFeedloops through each oracle and executes a call togetPrice()wrapped in atry/catch.An oracle can execute all of the 63/64th of the gas forwarded and leave the aggregator with insufficient amount to complete the rest of the call.
Furthermore, it can revert with a long data that has to be copied to memory in the
catchblock, again causing an OOG.Recommendation
Make sure to add only
PythOracleFeedandChainlinkOracleFeedas oracles.Resolution
Ethena Team: Acknowledged.
-
I-15 Informational OracleStale Emitted For Prices In Future Best Practices Resolved
Description
When an oracle returns a timestamp more than
MAX_FUTURE_TIMESTAMP_TOLERANCEseconds in the future, the code emitsOracleStale.This is semantically incorrect and can mislead monitoring/alerting systems diagnosing issues.
Recommendation
Consider emitting a more accurate event
Resolution
Ethena Team: Resolved.
-
I-16 Informational Inaccurate NatSpec For OFT Events And Errors Best Practices Resolved
Description
The
OFTEvents.BlacklistRedirect()event docs state theredirectedTois the owner/treasury, while the implementation redirects to therescueRecipient.This inconsistency can mislead operators and off-chain indexers, complicating incident response and audits.
The
NatSpecforOFTErrors.BlacklistException()says it is thrown when setting rate limits by a non-owner, but the error is actually used to signal blacklist violations in_update().Recommendation
Update the event
NatSpecsto reflect that redirection targetsrescueRecipientandBlackListException()is thrown when a blacklisted entity participates in a transfer.Resolution
Ethena Team: Resolved.
-
I-17 Informational Benefactor Mint Fee Cannot Be Changed Validation Resolved
Description
OnChainMinting.setBenefactorMintFee()reverts if the collateral is not active.if (!collateralState[collateral].config.isActive) { revert CollateralNotSupported(collateral); }This makes it impossible to change the mint fee for a disabled collateral. In contrast, the check is not present in
setBenefactorRedeemFee().Recommendation
Consider removing the check from
setBenefactorMintFee()Resolution
Ethena Team: Resolved.
-
I-18 Informational Event Parameters Documentation Mismatch Best Practices Resolved
Description
The event
IAggregateOracleFeed.OracleInvalidPrice()is documented with parameters in the order(bound, price), but the event signature is(price, bound), and emissions follow the signature order.Off-chain consumers relying on the
NatSpecdocs (or positional assumptions) can invert values and misdiagnose whether the violation was lower or upper bound, hindering incident response.Recommendation
Align documentation with the actual event signature.
Resolution
Ethena Team: Resolved.
-
I-19 Informational Fee Features Unsupported Documentation Resolved
Description
In the documentation several fee features such as
Volume DiscountsandMarket Conditionsbased fees are mentioned but not supported in the code.Recommendation
Consider if these fee features are intended to be supported for this release or confirm if these are future features.
Resolution
Ethena Team: Resolved.
-
I-20 Informational Lacking SafeCast Usage Best Practices Resolved
Description
In the
_getQuotefunction the resultingoracleAmountOutandoneToOneAmountOutvalues are downcasted to uint128 without usingSafeCast.Although these values should never be larger than the maximum
uint128it is a best practice to revert if this case ever would arise out of an abundance of caution.Recommendation
Consider using
SafeCastfor these instances.Resolution
Ethena Team: Resolved.
-
I-21 Informational addCollateral Can Be Used Inappropriately Unexpected Behavior Resolved
Description
In the
addCollateralfunction the check if the collateral already exists is based upon whether theisActivevalue is true.However, the
isActivevalue can be set to false for a collateral that is de-activated and is intended to be activated again in the future. In this case the collateral can be errantly “re-added” with theaddCollateralfunction, since it sees this collateral as having never been configured.The
updateCollateralfunction should be used instead in these cases to edit collateral configurations after they have already been added.Recommendation
Instead of relying on isActive, the
addCollateralfunction should validate that thecollateralCfg.custodianAddressvalue is equal to zero for the collateral configuration to ensure that only a collateral that truly has not been configured is being added.Resolution
Ethena Team: Resolved.
-
I-22 Informational removeCollateral Unexpectedly No-ops Unexpected Behavior Acknowledged
Description
The
removeCollateralfunction silently completes execution in the event that the collateral is errantly not configured.This is different from the behavior of the other collateral configuration related functions which instead revert in the case that the collateral is in an unexpected state.
Recommendation
Consider if the
removeCollateralfunction should behave similarly to the other collateral configuration functions and revert as well.Resolution
Ethena Team: Acknowledged.
-
I-23 Informational Missing Benefactor Address Validation Validation Acknowledged
Description
The
removeBenefactorfunction lacks anonlyValidAddress(benefactor)modifier unlike the other benefactor configuration functions.Recommendation
Consider adding an
onlyValidAddress(benefactor)modifier to theremoveBenefactorfunction.Resolution
Ethena Team: Acknowledged.
-
I-24 Informational Opaque BenefactorConfigUpdated Event Events Acknowledged
Description
The
BenefactorConfigUpdatedevent simply emits the address of the benefactor and provides no further information about the configuration that was made.This is unlike other configuration events such as the
CollateralConfigUpdatedevent which emits theCollateralConfigobject that was used to update the configuration.Recommendation
Consider including information in the
BenefactorConfigUpdatedevent about the configuration that was made or the final state of the benefactor config.Resolution
Ethena Team: Acknowledged.
-
I-25 Informational Missing Signer Address Validation Validation Acknowledged
Description
In the
removeDelegatedSignerfunction there is noonlyValidAddress(signer)modifier as there is with thesetDelegatedSignerfunction.Recommendation
Consider adding the
onlyValidAddress(signer)modifier to theremoveDelegatedSignerfunction.Resolution
Ethena Team: Acknowledged.
-
I-26 Informational rescueRecipient Can Be Blacklisted Informational Resolved
Description
The
xUSDOFTUpgradeablecontract implements a blacklist redirection mechanism in the_credit()function to preventLayerZerodouble-mint vulnerabilities.When a cross-chain transfer targets a blacklisted recipient, tokens are redirected to the
rescueRecipientaddress instead of reverting.However, if the
rescueRecipientitself becomes blacklisted, all cross-chain transfers to the blacklisted addresses will fail until either the recipient is removed from the blacklist or a newrescueRecipientis set.Recommendation
Consider adding validation in the
setRescueRecipientfunction as well as theaddToBlacklistfunction that prevents therescueRecipientfrom becoming a blacklisted address.Resolution
Ethena Team: Resolved.
-
I-27 Informational Total Minted/redeemed Per State Going Over Limit Informational Acknowledged
Description
The global, collateral and benefactor states for epochs and periods have limits which can be changed by actors with the appropriate roles. If a lowering of the limit happens, it's possible to end up with a state where
mintedIn*orredeemedIn*are over the new limit.This state is not dangerous since further mints/redeems would just fail, but it may be unexpected for third-party integrators.
Recommendation
Document the possibility of this peculiar state.
Resolution
Ethena Team: Acknowledged.
-
I-28 Informational Limit Reverts May Be Duplicated Best Practices Resolved
Description
Whenever an epoch or period limit is violated, the transaction reverts with an appropriate error. For example, if the mint limit for a global period is exceeded, the following error is thrown:
revert GlobalMaxMintPerPeriodExceeded(assetAmount, currentTotal, max, currentPeriod)All of the errors of this type accept either
currentEpochorcurrentPeriod, but no duration. It's possible to have the same epoch or period id for different durations, therefore some of the errors may be duplicated.Recommendation
Consider adding the durations to the revert data as well.
Resolution
Ethena Team: Resolved.
-
I-29 Informational Fee Collection Rounds Down Rounding Resolved
Description
In the
_getQuotefunction thefeeAmountcalculation uses round down division instead of round up division.Especially because the fee affects the collateralization of xUSD, it would be best to round in favor of the protocol and against the user.
Recommendation
Consider using roundup division for the
feeAmountcomputationResolution
Ethena Team: Resolved.
-
I-30 Informational Benefactor Addresses Must Handle Tokens Validation Resolved
Description
In the
_validateBenefactorfunction for all benefactor configurations the benefactor themselves are always allowed as anorder.beneficiaryassignment.This means that the benefactor should always be able to handle any of the collateral tokens that could possibly be redeemed or xUSD itself, even just to handle the case where an order accidentally assigned the benefactor as the beneficiary.
Recommendation
Consider removing the default whitelisting of the benefactor themselves from the allowed beneficiary in the
_validateBenefactorfunction and requiring that if the benefactor wishes to receive tokens directly that they must explicitly opt-in to this.Otherwise, clearly document that this is assumed to be supported at the
OnChainMintingcontract level.Resolution
Ethena Team: Resolved.
-
I-31 Informational GetQuote() Does Not Validate Price Freshness Math Resolved
Description
The function
getQuote()calls the internal function_getQuote(), which fetches the Oracle feed as well as anupdatedAttimestamp.The view function
getQuote(), which is intended to fetch quotes without executing orders, does not validate theupdatedAt.This means that users could potentially get outdated prices, leading to a potential difference in price when executing orders.
Recommendation
Consider if this is intended. If not, then consider informing the users of the potential discrepancy in user-facing documentation.
Resolution
Ethena Team: Resolved.
-
I-32 Informational Collateral Decimals Can Be Updated Validation Resolved
Description
The
updateCollateralConfigfunction accepts aCollateralConfigobject which will become the new configuration for the collateral.The
CollateralConfigobject includes a decimals value which assigns the decimals which are used for the underlying collateral.This decimals value however should not be changed from the original assignment as collateral tokens will never change their decimals denomination.
Recommendation
Consider restricting the decimals from being updated to a different value in the
updateCollateralConfigfunction.Additionally, guardrails can be added in the
addCollateralfunction to validate that the returneddecimals()value from the collateral token matches the decimals being configured, noting that this restricts the system to only be compatible with collateral tokens that implement thedecimalsexternal function.Resolution
Ethena Team: Resolved.
-
I-33 Informational getQuote Can Be Marked External Best Practices Resolved
Description
The
getQuotefunction is marked aspublicbut is not referenced within theOnChainMintingcontract and can therefore be marked asexternal.Recommendation
Mark the
getQuotefunction asexternal.Resolution
Ethena Team: Resolved.
-
I-34 Informational Unnecessary Optimizations Informational Acknowledged
Description
Since Solidity 0.8.22, the compiler performs advanced range analysis to determine when overflow checks can safely be omitted.
In a typical loop increment scenario such as for (
uint256 i = 0; i < length; i++), the compiler can prove that i will not overflow auint256and therefore, it automatically removes the checks.Manually placing increments in an unchecked block is now redundant, making the code less clear without providing a meaningful gas optimization.
Recommendation
Throughout the codebase, replace the unnecessary unchecked { ++i; } instances with a simple ++i in the for-loop declaration, allowing the compiler’s range analysis to handle overflow optimizations automatically.
This cleaner style increases code clarity while maintaining code efficiency in Solidity 0.8.22 and above.
Resolution
Ethena Team: Acknowledged.
-
I-35 Informational End Timestamp Functions Return The Next Start Informational Acknowledged
Description
OnChainMinting.getEpochEndTimestamp()andOnChainMinting.getPeriodEndTimestamp()aim to return the timestamp when the current epoch/period ends. The calculations are as follows:uint256 currentEpoch = _getCurrentEpoch(globalState); return (currentEpoch + 1) * globalState.config.epochDuration;The resulting timestamp will be the start of the next epoch/period, which may be unexpected for third-party integrators.
Recommendation
Consider either documenting this behavior or subtracting 1 from the end result.
Resolution
Ethena Team: Acknowledged.
-
I-36 Informational COLLATERAL_MANAGER_ROLE Can Change The Oracle Trust Assumptions Acknowledged
Description
OnChainMinter.updateCollateralConfig()can be invoked by addresses with theCOLLATERAL_MANAGER_ROLEto update the configuration for a given collateral.One of the fields in the
CollateralConfigstruct is theoracleFeedused to retrieve the price when minting and redeeming.The ability to change the oracle at any time creates risks for the protocol, since it can be set at as any address and even return fake data.
Recommendation
Consider whether the
oracleFeedshould be modifiable. If yes, you can give that right to theDEFAULT_ADMIN_ROLEand enforce thatoracleFeedis not changed inupdateCollateralConfig().Resolution
Ethena Team: Acknowledged.
-
I-37 Informational Beneficiary Can Be Set For Inactive Benefactor Informational Acknowledged
Description
OnChainMinting.setApprovedBeneficiary()is a permissionless function that allows benefactors to add or remove beneficiaries.The function doesn't check if the benefactor is active, which means a benefactor that has never been approved can set their beneficiary and emit the corresponding events -
BeneficiaryApprovedandBeneficiaryRemoved.This is not aligned with other functions which modify the beneficiary state, for example
setDelegatedSigner(), and it can cause confusion for any third-party integrators if they expect invalid benefactors to have empty config.Recommendation
Consider validating the
isActiveflag when adding a new beneficiary, but still allow removing the old one in order not to block benefactors from removing beneficiaries when they are temporarily disabled.Resolution
Ethena Team: Acknowledged.
-
I-38 Informational Regular xUSD Does Not Implement Blacklist Warning Resolved
Description
In the normal xUSD implementation there is no blacklist logic.
Recommendation
Consider if blacklist logic should be implemented for the basic, non-OFT xUSD implementation.
Resolution
Ethena Team: Resolved.
-
I-39 Informational Fee Exempt Mints Reduce Collateralization Warning Acknowledged
Description
Benefactors who are fee exempt reduce the overall collateralization of the system when they mint, because fees are considered a part of the collateral backing xUSD.
Consider the following example:
- The existing system has 110 USDC collateral and 100 xUSD supply.
- The collateralization rate is 110%.
- A fee exempt benefactor deposits 100 USDC to receive 100 xUSD.
- The system now has 210 USDC collateral and 100 xUSD.
- The collateralization rate has dropped to 105%.
Recommendation
Be aware of this collateralization reduction and consider it when allowing benefactors to be fee exempt.
Resolution
Ethena Team: Acknowledged.
-
I-40 Informational Peg Updates Are Sandwichable Gaming Acknowledged
Description
When the peg price of the xUSD asset is updated in the
OnChainMintingcontract a risk free sandwich arbitrage opportunity is created where a user may execute a mint order right before the peg price increase and execute a redeem order right after the peg price increase, gaining the difference in the mint/redemption rate for xUSD.Recommendation
Be sure to use a private RPC when updating the peg price. Furthermore, be aware that benefactors and signers have the ability to arbitrage this update. It may be prudent to fully disable minting and redeeming before making the update and broadcasting the peg increase.
Resolution
Ethena Team: Acknowledged.
Remediation Review
15 findings-
M-01 Medium Same Block OFT Transfers Are Blocked DoS Resolved
Description
When OFTs are being sent to another chain,
_amountCanBeSent()is executed to enforce rate limiting.A new check was added to avoid overflow when
limitandtimeSinceLastDepositare multiplied. A division bytimeSinceLastDepositis performed, but it's not checked whether its value is 0 or not.uint256 timeSinceLastDeposit = block.timestamp - _lastUpdated; if (_limit > type(uint256).max / timeSinceLastDeposit) {...}This means only 1
send()operation can be executed per block, as the rest of them will revert. This behavior can be weaponized by sending a small amount of tokens to DOS thesendfeature for the whole block.Recommendation
Handle the case where
timeSinceLastDeposit == 0separately.Resolution
Ethena Team: Resolved.
-
L-01 Low Missing Check For Maximum Oracles Allowed Validation Resolved
Description
A new constant
MAX_NUMBER_OF_ORACLES = 4was added toAggregateOracleFeedto limit the number of oracles being used. This is needed because theMathlibrary supports at most 4 oracles.While
addOracle()reverts if the array exceeds 4 oracles, there is no such check in the constructor. Because of this an aggregator can be deployed with more than 4 oracles and result in reverts in all cases.Recommendation
Validate the number of oracles being used in the constructor.
Resolution
Ethena Team: Resolved.
-
L-02 Low Missing rescueBlacklistedTokens Function Unexpected Behavior Resolved
Description
In the
xUSDOFTUpgradeablecontract there is arescueBlacklistedTokensfunction which rescues funds from a blacklisted address.Blacklist functionality has been introduced to the xUSD contract, however such a rescue function has not been added to
xUSD.Recommendation
Consider adding a similar
rescueBlacklistedTokensfunction to the xUSD contract.Resolution
Ethena Team: Resolved.
-
I-01 Informational Discrepancy In The Emission Of Burned Event Events Resolved
Description
xUSDOFTUpgradeable.burnFrom()emits theBurned()event only if the burner is a minter. In contrast, inxUSDthe event will be emitted for all burns, no matter the caller.Recommendation
Consider emitting
Burned()only on burns from a minter as well.Resolution
Ethena Team: Resolved.
-
I-02 Informational Inconsistency Between Tokens Mint Functions Validation Resolved
Description
As described in
I-08of the main review, there is a discrepancy between themint()functions inxUSDandxUSDOFTUpgradeable- the former allows 0 amount mints, while the latter reverts.Furthermore, the
xUSDOFTUpgradeableimplementation does not enforce the blacklist like thexUSDmint function does.The remediation commit provided,
14e5dbacf9aa235c165d39cb068afc8cf5cfd27c, doesn't fix the issue.Recommendation
Revert in
xUSD.mint()if the amount is 0.Resolution
Ethena Team: Resolved.
-
I-03 Informational Incorrect Constant Formatting Best Practices Resolved
Description
Throughout the onchain minting project constants are correctly formatted in screaming snake case. However, the storage location variable declarations do not follow this formatting.
Recommendation
Consider updating the formatting of the following variables to reflect the screaming snake case formatting for constants:
xUSDOFTUpgradeableStorageLocationOFTOwnable2StepUpgradeableStorageLocationRateLimiterUpgradeableStorageLocationxUSDStorageLocation
Resolution
Ethena Team: Resolved.
-
I-04 Informational Redundant Inheritance Pattern Best Practices Resolved
Description
The xUSD contract includes several redundant inheritances of Initializable and
ERC20Upgradeablewhich are not strictly necessary and become inherited through theUUPSUpgradeableandERC20BurnableUpgradeablecontracts respectively.The
xUSDOFTUpgradeablecontract does not include such redundant inheritances.Recommendation
Consider removing the redundant Initializable and
ERC20Upgradeableinheritances from the xUSD contract.Resolution
Ethena Team: Resolved.
-
I-05 Informational isMinter Function Discrepancy Best Practices Resolved
Description
In the
xUSDcontract the view function which exposes a predicate to check if an account is a whitelisted minter is calledminters, however in thexUSDOFTUpgradeablecontract this function is calledisMinter.Recommendation
Consider aligning the implementations by renaming the
xUSDfunction toisMinter.Resolution
Ethena Team: Resolved.
-
I-06 Informational removeCollateral Misleading Documentation Documentation Resolved
Description
Finding I-22 from the main review points out that the
removeCollateralbehavior differs from other configuration functions and no-ops instead of reverting.The Ethena team has acknowledged this as expected, however the documentation for the
removeCollateralfunction says,Reverts if collateral doesn't exist.However, this is not the case, as acknowledged by I-22.
Recommendation
Update the documentation of the
removeCollateralfunction to reflect the desired no-op behavior.Resolution
Ethena Team: Resolved.
-
I-07 Informational Inaccurate Validation Documentation Documentation Resolved
Description
The documentation for the
_validateBenefactorEpochLimitsand_validateBenefactorPeriodLimitsfunctions still suggests that itOnly validates if max > 0 (unlimited if max == 0).However, this is no longer the case.
Recommendation
Consider updating the documentation for these functions.
Resolution
Ethena Team: Resolved.
-
I-08 Informational Invalid SPDX Header Best Practices Resolved
Description
The SPDX license identifier is set to GPL-3.30, which is not a valid SPDX expression. This can break license detection tools and compliance pipelines.
Recommendation
Change the SPDX header to a valid identifier, e.g., // SPDX-License-Identifier: GPL-3.0.
Resolution
Ethena Team: Resolved.
-
I-09 Informational Missing Collateral Value Events Resolved
Description
In the
_getQuotefunction, whenoraclePrice == 0,_getQuotereverts withInvalidOraclePricebut passesaddress(0)as the collateral address. This makes debugging and monitoring difficult because the emitted error reports the wrong collateral.Recommendation
Consider adding the actual collateral token address in the revert for additional details.
Resolution
Ethena Team: Resolved.
-
I-10 Informational Rate Limit Caps Cause Unexpected Behavior Unexpected Behavior Resolved
Description
The overflow guard caps decay to
_limitwhen_limit * timeSinceLastDepositwould overflowuint256. While this avoids arithmetic errors, it can overestimate decay (treating it as a full-window decay in these cases) and thus grant more capacity than intended for small time intervals.Although practically hard to exploit (requires extreme limit/window), if this would arise due to an extreme configuration, it may cause unexpected behavior.
Recommendation
Instead of using explicit overflow checks, consider using a math library that handles intermediate overflow such as
OpenZeppelin Math.mulDiv.Resolution
Ethena Team: Resolved.
-
I-11 Informational Lacking Min/Max Oracle Price Validation Validation Resolved
Description
In the
AggregateOracleFeedconstructor the_minOraclePriceand_maxOraclePriceis accepted and assigned to storage without checking their validity relative to each other.Currently a
_maxOraclePricethat is smaller than the_minOraclePriceis a valid configuration.Recommendation
Consider validating that the
_maxOraclePriceis larger than the_minOraclePricein the constructor.Resolution
Ethena Team: Resolved.
-
I-12 Informational Outdated Interface Best Practices Resolved
Description
In the
IAggregateOracleFeedinterface the average price is still referenced instead of the now Median price being used.Recommendation
Consider updating the documentation in the interface to avoid confusion for integrators and users.
Resolution
Ethena Team: Resolved.
No findings match.
Invariants 17
The review's fuzzing suite asserted 17 invariants. 17 held.
Every invariant tested
| ID | Invariant | Result |
|---|---|---|
OFT-01 | After a successful OFT send, the amount in flight should be less than or equal to the | Held |
OFT-02 | rate limit After a successful OFT send, the amount in flight should increase with the amountLD | Held |
MINTING-01 | sent After a successful mint, mintedInEpoch and mintedInPeriod should increase with the same amount for all three states (global, | Held |
MINTING-02 | collateral, benefactor) After a successful redeem, redeemedInEpoch and redeemedInPeriod should increase with the same amount for | Held |
MINTING-03 | all three states (global, collateral, benefactor) After a successful mint, mintedInEpoch and mintedInPeriod should increase with the result of getQuote (global, collateral, | Held |
MINTING-04 | benefactor) After a successful redeem, redeemedInEpoch and redeemedInPeriod should increase with the input amount | Held |
MINTING-05 | (global, collateral, benefactor) After a successful mint, the benefactor collateral balance should decrease with order.amountIn | Held |
MINTING-06 | After a successful mint, the beneficiary xUSD balance should increase with the | Held |
MINTING-07 | result of getQuote After a successful redeem, the benefactor xUSD balance should decrease with | Held |
MINTING-08 | order.amountIn After a successful redeem, the beneficiary collateral balance should increase with the | Held |
MINTING-09 | result of getQuote The minted assets with a given collateral for an epoch or period should not exceed the globally minted assets for that epoch or | Held |
MINTING-10 | period The redeemed assets for a given collateral for an epoch or period should not exceed the globally redeemed assets for that epoch | Held |
MINTING-11 | or period The minted assets by a given benefactor for an epoch or period should not exceed the globally minted assets for that epoch or | Held |
MINTING-12 | period The redeemed assets by a given benefactor for an epoch or period should not exceed the globally redeemed assets for that epoch | Held |
MINTING-13 | or period After executing an order, the current epoch and period should be the same for each | Held |
MINTING-14 | state (global, collateral, benefactor) After a successful mint, if the epoch/period was rolled, mintedInEpoch/mintedInPeriod should be the result of getQuote for all three states (global, collateral, benefactor) | Held |
MINTING-15 | After a successful redeem, if the epoch/period was rolled, redeemedInEpoch/redeemedInPeriod should be the input amount for all three states (global, collateral, benefactor) | Held |
More from Ethena
All 7 reportsPut 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.