Platform: Code4rena
Start Date: 17/03/2023
Pot Size: $36,500 USDC
Total HM: 10
Participants: 98
Period: 3 days
Judge: leastwood
Total Solo HM: 5
Id: 223
League: ETH
Rank: 28/98
Findings: 2
Award: $100.36
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Sathish9098
Also found by: 0xAgro, 0xSmartContract, 0xdaydream, 0xnev, Awesome, Aymen0909, BRONZEDISC, Bauchibred, Deathstore, Diana, IceBear, Jerry0x, Kresh, Matin, Rolezn, Stryder, T1MOH, Udsen, adriro, alejandrocovrr, atharvasama, codeslide, cryptonue, descharre, igingu, jack, joestakey, libratus, lukris02, luxartvinsec, nadin, nasri136, reassor, scokaf, shark, slvDev, tnevler
22.7749 USDC - $22.77
Issue | Risk | Instances | |
---|---|---|---|
1 | burn function doesn't delete the NFT character data | Low | 1 |
2 | Immutable state variables lack zero address checks | Low | 3 |
3 | Related data should be grouped in a struct | NC | 2 |
4 | Duplicated require() checks should be refactored to a modifier | NC | 3 |
5 | constant should be defined rather than using magic numbers | NC | 2 |
burn
function doesn't delete the NFT character data :In the burn
function from the Namespace contract, when the NFT is burned its correspanding name (stored in tokenToName[_id]
) and tokenId (stored in nameToToken[associatedName]
) are deleted but the NFT character data stored in the nftCharacters
struct are not deleted and remains even after the burning operation, this can potentially impact the protocol working in the future if this data is used for a given purpose.
Issue occurs in the code below :
File: Namespace.sol Line 184-192
function burn(uint256 _id) external { address nftOwner = ownerOf(_id); if (nftOwner != msg.sender && getApproved[_id] != msg.sender && !isApprovedForAll[nftOwner][msg.sender]) revert CallerNotAllowedToBurn(); string memory associatedName = tokenToName[_id]; delete tokenToName[_id]; delete nameToToken[associatedName]; /** @audit Should also delete the nftCharacters data */ _burn(_id); }
Delete the NFT character data when burning a given NFT.
Constructors should check the values written in an immutable state variables(address) is not the zero value (address(0))
Instances include:
File: ProfilePicture.sol Line 58
cidNFT = ICidNFT(_cidNFT);
File: Tray.sol Line 109
namespaceNFT = _namespaceNFT;
File: Namespace.sol Line 78
tray = Tray(_tray);
Add non-zero address checks in the constructors for the instances aforementioned.
Input check statements used multiple times inside a contract should be refactored to a modifier for better readability and also to save gas.
The following check statements are repeated multiple times :
File: Tray.sol 120 if (!_exists(_id)) revert TrayNotMinted(_id); 196 if (!_exists(_trayId)) revert TrayNotMinted(_trayId); 204 if (!_exists(_trayId)) revert TrayNotMinted(_trayId);
Because this check is done at the start of the function it can be placed and can be refactored into a modifier to save gas, it should be replaced by a isAllowed
modifier as follow :
modifier isAllowed(uint256 _id){ if (!_exists(_id)) revert TrayNotMinted(_id); _; }
When there are mappings that use the same key value, having separate fields is error prone, for instance in case of deletion or with future new fields.
Instances include:
File: Namespace.sol Line 46-49
46 mapping(uint256 => string) public tokenToName; 49 mapping(uint256 => Tray.TileData[]) private nftCharacters;
Group the related data in a struct and use one mapping :
struct TokenToNftData { string tokenToName Tray.TileData[] nftCharacters; }
And it would be used as a state variable :
mapping(uint256 => TokenToNftData) public tokenToNftData;
constant
should be defined rather than using magic numbers :It is best practice to use constant variables rather than hex/numeric literal values to make the code easier to understand and maintain, but if they are used those numbers should be well docummented.
Instances include:
File: Namespace.sol
Replace the hex/numeric literals aforementioned with constants
.
#0 - c4-judge
2023-04-11T05:49:08Z
0xleastwood marked the issue as grade-b
🌟 Selected for report: 0xSmartContract
Also found by: 0xdaydream, 0xnev, Aymen0909, Deekshith99, Diana, EvanW, Fanz, JCN, Jerry0x, K42, Kresh, Madalad, MiniGlome, Polaris_tow, Rageur, ReyAdmirado, Rolezn, SAAJ, SaeedAlipoor01988, Sathish9098, Shubham, Udsen, Viktor_Cortess, Walter, anodaram, arialblack14, atharvasama, caspersolangii, codeslide, descharre, fatherOfBlocks, felipe, ginlee, igingu, lukris02, nadin, slvDev, tnevler, turvy_fuzz, viking71
77.5893 USDC - $77.59
Issue | Instances | |
---|---|---|
1 | Multiple address/IDs mappings can be combined into a single mapping of an address/id to a struct | 2 |
2 | Using storage instead of memory for struct/array saves gas | 1 |
3 | Duplicated input check statements should be refactored to a modifier | 3 |
4 | ++i/i++ should be unchecked{++i} /unchecked{i++} when it is not possible for them to overflow, as in the case when used in for & while loops | 12 |
Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key's keccak256 hash (Gkeccak256 - 30 gas) and that calculation's associated stack operations.
There are 2 instances of this issue in Lottery
:
File: Namespace.sol 46 mapping(uint256 => string) public tokenToName; 49 mapping(uint256 => Tray.TileData[]) private nftCharacters;
These mappings could be refactored into the following struct and mapping for example :
struct TokenToNftData { string tokenToName Tray.TileData[] nftCharacters; } mapping(uint256 => TokenToNftData) public tokenToNftData;
storage
instead of memory
for struct/array saves gas :When fetching data from a storage
location, assigning the data to a memory
variable causes all fields of the struct/array to be read from storage
, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory
variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory
keyword, declaring the variable with the storage
keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read.
The only time it makes sense to read the whole struct/array into a memory
variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory
, or if the array/struct is being read from another memory
array/struct.
There is 1 instance of this issue :
File: Bio.sol Line 45
string memory bioText = bio[_id];
The following check statements are repeated multiple times :
File: Tray.sol 120 if (!_exists(_id)) revert TrayNotMinted(_id); 196 if (!_exists(_trayId)) revert TrayNotMinted(_trayId); 204 if (!_exists(_trayId)) revert TrayNotMinted(_trayId);
Because this check is done at the start of the function it can be placed and can be refactored into a modifier to save gas, it should be replaced by a isAllowed
modifier as follow :
modifier isAllowed(uint256 _id){ if (!_exists(_id)) revert TrayNotMinted(_id); _; }
++i/i++
should be unchecked{++i}/unchecked{i++}
when it is not possible for them to overflow, as in the case when used in for & while loops :There are 12 instances of this issue:
File: Tray.sol 56 for (uint i; i < lengthInBytes; ++i) 100 for (uint i; i < lines; ++i) File: Namespace.sol 122 for (uint256 i; i < numCharacters; ++i) 127 for (uint256 j = i + 1; j < numCharacters; ++j) 147 for (uint256 j; j < numBytesChar; ++j) 174 for (uint256 i; i < numUniqueTrays; ++i) File: Utils.sol 146 for (uint256 i; i < numAbove; ++i) 155 for (uint256 i; i < numMiddle; ++i) 164 for (uint256 i; i < numBelow; ++i) 225 for (uint256 i; i < _tiles.length; ++i) File: Bio.sol 56 for (uint i; i < lengthInBytes; ++i) 100 for (uint i; i < lines; ++i)
#0 - c4-judge
2023-04-10T23:54:58Z
0xleastwood marked the issue as grade-a