Issue | Instances | |
---|---|---|
GAS-1 | Use assembly to check for address(0) |
5 |
GAS-2 | Cache array length outside of loop | 1 |
GAS-3 | State variables should be cached in stack variables rather than re-reading them from storage | 1 |
GAS-4 | Use calldata instead of memory for function arguments that do not get mutated | 1 |
GAS-5 | Pre-increments and pre-decrements are cheaper than post-increments and post-decrements | 3 |
GAS-6 | Use shift Right/Left instead of division/multiplication if possible | 14 |
GAS-7 | Use storage instead of memory for structs/arrays |
18 |
GAS-8 | Increments can be unchecked in for-loops |
14 |
GAS-9 | Use != 0 instead of > 0 for unsigned integer comparison | 6 |
GAS-10 | internal functions not called by the contract should be removed |
1 |
Saves 6 gas per instance
Instances (5):
File: canto-bio-protocol/src/Bio.sol
44: if (_ownerOf[_id] == address(0)) revert TokenNotMinted(_id);
File: canto-namespace-protocol/src/Namespace.sol
91: if (_ownerOf[_id] == address(0)) revert TokenNotMinted(_id);
File: canto-namespace-protocol/src/Tray.sol
240: if (startTokenId <= numPrelaunchMinted && to != address(0))
File: canto-pfp-protocol/src/ProfilePicture.sol
72: if (nftContract == address(0)) revert PFPNoLongerOwnedByOriginalOwner(_id);
95: if (_ownerOf[_pfpID] == address(0)) revert TokenNotMinted(_pfpID);
If not cached, the solidity compiler will always read the length of the array during each iteration. That is, if it is a storage array, this is an extra sload operation (100 additional extra gas for each iteration except for the first) and if it is a memory array, this is an extra mload operation (3 additional gas for each iteration except for the first).
Instances (1):
File: canto-namespace-protocol/src/Utils.sol
225: for (uint256 i; i < _tiles.length; ++i) {
[GAS-3] State variables should be cached in stack variables rather than re-reading them from storage
The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.
Saves 100 gas per instance
Instances (1):
File: canto-namespace-protocol/src/Tray.sol
163: trayTiledata[j] = _drawing(uint256(lastHash));
Mark data types as calldata
instead of memory
where possible. This makes it so that the data is not automatically loaded into memory. If the data passed into the function does not need to be changed (like updating values in an array), it can be passed in as calldata
. The one exception to this is if the argument must later be passed into another function that takes an argument that specifies memory
storage.
Instances (1):
File: canto-pfp-protocol/src/ProfilePicture.sol
57: constructor(address _cidNFT, string memory _subprotocolName) ERC721("Profile Picture", "PFP") {
Saves 5 gas per iteration
Instances (3):
File: canto-bio-protocol/src/Bio.sol
59: bytesOffset++;
90: strLines[insertedLines++] = string(bytesLines);
File: canto-namespace-protocol/src/Namespace.sol
154: uniqueTrays[numUniqueTrays++] = trayID;
Shifting left by N is like multiplying by 2^N and shifting right by N is like dividing by 2^N
Instances (14):
File: canto-namespace-protocol/src/Tray.sol
260: } else if (res < 104) {
262: } else if (res < 108) {
264: if (tileData.fontClass == 7) {
File: canto-namespace-protocol/src/Utils.sol
65: uint256 private constant EMOJIS_BYTE_OFFSET_SEVEN_BYTES = 1671; // 17 * 3 + 366 * 4 + 26 * 6
66: uint256 private constant EMOJIS_BYTE_OFFSET_EIGHT_BYTES = 1720; // 17 * 3 + 366 * 4 + 26 * 6 + 7 * 7
67: uint256 private constant EMOJIS_BYTE_OFFSET_FOURTEEN_BYTES = 1744; // 17 * 3 + 366 * 4 + 26 * 6 + 7 * 7 + 3 * 8
68:
68:
90: supportsSkinToneModifier = _characterIndex >= EMOJIS_LE_FOUR_BYTES - EMOJIS_MOD_SKIN_TONE_FOUR_BYTES;
100: } else {
149: character = abi.encodePacked(
158: character = abi.encodePacked(
167: character = abi.encodePacked(
202: return abi.encodePacked(FONT_SQUIGGLE[5 + offset], FONT_SQUIGGLE[6 + offset]);
Using memory
copies the struct or array in memory. Use storage
to save the location in storage and have cheaper reads:
Instances (18):
File: canto-bio-protocol/src/Bio.sol
46: bytes memory bioTextBytes = bytes(bioText);
47: uint lengthInBytes = bioTextBytes.length;
51: bool prevByteWasContinuation;
55: uint bytesOffset;
99: string memory text = '<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle">';
100: for (uint i; i < lines; ++i) {
104: bytes(
File: canto-namespace-protocol/src/Namespace.sol
93: bytes(
118: uint256 numBytes;
122: for (uint256 i; i < numCharacters; ++i) {
134: uint8 characterModifier = tileData.characterModifier;
145: tileData.characterModifier = characterModifier;
169: uint256 currentRegisteredID = nameToToken[nameToRegister];
189: delete tokenToName[_id];
File: canto-namespace-protocol/src/Tray.sol
129: for (uint256 i; i < TILES_PER_TRAY; ++i) {
133: bytes(
File: canto-namespace-protocol/src/Utils.sol
105: EMOJIS[byteOffset],
146: for (uint256 i; i < numAbove; ++i) {
Instances (14):
File: canto-bio-protocol/src/Bio.sol
56: for (uint i; i < lengthInBytes; ++i) {
100: for (uint i; i < lines; ++i) {
File: canto-namespace-protocol/src/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: 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) {
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) {
Instances (6):
File: canto-bio-protocol/src/Bio.sol
2: pragma solidity >=0.8.0;
60: if ((i > 0 && (i + 1) % 40 == 0) || prevByteWasContinuation || i == lengthInBytes - 1) {
File: canto-namespace-protocol/src/Namespace.sol
2: pragma solidity >=0.8.0;
File: canto-namespace-protocol/src/Tray.sol
2: pragma solidity >=0.8.0;
File: canto-namespace-protocol/src/Utils.sol
2: pragma solidity >=0.8.0;
File: canto-pfp-protocol/src/ProfilePicture.sol
2: pragma solidity >=0.8.0;
If the functions are required by an interface, the contract should inherit from that interface and use the override
keyword
Instances (1):
File: canto-namespace-protocol/src/Utils.sol
222: function generateSVG(Tray.TileData[] memory _tiles, bool _isTray) internal pure returns (string memory) {
Issue | Instances | |
---|---|---|
NC-1 | Missing checks for address(0) when assigning values to address state variables |
4 |
NC-2 | Constants should be defined rather than using magic numbers | 3 |
NC-3 | Functions not used internally could be marked external | 4 |
Instances (4):
File: canto-namespace-protocol/src/Namespace.sol
80: revenueAddress = _revenueAddress;
206: revenueAddress = _newRevenueAddress;
File: canto-namespace-protocol/src/Tray.sol
107: revenueAddress = _revenueAddress;
220: revenueAddress = _newRevenueAddress;
Instances (3):
File: canto-bio-protocol/src/Bio.sol
54: bytes memory bytesLines = new bytes(80);
91: bytesLines = new bytes(80);
File: canto-namespace-protocol/src/Namespace.sol
113: uint256 fusingCosts = 2**(13 - numCharacters) * 1e18;
Instances (4):
File: canto-bio-protocol/src/Bio.sol
43: function tokenURI(uint256 _id) public view override returns (string memory) {
File: canto-namespace-protocol/src/Namespace.sol
90: function tokenURI(uint256 _id) public view override returns (string memory) {
File: canto-namespace-protocol/src/Tray.sol
119: function tokenURI(uint256 _id) public view override returns (string memory) {
File: canto-pfp-protocol/src/ProfilePicture.sol
70: function tokenURI(uint256 _id) public view override returns (string memory) {
Issue | Instances | |
---|---|---|
L-1 | Unspecific compiler version pragma | 5 |
Instances (5):
File: canto-bio-protocol/src/Bio.sol
2: pragma solidity >=0.8.0;
File: canto-namespace-protocol/src/Namespace.sol
2: pragma solidity >=0.8.0;
File: canto-namespace-protocol/src/Tray.sol
2: pragma solidity >=0.8.0;
File: canto-namespace-protocol/src/Utils.sol
2: pragma solidity >=0.8.0;
File: canto-pfp-protocol/src/ProfilePicture.sol
2: pragma solidity >=0.8.0;
Issue | Instances | |
---|---|---|
M-1 | Centralization Risk for trusted owners | 9 |
Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds.
Instances (9):
File: canto-namespace-protocol/src/Namespace.sol
11: contract Namespace is ERC721, Owned {
77: ) ERC721("Namespace", "NS") Owned(msg.sender) {
196: function changeNoteAddress(address _newNoteAddress) external onlyOwner {
204: function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {
File: canto-namespace-protocol/src/Tray.sol
13: contract Tray is ERC721A, Owned {
104: ) ERC721A("Namespace Tray", "NSTRAY") Owned(msg.sender) {
210: function changeNoteAddress(address _newNoteAddress) external onlyOwner {
218: function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {
225: function endPrelaunchPhase() external onlyOwner {