DittoETH

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

rETH price feed insufficiently validated and no corrective measure

Summary

The only price feed to get the rETH value for the following features:

  • Yield calculation

  • deposit()

  • depositEth() for zethAmounts returns

  • zETH to ETH conversion on BridgeRouterFacet::_ethConversion

It is totally dependend on Rocket's Pool DAO, which has a risk of consensus attacks on RPL nodes, where nodes may submit incorrect exchange rate data.
Which is a single point of failure with no implemented corrective measure.

Vulnerability Details

To determine the current exchange rate between rETH and the underlying staked ether, the getEthValue() function is used. This function relies on the reported ether balance from nodes and stored in a special contract called RocketNetworkBalances. Ether balance is divided by the current rETH supply and that value is considered as the current exchange rate.
DittoETH relies exclusively on rocketETHToken::getEthValue to know the ETH value of the BridgeReth rETH balances, getEthValue is used to compute the zethTotalNew in LibVault.sol.

RocketPools getEthValue call trace:
Source

File: RocketTokenRETH.sol
// Calculate the amount of ETH backing an amount of rETH
function getEthValue(uint256 _rethAmount) override public view returns (uint256) {
// Get network balances
RocketNetworkBalancesInterface rocketNetworkBalances = RocketNetworkBalancesInterface(getContractAddress("rocketNetworkBalances"));
uint256 totalEthBalance = rocketNetworkBalances.getTotalETHBalance();
uint256 rethSupply = rocketNetworkBalances.getTotalRETHSupply();
// Use 1:1 ratio if no rETH is minted
if (rethSupply == 0) { return _rethAmount; }
// Calculate and return
return _rethAmount.mul(totalEthBalance).div(rethSupply);
}

Source

File: RocketNetworkBalances.sol
function getTotalETHBalance() override public view returns (uint256) {
return getUint(keccak256("network.balance.total"));
}

totalEthBalance setter:
Source

File: RocketNetworkBalances.sol
function setTotalETHBalance(uint256 _value) private {
setUint(keccak256("network.balance.total"), _value);
}

Which is set by executeUpdateBalances and submitBalances in the same contract, only callable by the DAO.
Network ether balances are submitted by the trusted oracles also known as nodes. The submitBalances function call will only pass once the required number of balance submissions from node operators is reached. It’s worth noting that there are no checks on balance changes, which could potentially have a significant impact in the event of an RPL nodes consensus attack.
It's crucial to remain vigilant about the risk of consensus attacks on RPL nodes, where nodes may submit incorrect exchange rate data.

Impact

Potential incorrect yield distribution and zETH -> ETH conversion, which would reduce overall DittoETH liquidity and attractiveness.
Users that have already deposited any LSD, would got an inexact value for their ETH withdrawing.
Potentially losing user funds.

Tools Used

Manual review, Rocket Pool documentation.

Recommendations

DittoETH shouldn't rely exclusively on rocketETHToken::getEthValue to know the ETH value of the BridgeReth rETH balances.
Governance should be able to change price feed to an alternative one.

Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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