DittoETH

Ditto
DeFiFoundryOracle
55,000 USDC
View results
Submission Details
Severity: medium
Valid

A user can not unstake native ETH if the RocketPool would face a DOS attack, due to the BridgeReth#`unstake()` only rely on unstaking root via the RocketPool by burning rETH

Summary

A user, who call the BridgeReth#unstake() via the BridgeRouterFacet#unstakeEth(), can not unstake native ETH if a DOS for the RocketPool protocol would occur due to that the BridgeReth#unstake() rely on unstaking a native ETH via the RocketPool only (by calling the RocketETHToken#burn() function).

Vulnerability Details

When a user withdraw the amount of native ETH, the user call the BridgeRouterFacet#unstakeEth().
Within the BridgeRouterFacet#unstakeEth(), the BridgeSteth#unstake()or BridgeReth#unstake() would be called like this:
https://github.com/Cyfrin/2023-09-ditto/blob/main/contracts/facets/BridgeRouterFacet.sol#L138

function unstakeEth(address bridge, uint88 zethAmount)
external
nonReentrant
onlyValidBridge(bridge)
{
...
uint88 ethAmount = _ethConversion(vault, zethAmount);
...
IBridge(bridge).unstake(msg.sender, ethAmount); ///<----------- @audit
...

Within the BridgeReth#unstake(), the RocketETHToken#burn() would be called to unstake the amount (rethValue) of rETH from the RocketPool in exchange for obtaining the equal amount of native ETH.
Then, the netBalance of native ETH-unstaked would be sent to the user (to) like this:
https://github.com/Cyfrin/2023-09-ditto/blob/main/contracts/bridges/BridgeReth.sol#L102
https://github.com/Cyfrin/2023-09-ditto/blob/main/contracts/bridges/BridgeReth.sol#L105

function unstake(address to, uint256 amount) external onlyDiamond {
IRocketTokenRETH rocketETHToken = _getRethContract();
uint256 rethValue = rocketETHToken.getRethValue(amount);
uint256 originalBalance = address(this).balance;
rocketETHToken.burn(rethValue); ///<--------------------------- @audit
uint256 netBalance = address(this).balance - originalBalance;
...
(bool sent,) = to.call{value: netBalance}(""); ///<--------------------------- @audit
...
}

According to the past finding in the past contest in Code4rena, RocketPool rETH tokens have a "deposit delay" like this:

RocketPool rETH tokens have a deposit delay that prevents any user who has recently deposited to transfer or burn tokens. In the past this delay was set to 5760 blocks mined (aprox. 19h, considering one block per 12s).

This delay can prevent users from unstaking native ETH from RocketPool if a malicious user staked their native ETH recently and directly into the RocketPool with the minmum amount (i.e. 1 wei) and then the malicious user repeatedly stake with the same way.

An attack scenario:
Let's say Bob is a malicious actor and Alice is a user of the DittoETH protocol.

1/ Bob stakes with the minimum amount (i.e. 1 wei) via the RocketPool directly, consequently triggering deposit to RocketPool and resetting the deposit delay.
(NOTE:At this time, Bob does not need to stake their native ETH via the DittoETH protocol. It's OK for him to stake their native ETH via the RocketPool directly)

2/ Alice tries to unstake her funds by calling the BridgeReth#unstake() via the BridgeRouterFacet#unstakeEth(). At that time, the RocketETHToken#burn() would be called via the BridgeReth#unstake() to burn rETH in exchange for native ETH. But during rETH burn in the BridgeReth contract, it fails due to the delay check in the RocketPool, reverting the unstake call.

3/ If Bob manages to repeatedly stake the minimum amount (i.e. 1 wei) via the RocketPool directly every 19h (or any other interval less then the deposit delay), all future calls to unstake will revert.

As we can see above, a DOS for the RocketPool protocol lead to preventing the users from unstaking native ETH from RocketPool via the BridgeReth#unstake() (via the BridgeRouterFacet#unstakeEth())

Impact

A user, who call the BridgeReth#unstake() via the BridgeRouterFacet#unstakeEth(), can not unstake native ETH if a DOS for the RocketPool protocol would occur due to that the BridgeReth#unstake() rely on unstaking a native ETH via the RocketPool only (by calling the RocketETHToken#burn() function).

Tools Used

  • Manual review

Recommendations

Within the BridgeReth#unstake(), consider adding an implementation that enable to switch the root of unstaking native ETH from through the RocketETHToken#burn() to through swapping on UniswapV3 if unstaking native ETH would be blocked by a DOS attack for RocketPool protocol.

Updates

Lead Judging Commences

0xnevi Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-88

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.