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
The first founder is to be allocated less tokens than other founders.
Summary
Due to computational errors, the first founder is allocated less tokens than the other founders.
Vulnerability Detail
Token.sol#_addFounders function is as follows.
File: Token.sol
120: function _addFounders(IManager.FounderParams[] calldata_founders, uint256reservedUntilTokenId) internal {
121: // Used to store the total percent ownership among the founders122: uint256 totalOwnership;
123:
124: uint8 numFoundersAdded =0;
125:
126: unchecked {
127: // For each founder:128: for (uint256 i; i < _founders.length; ++i) {
129: // Cache the percent ownership130: uint256 founderPct = _founders[i].ownershipPct;
131:
132: // Continue if no ownership is specified133: if (founderPct ==0) {
134: continue;
135: }
136:
137: // Update the total ownership and ensure it's valid138: totalOwnership += founderPct;
139:
140: // Check that founders own less than 100% of tokens141: if (totalOwnership >99) {
142: revertINVALID_FOUNDER_OWNERSHIP();
143: }
144:
145: // Compute the founder's id146: uint256 founderId = numFoundersAdded++;
147:
148: // Get the pointer to store the founder149: Founder storage newFounder = founder[founderId];
150:
151: // Store the founder's vesting details152: newFounder.wallet = _founders[i].wallet;
153: newFounder.vestExpiry =uint32(_founders[i].vestExpiry);
154: // Total ownership cannot be above 100 so this fits safely in uint8155: newFounder.ownershipPct =uint8(founderPct);
156:
157: // Compute the vesting schedule158: uint256 schedule =100/ founderPct;
159:
160: // Used to store the base token id the founder will recieve161: uint256 baseTokenId = reservedUntilTokenId;
162:
163: // For each token to vest:164: for (uint256 j; j < founderPct; ++j) {
165: // Get the available token id166: baseTokenId =_getNextTokenId(baseTokenId);
167:
168: // Store the founder as the recipient169: tokenRecipient[baseTokenId] = newFounder;
170:
171: emitMintScheduled(baseTokenId, founderId, newFounder);
172:
173: // Update the base token id174: baseTokenId = (baseTokenId + schedule) %100;
175: }
176: }
177:
178: // Store the founders' details179: settings.totalOwnership =uint8(totalOwnership);
180: settings.numFounders = numFoundersAdded;
181: }
182: }
baseTokenId is initialized to reservedUntilTokenId and then it's value is determined by _getNextTokenId function in line 166.
The code of Token.sol#_getNextTokenId function is the following.
At the start time, the condition of line 188 does not hold. Thus the first baseTokenId value of the first founder will be equal to reservedUntilTokenId.
So the first baseTokenId value of the first founder will be over 100 when reservedUntilTokenId >= 100.
On the other hand, Token.sol#_isForFounder function is as follows.
File: Token.sol
263: function _isForFounder(uint256_tokenId) privatereturns (bool) {
264: // Get the base token id265: uint256 baseTokenId = _tokenId %100;
266:
267: // If there is no scheduled recipient:268: if (tokenRecipient[baseTokenId].wallet ==address(0)) {
269: returnfalse;
270:
271: // Else if the founder is still vesting:272: } elseif (block.timestamp< tokenRecipient[baseTokenId].vestExpiry) {
273: // Mint the token to the founder274: _mint(tokenRecipient[baseTokenId].wallet, _tokenId);
275:
276: returntrue;
277:
278: // Else the founder has finished vesting:279: } else {
280: // Remove them from future lookups281: delete tokenRecipient[baseTokenId];
282:
283: returnfalse;
284: }
285: }
From above, baseTokenId < 100 always hold.
Thus when reservedUntilTokenId >= 100, the first founder is to be allocated less tokens than other founders.
Example:
Suppose that _founders.length == 2, _founders[0].ownershipPct = 2, _founders[1].ownershipPct = 2 and reservedUntilTokenId == 100.
Then, as a result of _addFounders function, it is hold that tokenRecipient[100] = tokenRecipient[50] = _founders[0] and tokenRecipient[1] = tokenRecipient[51] = _founders[1].
Since 0 <= baseTokenId < 100 holds in _isForFounder function, tokenRecipient[100] will never be used and thus _founder[0] will be allocated tokens as half of the _founder[1].
Impact
The first founder is to be allocated less tokens than other founders.
In particular, when ownershipPct == 1 of the first founder, he will not be allocated a token at all.
sherlock-admin
changed the title
Bald Carob Swallow - The first founder is to be allocated less tokens than other founders.
dany.armstrong90 - The first founder is to be allocated less tokens than other founders.
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
dany.armstrong90
medium
The first founder is to be allocated less tokens than other founders.
Summary
Due to computational errors, the first founder is allocated less tokens than the other founders.
Vulnerability Detail
Token.sol#_addFounders
function is as follows.baseTokenId
is initialized toreservedUntilTokenId
and then it's value is determined by_getNextTokenId
function in line 166.The code of
Token.sol#_getNextTokenId
function is the following.At the start time, the condition of line 188 does not hold. Thus the first
baseTokenId
value of the first founder will be equal toreservedUntilTokenId
.So the first
baseTokenId
value of the first founder will be over 100 whenreservedUntilTokenId >= 100
.On the other hand,
Token.sol#_isForFounder
function is as follows.From above,
baseTokenId < 100
always hold.Thus when
reservedUntilTokenId >= 100
, the first founder is to be allocated less tokens than other founders.Example:
_founders.length == 2
,_founders[0].ownershipPct = 2
,_founders[1].ownershipPct = 2
andreservedUntilTokenId == 100
._addFounders
function, it is hold thattokenRecipient[100] = tokenRecipient[50] = _founders[0]
andtokenRecipient[1] = tokenRecipient[51] = _founders[1]
.0 <= baseTokenId < 100
holds in_isForFounder
function,tokenRecipient[100]
will never be used and thus_founder[0]
will be allocated tokens as half of the_founder[1]
.Impact
The first founder is to be allocated less tokens than other founders.
In particular, when
ownershipPct == 1
of the first founder, he will not be allocated a token at all.Code Snippet
https://github.com/sherlock-audit/2023-09-nounsbuilder/blob/main/nouns-protocol/src/token/Token.sol#L161
Tool used
Manual Review
Recommendation
Modify
Token.sol#L161
as follows.Duplicate of #42
The text was updated successfully, but these errors were encountered: