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.
alice deploy an optionToken = {receiver = "alice" , call = false, payoutToken = USDC, quoteToken = Token_A}
alice sells the optionToken
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.
After the eligible time, the user goes to perform exercise, because of the blacklist, will always revert
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
Add claimsToken[token][user] mechanism, in exercise()claimsToken[token][user]+=amount, not directly transfer token to receiver, receiver need to claim themselves
bin2chen
high
receiver can prevent exercise then force OptionToken to expire
Summary
When executing
exercise()
, the correspondingquoteToken
orpayoutToken
will be paid toreceiver
immediately. Ifreceiver
is in thetoken
blacklist, thenexercise()
will always fail, causing theoptionToken
to be forced to expire User loses fundsVulnerability Detail
When
exercise()
, the user needs to transfer the corresponding token toreceiver
This way
receiver
can maliciously blockexercise
until it expiresExample.
optionToken
eligible
time is up, alice maliciously enters USDC blacklist Note: also can specify a blacklisted address of your own when deployoptionToken
, that it is difficult for the user who bought it to notice.eligible
time, the user goes to performexercise
, because of the blacklist, will always revertreclaim
to get back thequoteToken
. Note:quoteToken
is not blacklisted, so it can be reclaimImpact
receiver
forces OptionToken to expire, causing the user to lose the fundsCode 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, inexercise()
claimsToken[token][user]+=amount
, not directly transfer token toreceiver
,receiver
need to claim themselvesDuplicate of #81