Count | Title | Instances |
---|---|---|
N-01 | For mordern and more readable code, update import usages | 9 |
N-02 | Solidity compiler optimizations can be problematic | - |
N-03 | Constructor lacks zero-address checks | 2 |
N-04 | Critical address changes should use 2 step procedure | 4 |
N-05 | Event missing parameter | 1 |
N-06 | Initial value check missing in change functions |
7 |
N-07 | Lack of input validation for amount of trays minted in buy function |
1 |
Total Non-Critical Issues | 7 |
---|
Count | Title | Instances |
---|---|---|
R-01 | Use bytes.concat()/string.concat() instead of abi.encodePacked() |
24 |
R-02 | Use delete instead of zero assignment to clear storage variables |
5 |
R-03 | Move validation statements to top of function when validating input parameters | 1 |
R-04 | Number values can be refactored to use _ | 6 |
R-05 | State variables not changed after deployment can be immutable | 1 |
Total Refactor Issues | 5 |
---|
Count | Explanation | Instances |
---|---|---|
O-01 | Missing natspec comments | 2 |
O-02 | Add return parameters in natspec comments | 8 |
O-03 | Commented out code | 1 |
O-04 | Use of unlocked pragma using >= |
5 |
O-05 | Contracts does not comply with order of function for solidity style guide | 7 |
Total Ordinary Issues | 5 |
---|
9 results - 5 files
/ProfilePicture.sol
5: import "../interface/Turnstile.sol";
6: import "../interface/ICidNFT.sol";
/Bio.sol
7: import "../interface/Turnstile.sol";
/Namespace.sol
7: import "./Tray.sol";
8: import "./Utils.sol";
9: import "../interface/Turnstile.sol";
/Tray.sol
10: import "./Utils.sol";
11: import "../interface/Turnstile.sol";
/Utils.sol
4: import "./Tray.sol";
Description: Solidity code is also cleaner in another way that might not be noticeable: the struct Point. We were importing it previously with global import but not using it. The Point struct polluted the source code with an unnecessary object we were not using because we did not need it. This was breaking the rule of modularity and modular programming: only import what you need Specific imports with curly braces allow us to apply this rule better. https://betterprogramming.pub/solidity-tutorial-all-about-imports-c65110e41f3a
Recommendation:
import {contract1 , contract2} from "filename.sol"
;
/foundry.toml
1: [profile.default]
2: optimizer = true
3: optimizer_runs = 1_000
4: via-ir=true
Description 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. 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.
Context: 3 results - 2 files
/Namespace.sol
73: constructor(
74: address _tray,
75: address _note,
76: address _revenueAddress
77: ) ERC721("Namespace", "NS") Owned(msg.sender) {
80: revenueAddress = _revenueAddress;
/Tray.sol
98: constructor(
99: bytes32 _initHash,
100: uint256 _trayPrice,
101: address _revenueAddress,
102: address _note,
103: address _namespaceNFT
104: ) ERC721A("Namespace Tray", "NSTRAY") Owned(msg.sender) {
107: revenueAddress = _revenueAddress;
109: namespaceNFT = _namespaceNFT;
Description: Implement Zero-address checks in the constructors, to avoid the risk of setting a storage variable as zero-address at deploy time
Recomendation: Add a zero-address check for the address variables for the instances above
Context:
4 results - 2 files
/Namespace.sol
196: function changeNoteAddress(address _newNoteAddress) external onlyOwner {
197: address currentNoteAddress = address(note);
198: note = ERC20(_newNoteAddress);
199: emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress);
200: }
204: function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {
205: address currentRevenueAddress = revenueAddress;
206: revenueAddress = _newRevenueAddress;
207: emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress);
208: }
/Tray.sol
210: function changeNoteAddress(address _newNoteAddress) external onlyOwner {
211: address currentNoteAddress = address(note);
212: note = ERC20(_newNoteAddress);
213: emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress);
214: }
218: function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {
219: address currentRevenueAddress = revenueAddress;
220: revenueAddress = _newRevenueAddress;
221: emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress);
222: }
Description Lack of two-step procedure for critical operations leaves them error-prone. Consider adding a two-step procedure on the critical functions.
Recommendation: Consider adding a two-steps pattern on critical changes to avoid mistakenly transferring ownership of roles or critical functionalities to the wrong address.
1 result - 1 file
/Tray.sol
80: event PrelaunchEnded();
225: function endPrelaunchPhase() external onlyOwner {
226: if (prelaunchMinted != type(uint256).max) revert PrelaunchAlreadyEnded();
227: prelaunchMinted = _nextTokenId() - 1;
228: emit PrelaunchEnded();
229: }
Description:
There is a event missing parameter. Consider adding a parameter such as prelaunchMinted
for better understanding of event emitted
/Namespace.sol
4 result - 2 files
196: function changeNoteAddress(address _newNoteAddress) external onlyOwner {
197: address currentNoteAddress = address(note);
198: note = ERC20(_newNoteAddress);
199: emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress);
200: }
204: function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {
205: address currentRevenueAddress = revenueAddress;
206: revenueAddress = _newRevenueAddress;
207: emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress);
208: }
/Tray.sol
210: function changeNoteAddress(address _newNoteAddress) external onlyOwner {
211: address currentNoteAddress = address(note);
212: note = ERC20(_newNoteAddress);
213: emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress);
214: }
218: function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {
219: address currentRevenueAddress = revenueAddress;
220: revenueAddress = _newRevenueAddress;
221: emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress);
222: }
Description:
There is missing initial value in change
functions. Checking initial values before changing can help avoid unecessary gas usage.
Reccomendation Checking whether the current value and the new value are the same should be added
Context:
150: function buy(uint256 _amount) external
151: uint256 startingTrayId = _nextTokenId();
152: if (prelaunchMinted == type(uint256).max) {
153: // Still in prelaunch phase
154: if (msg.sender != owner) revert OnlyOwnerCanMintPreLaunch();
155: if (startingTrayId + _amount - 1 > PRE_LAUNCH_MINT_CAP) revert MintExceedsPreLaunchAmount();
156: } else {
157: SafeTransferLib.safeTransferFrom(note, msg.sender, revenueAddress, _amount * trayPrice);
158: }
Description:
In the buy
function, zero value checks can be made to ensure _amount
bought is not zero and prevent wastage of gas by transferring zero funds to revenueAddress
Recommendation:
Add a zero value check for _amount
of trays to be minted:
function buy(uint256 _amount) external {
if (amount == 0) revert ZeroTraysBought;
}
24 results - 4 files
/Bio.sol
43: function tokenURI(uint256 _id) public view override returns (string memory)
116: return string(abi.encodePacked("data:application/json;base64,", json));
/Namespace.sol
90: function tokenURI(uint256 _id) public view override returns (string memory) {
91: if (_ownerOf[_id] == address(0)) revert TokenNotMinted(_id);
92: string memory json = Base64.encode(
93: bytes(
94: string(
95: abi.encodePacked(
96: '{"name": "',
97: tokenToName[_id],
98: '", "image": "data:image/svg+xml;base64,',
99: Base64.encode(bytes(Utils.generateSVG(nftCharacters[_id], false))),
100: '"}'
101: )
102: )
103: )
104: );
105: return string(abi.encodePacked("data:application/json;base64,", json));
106: }
/Tray.sol
132: string memory json = Base64.encode(
133: bytes(
134: string(
135: abi.encodePacked(
136: '{"name": "Tray #',
137: LibString.toString(_id),
138: '", "image": "data:image/svg+xml;base64,',
139: Base64.encode(bytes(Utils.generateSVG(nftTiles, true))),
140: '"}'
141: )
142: )
143: )
144: );
145: return string(abi.encodePacked("data:application/json;base64,", json));
/Utils.sol
73: function characterToUnicodeBytes
104: bytes memory character = abi.encodePacked(
105: EMOJIS[byteOffset],
106: EMOJIS[byteOffset + 1],
107: EMOJIS[byteOffset + 2]
108: );
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(
150: character,
151: ZALGO_ABOVE_LETTER[characterIndex],
152: ZALGO_ABOVE_LETTER[characterIndex + 1]
153: );
158: character = abi.encodePacked(
159: character,
160: ZALGO_OVER_LETTER[characterIndex],
161: ZALGO_OVER_LETTER[characterIndex + 1]
162: );
167: character = abi.encodePacked(
168: character,
169: ZALGO_BELOW_LETTER[characterIndex],
170: ZALGO_BELOW_LETTER[characterIndex + 1]
171: );
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]);
Description:
Use a solidity version of at least 0.8.4 to get bytes.concat()
instead of abi.encodePacked(<bytes>,<bytes>)
Use a solidity version of at least 0.8.12 to get string.concat()
instead of abi.encodePacked(<str>,<str>)
Context:
5 results - 3 files
/ProfilePicture.sol
94: function getPFP(uint256 _pfpID) public view returns (address nftContract, uint256 nftID)
102: nftContract = address(0);
103: nftID = 0; // Strictly not needed because nftContract has to be always checked, but reset nevertheless to 0
/Bio.sol
43: function tokenURI(uint256 _id) public view override returns (string memory)
92: prevByteWasContinuation = false;
93: bytesOffset = 0;
/Namespace.sol
110: function fuse(CharacterData[] calldata _characterList) external
129: isLastTrayEntry = false;
Description:
delete
has the same effect of reassigning variables to its default values based on their type and even saves gas
Reccomendation: Use delete instead of zero assignment to clear variables
Context:
1 result - 1 file
/ProfilePicture.sol
79: function mint(address _nftContract, uint256 _nftID) external
81: if (ERC721(_nftContract).ownerOf(_nftID) != msg.sender)
82: revert PFPNotOwnedByCaller(msg.sender, _nftContract, _nftID);
Description: Input parameters that require validation should be done first in functions to avoid execution of the rest of the function logic and waste gas
Recommendation: Consider shifting the input validation to the top of the function for the above instances
Context:
6 results - 2 files
/Utils.sol
64: uint256 private constant EMOJIS_BYTE_OFFSET_SIX_BYTES = 1515; // 17 * 3 + 366 * 4
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
256: function iteratePRNG(uint256 _currState) internal pure returns (uint256 iteratedState) {
257: unchecked {
258: iteratedState = _currState * 15485863;
259: iteratedState = (iteratedState * iteratedState * iteratedState) % 2038074743;
260: }
261: }
Description: Throughout the codebase, the project have generally practiced the use of _ for large number values except for the above instance
Reccomendation: Consider using underscore for number value to improve readability
1 result - 1 file
/ProfilePicture.sol
35: string public subprotocolName;
Description:
State variables that are not changed after deployment time (set in constructor) can be set as immutable
. This also saves deployment gas.
2 result - 1 file
/Tray.sol
231: function _beforeTokenTransfers
245: function _drawing(uint256 _seed) private pure returns (TileData memory tileData)
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.
Recommendation Include Natspec comments for the above instances
8 results - 5 files
/ProfilePicture.sol
70: function tokenURI(uint256 _id) public view override returns (string memory)
/Bio.sol
43: function tokenURI(uint256 _id) public view override returns (string memory)
/Namespace.sol
90: function tokenURI(uint256 _id) public view override returns (string memory)
/Tray.sol
119: function tokenURI(uint256 _id) public view override returns (string memory)
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)
/Utils.sol
73: function characterToUnicodeBytes
222: function generateSVG(Tray.TileData[] memory _tiles, bool _isTray) internal pure returns (string memory)
267: function _getUtfSequence(bytes memory _startingSequence, uint8 _characterIndex)
Description: 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.
If Return parameters are declared, you must prefix them with ”/// @return”.
Some code analysis programs do analysis by reading NatSpec details, if they can’t see the “@return” tag, they do incomplete analysis.
Recommendation Include return parameters in NatSpec comments
Context:
1 result - 1 file
/Utils.sol
55: // uint256 constant EMOJIS_LE_FOURTEEN_BYTES = 420;
Recommendation: Remove commented out code
5 results - 5 files
/ProfilePicture.sol
2: pragma solidity >=0.8.0;
/Bio.sol
2: pragma solidity >=0.8.0;
/Namespace.sol
2: pragma solidity >=0.8.0;
/Tray.sol
2: pragma solidity >=0.8.0;
/Utils.sol
2: pragma solidity >=0.8.0;
Description:
All the contracts in scope uses >=
to specify solidity compiler version.
Using >=
without also specifying <=
will lead to failures to compile, or external project incompatability, when the major version changes and there are breaking-changes, so locking of pragma is preferred regardless of the instance counts unless a contract is intended for consumption by other developers
Functions should be laid out in the following order for ease of search
- constructor
- receive function (if exists)
- fallback function (if exists)
- external
- public
- internal
- private
Within a grouping, place the view and pure functions last.
7 results - 4 files
/ProfilePicture.sol
79: function mint(address _nftContract, uint256 _nftID) external
/Bio.sol
43: function tokenURI(uint256 _id) public view override returns (string memory)
/Namespace.sol
90: function tokenURI(uint256 _id) public view override returns (string memory)
/Tray.sol
119: function tokenURI(uint256 _id) public view override returns (string memory)
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)
276: function _startTokenId() internal pure override returns (uint256)