Open code423n4 opened 2 years ago
While I would be surprised by any realistic scenario of a smart contract that would implement onERC721Received
and not return the specified selector, I have to acknowledge that technically we can get multiple false positives, especially when dealing with contracts that have a fallback
function .
That said, to me that was not sufficient to constitute a valid finding.
However, I went and checked the EIP-721 and the standard definition of safeTransferFrom
, which can be verified here:
https://eips.ethereum.org/EIPS/eip-721
According to the standard the function must check for the return value, and for that reason I believe the finding to be valid.
While I would be surprised if this causes any issue in the real world, because the function is non-conformant to the standard and technically a loss can happen because of it, I believe Medium Severity to be appropriate
To confirm, I doubt any meaningful loss will ever happen.
However this finding is basically: "Incorrect implementation of onERC721Received
" and it's a valid finding at that
Lines of code
https://github.com/code-423n4/2022-05-velodrome/blob/7fda97c570b758bbfa7dd6724a336c43d4041740/contracts/contracts/VotingEscrow.sol#L462-L472 https://github.com/code-423n4/2022-05-velodrome/blob/7fda97c570b758bbfa7dd6724a336c43d4041740/contracts/contracts/VotingEscrow.sol#L378-L406
Vulnerability details
Impact
veNFTs may be sent to contracts that cannot handle them, and therefore all rewards and voting power, as well as the underlying are locked forever
Proof of Concept
The original code had the following warning:
https://github.com/solidlyexchange/solidly/blob/eac1ef02ca8736138b25062c02646ea43d5bb6e4/contracts/ve.sol#L143-L144
The minting code, which creates the locks, does not do this check:
https://github.com/code-423n4/2022-05-velodrome/blob/7fda97c570b758bbfa7dd6724a336c43d4041740/contracts/contracts/VotingEscrow.sol#L462-L472
Once a lock is already minted, if it's transfered to a contract, the code does attempt to check for the issue:
https://github.com/code-423n4/2022-05-velodrome/blob/7fda97c570b758bbfa7dd6724a336c43d4041740/contracts/contracts/VotingEscrow.sol#L378-L406
While the transfer function does in fact execute
onERC721Received
, it doesn't actually do a check of thebytes4
variable - it only checks for a non-zero length. The ERC721 standard says that for the function call to be valid, it must return thebytes4
function selector, otherwise it's invalid. If a user of the escrow system uses a contract that is attempting to explicitly reject NFTs by returning zero in itsonERC721Received()
function, theVotingEscrow
will interpret that response as success and will transfer the NFT, potentially locking it forever. If the lock is minted to a contract, no checks are done, and the NFT can be locked forever.Tools Used
Code inspection
Recommended Mitigation Steps
Call
onERC721Received()
in_mint()
and ensure that the return value equalsIERC721Receiver.onERC721Received.selector
in both_mint()
andsafeTransferFrom()