30951 - [SC - Low] Incorrect ownerOf implementation makes veALCX n...
Submitted on May 8th 2024 at 21:43:15 UTC by @marchev for Boost | Alchemix
Report ID: #30951
Report type: Smart Contract
Report severity: Low
Target: https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/VotingEscrow.sol
Impacts:
Contract fails to deliver promised returns, but doesn't lose value
Description
Brief/Intro
The ERC721 standard requires that ownership queries for NFTs assigned to the zero address must revert, indicating invalid/non-existent NFT. However, the VotingEscrow#ownerOf()
implementation in veALCX returns address(0)
instead, violating this standard. This deviation risks causing functional and security issues in external systems that integrate with veALCX and rely on standard ERC721 behavior.
Vulnerability Details
The ERC721 specification states the following:
This means that every compliant ERC721 token must implement the ownerOf()
function so that it throws if a _tokenId
is passed that belongs to address(0)
. However, VotingEscrow
's implementation looks like this:
For a non-existent _tokenId
, this implementation will return address(0)
instead of reverting. This behavior is not compliant with the ERC721 specification.
This non-compliance can lead to serious compatibility issues with external integrations that rely on standard ERC721 behavior. Applications expecting a revert when querying ownership of an invalid NFT might instead receive a non-reverting response, potentially resulting in erroneous processing or security vulnerabilities in systems interacting with veALCX.
Impact Details
Non-compliance with the ERC721 standard in veALCX's implementation could lead to interoperability issues and security vulnerabilities in external systems that rely on standard behavior for processing ownership data. This could result in erroneous executions and potential breaches in decentralized applications interfacing with veALCX.
To better demonstrate the potential impact of this vulnerability, I have developed a PoC that showcases how this issue could lead to a permanent loss of NFTs. Please see it in the Proof of Concept section below.
The designation of the vulnerability as Medium severity is justified given its high potential impact on the disruption of integrations with other protocols. Similar vulnerabilities have been reported and classified with medium severity, underscoring the consistency in assessing the potential risks associated with non-compliance to the ERC721 standard. Examples:
https://solodit.xyz/issues/m-19-holographerc721approve-not-eip-721-compliant-code4rena-holograph-holograph-contest-git
https://solodit.xyz/issues/m-02-violation-of-erc-721-standard-in-verbstokentokenuri-implementation-code4rena-collective-collective-git
https://solodit.xyz/issues/m-24-mintableincentivizederc721-and-ntoken-do-not-comply-with-erc721-breaking-composability-code4rena-paraspace-paraspace-contest-git
References
Problematic implementation:
https://github.com/alchemix-finance/alchemix-v2-dao/blob/f1007439ad3a32e412468c4c42f62f676822dc1f/src/VotingEscrow.sol#L230-L232
Docs references which state that ERC721 compliance is expected:
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/src/VotingEscrow.sol#L16
https://github.com/alchemix-finance/alchemix-v2-dao/blob/main/CONTRACTS.md
Proof of Concept
Let's examine a hypothetical scenario:
Context: LiquidityHub is a fictional platform where users can stake their veALCX. In return, they receive enhanced rewards. The LiquidityHub manages these veALCXes, allowing users to stake on behalf of the hub and later redeem them. Upon redemption, LiquidityHub deducts an exit service fee before returning the remaining balance to the user.
Here’s how the vulnerability could result in Alice's veALCX becoming irretrievably stuck:
Alice uses the
VotingEscrow
contract to create a veALCX lock on behalf of theLiquidityHub
, which assigns the ownership of the veALCX to the LiquidityHub.Alice calls the
stake()
function on theLiquidityHub
contract with the veNFT's token ID. The function verifies that the LiquidityHub is the owner before marking it as staked.When Alice wishes to redeem, she calls
startCooldown()
and after the cooldown period expires triggers theredeem()
function in the LiquidityHub.The LiquidityHub calls
withdraw()
in theVotingEscrow
to "burn" the veALCX and withdraw the BPT tokens.The LiquidityHub checks the burn status by expecting the
ownerOf()
call to revert, indicating the token no longer exists.If
ownerOf()
does not revert and returns any address, the LiquidityHub reverts the transaction, signaling an unsuccessful burn. IfownerOf()
reverts, the LiquidityHub clears its record of the token, recognizing the successful redemption and burn.
The following coded PoC code illustrates the potential impact and how the veALCX can remain stuck due to a failed validation of its burned status.
Add the following contract at the end of VotingEscrow.t.sol
:
Also, add the following test case to VotingEscrowTest
:
Make sure the following entries are updated in Makefile
:
Run the PoC via make test_file_test
This example is streamlined to focus on the core issue, leaving out many details of the LiquidityHub’s operations for clarity.
Last updated