code-423n4 / 2023-05-xeth-findings

0 stars 0 forks source link

Deflation bricking #38

Closed code423n4 closed 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/code-423n4/2023-05-xeth/blob/d86fe0a9959c2b43c62716240d981ae95224e49e/src/wxETH.sol#L1

Vulnerability details

Impact

First staker can block staking by making exchangeRate() == 0.

Proof of Concept

As can be seen

function exchangeRate() public view returns (uint256) {
    /// @dev if there are no tokens minted, return the initial exchange rate
    uint256 _totalSupply = totalSupply();
    if (_totalSupply == 0) {
        return INITIAL_EXCHANGE_RATE;
    }

    /// @dev calculate the cash on hand by removing locked funds from the total xETH balance
    /// @notice this balanceOf call will include any lockedFunds,
    /// @notice as the locked funds are also in xETH
    uint256 cashMinusLocked = xETH.balanceOf(address(this)) - lockedFunds;

    /// @dev return the exchange rate by dividing the cash on hand by the total supply
    return (cashMinusLocked * BASE_UNIT) / _totalSupply;
}

exchangeRate() will return 0 if totalSupply() > 0 and totalSupply() > (xETH.balanceOf(address(this)) - lockedFunds) * BASE_UNIT. If this is the case, then stake()

function stake(uint256 xETHAmount) external drip returns (uint256) {
    /// @dev calculate the amount of wxETH to mint
    uint256 mintAmount = previewStake(xETHAmount);

    /// @dev transfer xETH from the user to the contract
    xETH.safeTransferFrom(msg.sender, address(this), xETHAmount);

    /// @dev emit event
    emit Stake(msg.sender, xETHAmount, mintAmount);

    /// @dev mint the wxETH to the user
    _mint(msg.sender, mintAmount);

    return mintAmount;
}

will revert in its call to previewStake()

function previewStake(uint256 xETHAmount) public view returns (uint256) {
    /// @dev if xETHAmount is 0, revert.
    if (xETHAmount == 0) revert AmountZeroProvided();

    /// @dev calculate the amount of wxETH to mint before transfer
    return (xETHAmount * BASE_UNIT) / exchangeRate();
}

from division by zero.

Stake 3 xETH. Let drip until balance is 5 xETH. Exchange rate is now 1.666...e18. This causes a rounding error in stake such that if 6e18 xETH is staked 1.8e18 + 1 wxETH is returned. Unstake all and balance is 0 but totalSupply is 1.

Assessed type

call/delegatecall

d3e4 commented 1 year ago

Sorry about this rushed submission. This is invalid.

c4-sponsor commented 1 year ago

vaporkane requested judge review

c4-sponsor commented 1 year ago

vaporkane marked the issue as disagree with severity

c4-judge commented 1 year ago

kirk-baird marked the issue as unsatisfactory: Invalid