sherlock-audit / 2023-06-bond-judging

3 stars 3 forks source link

bin2chen - receiver can prevent exercise then force OptionToken to expire #70

Closed github-actions[bot] closed 1 year ago

github-actions[bot] commented 1 year ago

bin2chen

high

receiver can prevent exercise then force OptionToken to expire

Summary

When executing exercise(), the corresponding quoteToken or payoutToken will be paid to receiver immediately. If receiver is in the token blacklist, then exercise() will always fail, causing the optionToken to be forced to expire User loses funds

Vulnerability Detail

When exercise(), the user needs to transfer the corresponding token to receiver

    function exercise(
        FixedStrikeOptionToken optionToken_,
        uint256 amount_
    ) external override nonReentrant {
.....
        // If not receiver, require payment
        if (msg.sender != receiver) {
            // If call, transfer in quote tokens equivalent to the amount of option tokens being exercised * strike price
            // If put, transfer in payout tokens equivalent to the amount of option tokens being exercised
            if (call) {
                // Calculate protocol fee
                uint256 fee = (quoteAmount * protocolFee) / FEE_DECIMALS;
                fees[quoteToken] += fee;

                // Transfer proceeds from user
                // Check balances before and after transfer to ensure that the correct amount was transferred
                // @-audit this does enable potential malicious option tokens that can't be exercised
                // However, we view it as a "buyer beware" situation that can handled on the front-end
                uint256 startBalance = quoteToken.balanceOf(address(this));
                quoteToken.safeTransferFrom(msg.sender, address(this), quoteAmount);
                uint256 endBalance = quoteToken.balanceOf(address(this));
                if (endBalance < startBalance + quoteAmount)
                    revert Teller_UnsupportedToken(address(quoteToken));

                // Transfer proceeds minus fee to receiver
@>              quoteToken.safeTransfer(receiver, quoteAmount - fee);
            } else {
                // Calculate protocol fee (in payout tokens)
                uint256 fee = (amount_ * protocolFee) / FEE_DECIMALS;
                fees[payoutToken] += fee;

                // Transfer proceeds from user
                // Check balances before and after transfer to ensure that the correct amount was transferred
                // @-audit this does enable potential malicious option tokens that can't be exercised
                // However, we view it as a "buyer beware" situation that can handled on the front-end
                uint256 startBalance = payoutToken.balanceOf(address(this));
                payoutToken.safeTransferFrom(msg.sender, address(this), amount_);
                uint256 endBalance = payoutToken.balanceOf(address(this));
                if (endBalance < startBalance + amount_)
                    revert Teller_UnsupportedToken(address(payoutToken));

                // Transfer proceeds minus fee to receiver
@>              payoutToken.safeTransfer(receiver, amount_ - fee);
            }
        }

        // Burn option tokens
        optionToken.burn(msg.sender, amount_);

        if (call) {
            // Transfer payout tokens to user
            payoutToken.safeTransfer(msg.sender, amount_);
        } else {
            // Transfer quote tokens to user
            quoteToken.safeTransfer(msg.sender, quoteAmount);
        }
    }

This way receiver can maliciously block exercise until it expires

Example.

  1. alice deploy an optionToken = {receiver = "alice" , call = false, payoutToken = USDC, quoteToken = Token_A}
  2. alice sells the optionToken
  3. before eligible time is up, alice maliciously enters USDC blacklist Note: also can specify a blacklisted address of your own when deploy optionToken, that it is difficult for the user who bought it to notice.
  4. After the eligible time, the user goes to perform exercise, because of the blacklist, will always revert
  5. until after the expiration alice executes reclaim to get back the quoteToken. Note: quoteToken is not blacklisted, so it can be reclaim

Impact

receiver forces OptionToken to expire, causing the user to lose the funds

Code Snippet

https://github.com/sherlock-audit/2023-06-bond/blob/main/options/src/fixed-strike/FixedStrikeOptionTeller.sol#L378

Tool used

Manual Review

Recommendation

Add claimsToken[token][user] mechanism, in exercise() claimsToken[token][user]+=amount, not directly transfer token to receiver, receiver need to claim themselves

Duplicate of #81