sherlock-2023-11-nouns-builder-h01
[H-01] when reservedUntilTokenId > 100 first funder loss 1% NFT
Summary
reservedUntilTokenId 가 100보다 클 때, 첫번째 baseTokenId를 잘못 설정하여 첫번째 창립자가 자신에게 할당된 NFT 중 1%를 받을 수 없게 된다.
Keyword
logic flaw, arithmetic error
Vulnerability
DAO 창립자에게 줄 NFT를 예약하기 위해 _addFounders 함수를 호출한다. 창립자에게 특정 끝번호(0~99)를 특정 창립자에게 민팅하도록 예약한다.
reservedUntilTokenId 파라미터는 별개의 민터로 민팅하기 위해 예약해둔 토큰 id가 끝나는 지점으로, 이 tokenId부터 창립자 또는 낙찰자에게 NFT 발행을 시작한다.
function _addFounders(IManager.FounderParams[] calldata _founders, uint256 reservedUntilTokenId) internal {
...
@> uint256 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;
emit MintScheduled(baseTokenId, founderId, newFounder);
// Update the base token id
baseTokenId = (baseTokenId + schedule) % 100;
}
...
}
function _getNextTokenId(uint256 _tokenId) internal view returns (uint256) {
unchecked {
@> while (tokenRecipient[_tokenId].wallet != address(0)) {
_tokenId = (++_tokenId) % 100;
}
return _tokenId;
}
}reservedUntilTokenId > 100 일 때, 파라미터 _founders 배열의 첫 번째로 등록된 창립자(_founders[0])는 원래 받아야 하는 양의 1%의 NFT를 받을 수 없게 된다.
예를 들어 reservedUntilTokenId = 200 일 때, 첫 번째 호출된 _getNextTokenId(200) 의 결과는 200이며 tokenRecipient[200] = founder_0 로 설정된다.
founder[0].founderPct = 10 이라 가정하면 founder_0는 reservedUntilTokenId 이전까지의 NFT를 10% 할당받을 수 있다. founder_0는 다음과 같은 tokenId를 할당받는다.
- tokenRecipient[200].wallet = founder_0 (첫번째
_getNextTokenId(200)가 200을 리턴하므로) - tokenRecipient[10].wallet = founder_0 (두번째
_getNextTokenId((200 + 10) %100 = 10)로 10을 리턴) - tokenRecipient[20].wallet = founder_0
- …
- tokenRecipient[90].wallet = founder
하지만 첫번째에 설정된 200번 NFT는 founder_0 에게 발행될 수 없다. _isForFounder 함수에서 창립자에게 할당된 NFT를 확인하고 발행하는데, 모듈러 계산을 통해 끝자리만 확인하기 때문이다. 끝자리 200을 예약했지만 _tokenId % 100 의 결과가 200이 나올 수 없으므로 발행받을 수 없다.
function _isForFounder(uint256 _tokenId) private returns (bool) {
// Get the base token id
@> uint256 baseTokenId = _tokenId % 100;
// If there is no scheduled recipient:
@> if (tokenRecipient[baseTokenId].wallet == address(0)) {
return false;
// Else if the founder is still vesting:
} else if (block.timestamp < tokenRecipient[baseTokenId].vestExpiry) {
// Mint the token to the founder
@> _mint(tokenRecipient[baseTokenId].wallet, _tokenId);
return true;
// Else the founder has finished vesting:
} else {
// Remove them from future lookups
delete tokenRecipient[baseTokenId];
return false;
}
}Impact
reservedUntilTokenId 가 100보다 클 때, 첫번째 창립자가 자신에게 할당된 NFT 중 1%를 받을 수 없게 된다.
Mitigation
_addFounders에서 등록하는 baseTokenId는 0부터 시작한다.
function _addFounders(IManager.FounderParams[] calldata _founders, uint256 reservedUntilTokenId) internal {
...
// Used to store the base token id the founder will recieve
- uint256 baseTokenId = reservedUntilTokenId;
+ uint256 baseTokenId = 0;또는 reservedUntilTokenId % 100 부터 시작한다.
function _addFounders(IManager.FounderParams[] calldata _founders, uint256 reservedUntilTokenId) internal {
...
// Used to store the base token id the founder will recieve
- uint256 baseTokenId = reservedUntilTokenId;
+ uint256 baseTokenId = reservedUntilTokenId % 100;tags: bughunting, nouns dao, smart contract, solidity, logic flaw, arithmetic error, severity high