The supply storage variable can be inflated due to the merge function not properly updating it. Any function that would rely on this would have unexpected behavior.
Vulnerability Detail
When a user goes to split there NFT in VotingEscrow it will:
First do some checks if the user is allowed and checks their input variables
Call _remove_from to remove the old data from the initial NFT and supply
Call _mint to create the new NFT
Finally call _deposit_for to update for the new NFT
function split(uint _tokenId,uint amount) external {
// check permission and vote
require(attachments[_tokenId] == 0 && !voted[_tokenId], "attached");
require(_isApprovedOrOwner(msg.sender, _tokenId));
require(!blockedSplit[_tokenId],"split blocked");
// save old data and totalWeight
address _to = idToOwner[_tokenId];
LockedBalance memory _locked = locked[_tokenId];
uint end = _locked.end;
uint value = uint(int256(_locked.amount));
require(value > amount,"amount > value");
// save end
uint unlock_time = end;
require(unlock_time > block.timestamp, 'Can only lock until time in the future');
require(unlock_time <= block.timestamp + MAXTIME, 'Voting lock can be 52 weeks max');
// remove old data
_remove_from(_tokenId, amount, unlock_time, _locked);
// mint
++tokenId;
uint _newTokenId = tokenId;
_mint(_to, _newTokenId);
_deposit_for(_newTokenId, amount, unlock_time, locked[_newTokenId], DepositType.SPLIT_TYPE);
}
When a user goes to merge their two NFTs if will do the following:
First do some checks if the user is allowed and checks their from and to NFT data
Call _burn to remove the _from NFT
Finally call _deposit_for to update for the _to NFT
function merge(uint _from, uint _to) external {
require(attachments[_from] == 0 && !voted[_from], "attached");
require(_from != _to);
require(_isApprovedOrOwner(msg.sender, _from));
require(_isApprovedOrOwner(msg.sender, _to));
LockedBalance memory _locked0 = locked[_from];
LockedBalance memory _locked1 = locked[_to];
uint value0 = uint(int256(_locked0.amount));//locked0 amount
uint end = _locked0.end >= _locked1.end ? _locked0.end : _locked1.end;
locked[_from] = LockedBalance(0, 0);
_checkpoint(_from, _locked0, LockedBalance(0, 0));
_burn(_from);//smell burning right one and is end times updating correctly
_deposit_for(_to, value0, end, _locked1, DepositType.MERGE_TYPE);
}
The major difference between the two is that merge does not call _remove_from which is not directly required. However, there is a part of _remove_from that is important, which is updating supply. Thus, merge is only adding to the supply variable with _deposit_for. Leading to these two function flows to not be asymmetric when updating the supply. This would allow anyone to easily inflate the supply variable by:
0xNazgul
Medium
VotingEscrow
Can Have Inflated SupplySummary
The
supply
storage variable can be inflated due to themerge
function not properly updating it. Any function that would rely on this would have unexpected behavior.Vulnerability Detail
When a user goes to
split
there NFT inVotingEscrow
it will:_remove_from
to remove the old data from the initial NFT and supply_mint
to create the new NFTFinally call
_deposit_for
to update for the new NFTWhen a user goes to merge their two NFTs if will do the following:
from
andto
NFT data_burn
to remove the_from
NFTFinally call
_deposit_for
to update for the_to
NFTThe major difference between the two is that
merge
does not call_remove_from
which is not directly required. However, there is a part of_remove_from
that is important, which is updatingsupply
. Thus,merge
is only adding to thesupply
variable with_deposit_for
. Leading to these two function flows to not be asymmetric when updating thesupply
. This would allow anyone to easily inflate thesupply
variable by:split
merge
Impact
Add this POC to VotingEscrow.t.sol:
Code Snippet
VotingEscrow.sol#L1195
Tool used
Manual Review
Recommendation
Consider properly updated the
supply
storage variable when merging two NFT tokens.Duplicate of #214