Open sherlock-admin4 opened 8 months ago
The protocol team fixed this issue in the following PRs/commits: https://github.com/rio-org/rio-sherlock-audit/pull/11
The protocol team fixed this issue in PR/commit rio-org/rio-sherlock-audit#11.
Fixed The check is removed. To mitigate the differences in the share allocation to operators inside rio and the actual share allocation to operators in eigenlayer, an additional function is added to sync
The Lead Senior Watson signed off on the fix.
mstpr-brainbot
high
Depositing to EigenLayer can revert due to round downs in converting shares<->assets
Summary
When the underlying tokens deposited from depositPool to EigenLayer strategy, there are bunch of converting operations done which rounds down the solution at some point and the require check reverts hence, the depositing might not be possible due to this small round down issue.
Vulnerability Detail
Best to go for this is an example, so let's do it.
Assume the deposit pool has 111 1e18 stETH waiting for rebalance to be deposited to EigenLayer and there is only 1 operator with 1 strategy allowed which is the EigenLayers stETH strategy. Also, assume the EigenLayer has 3333 1e18 stETH in total and 3232 * 1e18 shares in supply. Also, note that the EigenLayer uses virtual shares offset which is 1e3.
Now, let's say there is no withdrawal queue to ease the complexity of the issue and rebalance is called and the balance in the deposit pool will be forwarded to EigenLayer strategy as follows:
Then, the
depositBalanceIntoEigenLayer
will trigger theOperatorOperations.depositTokenToOperators
function as follows:As we can see in the above snippet, the underlying tokens to be deposited which is 111 * 1e18 stETH in our example will be converted to EigenLayer strategy shares via
assetRegistry().convertToSharesFromAsset
Now, how does EigenLayer calculates how much shares to be minted given an underlying token deposit is as follows:
Now, let's plugin our numbers in the example to calculate how much shares would be minted according to EigenLayer:
virtualTotalShares
= 3232 1e18 + 1e3virtualTokenBalance
= 3333 1e18 + 1e3amountUnderlying
= 111 * 1e18and when we do the math we will calculate the shares to be minted as: 107636363636363636364
Then, the library function will be executed as follows:
The very first line of the above snippet executes the
operatorRegistry.allocateStrategyShares
, let's examine that:So, let's value the above snippet aswell considering the cap is not reached. As we can see the how much underlying token needed is again calculated by querying the EigenLayer strategy
sharesToUnderlyingView
, so let's first calculate that:Let's put the values to above snippet:
virtualTotalShares
= 3232 1e18 + 1e3virtualTokenBalance
= 3333 1e18 + 1e3amountShares
= 107636363636363636364 *hence, the return value is 110999999999999999999(as you noticed it is not 111 1e18 as we expect!)**sharesToAllocate
= remainingShares = newShareAllocation = 107636363636363636364newTokenAllocation
= 110999999999999999999sharesAllocated
= 107636363636363636364Now, let's go back to
depositTokenToOperators
function and move with the execution flow:as we can see the underlying tokens we calculated (110999999999999999999) is deposited to EigenLayer for shares here and then compared in the last line in the if check as follows:
stakeERC20
will stake 110999999999999999999 tokens and in exchange will receive 107636363636363636363 shares. Then thesharesReceived
will be compared with the initial share amount calculation which is 107636363636363636364hence, the last if check will revert because 107636363636363636363 != 107636363636363636364
Coded PoC:
Impact
The issue described above can happen frequently as long as the perfect division is not happening when converting shares/assets. In order to solve the issue the amounts and shares has to be perfectly divisible such that the rounding down is not an issue. This can be fixed by owner to airdrop some assets such that this is possible. However, considering how frequent and easy the above scenario can happen and owner needs to do some math to fix the issue, I'll label this as high.
Code Snippet
https://github.com/sherlock-audit/2024-02-rio-vesting-core-protocol/blob/4f01e065c1ed346875cf5b05d2b43e0bcdb4c849/rio-sherlock-audit/contracts/restaking/RioLRTDepositPool.sol#L47-L67
https://github.com/sherlock-audit/2024-02-rio-vesting-core-protocol/blob/4f01e065c1ed346875cf5b05d2b43e0bcdb4c849/rio-sherlock-audit/contracts/restaking/RioLRTAssetRegistry.sol#L215-L221
https://github.com/Layr-Labs/eigenlayer-contracts/blob/5c192e1a780c22e027f6861f958db90fb9ae263c/src/contracts/strategies/StrategyBase.sol#L211-L243
https://github.com/sherlock-audit/2024-02-rio-vesting-core-protocol/blob/4f01e065c1ed346875cf5b05d2b43e0bcdb4c849/rio-sherlock-audit/contracts/utils/OperatorOperations.sol#L51-L68
https://github.com/sherlock-audit/2024-02-rio-vesting-core-protocol/blob/4f01e065c1ed346875cf5b05d2b43e0bcdb4c849/rio-sherlock-audit/contracts/restaking/RioLRTOperatorRegistry.sol#L342-L392
https://github.com/sherlock-audit/2024-02-rio-vesting-core-protocol/blob/4f01e065c1ed346875cf5b05d2b43e0bcdb4c849/rio-sherlock-audit/contracts/restaking/RioLRTOperatorDelegator.sol#L174-L179
Tool used
Manual Review
Recommendation