DittoETH

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

If the dao removes a bridge, user's deposited tokens for that bridge will be lost.

Summary

If the dao removes a bridge for any (non-malicious) reason, user's deposited tokens for that bridge will be lost.

Vulnerability Details

In the OwnerFacet.sol the dao of the system has the option to remove a bridge by calling the deleteBridge() function. There is no check if there are any assets in the bridge. Also users may deposit funds in the bridge during the voting period.

POC
Add the following function in the BridgeRouter.t.sol

function test_DeleteBridgeWithAssets() public {
console.log("Sender ethEscrowed in vault 2 before deposit: ", diamond.getVaultUserStruct(2, sender).ethEscrowed);
deal(_rethA, sender, 10000 ether);
vm.startPrank(sender);
uint88 deposit1 = 1000 ether;
uint88 withdrawAmount = 100 ether;
diamond.deposit(_bridgeRethToBeDeleted, deposit1);
console.log("Sender ethEscrowed in vault2 after deposit: ", diamond.getVaultUserStruct(2, sender).ethEscrowed);
diamond.withdraw(_bridgeRethToBeDeleted, withdrawAmount);
console.log("Sender ethEscrowed after withdraw: ", diamond.getVaultUserStruct(2, sender).ethEscrowed);
vm.stopPrank();
console.log("Balance of reth in the bridgeRethToBeDeleted: ", rethA.balanceOf(_bridgeRethToBeDeleted));
/// INFO: DAO deletes the bridge after a vote has been passed
vm.startPrank(owner) ;
diamond.deleteBridge(_bridgeRethToBeDeleted);
vm.stopPrank();
vm.startPrank(sender);
vm.expectRevert();
diamond.withdraw(_bridgeRethToBeDeleted, withdrawAmount);
console.log("Balance of reth in the bridgeRethToBeDeleted: ", rethA.balanceOf(_bridgeRethToBeDeleted));
vm.stopPrank();
}

In order to run this test, you also have to add

rethA.approve(
_bridgeRethToBeDeleted,
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
);

to setUp() function of the BridgeROuter.t.sol contract

In DeployHelper.sol another bridge and vault have to be added in order for the test to run:

/// INFO: added by auditor
IBridge public bridgeRethToBeDeleted;
address public _bridgeRethToBeDeleted;
IAsset public zethToBeDeletedVault;
address public _zethToBeDeletedVault;
IRocketStorage public rocketStorageA;
address public _rocketStorageA;
IRocketTokenRETH public rethA;
address public _rethA;

Add the following to deployContracts() function

if (chainId == 31337) {
//mocks
_immutableCreate2Factory = deployCode("ImmutableCreate2Factory.sol");
if (isMock) {
_steth = deployCode("STETH.sol");
_unsteth = deployCode("UNSTETH.sol", abi.encode(_steth));
_rocketStorage = deployCode("RocketStorage.sol");
_reth = deployCode("RocketTokenRETH.sol");
reth = IRocketTokenRETH(_reth);
_ethAggregator = deployCode("MockAggregatorV3.sol");
/// INFO: added by auditor
_rocketStorageA = deployCode("RocketStorage.sol");
_rethA = deployCode("RocketTokenRETH.sol");
rethA = IRocketTokenRETH(_rethA);
}
rocketStorage = IRocketStorage(_rocketStorage);
/// INFO: added by auditor
rocketStorageA = IRocketStorage(_rocketStorageA);
steth = ISTETH(_steth);
unsteth = IUNSTETH(payable(_unsteth));
ethAggregator = IMockAggregatorV3(_ethAggregator);
}
/// INFO: Added by auditor
_zethToBeDeletedVault = factory.safeCreate2(
salt,
abi.encodePacked(
vm.getCode("Asset.sol:Asset"), abi.encode(_diamond, "Zebra ETH Two", "ZETHT")
)
);
_bridgeRethToBeDeleted = factory.safeCreate2(
salt,
abi.encodePacked(
vm.getCode("BridgeReth.sol:BridgeReth"),
abi.encode(_rocketStorageA, _diamond)
)
);
bridgeRethToBeDeleted = IBridge(_bridgeRethToBeDeleted);
MTypes.CreateVaultParams memory vaultParams;
vaultParams.zethTithePercent = 10_00;
vaultParams.dittoMatchedRate = 1;
vaultParams.dittoShorterRate = 1;
diamond.createVault({zeth: _zeth, vault: Vault.CARBON, params: vaultParams});
MTypes.CreateVaultParams memory vaultParamsTwo;
vaultParamsTwo.zethTithePercent = 9_00;
vaultParamsTwo.dittoMatchedRate = 1;
vaultParamsTwo.dittoShorterRate = 1;
zethToBeDeletedVault = IAsset(_zethToBeDeletedVault);
diamond.createVault({zeth: _zethToBeDeletedVault, vault: 2, params: vaultParamsTwo});
STypes.Vault memory carbonVaultConfigTwo = diamond.getVaultStruct(2);
assertEq(carbonVaultConfigTwo.zethTithePercent, 9_00);
diamond.createBridge({
bridge: _bridgeRethToBeDeleted,
vault: 2,
withdrawalFee: 150,
unstakeFee: 0
});
if (isMock) {
rocketStorage.setDeposit(_reth);
rocketStorage.setReth(_reth);
/// INFO: added by auditor
rocketStorageA.setDeposit(_rethA);
rocketStorageA.setReth(_rethA);
_setETH(4000 ether);
}

To run the test use forge test -vvv --mt test_DeleteBridgeWithAsset

Impact

User's deposited RETH or STETH in that bridge will be lost, as the user doesn't have the option to withdraw them. Because the withdraw functions can only be called trough the Diamond.sol

Tools Used

Manual Review

Recommendations

In deleteBridge() make sure that the contract doesn't have any assets

Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-118

dimulski Auditor
over 1 year ago
0xnevi Lead Judge
over 1 year ago
dimulski Auditor
over 1 year ago
dimulski Auditor
over 1 year ago
0xnevi Lead Judge
over 1 year ago
dimulski Auditor
over 1 year ago
0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

finding-118

Support

FAQs

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