DittoETH

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

Instant arbitrage opportunity through rETH and stETH price discrepancy

Summary

User can choose to withdraw their zETH to be a rETH or stETH, while in reality most user will choose the best return (highest value between rETH and stETH), instant arbitrage will happen and this will trigger pool imbalance, draining one over the other.

Vulnerability Details

In DittoETH, they accept two special types of Ethereum tokens: rETH and stETH. These tokens are based on regular ETH but are designed to stay close in value to one regular Ether. However, in reality, they can have slightly different values. rETH, stETH.

In practice, when user want to withdraw, they can choose between rETH and stETH based on which one is worth more at that moment. The system doesn't really care which one you put in when a user first deposited their asset.

Now, here's where it gets interesting. Because rETH and stETH can have slightly different values, a savvy user could deposit the cheaper one, get a zeth, and then withdraw the more valuable rETH and stETH. a quick way to make some extra profit

As we can see on line 110-112, the rETH or stETH withdrawn is depends on ethAmount, which from _ethConversion it's amount is 'equal' between rETH and stETH

File: BridgeRouterFacet.sol
088: function withdraw(address bridge, uint88 zethAmount)
089: external
090: nonReentrant
091: onlyValidBridge(bridge)
092: {
093: if (zethAmount == 0) revert Errors.ParameterIsZero();
094:
095: uint88 fee;
096: uint256 withdrawalFee = bridge.withdrawalFee();
097: uint256 vault;
098: if (bridge == rethBridge || bridge == stethBridge) {
099: vault = Vault.CARBON;
100: } else {
101: vault = s.bridge[bridge].vault;
102: }
103:
104: if (withdrawalFee > 0) {
105: fee = zethAmount.mulU88(withdrawalFee);
106: zethAmount -= fee;
107: s.vaultUser[vault][address(this)].ethEscrowed += fee;
108: }
109:
110: uint88 ethAmount = _ethConversion(vault, zethAmount);
111: vault.removeZeth(zethAmount, fee);
112: IBridge(bridge).withdraw(msg.sender, ethAmount);
113: emit Events.Withdraw(bridge, msg.sender, zethAmount, fee);
114: }
...
175: function _ethConversion(uint256 vault, uint88 amount) private view returns (uint88) {
176: uint256 zethTotalNew = vault.getZethTotal();
177: uint88 zethTotal = s.vault[vault].zethTotal;
178:
179: if (zethTotalNew >= zethTotal) {
180: // when yield is positive 1 zeth = 1 eth
181: return amount;
182: } else {
183: // negative yield means 1 zeth < 1 eth
184: return amount.mulU88(zethTotalNew).divU88(zethTotal);
185: }
186: }

Impact

Instant arbitrage opportunity through rETH and stETH price discrepancy, will also trigger imbalance between rETH and stETH pool.

Tools Used

Manual analysis

Recommendations

Consider to use oracle to adjust the price difference between rETH and stETH

Updates

Lead Judging Commences

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

finding-579

Support

FAQs

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