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: 29/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
Rather than using abi.encodePacked for appending bytes, since version 0.8.4, bytes.concat() is enabled Since version 0.8.4 for appending bytes, bytes.concat() can be used instead of abi.encodePacked(,).
File: canto-bio-protocol/src/Bio.sol 116: return string(abi.encodePacked("data:application/json;base64,", json));
File: canto-namespace-protocol/src/Namespace.sol 95: abi.encodePacked( 105: return string(abi.encodePacked("data:application/json;base64,", json));
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L95 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L105
File: canto-namespace-protocol/src/Tray.sol 135: abi.encodePacked( 145: return string(abi.encodePacked("data:application/json;base64,", json));
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L135 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L145
File: canto-namespace-protocol/src/Utils.sol 104: bytes memory character = abi.encodePacked( 110: character = abi.encodePacked(character, EMOJIS[byteOffset + i]); 115: character = abi.encodePacked(character, hex"F09F8FBB"); 117: character = abi.encodePacked(character, hex"F09F8FBC"); 119: character = abi.encodePacked(character, hex"F09F8FBD"); 121: character = abi.encodePacked(character, hex"F09F8FBE"); 123: character = abi.encodePacked(character, hex"F09F8FBF"); 135: return abi.encodePacked(bytes1(asciiStartingIndex + uint8(_characterIndex))); 145: bytes memory character = abi.encodePacked(bytes1(asciiStartingIndex + uint8(_characterIndex))); 149: character = abi.encodePacked( 158: character = abi.encodePacked( 167: character = abi.encodePacked( 197: return abi.encodePacked(FONT_SQUIGGLE[0], FONT_SQUIGGLE[1]); 199: return abi.encodePacked(FONT_SQUIGGLE[2], FONT_SQUIGGLE[3], FONT_SQUIGGLE[4]); 202: return abi.encodePacked(FONT_SQUIGGLE[5 + offset], FONT_SQUIGGLE[6 + offset]); 204: return abi.encodePacked(FONT_SQUIGGLE[47]); 206: return abi.encodePacked(FONT_SQUIGGLE[48], FONT_SQUIGGLE[49], FONT_SQUIGGLE[50]);
Pragma statements can be allowed to float when a contract is intended for consumption by other developers, as in the case with contracts in a library or EthPM package. Otherwise, the developer would need to manually update the pragma in order to compile locally. https://swcregistry.io/docs/SWC-103
Ethereum Smart Contract Best Practices - Lock pragmas to specific compiler version. https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/locking-pragmas/
Order of Functions; ordering helps readers identify which functions they can call and to find the constructor and fallback definitions easier. But there are contracts in the project that do not comply with this.
https://docs.soliditylang.org/en/v0.8.17/style-guide.html
Functions should be grouped according to their visibility and ordered:
constructor
receive function (if exists)
fallback function (if exists)
external
public
internal
private
within a grouping, place the view and pure functions last
File: canto-pfp-protocol/src/ProfilePicture.sol 60: if (block.chainid == 7700) {
File: canto-namespace-protocol/src/Namespace.sol 81: if (block.chainid == 7700) {
File: canto-namespace-protocol/src/Utils.sol 258: iteratedState = _currState * 15485863; 259: iteratedState = (iteratedState * iteratedState * iteratedState) % 2038074743;
There are many addresses and constants used in the system. It is recommended to put the most used ones in one file (for example constants.sol, use inheritance to access these values).
This will help with readability and easier maintenance for future changes. This also helps with any issues, as some of these hard-coded values are admin addresses.
Use and import this file in contracts that require access to these values. This is just a suggestion, in some use cases this may result in higher gas usage in the distribution.
It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as Defi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
https://docs.soliditylang.org/en/v0.8.15/natspec-format.html
Include return parameters in NatSpec comments
This codebase will be difficult to navigate, as there are no descriptive naming conventions that specify which files should contain meaningful logic.
Prefixes should be added like this by filing:
Interface I_ absctract contracts Abs_ Libraries Lib_ We recommend that you implement this or a similar agreement.
foundry.toml: 1: [profile.default] 2: optimizer = true 3: optimizer_runs = 1_000 4: via-ir=true
Protocol has enabled optional compiler optimizations in Solidity. There have been several optimization bugs with security implications. Moreover, optimizations are actively being developed. Solidity compiler optimizations are disabled by default, and it is unclear how many contracts in the wild actually use them. Therefore, it is unclear how well they are being tested and exercised. High-severity security issues due to optimization bugs have occurred in the past. A high-severity bug in the emscripten-generated solc-js compiler used by Truffle and Remix persisted until late 2018. The fix for this bug was not reported in the Solidity CHANGELOG. Another high-severity optimization bug resulting in incorrect bit shift results was patched in Solidity 0.5.6. More recently, another bug due to the incorrect caching of keccak256 was reported. A compiler audit of Solidity from November 2018 concluded that the optional optimizations may not be safe. It is likely that there are latent bugs related to optimization and that new bugs will be introduced due to future optimizations. Exploit Scenario: A latent or future bug in Solidity compiler optimizations—or in the Emscripten transpilation to solc-js—causes a security vulnerability in the contracts. Recommendation: Short term, measure the gas savings from optimizations and carefully weigh them against the possibility of an optimization-related bug. Long term, monitor the development and adoption of Solidity compiler optimizations to assess their maturity.
#0 - c4-judge
2023-04-11T05:47:37Z
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
File: canto-namespace-protocol/src/Namespace.sol 150: numBytes += numBytesChar;
File: canto-pfp-protocol/src/ProfilePicture.sol 80: uint256 tokenId = ++numMinted;
File: canto-bio-protocol/src/Bio.sol 56: for (uint i; i < lengthInBytes; ++i) { 59: bytesOffset++; 90: strLines[insertedLines++] = string(bytesLines); 100: for (uint i; i < lines; ++i) { 124: uint256 tokenId = ++numMinted;
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-bio-protocol/src/Bio.sol#L56 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-bio-protocol/src/Bio.sol#L59 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-bio-protocol/src/Bio.sol#L90 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-bio-protocol/src/Bio.sol#L100 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-bio-protocol/src/Bio.sol#L124
File: canto-namespace-protocol/src/Namespace.sol 115: uint256 namespaceIDToMint = ++nextNamespaceIDToMint; 122: for (uint256 i; i < numCharacters; ++i) { 127: for (uint256 j = i + 1; j < numCharacters; ++j) { 147: for (uint256 j; j < numBytesChar; ++j) { 154: uniqueTrays[numUniqueTrays++] = trayID; 174: for (uint256 i; i < numUniqueTrays; ++i) {
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L115 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L122 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L127 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L147 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L154 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L174
File: canto-namespace-protocol/src/Tray.sol 129: for (uint256 i; i < TILES_PER_TRAY; ++i) { 159: for (uint256 i; i < _amount; ++i) { 161: for (uint256 j; j < TILES_PER_TRAY; ++j) {
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L129 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L159 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L161
File: canto-namespace-protocol/src/Utils.sol 109: for (uint256 i = 3; i < numBytes; ++i) { 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) {
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L109 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L146 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L155 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L164 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Utils.sol#L225
As a rule of thumb, use bytes for arbitrary-length raw byte data and string for arbitrary-length string (UTF-8) data. If you can limit the length to a certain number of bytes, always use one of bytes1 to bytes32 because they are much cheaper.
File: canto-pfp-protocol/src/ProfilePicture.sol 35: string public subprotocolName; 57: constructor(address _cidNFT, string memory _subprotocolName) ERC721("Profile Picture", "PFP") {
File: canto-bio-protocol/src/Bio.sol 18: mapping(uint256 => string) public bio; 45: string memory bioText = bio[_id]; 50: string[] memory strLines = new string[](lines); 90: strLines[insertedLines++] = string(bytesLines); 97: string 99: string memory text = '<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle">'; 101: text = string.concat(text, '<tspan x="50%" dy="20">', strLines[i], "</tspan>"); 103: string memory json = Base64.encode( 105: string.concat( 111: Base64.encode(bytes(string.concat(svg, text, "</text></svg>"))), 116: return string(abi.encodePacked("data:application/json;base64,", json)); 121: function mint(string calldata _bio) external {
File: canto-namespace-protocol/src/Namespace.sol 43: mapping(string => uint256) public nameToToken; 46: mapping(uint256 => string) public tokenToName; 92: string memory json = Base64.encode( 94: string( 105: return string(abi.encodePacked("data:application/json;base64,", json)); 168: string memory nameToRegister = string(bName); 188: string memory associatedName = tokenToName[_id];
File: canto-namespace-protocol/src/Tray.sol 132: string memory json = Base64.encode( 134: string( 145: return string(abi.encodePacked("data:application/json;base64,", json));
File: canto-namespace-protocol/src/Utils.sol 223: string memory innerData; 226: innerData = string.concat( 231: string( 237: innerData = string.concat( 246: string.concat(
Saves ~13 gas per instance
File: canto-pfp-protocol/src/ProfilePicture.sol 57: constructor(address _cidNFT, string memory _subprotocolName) ERC721("Profile Picture", "PFP") {
File: canto-bio-protocol/src/Bio.sol 32: constructor() ERC721("Biography", "Bio") {
File: canto-namespace-protocol/src/Namespace.sol 73: constructor(
File: canto-namespace-protocol/src/Tray.sol 98: constructor(
File: canto-pfp-protocol/src/ProfilePicture.sol 94: function getPFP(uint256 _pfpID) public view returns (address nftContract, uint256 nftID) {
File: canto-namespace-protocol/src/Tray.sol 195: function getTile(uint256 _trayId, uint8 _tileOffset) external view returns (TileData memory tileData) { 203: function getTiles(uint256 _trayId) external view returns (TileData[TILES_PER_TRAY] memory tileData) { 245: function _drawing(uint256 _seed) private pure returns (TileData memory tileData) {
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L195 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L203 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L245
File: canto-namespace-protocol/src/Utils.sol 256: function iteratePRNG(uint256 _currState) internal pure returns (uint256 iteratedState) {
We can use the uint types uint8, uint16, uint32, etc, though Solidity reserves 256 bits of storage regardless of the uint type meaning, in normal instances, there's no cost saving. However, if you have multiple uints inside a struct, using a smaller-sized uint when possible will allow Solidity to pack these variables together to take up less storage.
File: canto-namespace-protocol/src/Namespace.sol 34: uint8 tileOffset; 36: uint8 skinToneModifier;
File: canto-namespace-protocol/src/Tray.sol 63: uint8 characterModifier;
Using nested is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.
File: canto-bio-protocol/src/Bio.sol 60: if ((i > 0 && (i + 1) % 40 == 0) || prevByteWasContinuation || i == lengthInBytes - 1) { 71: if (
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-bio-protocol/src/Bio.sol#L60 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-bio-protocol/src/Bio.sol#L71-L83
File: canto-namespace-protocol/src/Namespace.sol 136: if (tileData.fontClass != 0 && _characterList[i].skinToneModifier != 0) { 157: if ( 186: if (nftOwner != msg.sender && getApproved[_id] != msg.sender && !isApprovedForAll[nftOwner][msg.sender])
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L136 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L157-L161 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L186
File: canto-namespace-protocol/src/Tray.sol 175: if ( 184: if (numPrelaunchMinted != type(uint256).max && _id <= numPrelaunchMinted) 240: if (startTokenId <= numPrelaunchMinted && to != address(0))
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L175-L180 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L184 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L240
File: canto-namespace-protocol/src/Namespace.sol 78: tray = Tray(_tray); 79: note = ERC20(_note); 80: revenueAddress = _revenueAddress;
File: canto-namespace-protocol/src/Tray.sol 107: revenueAddress = _revenueAddress; 108: note = ERC20(_note); 109: namespaceNFT = _namespaceNFT;
File: canto-pfp-protocol/src/ProfilePicture.sol 103: nftID = 0; // Strictly not needed because nftContract has to be always checked, but reset nevertheless to 0
type(uint128).max or type(uint256).max, etc. it uses more gas in the distribution process and also for each transaction than constant usage.
File: canto-namespace-protocol/src/Tray.sol 122: if (numPrelaunchMinted != type(uint256).max) { 152: if (prelaunchMinted == type(uint256).max) { 184: if (numPrelaunchMinted != type(uint256).max && _id <= numPrelaunchMinted) 226: if (prelaunchMinted != type(uint256).max) revert PrelaunchAlreadyEnded(); 238: if (numPrelaunchMinted != type(uint256).max) {
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L122 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L152 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L184 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L226 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L238
File: canto-pfp-protocol/src/ProfilePicture.sol 87: emit PfpAdded(msg.sender, tokenId, _nftContract, _nftID);
File: canto-bio-protocol/src/Bio.sol 127: emit BioAdded(msg.sender, tokenId, _bio);
File: canto-namespace-protocol/src/Namespace.sol 179: emit NamespaceFused(msg.sender, namespaceIDToMint, nameToRegister); 199: emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); 207: emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress);
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L179 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L199 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Namespace.sol#L207
File: canto-namespace-protocol/src/Tray.sol 213: emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); 221: emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress);
https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L213 https://github.com/code-423n4/2023-03-canto-identity/blob/077372297fc419ea7688ab62cc3fd4e8f4e24e66/canto-namespace-protocol/src/Tray.sol#L221
#0 - c4-judge
2023-04-10T23:55:41Z
0xleastwood marked the issue as grade-a