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
First founder receives less token allocation than expected
See Token.solhere
See Token.sol::_getNextTokenId()here
See Token.sol::_isForFounder()here
Summary
If reservedUntilTokenId is set to any three digit number (e.g. 100); the first founder in the founders array loses 1% of token supply during their vesting period.
Vulnerability Detail
Token.sol::_addFounders() takes in two parameters; _foundersand reservedUntilTokenId. The function loops through each founder in the _founders array assigning tokens based on percentages due.
For each founder the starting point for assigning their tokens is reservedUntilTokenId:
uint256 baseTokenId = reservedUntilTokenId;
For all except the first token checked in _getNextTokenId, see here, the input token ID is modulated.
This means that _getNextTokenId returns an unmodulated token ID from the tokenRecipient for all except the very first token checked; unmodulated meaning a value between0 - 99.
This returned token is then assigned to that user in the tokenRecipient mapping in _addFounders
tokenRecipient[baseTokenId] = newFounder;
During the minting phase; assignments in the tokenRecipient mapping are checked to determine, via `Token.sol::_isForFounder()', whether a token can be used for an auction or should be minted to a founder; see here.
The problem with allocating the first token to the initial founder arises because the check in _getNextTokenId does not include modulation. Consequently, this leads to the function returning false for the condition:
while (tokenRecipient[_tokenId].wallet !=address(0)) {
This allows any provided value to be directly returned and stored in tokenRecipient. This behavior does not present a problem for token IDs that are single or double-digit numbers. H
owever, if this value has three digits i.e. reservedUntilTokenId = 100; it will be stored unchanged in the tokenRecipient mapping.
Impact
The first founder in the _founders array loses a significant amount of voting power and tokens. It will also throw the voting dynamics of the DAO into disarray.
Using as an example reservedUntilTokenId = 100 and the first founder allocation percentage of 1 ; the first founder should be entitled to 1% of all tokens minted from 100 onwards within their vesting period, e.g. Token IDs 100, 200, 300, 400, etc. However they would receive nothing.
Code Snippet
Proof of Concept
Add the following function to NounsBuilderTest.sol:
function test_firstFounderAllocationLoss() public {
uint256[] memory percentages =newuint256[](1);
address[] memory founders =newaddress[](percentages.length);
uint256[] memory vesting =newuint256[](percentages.length);
for (uint256 i =0; i < percentages.length; i++) {
// Set founders[0] percentage to 1%
percentages[i] =1;
founders[i] = founder;
vesting[i] =4weeks;
}
uint256 reservedUntilTokenId =100;
vm.prank(founder);
deployWithCustomFoundersAndReserveToken(founders, percentages, vesting, reservedUntilTokenId);
// unpause mints first token for auction// it should be 101 as 100 should be minted for founder
vm.prank(founder);
auction.unpause();
// assert first auction token is 100
(uint256tokenId, , , , , ) = auction.auction();
assertEq(tokenId, 100);
// Loop 500 times; we expect founder to get 1% i.e. 5 tokensfor (uint256 i =0; i <500; i++) {
vm.prank(address(auction));
token.mint();
}
// Assert founder receives no tokens out of first 500 mintsuint256 founderBalance = token.balanceOf(founder);
assertEq(founderBalance, 0);
}
Tool used
Foundry
Manual Review
Recommendation
In _getNextTokenId, modulate _tokenId before entering the while loop:
sherlock-admin
changed the title
Nutty Rouge Toad - First founder receives less token allocation than expected
Falconhoof - First founder receives less token allocation than expected
Dec 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
Falconhoof
high
First founder receives less token allocation than expected
See
Token.sol
hereSee
Token.sol::_getNextTokenId()
hereSee
Token.sol::_isForFounder()
hereSummary
If
reservedUntilTokenId
is set to any three digit number (e.g. 100); the first founder in the founders array loses 1% of token supply during their vesting period.Vulnerability Detail
Token.sol::_addFounders()
takes in two parameters;_founders
andreservedUntilTokenId
. The function loops through each founder in the_founders
array assigning tokens based on percentages due.For each founder the starting point for assigning their tokens is
reservedUntilTokenId
:For all except the first token checked in
_getNextTokenId
, see here, the input token ID is modulated.This means that
_getNextTokenId
returns anunmodulated
token ID from thetokenRecipient
for all except the very first token checked; unmodulated meaning a value between0 - 99
.This returned token is then assigned to that user in the
tokenRecipient
mapping in_addFounders
tokenRecipient[baseTokenId] = newFounder;
During the minting phase; assignments in the
tokenRecipient
mapping are checked to determine, via `Token.sol::_isForFounder()', whether a token can be used for an auction or should be minted to a founder; see here.The problem with allocating the first token to the initial founder arises because the check in
_getNextTokenId
does not include modulation. Consequently, this leads to the function returning false for the condition:This allows any provided value to be directly returned and stored in tokenRecipient. This behavior does not present a problem for token IDs that are single or double-digit numbers. H
owever, if this value has three digits
i.e. reservedUntilTokenId = 100
; it will be stored unchanged in thetokenRecipient
mapping.Impact
The first founder in the
_founders
array loses a significant amount of voting power and tokens. It will also throw the voting dynamics of the DAO into disarray.Using as an example
reservedUntilTokenId = 100
and the first founder allocation percentage of1
; the first founder should be entitled to1%
of all tokens minted from100
onwards within their vesting period,e.g. Token IDs 100, 200, 300, 400, etc
. However they would receive nothing.Code Snippet
Proof of Concept
Add the following function to NounsBuilderTest.sol:
Add the following test to Token.t.sol and run:
Tool used
Foundry
Manual Review
Recommendation
In
_getNextTokenId
, modulate_tokenId
before entering the while loop:Duplicate of #42
The text was updated successfully, but these errors were encountered: