MorpheusAI

MorpheusAI
Foundry
22,500 USDC
View results
Submission Details
Severity: medium
Invalid

Loss of funds and Unnecessary Bridging will occur when `overplus` returns a very small amount

Summary

When The overplus is called it returns depositTokenContractBalance_ - totalDepositedInPublicPools;, which happens as a result of the lido stETH rebasing system, but the difference in these two balances, might be miniscule, as a result some of the subtraction may return small amounts like 0.0001stETH, which will actually pass the require(overplus_ > 0, "DS: overplus is zero"); check, and then the function will attempt to bridge that token, but in the L1Sender contract when it callssendDepoditTokens the wrap function it calls from theIWstETH according to lido, wrapping stETH is done according to an exchange rate which is updated every day, this means for example, 1 stETH - 0.98WstETH, with this conversion rate the small amount stETH will definatley round down to zero, which will cause loss of tokens and Bridging a zero amount WstETH.

Vulnerability Details

The Following Steps will describe how this vunerability will occur

  • Owner tries to Bridge the overplus as shown below

function bridgeOverplus(
uint256 gasLimit_,
uint256 maxFeePerGas_,
uint256 maxSubmissionCost_
) external payable onlyOwner returns (bytes memory) {
uint256 overplus_ = overplus();
require(overplus_ > 0, "DS: overplus is zero");
IERC20(depositToken).safeTransfer(l1Sender, overplus_);
bytes memory bridgeMessageId_ = L1Sender(l1Sender).sendDepositToken{value: msg.value}(
gasLimit_,
maxFeePerGas_,
maxSubmissionCost_
);
emit OverplusBridged(overplus_, bridgeMessageId_);
return bridgeMessageId_;
}

Inside that function it checks the returned overplus value, which is actually just depositTokenContractBalance_ - totalDepositedInPublicPools, this calculation may not always return the value the protocol expects, it may return a very small amount , like 0.0001stETH, shown below the protocol tries to make sure that the overplus is greater than zero as shown below

require(overplus_ > 0, "DS: overplus is zero");

This check will pass and it will call to bridge the token,

  • Now Inside the sendDepositFunction we will explore where the issue will happen

https://github.com/Cyfrin/2024-01-Morpheus/blob/07c900d22073911afa23b7fa69a4249ab5b713c8/contracts/L1Sender.sol#L99C5-L123C1

function sendDepositToken(
uint256 gasLimit_,
uint256 maxFeePerGas_,
uint256 maxSubmissionCost_
) external payable onlyDistribution returns (bytes memory) {
DepositTokenConfig storage config = depositTokenConfig;
// Get current stETH balance
uint256 amountUnwrappedToken_ = IERC20(unwrappedDepositToken).balanceOf(address(this));
// Wrap all stETH to wstETH
uint256 amount_ = IWStETH(config.token).wrap(amountUnwrappedToken_);
//@audit Wrapped token may round to zero if the amountUnwrappedToken is a very small amount due to lido conversion rate that is updated every day
bytes memory data_ = abi.encode(maxSubmissionCost_, "");
return
IGatewayRouter(config.gateway).outboundTransfer{value: msg.value}(
config.token,
config.receiver,
amount_,
gasLimit_,
maxFeePerGas_,
data_
);
}
  • Here when the wrap method is called on the unwrapped amount, it will round down to zero if the amount you are about to wrap is a very small amount, due to lido daily set conversion rate

This Conversion rate system is linked here below

https://stake.lido.fi/wrap

At any given time, anyone holding wstETH can convert any amount of it to stETH at a fixed rate, and vice versa. Normally, the rate gets updated once a day, when stETH undergoes a rebase.

from Lido site

  • But the main issue is that the function does not consider that kind of situation and tries to bridge the wrapped amount like that, leading to a situation of bridging zero amount meanwhile spending expensive gas for computation on the destination chain leading to losing funds on both chains

Impact

Loss Of Funds When trying to perform expensive Cross chain action that is sure to revert on the destination chain

Tools Used

Manual Review

Recommendations

In the sendDepositToken function before bridging the follow checks should be implemented
require(amount_ > 0, 'cant bridge zero WstETH')

Updates

Lead Judging Commences

inallhonesty Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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