Open sherlock-admin2 opened 8 months ago
1 comment(s) were left on this issue during the judging contest.
takarez commented:
seem valid; high(5)
The protocol team fixed this issue in PR/commit https://github.com/Tapioca-DAO/Tapioca-bar/pull/376.
0xadrii
high
Withdrawing to other chain when exercising options won’t work as expected, leading to DoS
Summary
Withdrawing to another chain when exercising options will always fail because the implemented functionality does not bridge the tokens exercised in the option, and tries to perform a regular cross-chain call instead.
Vulnerability Detail
Tapioca incorporates a DAO Share Options (DSO) program where users can lock USDO in order to obtain TAP tokens at a discounted price.
In order to exercise their options, users need to execute a compose call with a message type of
MSG_TAP_EXERCISE
, which will trigger theUsdoOptionReceiverModule
'sexerciseOptionsReceiver()
function.When exercising their options, users can decide to bridge the obtained TAP tokens into another chain by setting the
msg_.withdrawOnOtherChain
totrue
:As the code snippet shows,
exerciseOptionsReceiver()
will perform mainly 2 steps:_options.target.exerciseOption()
. This will make USDO tokens serving as a payment for thetapOft
tokens be transferred from the user, and in exchange the corresponding optiontapOft
tokens will be transferred to the USDO contract so that they can later be transferred to the user.TAP tokens will be sent to the user. This can be done in two ways:
msg_.withdrawOnOtherChain
asfalse
), thetapOft
tokens will simply be transferred to the_options.from
address, succesfully exercising the optionOn the other hand, if the user decides to bridge the exercised option, the internal
_sendPacket()
function will be triggered, which will perform a call via LayerZero to the destination chain:The problem with the approach followed when users want to bridge the exercised options is that the contract will not actually bridge the exercised
tapOft
tokens by calling thetapOft
'ssendPacket()
function (which is the actual way by which the token can be transferred cross-chain). Instead, the contract calls_sendPacket()
, a function that will try to perform a USDO cross-chain call (instead of atapOft
cross-chain call). This will make the_debit()
function inside_sendPacket()
be executed, which will try to burn USDO tokens from themsg.sender
:This leads to two possible outcomes:
msg.sender
(the LayerZero endpoint) has enoughamountSentLD
of USDO tokens to be burnt. In this situation, USDO tokens will be incorrectly burnt from the user, leading to a loss of balance for him. After this, the burnt USDO tokens will be bridged. This outcome greatly affect the user in two ways:tapOft
tokens remain stuck forever in the USDO contract because they are never actually bridgedmsg.sender
(LayerZero endpoint) does not have enoughamountSentLD
of USDO tokens to be burnt. In this case, an error will be thrown and the whole call will revert, leading to a DoSProof of Concept
The following poc shows how the function will be DoS’ed due to the sender not having enough USDO to be burnt. In order to execute the Poc, perform the following steps:
Remove the
_checkWhitelistStatus(OFTMsgCodec.bytes32ToAddress(msg_.lzSendParams.sendParam.to));
line inUsdoOptionReceiverModule.sol
'sexerciseOptionsReceiver()
function (it is wrong and related to another vulnerability)Paste the following code in
Tapioca-bar/test/Usdo.t.sol
:Run the poc with the following command, inside the Tapioca-bar repo:
forge test --mt testVuln_exercise_option
We can see how the "ERC20: burn amount exceeds balance" error is thrown due to the issue mentioned in the report.
Impact
High. As demonstrated, two critical outcomes might affect the user:
tapOft
funds will remain stuck forever in the USDO contract and USDO will be incorrectly burnt frommsg.sender
Code Snippet
https://github.com/sherlock-audit/2024-02-tapioca/blob/main/Tapioca-bar/contracts/usdo/modules/UsdoOptionReceiverModule.sol#L120-L121
https://github.com/sherlock-audit/2024-02-tapioca/blob/main/Tapioca-bar/contracts/usdo/modules/UsdoOptionReceiverModule.sol#L149-L159
Tool used
Manual Review, foundry
Recommendation
If users decide to bridge their exercised tapOft, the sendPacket() function incorporated in the tapOft contract should be used instead of UsdoOptionReceiverModule’s internal _sendPacket() function, so that the actual bridged asset is the tapOft and not the USDO.