Ebisu
Smart Contract Security Assessment
May 28, 2025

SUMMARY
ABSTRACT
Dedaub was commissioned to perform a security audit of the Ebisu protocol, a friendly fork of Liquity V2. Among other things, the protocol’s main additions include:
- Contract upgradeability
- Support for collateral tokens beyond WETH, stETH, and rETH
- The ability to dynamically add collateral tokens during the lifetime of the protocol
SETTING & CAVEATS
This audit report mainly covers the contracts of the at-the-time private repository https://github.com/ebisufinance/ebisu-money/ of the Ebisu Protocol at commit dc6dc20cfb29994a8de5a9bd4d545d4acbec433f.
Since the protocol is a friendly fork of LiquityV2, the team also reviewed all changes compared to LiquityV2’s repository (https://github.com/liquity/bold) at commit 1652751c503a0a295a63b2c6b921d45d2f09a7fc, which was assumed to be safe.
2 auditors worked on the codebase for 6 days on the following contracts:
The audit’s main target is security threats, i.e., what the community understanding would likely call "hacking", rather than the regular use of the protocol. Functional correctness (i.e. issues in "regular use") is a secondary consideration. Typically it can only be covered if we are provided with unambiguous (i.e. full-detail) specifications of what is the expected, correct behavior. In terms of functional correctness, we often trusted the code’s calculations and interactions, in the absence of any other specification. Functional correctness relative to low-level calculations (including units, scaling and quantities returned from external protocols) is generally most effectively done through thorough testing rather than human auditing.
Because the protocol aims to support various Bitcoin wrapper assets (i.e., cbBTC, LBTC, WBTC), one of the audit’s main focuses was ensuring that the accounting inherited from LiquityV2 was successfully adjusted to work with tokens that have 8 decimals of precision.
VULNERABILITIES & FUNCTIONAL ISSUES
This section details issues affecting the functionality of the contract. Dedaub generally categorizes issues according to the following severities, but may also take other considerations into account such as impact or difficulty in exploitation:
- User or system funds can be lost when third-party systems misbehave
- DoS, under specific conditions
- Part of the functionality becomes unusable due to a programming error
- Breaking important system invariants but without apparent consequences
- Buggy functionality for trusted users where a workaround exists
- Security issues which may manifest when the system evolves
Issue resolution includes “dismissed” or “acknowledged” but no action taken, by the client, or “resolved”, per the auditors.
CRITICAL SEVERITY
[No critical severity issues]
HIGH SEVERITY
[No high severity issues]
MEDIUM SEVERITY
[No medium severity issues]
LOW SEVERITY
Adding a new branch to Ebisu is achieved through a call to EbisuBranchFactory::_setupContractConnections, which in turn calls CollateralRegistry::addNewBranch for setup:
CollateralRegistry::addNewBranch:90function addNewBranch(
address _token,
...,
IEbisuBranchManager _branchManager
) external virtual onlyBranchFactory {
...
if (collTokenToBranchManager[_token] == IEbisuBranchManager(address(0))) {
collTokenToBranchManager[_token] = _branchManager;
}
}
The issue is when a branch shuts down, a new branch may be redeployed for the same token. As can be seen in the above code, in this scenario the new branch manager will not be associated with the fresh branch, but the old branch manager will be. This is problematic, since the old branch manager will be pointing to old versions of the branch contracts.
During liquidations in which the debt is offset in the Stability Pool, a small portion of the collateral that is being seized is offered as compensation to the liquidator for the gas spent. In LiquityV2, this consists of 0.5% of the trove’s liquidated with a cap at 2 ETH:
EbisuBranchManager::_getCollGasCompensation:889function _getCollGasCompensation(uint256 _coll) internal pure returns (uint256) {
return LiquityMath._min(_coll / COLL_GAS_COMPENSATION_DIVISOR, COLL_GAS_COMPENSATION_CAP);
}
While COLL_GAS_COMPENSATION_DIVISOR being 200 represents 50/1000=0.5%, the protocol also has COLL_GAS_COMPENSATION_CAP be 2 ETH = 2e18 tokens.
For 8-decimal assets, this corresponds to 2e18 = 2e10 × 1e8 token units, ultimately yielding 2e10 tokens. This represents an unrealistically high amount—especially for assets wrapping BTC—so in practice, the collateral gas compensation is uncapped for those kinds of assets.
During liquidations that are offset in the Stability Pool, the corresponding percentage of the trove’s collateral that will be offset is calculated based on the percentile of the trove’s debt that is being offset as follows:
EbisuBranchManager::getOffsetAndRedistributionVals:347...
collSPPortion = _entireTroveColl * debtToOffset / _entireTroveDebt;
...
When debtToOffset is a small enough number () the above expression is approximately scaled at 8-18 < 0 decimals of precision, meaning that collSPPortion can end up being 0 when:
- A liquidation takes place when the Stability Pool has been freshly created in a new 8-decimal collateral branch (i.e.,
SP_balance= )
[or]
- A liquidation takes place when the Stability Pool has low BOLD balance just above 1e18 (i.e.
SP_balance= )
Since the minimum debt that can be created is 2000 USD (~0.02 BTC = 1e6 units of a BTC wrapper asset), if the protocol does not wish to directly address the potential scaling issue, it must ensure that the Stability Pool always holds more than 1e12 BOLD for offsets (in addition to the 1e18 minimum BOLD balance).
CENTRALIZATION CONSIDERATIONS
It is often desirable for DeFi protocols to assume no trust in a central authority, including the protocol’s owner. Even if the owner is reputable, users are more likely to engage with a protocol that guarantees no catastrophic failure even in the case the owner gets hacked/compromised. We list considerations of this kind below. (These items should be considered in the context of usage/deployment, as they are not uncommon. Several high-profile, high-value protocols have significant centralization threats.)
The ebisuAdmin is an address that:
- Can change important branch parameters in
EbisuBranchManager - Can control who has access to proposing parameters in
EbisuBranchManager - Can initiate an upgrade proposal for any protocol contract
- Can set the factory responsible for creating a new collateral branch
- Can add support for more collateral tokens
Although this entity has critical privileges over the protocol, parameters cannot be arbitrarily set but instead are typically after certain sanity checks take place. Additionally, the version of upgradeable contracts that are present in the protocol cannot be upgraded atomically. Instead, each upgrade can only take effect after a timelock period passes (7 days).
More importantly, the protocol aims at giving all those privileges to governance in the future, preventing a single address from having the above-mentioned privileges.
OTHER / ADVISORY ISSUES
This section details issues that are not thought to directly affect the functionality of the project, but we recommend considering them.
AddressRegistry expects _ccr to be between 1e18 and 2e18 (line 83) however EbisuBranchManager line 162 expects _CCR to be between MCR and 3e18.
EbisuBranchManager::setMinAnnualInterestRate on line 176 reverts if rate <= 0, however the previous line reverts when _rate <= MIN_ANNUAL_INTEREST_RATE. MIN_ANNUAL_INTEREST_RATE is a uint256, thus when _rate <= 0 this previous revert will trigger.
The following functions give redundant calling permissions (i.e., the implementation of the contract given permission never actually calls the function):
DefaultPool::sendCollToActivePooltoEbisuBranchManager.CollSurplusPool::accountSurplustoEbisuBranchManager.ActivePool::setShutdownFlagto TroveManager.SortedTroves::removetoBorrowerOperations.SortedTroves::inserttoBorrowerOperationsHelper.SortedTroves::reInserttoBorrowerOperations.SortedTroves::reInsertBatchtoBorrowerOperations.
EbisuBranchManagerlines 113, 115, and 118 are redundant; these setactivePool,priceFeed, anddefaultPoolwhich are already set by the call toLiquityBase::initializeon line 109.EbisuBranchManagerline 130 setsdebtCap = 0, which is unnecessary since this is the default value.TroveManagerline 747 setsTroves[_troveId].lastInterestRateAdjTimeto the latest timestamp. This is unneeded since it is already performed by the call to_initializeTroveon line 744.
EbisuBranchManager::finalizeParameterChange finalises proposals for changes to several parameters, including for debtCap. For the latter this is not needed, since debtCap is set immediately by EbisuBranchManager::setDebtCap on line 204, and is never added as a proposal to proposedValues.
The comment on line EbisuBranchManager:301 states that only ebisuAdmin should call EbisuBranchManager::setMinDebt, but it uses the modifier onlyAuthorized(Parameter.MIN_DEBT) rather than onlyEbisuAdmin.
The following internal functions are not used anywhere:
-
EbisuBranchFactory::getBytecode -
EbisuBranchFactory::getAddress -
TroveManager::_getCollGasCompensation
One of the collateral assets to be supported is Ethena’s sUSDe. sUSDe is an ERC-4626 vault implementation for Ethena’s stable coin - USDe - which is meant to tokenize the yield that the protocol generates from various sources.
The asset’s price feed somewhat resembles LiquityV2’s logic for RETH:
sUSDePriceFeed::_fetchPricePrimary:65...
// Get canonical rate using previewRedeem
uint256 sUsdeRate = sUsde.previewRedeem(1 ether);
uint256 canonicalPrice = usdeUsdPrice * sUsdeRate / 1e18;
uint256 price;
// If it's a redemption and canonical is within 2% of market, use the max to mitigate unwanted redemption oracle arb
if (_isRedemption && _withinDeviationThreshold(sUsdeUsdPrice, canonicalPrice, SUSDE_USD_DEVIATION_THRESHOLD)) {
price = LiquityMath._max(sUsdeUsdPrice, canonicalPrice);
} else {
// Take the minimum of (market, canonical) in order to mitigate against upward market price manipulation
price = LiquityMath._min(sUsdeUsdPrice, canonicalPrice);
}
...
However, sUSDe being a typical ERC-4626 implementation means that sUSDe’s exchange rate is given as:
0x9d39a5de30e57443bff2a8307a4256c8797a3497::ERC4626::_convertToAssets:207function totalAssets() public view virtual override returns (uint256) {
return _asset.balanceOf(address(this));
}
...
function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) {
return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
}
...
It becomes apparent that the asset’s rate can be inflated by directly transferring USDe to the sUSDe contract—unlike RETH’s rate, which is strictly calculated based on Rocket Pool’s internal accounting.
Since the oracle always returns the minimum price between Chainlink’s sUSDe feed and sUSDe’s canonical rate, a user receives the best possible price when a redemption occurs and the two prices differ by more than the set deviation threshold (2%). When the collateral is priced lower, the same amount of BOLD tokens corresponds to more collateral tokens.
A possible scenario would involve a user:
- Inflating the sUSDe exchange rate (atomically)
- Redeeming a large amount of BOLD tokens
- With the redemption occurring at the market price, the user then redeems all received sUSDe for USDe at the inflated rate
This represents a buy-low, sell-high scheme—constituting a simple arbitrage loop. A thorough risk analysis of this scenario can be found here: https://hackmd.io/@DpO5w3VJTCC5KnEfWwp07Q/SJhT8Mzzel
Ultimately, as long as sUSDe’s USDe balance is in the billions, there is no practical risk of an attack using this method. Even when the balance falls to the millions, only users redeeming millions of BOLD would be in a position to execute this arbitrage loop.
The code is compiled with Solidity 0.8.24. Version 0.8.24 has no known bugs.
DISCLAIMER
The audited contracts have been analyzed using automated techniques and extensive human inspection in accordance with state-of-the-art practices as of the date of this report. The audit makes no statements or warranties on the security of the code. On its own, it cannot be considered a sufficient assessment of the correctness of the contract. While we have conducted an analysis to the best of our ability, it is our recommendation for high-value contracts to commission several independent audits, a public bug bounty program, as well as continuous security auditing and monitoring through Dedaub Security Suite.
ABOUT DEDAUB
Dedaub offers significant security expertise combined with cutting-edge program analysis technology to secure some of the most prominent protocols in DeFi. The founders, as well as many of Dedaub's auditors, have a strong academic research background together with a real-world hacker mentality to secure code. Protocol blockchain developers hire us for our foundational analysis tools and deep expertise in program analysis, reverse engineering, DeFi exploits, cryptography and financial mathematics.