You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository has been archived by the owner on Jun 2, 2024. It is now read-only.
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
Founders won't get the ownership percentage they deserve when reservedUntilTokenId >= 100
Summary
For adding funders of a token the function _addFounders() is used. This function will set which tokens each founder will receive depending in their founderPct by storing the founder's wallet in the tokenRecipient.
// Used to store the base token id the founder will recieveuint256 baseTokenId = reservedUntilTokenId;
// For each token to vest:for (uint256 j; j < founderPct; ++j) {
// Get the available token id
baseTokenId =_getNextTokenId(baseTokenId);
// Store the founder as the recipient
tokenRecipient[baseTokenId] = newFounder;
emitMintScheduled(baseTokenId, founderId, newFounder);
// Update the base token id
baseTokenId = (baseTokenId + schedule) %100;
}
}
The baseTokenId is selected by using the function _getNextTokenId().
function _getNextTokenId(uint256_tokenId) internalviewreturns (uint256) {
unchecked {
while (tokenRecipient[_tokenId].wallet !=address(0)) {
_tokenId = (++_tokenId) %100;
}
return _tokenId;
}
}
Later, when the tokens are minted, they will be minted to a founder depending in the tokenId to be minted and the tokenRecipient that is fulfilled in _addFounders() as shown before.
function _isForFounder(uint256_tokenId) privatereturns (bool) {
// Get the base token iduint256 baseTokenId = _tokenId %100;
// If there is no scheduled recipient:if (tokenRecipient[baseTokenId].wallet ==address(0)) {
returnfalse;
// Else if the founder is still vesting:
} elseif (block.timestamp< tokenRecipient[baseTokenId].vestExpiry) {
// Mint the token to the founder_mint(tokenRecipient[baseTokenId].wallet, _tokenId);
returntrue;
// Else the founder has finished vesting:
} else {
// Remove them from future lookupsdelete tokenRecipient[baseTokenId];
returnfalse;
}
}
As we can see in _isForFounder(), all the possible values for tokenRecipient should be between 0-99, since the baseTokenId < 100. The problem is that the first baseTokenId used for storing the founders information in tokenRecipient in _addFounders() will be wrongly computed whenever the reservedUntilTokenId >= 100.
Vulnerability Detail
Note that this vulnerability doesn't arise in the first deployment since reservedUntilTokenId is not yet assigned in the initialize before calling _addFounders() and we have variable shadowing. However, the issue will arise when the founders are updated by calling updateFounders().
Here is an example where the founder won't get any tokens allocated due to the miscalculation of the first baseTokenId when reservedUntilTokenId >= 100, let's say that reservedUntilTokenId == 200 just to have an example.
There is a token with some founders.
Founders are updated.
2.1. All founders info is cleaned.
2.2. The new founder is Alice, who has a founderPct == 1, meaning that for every 100 tokens minted she should receive 1.
2.3. _addFounders() is called with Alice's info.
2.3.1. As we can see, baseTokenId = reservedUntilTokenId, so the function _getNextTokenId() will be called with 200 as a parameter. The function will return 200 since the while won't be entered in the first iteration.
2.3.2. tokenRecipient[200] = aliceFounderInfo will be set. As she only has founderPct == 1, the function _addFounders() ends.
Whenever a new token is minted, the function _isForFounder() will check if the token should be minted to Alice, but she won't receive any tokens since the baseTokenId < 100 always in that function, and her assigned slot is tokenRecipient[200].
Impact
Each time that _addFounders() is called in a Token where reservedUntilTokenId >= 100 the first slot assigned in the tokenRecipient will be wrongly set, meaning that the first founder to be added will receive less Tokens than expected. In total, founders will receive 1% less tokens than expected. In cases where the founders are supposed to receive a small percentage of the NFTs the impact will be bigger. If the founders are expected to receive 1% of the tokens, they won't receive any tokens.
Fix the _getNextTokenId() function so that the tokenId returned is always between 0-99 as expected. A possible fix would be changing the while for a do while.
sherlock-admin
changed the title
Real Hazelnut Tiger - Founders won't get the ownership percentage they deserve when reservedUntilTokenId >= 100
xAriextz - Founders won't get the ownership percentage they deserve when reservedUntilTokenId >= 100Dec 13, 2023
Sign up for freeto subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Labels
DuplicateA valid issue that is a duplicate of an issue with `Has Duplicates` labelHighA valid High severity issueRewardA payout will be made for this issue
xAriextz
high
Founders won't get the ownership percentage they deserve when
reservedUntilTokenId >= 100
Summary
For adding funders of a token the function
_addFounders()
is used. This function will set which tokens each founder will receive depending in theirfounderPct
by storing the founder's wallet in thetokenRecipient
.The
baseTokenId
is selected by using the function_getNextTokenId()
.Later, when the tokens are minted, they will be minted to a founder depending in the
tokenId
to be minted and thetokenRecipient
that is fulfilled in_addFounders()
as shown before.As we can see in
_isForFounder()
, all the possible values fortokenRecipient
should be between 0-99, since thebaseTokenId < 100
. The problem is that the firstbaseTokenId
used for storing the founders information intokenRecipient
in_addFounders()
will be wrongly computed whenever thereservedUntilTokenId >= 100
.Vulnerability Detail
Note that this vulnerability doesn't arise in the first deployment since
reservedUntilTokenId
is not yet assigned in theinitialize
before calling_addFounders()
and we have variable shadowing. However, the issue will arise when the founders are updated by callingupdateFounders()
.Here is an example where the founder won't get any tokens allocated due to the miscalculation of the first
baseTokenId
whenreservedUntilTokenId >= 100
, let's say thatreservedUntilTokenId == 200
just to have an example.2.1. All founders info is cleaned.
2.2. The new founder is Alice, who has a
founderPct == 1
, meaning that for every 100 tokens minted she should receive 1.2.3.
_addFounders()
is called with Alice's info.2.3.1. As we can see,
baseTokenId = reservedUntilTokenId
, so the function_getNextTokenId()
will be called with200
as a parameter. The function will return200
since thewhile
won't be entered in the first iteration.2.3.2.
tokenRecipient[200] = aliceFounderInfo
will be set. As she only hasfounderPct == 1
, the function_addFounders()
ends._isForFounder()
will check if the token should be minted to Alice, but she won't receive any tokens since thebaseTokenId < 100
always in that function, and her assigned slot istokenRecipient[200]
.Impact
Each time that
_addFounders()
is called in a Token wherereservedUntilTokenId >= 100
the first slot assigned in thetokenRecipient
will be wrongly set, meaning that the first founder to be added will receive less Tokens than expected. In total, founders will receive 1% less tokens than expected. In cases where the founders are supposed to receive a small percentage of the NFTs the impact will be bigger. If the founders are expected to receive 1% of the tokens, they won't receive any tokens.Code Snippet
https://github.com/sherlock-audit/2023-09-nounsbuilder/blob/main/nouns-protocol/src/token/Token.sol#L120-L194
Tool used
Manual Review
Recommendation
Fix the
_getNextTokenId()
function so that thetokenId
returned is always between 0-99 as expected. A possible fix would be changing thewhile
for ado while
.Duplicate of #42
The text was updated successfully, but these errors were encountered: