Malicious user can flash loan to inflate their voting balance
Summary
The issue allows users to potentially inflate their voting power within a single block, bypassing intended flash loan protection.
The problem stems from inconsistent implementation of a safety check across different functions that calculate voting power. While the balanceOfNFT function includes a check to prevent newly transferred tokens from having voting power in the same block, this check is absent in the balanceOfNFTAt and _balanceOfNFT functions. As a result, protocols using these latter functions to determine voting power could be susceptible to flash loan attacks.
The balanceOfNFT function checks if the ownership has changed in the current block and returns zero to ensure that newly transfered tokens have zero voting power, preventing flash loan attacks as can be seen from the comments in the _transferFrom function below on lines 334 and 335 where ownership_change[_tokenId] is set to the current block number.
As a result, Velocimeter or an external protocol calling balanceOfToken and balanceOfTokenAt external functions to find voting balance will return different voting balances for the same _tokenId depending on which function they called.
The function getVotes uses the _balanceOfNFT function to determine the current voting power of an account. It is now possible that any protocol using the getVotes function will not be protected from flash loans inflating the voting power.
R-Nemes
High
Malicious user can flash loan to inflate their voting balance
Summary
The issue allows users to potentially inflate their voting power within a single block, bypassing intended flash loan protection.
The problem stems from inconsistent implementation of a safety check across different functions that calculate voting power. While the
balanceOfNFT
function includes a check to prevent newly transferred tokens from having voting power in the same block, this check is absent in thebalanceOfNFTAt
and_balanceOfNFT
functions. As a result, protocols using these latter functions to determine voting power could be susceptible to flash loan attacks.Vulnerability Detail
v4-contracts/contracts/VotingEscrow.sol
https://github.com/sherlock-audit/2024-06-velocimeter/blob/main/v4-contracts/contracts/VotingEscrow.sol#L1031-L1034?plain=1
The
balanceOfNFT
function checks if the ownership has changed in the current block and returns zero to ensure that newly transfered tokens have zero voting power, preventing flash loan attacks as can be seen from the comments in the_transferFrom
function below on lines334
and335
whereownership_change[_tokenId]
is set to the current block number.v4-contracts/contracts/VotingEscrow.sol
https://github.com/sherlock-audit/2024-06-velocimeter/blob/main/v4-contracts/contracts/VotingEscrow.sol#L315-L342?plain=1
However, this check is not present in the functions
balanceOfNFTAt
or_balanceOfNFT
.v4-contracts/contracts/VotingEscrow.sol
https://github.com/sherlock-audit/2024-06-velocimeter/blob/main/v4-contracts/contracts/VotingEscrow.sol#L1036-L1038?plain=1
v4-contracts/contracts/VotingEscrow.sol
https://github.com/sherlock-audit/2024-06-velocimeter/blob/main/v4-contracts/contracts/VotingEscrow.sol#L1017-L1029?plain=1
As a result, Velocimeter or an external protocol calling
balanceOfToken
andbalanceOfTokenAt
external functions to find voting balance will return different voting balances for the same_tokenId
depending on which function they called.The function
getVotes
uses the_balanceOfNFT
function to determine the current voting power of an account. It is now possible that any protocol using thegetVotes
function will not be protected from flash loans inflating the voting power.v4-contracts/contracts/VotingEscrow.sol
https://github.com/sherlock-audit/2024-06-velocimeter/blob/main/v4-contracts/contracts/VotingEscrow.sol#L1292-L1304?plain=1
Impact
Users are able to inflate their voting power within a single block.
Code Snippet
POC: Add the following test file to the test suite
Console Output
Tool used
Forge
Manual Review
Recommendation
Implement the flashloan protection in the
_balanceOfNFT
function by adding this following line to the beginning of the function.if (ownership_change[_tokenId] == block.number) return 0;
Duplicate of #286