Platform: Code4rena
Start Date: 16/02/2023
Pot Size: $144,750 USDC
Total HM: 17
Participants: 154
Period: 19 days
Judge: Trust
Total Solo HM: 5
Id: 216
League: ETH
Rank: 48/154
Findings: 2
Award: $204.11
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: GalloDaSballo
Also found by: 0xBeirao, 0xRobocop, AkshaySrivastav, KingNFT, MiloTruck, PaludoX0, bin2chen, hansfriese, imare, kaden
142.8544 USDC - $142.85
The _rebalance()
of ActivePool
contract may revert due to arithmetic underflow, results in the majority of functionality of Ethos-Core
becoming temporarily unavailable.
The issue arises on L251 of ActivePool.sol
, as the convert between Assets
and Shares
are rounded down, so the vars.sharesToAssets
has a high chance to be less than vars.currentAllocated
in the period that a new deposit
to vault has been done but no enough new yield gain is generated to compensate for the rounding down loss.
File: Ethos-Core\contracts\ActivePool.sol 239: function _rebalance(address _collateral, uint256 _amountLeavingPool) internal { 240: LocalVariables_rebalance memory vars; 241: 242: // how much has been allocated as per our internal records? 243: vars.currentAllocated = yieldingAmount[_collateral]; 244: 245: // what is the present value of our shares? 246: vars.yieldGenerator = IERC4626(yieldGenerator[_collateral]); 247: vars.ownedShares = vars.yieldGenerator.balanceOf(address(this)); 248: vars.sharesToAssets = vars.yieldGenerator.convertToAssets(vars.ownedShares); ... 251: vars.profit = vars.sharesToAssets.sub(vars.currentAllocated); // @audit might revert 252: if (vars.profit < yieldClaimThreshold[_collateral]) { 253: vars.profit = 0; 254: } ... 309: }
Let's illustrate with an example Given initial state
collateral = WETH activePoolShares = 500 activePoolYieldingAmount = 555 totalSharesOfValut = 1000 totalAssetsOfValut = 1110
this is an idea state with exactly half shares half assets, no convert rounding down error, but any change on it will have a high chance to introduce rounding error. We can see it in the following steps.
The active pool deposits another 100 wei
to the vault, then
activePoolYieldingAmount = 555 + 100 = 655
and according to L334 of _deposit()
of ReaperVaultV2.sol
File: Ethos-Vault\contracts\ReaperVaultV2.sol 319: function _deposit(uint256 _amount, address _receiver) internal nonReentrant returns (uint256 shares) { ... 331: if (totalSupply() == 0) { 332: shares = _amount; 333: } else { 334: shares = (_amount * totalSupply()) / freeFunds; // use "freeFunds" instead of "pool" 335: } 336: _mint(_receiver, shares); ... 338: }
the active pool will receive
addedAtcivePoolShare = 100 * 1000 / 1110 = 90 // rounding down
and
totalAssetsOfValut = 1110 + 100 = 1210 totalSharesOfValut = 1000 + 90 = 1090 activePoolShares = 500 + 90 = 590
From this time point till the vault yields enough new gain, vars.sharesToAssets.sub(vars.currentAllocated)
triggers revert
vars.sharesToAssets = 590 * 1210 / 1090 = 654 // rounding down vars.currentAllocated = activePoolYieldingAmount = 655 vars.sharesToAssets.sub(vars.currentAllocated) = 654 - 655 // trigger revert
As the _rebalance()
will be called on any collateral change of ActivePool
, this issue will cause the majority of operations of Ethos-Core
becoming temporarily unavailable.
Manually review
Check if vars.sharesToAssets
is larger than vars.currentAllocated
before sub
.
#0 - c4-judge
2023-03-08T15:47:22Z
trust1995 marked the issue as duplicate of #747
#1 - c4-judge
2023-03-08T15:47:27Z
trust1995 marked the issue as satisfactory