-
Notifications
You must be signed in to change notification settings - Fork 5
0xbepresent - The Token::setReservedUntilTokenId()
function does not update the assigned founders in the tokenRecipient[]
array causing founders being unable to claim some assigned tokens
#67
Comments
This is not an issue. the founders allocation applies after the token reserve. in the given example a founder will still receive 99% of tokens after the reserve (ending with tokenId 98). they only receive one token in the example because they only checked until tokenId 98 which is the only non reserved token. |
Imo, this is also a duplicate of the family under #42 (see comments there), going to duplicate. |
Token::setReservedUntilTokenId()
function does not update the assigned founders in the tokenRecipient[]
array causing founders being unable to claim some assigned tokensToken::setReservedUntilTokenId()
function does not update the assigned founders in the tokenRecipient[]
array causing founders being unable to claim some assigned tokens
Escalate In order to explain why this issue is not a duplicated of #42 I need to provide some context. The fix implemented here #42 (comment) DOES NOT fix the problem since if The token is initialized with
As we see, the allocation for founders will only be 9 tokens since 89 tokens will be reserved for airdrops and marketing stuff. This can be seen in the following test which we see that the founder only ends up minting 9 tokens: // File: Token.t.sol
//
function test_FounderWontBeAbleToClaimTokensIfTheTokenIsInitializedUsingAReservedUntilTokenId() public {
// Founders won't be able to claim assigned ownership tokens if the token is initialized using
// non-zero reservedUntilTokenId
//
// 1. Token is deployed and a founder is assigned with owner ship of 99 and reservedUntilTokenId = 90.
createUsers(1, 1 ether);
address[] memory wallets = new address[](1);
uint256[] memory percents = new uint256[](1);
uint256[] memory vestExpirys = new uint256[](1);
address founder = otherUsers[0];
wallets[0] = founder;
percents[0] = 99;
vestExpirys[0] = 4 weeks;
deployWithCustomFoundersAndCustomReserve(wallets, percents, vestExpirys, 90);
//
// 2. Assert the scheduledRecipient and the founder is assigned the position 0, to 99
// That's is a problem because ids 0 to 90 are reserved and the founder won't be able to claim those tokens.
assertEq(token.totalFounders(), 1);
assertEq(token.totalFounderOwnership(), 99);
unchecked {
for (uint256 i = 0; i < 99; i++) {
assertEq(token.getScheduledRecipient(i).wallet, founder);
}
}
//
// 3. Auction starts and the founder is only able to claim 9 token because the tokenId starts at number 90
// As a result, the previous assigned tokens to the founder will be unclaimable.
vm.prank(address(auction));
token.mint();
assertEq(token.balanceOf(founder), 9);
} --- a/nouns-protocol/src/token/Token.sol
+++ b/nouns-protocol/src/token/Token.sol
@@ -158,7 +158,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
uint256 schedule = 100 / founderPct;
// Used to store the base token id the founder will recieve
- uint256 baseTokenId = reservedUntilTokenId;
+ uint256 baseTokenId = 0;
// For each token to vest:
for (uint256 j; j < founderPct; ++j) { The best solution is the one proposed here #77 in which it involves
Now the allocation for founders will be // File: Token.t.sol
//
function test_ReservedUntilTokenIdFix() public {
//
// 1. Token is deployed and a founder is assigned with owner ship of 99 and reservedUntilTokenId = 90.
createUsers(1, 1 ether);
address[] memory wallets = new address[](1);
uint256[] memory percents = new uint256[](1);
uint256[] memory vestExpirys = new uint256[](1);
address founder = otherUsers[0];
wallets[0] = founder;
percents[0] = 99;
vestExpirys[0] = 4 weeks;
deployWithCustomFoundersAndCustomReserve(wallets, percents, vestExpirys, 90);
//
// 2. Assert the scheduledRecipient and the founder is assigned the position 90,...,189 (90 + 99)
assertEq(token.totalFounders(), 1);
assertEq(token.totalFounderOwnership(), 99);
unchecked {
for (uint256 i = 90; i < 189; i++) {
assertEq(token.getScheduledRecipient(i).wallet, founder);
}
}
// 3. Auction starts and the founder is able to claim all tokens
vm.prank(address(auction));
token.mint();
assertEq(token.balanceOf(founder), 99);
} +++ b/nouns-protocol/src/token/Token.sol
@@ -171,7 +171,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
emit MintScheduled(baseTokenId, founderId, newFounder);
// Update the base token id
- baseTokenId = (baseTokenId + schedule) % 100;
+ baseTokenId = (baseTokenId + schedule) % (100 + reservedUntilTokenId);
}
}
@@ -186,7 +186,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
function _getNextTokenId(uint256 _tokenId) internal view returns (uint256) {
unchecked {
while (tokenRecipient[_tokenId].wallet != address(0)) {
- _tokenId = (++_tokenId) % 100;
+ _tokenId = (++_tokenId) % (100 + reservedUntilTokenId);
}
return _tokenId;
@@ -262,7 +262,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
/// @param _tokenId The ERC-721 token id
function _isForFounder(uint256 _tokenId) private returns (bool) {
// Get the base token id
- uint256 baseTokenId = _tokenId % 100;
+ uint256 baseTokenId = _tokenId % (100 + reservedUntilTokenId);
// If there is no scheduled recipient:
if (tokenRecipient[baseTokenId].wallet == address(0)) {
@@ -367,7 +367,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
/// NOTE: If a founder is returned, there's no guarantee they'll receive the token as vesting expiration is not considered
/// @param _tokenId The ERC-721 token id
function getScheduledRecipient(uint256 _tokenId) external view returns (Founder memory) {
- return tokenRecipient[_tokenId % 100];
+ return tokenRecipient[_tokenId % (100 + reservedUntilTokenId)];
}
/// @notice Update the list of allocation owners
@@ -414,7 +414,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
for (uint256 j; j < cachedFounder.ownershipPct; ++j) {
// Get the next index that hasn't already been cleared
while (clearedTokenIds[baseTokenId] != false) {
- baseTokenId = (++baseTokenId) % 100;
+ baseTokenId = (++baseTokenId) % (100 + reservedUntilTokenId);
}
delete tokenRecipient[baseTokenId];
@@ -423,7 +423,7 @@ contract Token is IToken, VersionedContract, UUPS, Ownable, ReentrancyGuard, ERC
emit MintUnscheduled(baseTokenId, i, cachedFounder);
// Update the base token id
- baseTokenId = (baseTokenId + schedule) % 100;
+ baseTokenId = (baseTokenId + schedule) % (100 + reservedUntilTokenId);
}
}
} Note: The following function has to be added in +++ b/nouns-protocol/test/utils/NounsBuilderTest.sol
@@ -274,6 +274,25 @@ contract NounsBuilderTest is Test {
setMockMetadata();
}
+ function deployWithCustomFoundersAndCustomReserve(
+ address[] memory _wallets,
+ uint256[] memory _percents,
+ uint256[] memory _vestExpirys,
+ uint256 _reservedUntilTokenId
+ ) internal virtual {
+ setFounderParams(_wallets, _percents, _vestExpirys);
+
+ setMockTokenParamsWithReserve(_reservedUntilTokenId);
+
+ setMockAuctionParams();
+
+ setMockGovParams();
+
+ deploy(foundersArr, tokenParams, auctionParams, govParams);
+
+ setMockMetadata();
+ }
+ Now, given that context, the fix proposed here #77 DOES NOT solve the problem of this Issue, the problem of this issue #67 is that you have to delete all the founders' assignments and recalculate so that the founders are assigned correctly and do not lose tokens. Following the previous example and assuming that the fix proposed here #77 is applied: The token is initialized with
Now, the owner updates
As you can see, the founder lost some tokens. So to fix this in
As you can see, the founder will NO longer lose tokens. In conclusion, this issue is different and is NOT a duplicate of #77 #42 since the solution proposed here must delete the previous assignment for the founders and assign again using a different Hope the information helps to solve the problem. |
You've created a valid escalation! To remove the escalation from consideration: Delete your comment. You may delete or edit your escalation comment anytime before the 48-hour escalation window closes. After that, the escalation becomes final. |
founders are reserved a % of tokens not actual tokenIds. ie when you mention if reservedUntilTokenId is set to 90 founder will receive x% of all tokens minted after tokenId 90. |
I agree with invalidation, will allow for final comments, tagging @0xbepresent. |
Result: |
Escalations have been resolved successfully! Escalation status:
|
0xbepresent
medium
The
Token::setReservedUntilTokenId()
function does not update the assigned founders in thetokenRecipient[]
array causing founders being unable to claim some assigned tokensSummary
The
Token::setReservedUntilTokenId()
function does not update the assigned founders in thetokenRecipient[]
array causing founders being unable to claim some assigned tokens.Vulnerability Detail
Founders are assigned in the
tokenRecipient[]
array once the token is initialized in the Token:_addFounders() function. The position assigned in thetokenRecipient[]
array depends if the token is initialized usingreservedUntilTokenId
code line 161:The issue arises when the
reservedUntilTokenId
can be updated using Token::setReservedUntilTokenId() function but the founderstokenRecipient[]
will not be updated with the newreservedUntilTokenId
value. As a result, some assigned tokens to the founder will be unclaimable.Impact
Founders will not be able to claim some assigned tokens if the
reservedUntilTokenId
is updated because the assignedtokenRecipient
is not updated. Please see the next scenario:reservedUntilTokenId to 98
using the setReservedUntilTokenId() function.receive one token
, since during the minting process the tokenId will start at 98 ID and no previous token IDs will be minted for the founder.treasury
causing that a proposal needs to be created if owner wants to update the founders list in order to fix the problem.The next test reveals that the founder is assigned 99 initially, but ends up receiving only 1 token.
Code Snippet
Tool used
Manual review
Recommendation
The founder tokenRecipient[] array should be updated using the new
reservedUntilTokenId
in the setReservedUntilTokenId() function:The text was updated successfully, but these errors were encountered: