Platform: Code4rena
Start Date: 21/02/2024
Pot Size: $200,000 USDC
Total HM: 22
Participants: 36
Period: 19 days
Judge: Trust
Total Solo HM: 12
Id: 330
League: ETH
Rank: 28/36
Findings: 1
Award: $249.82
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: serial-coder
Also found by: 0x11singh99, Jorgect, Mrxstrange, Rhaydden, josephdara, nonseodion, unix515
249.8161 USDC - $249.82
_callOptionalReturn()
is not reverted and returns false
even if the low level call returned false
.function _callOptionalReturn( address token, bytes memory data ) internal returns (bool call) { ( bool success, bytes memory returndata ///<-------------- @audit ) = token.call( data ); Â Â Â Â bool results = returndata.length == 0 || abi.decode( ///<------------ @audit returndata, (bool) ); ... call = success && results ///<--------------- @audit && token.code.length > 0; }
_callOptionalReturn()
returned false
.
_safeTransfer()
_safeTransferFrom()
_safeApprove()
function _safeTransfer( address _token, address _to, uint256 _value ) internal { _callOptionalReturn( ///<-------------- @audit _token, abi.encodeWithSelector( IERC20.transfer.selector, _to, _value ) ); }
So even if the input _token
's transfer()
returned false
, the following functions are not reverted and works as if calling were successful .
_safeTransfer()
_safeTransferFrom()
_safeApprove()
This may result in some funds being recorded as transferred in the ledger but not actually transferred.
As reference, SafeERC20#_callOptionalReturn()
of openzeppelin is reverted with SafeERC20FailedOperation
error when the low level call returned false
.
function _callOptionalReturn(IERC20 token, bytes memory data) private { bytes memory returndata = address(token).functionCall(data); if (returndata.length != 0 && !abi.decode(returndata, (bool))) { ///<---- @audit revert SafeERC20FailedOperation(address(token)); } }
_poolToken
's transfer()
returns false
(not reverted) if it fell in some circumstances.Bob
has some amount of poolToken
.Bob
calls WiseLending#withdrawExactAmount()
to withdraw some amount of _poolToken
.
WiseLending#withdrawExactAmount()
updates the size of pool with the following internal call flows.
_handleWithdrawAmount()
->_coreWithdrawToken()
->_coreWithdrawBare()
function _coreWithdrawBare( uint256 _nftId, address _poolToken, uint256 _amount, uint256 _shares ) private { _updatePoolStorage( _poolToken, _amount, ///<---------------- @audit _shares, _decreaseTotalPool, ///<---------------- @audit _decreasePseudoTotalPool, ///<---------------- @audit _decreaseTotalDepositShares ); ... }
WiseLending#withdrawExactAmount()
then calls TransferHelper#_safeTransfer()
to transfer funds to Bob
finally.
function withdrawExactAmount( uint256 _nftId, address _poolToken, uint256 _withdrawAmount ) external syncPool(_poolToken) healthStateCheck(_nftId) returns (uint256) {Â Â ... _safeTransfer( ///<------------------- @audit _poolToken, msg.sender, _withdrawAmount ); return withdrawShares; }
Even if _poolToken
's transfer()
returned false
, TransferHelper#_safeTransfer()
is not reverted and then WiseLending#withdrawExactAmount()
works as if calling were successful.
So the pool size of poolToken
was already updated, but some amount (_withdrawAmount
) of _poolToken
were not really transferred to Bob
.
Manual Review
Please update CallOptionaReturn#_callOptionalReturn()
as following.
function _callOptionalReturn( address token, bytes memory data ) internal returns (bool call) { ( bool success, bytes memory returndata ) = token.call( data ); bool results = returndata.length == 0 || abi.decode( returndata, (bool) ); -- if (success == false) { ++ if (success == false || results == false) { revert(); } call = success && results && token.code.length > 0; }
Invalid Validation
#0 - c4-pre-sort
2024-03-17T14:30:34Z
GalloDaSballo marked the issue as duplicate of #212
#1 - c4-pre-sort
2024-03-18T16:27:13Z
GalloDaSballo marked the issue as sufficient quality report
#2 - c4-judge
2024-03-26T14:31:40Z
trust1995 marked the issue as satisfactory
#3 - c4-judge
2024-03-26T19:10:53Z
trust1995 changed the severity to 2 (Med Risk)