DittoETH

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

No slippage protection in withdraw()

Summary

If a user deposits STETH tokens he/she receives ZETH tokens equivalent to the amount of deposited STETH. The deposit is combined with deposits of RETH tokens in the Carbon vault. If the price of RETH drops more than the price of ETH and the yield becomes negative, when users withdraw they will receive less STETH tokens than the ZETH tokens that they burn. Although this is a mechanism of the protocol to protect itself against volatile market conditions, users are not protected from burning all their ZETH tokens and receiving less STETH tokens than expected. Also there is no deadline specified so even if when the user created the transaction the yield was not negative, by the time it is picked and executed by the node market condition may have changed, and the yield may have became negative which results in users loosing funds.

Vulnerability Details

POC
In BridgeRouter.t.sol add the following

Create user alice:

address public alice = address(12);

In setUp add the following:

vm.startPrank(alice);
steth.approve(
_bridgeSteth,
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);
vm.stopPrank();

Add the following test:

function test_minAmountOut() public {
console.log("");
console.log("Balances of the carbon vault, alice and sender before deposits");
console.log("Here is the value in ETH of the staked RETH and STETH tokens in the carbon vault", diamond.getZethTotal(Vault.CARBON));
console.log("Here is the sender zeth tokens for the carbon vault: ", diamond.getVaultUserStruct(Vault.CARBON, sender).ethEscrowed);
console.log("Here is the begginer zeth tokens for the carbon vault: ", diamond.getVaultUserStruct(Vault.CARBON, alice).ethEscrowed);
deal(_reth, sender, 100 ether);
deal(_steth, alice, 10 ether);
console.log("");
console.log("Balances of alice and sender after dealing them RETH and STETH tokens");
console.log("reth balance of sender: ", reth.balanceOf(sender));
console.log("steth balance of begginer: ", steth.balanceOf(alice));
/// INFO: Sender deposits 100 RETH
vm.startPrank(sender);
uint88 deposit1 = 100 ether;
diamond.deposit(_bridgeReth, deposit1);
console.log("");
console.log("Sender deposits 100 RETH");
console.log("Here is the sender zeth tokens for the carbon vault: ", diamond.getVaultUserStruct(Vault.CARBON, sender).ethEscrowed);
console.log("Here is the value in ETH of the staked RETH and STETH tokens in the carbon vault: ", diamond.getZethTotal(Vault.CARBON));
vm.stopPrank();
/// INFO: Alice deposits 10 STETH
vm.startPrank(alice);
uint88 deposit2 = 10 ether;
diamond.deposit(_bridgeSteth, deposit2);
console.log("");
console.log("Alice deposits 10 STETH");
console.log("Here is Alice zeth tokens for the carbon vault: ", diamond.getVaultUserStruct(Vault.CARBON, alice).ethEscrowed);
console.log("Here is the value in ETH of the staked RETH and STETH tokens in the carbon vault: ", diamond.getZethTotal(Vault.CARBON));
vm.stopPrank();
/// INFO: Simulate a market condition where the price of RETH falls down
uint256 _ethSupply = 0.8 ether;
uint256 _rethSupply = 1 ether;
address reth_address = bridgeReth.getBaseCollateral();
IRocketTokenRETH rocketTokenRETH = IRocketTokenRETH(reth_address);
rocketTokenRETH.submitBalances(_ethSupply, _rethSupply);
/// INFO: Get the Steth bridge withdraw fee
console.log("");
console.log("Withdraw fee of the Steth bridge: ", diamond.getBridgeStruct(_bridgeSteth).withdrawalFee);
/// INFO: Alice withdraws all of her staked STETH
vm.startPrank(alice);
diamond.withdraw(_bridgeSteth, deposit2);
console.log("");
console.log("Alice withdraws 10 STETH");
console.log("Here is the amount of ZETH tokens in the carbon vault: ", diamond.getVaultStruct(Vault.CARBON).zethTotal);
console.log("Here is the alice zeth tokens for the carbon vault: ", diamond.getVaultUserStruct(Vault.CARBON, alice).ethEscrowed);
console.log("Balance of steth of alice: ", steth.balanceOf(alice));
vm.stopPrank();
}

Output:

Balances of the carbon vault, alice and sender before deposits
Here is the value in ETH of the staked RETH and STETH tokens in the carbon vault 0
Here is the amount of ZETH tokens in the carbon vault: 0
Here is the sender zeth tokens for the carbon vault: 0
Here is the begginer zeth tokens for the carbon vault: 0
Balances of alice and sender after dealing them RETH and STETH tokens
reth balance of sender: 100000000000000000000
steth balance of begginer: 10000000000000000000
Sender deposits 100 RETH
Here is the sender zeth tokens for the carbon vault: 100000000000000000000
Here is the value in ETH of the staked RETH and STETH tokens in the carbon vault: 100000000000000000000
Here is the amount of ZETH tokens in the carbon vault: 100000000000000000000
Alice deposits 10 STETH
Here is the alice zeth tokens for the carbon vault: 10000000000000000000
Here is the value in ETH of the staked RETH and STETH tokens in the carbon vault: 110000000000000000000
Here is the amount of ZETH tokens in the carbon vault: 110000000000000000000
Withdraw fee of the Steth bridge: 0
Alice withdraws 10 STETH
Here is the amount of ZETH tokens in the carbon vault: 100000000000000000000
Here is the alice zeth tokens for the carbon vault: 0
Balance of steth of alice: 8181818181818181818

To run the test use: forge test -vvv --mt test_minAmountOut

Impact

When a user decides to withdraw his/her STETH tokens, he/she may receive less STETH tokens, than he/she wants and already burned ZETH tokens for. Resulting in user losing funds.

Tools Used

Manual review

Recommendations

Add a minAmountOut parameter that a user can set to the withdraw() function, and revert if the amount returned from the _ethCOnversion() function is less.

Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other
dimulski Auditor
over 1 year ago
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.