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: 15/98
Findings: 2
Award: $308.01
🌟 Selected for report: 1
🚀 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
230.4175 USDC - $230.42
note,revenueAddress,prelaunchMinted values are not a constant value and can be changed with changeNoteAddress(),changeRevenueAddress() functions, before a function using note,revenueAddress,prelaunchMinted state variable value in the project, changeNoteAddress(),changeRevenueAddress() functions can be triggered by onlyOwner and operations can be blocked
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); }
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); } /// @notice End the prelaunch phase and start the public mint function endPrelaunchPhase() external onlyOwner { if (prelaunchMinted != type(uint256).max) revert PrelaunchAlreadyEnded(); prelaunchMinted = _nextTokenId() - 1; emit PrelaunchEnded(); }
Recommended Mitigation Steps Use a timelock to avoid instant changes of the parameters.
tx.origin is a global variable in Solidity that returns the address of the account that sent the transaction.
Using the variable could make a contract vulnerable if an authorized account calls a malicious contract. You can impersonate a user using a third party contract.
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
63: turnstile.register(tx.origin);
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
36: turnstile.register(tx.origin);
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
.mint won’t check if the recipient is able to receive the NFT. If an incorrect address is passed, it will result in a silent failure and loss of asset
OpenZeppelin recommendation is to use the safe variant of _mint
Recommendation Replace _mint() with _safeMint()
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
126: _mint(msg.sender, tokenId);
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
49: uint lines = (lengthInBytes - 1) / 40 + 1;
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
224: uint256 dx = 400 / (_tiles.length + 1);
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
77: uint8(bioTextBytes[i + 4]) >= 187 && 78: uint8(bioTextBytes[i + 4]) <= 191) ||
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
163: trayTiledata[j] = _drawing(uint256(lastHash));
250 : tileData.characterIndex = uint16(charRandValue % NUM_CHARS_EMOJIS);
252: tileData.characterIndex = uint16(charRandValue % NUM_CHARS_LETTERS);
tileData.fontClass = 3 + uint8((res - 80) / 8); } else if (res < 104) { tileData.fontClass = 5 + uint8((res - 96) / 4); } else if (res < 108) { tileData.fontClass = 7 + uint8((res - 104) / 2);
267: tileData.characterModifier = uint8(zalgoSeed % 256);
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
272: uint8 lastByteVal = uint8(_startingSequence[3]);
278: _startingSequence[2] = bytes1(uint8(_startingSequence[2]) + 1);
Recommended Mitigation Steps: Consider using OpenZeppelin’s SafeCast library to prevent unexpected overflows when casting from uint256.
Impact The onlyOwner role has a single point of failure and onlyOwner can use critical a few functions.
Even if protocol admins/developers are not malicious there is still a chance for Owner keys to be stolen. In such a case, the attacker can cause serious damage to the project due to important functions. In such a case, users who have invested in project will suffer high financial losses
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); }
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); } /// @notice End the prelaunch phase and start the public mint function endPrelaunchPhase() external onlyOwner { if (prelaunchMinted != type(uint256).max) revert PrelaunchAlreadyEnded(); prelaunchMinted = _nextTokenId() - 1; emit PrelaunchEnded(); }
Recommended Mitigation Steps Add a time lock to critical functions. Admin-only functions that change critical parameters should emit events and have timelocks.
Events allow capturing the changed parameters so that off-chain tools/interfaces can register such changes with timelocks that allow users to evaluate them and consider if they would like to engage/exit based on how they perceive the changes as affecting the trustworthiness of the protocol or profitability of the implemented financial services.
Also detail them in documentation and NatSpec comments.
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); }
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); }
Recommended Mitigation:
Add address(0) check before critical changes
On several locations in the code precautions are not being taken for not dividing by 0, this will revert the code. These functions can be called with 0 value in the input, this value is not checked for being bigger than 0, that means in some scenarios this can potentially trigger a division by zero
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
224: uint256 dx = 400 / (_tiles.length + 1);
Recommanad Mitigation:
Need to check the (_tiles.length + 1) value is not 0
Context: ALL CONTRACT SCOPE
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
2: pragma solidity >=0.8.0;
Recommendations: Use at least solidity 0.8.17
It’s possible to name the imports to improve code readability. E.g. import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; can be rewritten as import {IERC20} from “import “@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol”
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
import "../interface/Turnstile.sol"; import "../interface/ICidNFT.sol";
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
import "../interface/Turnstile.sol";
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
import "./Tray.sol"; import "./Utils.sol"; import "../interface/Turnstile.sol";
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
import "./Utils.sol"; import "../interface/Turnstile.sol";
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
4: import "./Tray.sol";
Recommandations:
import {IERC20} from “import “@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol”
It is not good practice to hardcode values, but if you are dealing with addresses much less, these can change between implementations, networks or projects, so it is convenient to remove these values from the source code
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
62: Turnstile turnstile = Turnstile(0xEcf044C5B4b867CFda001101c617eCd347095B44);
60: if (block.chainid == 7700) {
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
33: if (block.chainid == 7700) { 35: Turnstile turnstile = Turnstile(0xEcf044C5B4b867CFda001101c617eCd347095B44);
@return tag is missing
Recommandations:
Add @return tag when ever function returns any value
Some event-emit description hasn’t parameter. Add to parameter for front-end website or client app , they can has that something has happened on the blockchain
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
80: event PrelaunchEnded();
Consider changing the variable to be an unnamed one
Tray.sol#L245 Tray.sol#L203 Tray.sol#L195
CONTEXT ALL CONTRACTS
Consider using only one approach throughout the codebase, e.g. only uint or only uint256
Recommendations:
only uint or only uint256
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
14: ICidNFT private immutable cidNFT;
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
17: Tray public immutable tray;
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
37: uint256 public immutable trayPrice;
50: address public immutable namespaceNFT;
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
103: nftID = 0;
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
93: bytesOffset = 0;
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
function mint(address _nftContract, uint256 _nftID) external { uint256 tokenId = ++numMinted; if (ERC721(_nftContract).ownerOf(_nftID) != msg.sender) revert PFPNotOwnedByCaller(msg.sender, _nftContract, _nftID); ProfilePictureData storage pictureData = pfp[tokenId]; pictureData.nftContract = _nftContract;
internal and private functions : the mixedCase format starting with an underscore (_mixedCase starting with an underscore)
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
258: iteratedState = _currState * 15485863; 259: iteratedState = (iteratedState * iteratedState * iteratedState) % 2038074743;
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
258: iteratedState = _currState * 15485863; 259: iteratedState = (iteratedState * iteratedState * iteratedState) % 2038074743;
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); }
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
257: unchecked {
Recommendation
Use should fuzzing test like Echidna.
As Alberto Cuesta Canada said:
Fuzzing is not easy, the tools are rough, and the math is hard, but it is worth it. Fuzzing gives me a level of confidence in my smart contracts that I didn’t have before. Relying just on unit testing anymore and poking around in a testnet seems reckless now.
(https://medium.com/coinmonks/smart-contract-fuzzing-d9b88e0b0a05)
Context All Contracts
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. (https://docs.soliditylang.org/en/v0.8.15/natspec-format.html)
###[NC-18] Function writing that does not comply with the Solidity Style Guide
Context All Contracts
Description 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
It is a good practice to give time for users to react and adjust to critical changes. A timelock provides more guarantees and reduces the level of trust required, thus decreasing risk for users. It also indicates that the project is legitimate (less risk of a malicious owner making a sandwich attack on a user). Consider adding a timelock to
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); }
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); } /// @notice End the prelaunch phase and start the public mint function endPrelaunchPhase() external onlyOwner { if (prelaunchMinted != type(uint256).max) revert PrelaunchAlreadyEnded(); prelaunchMinted = _nextTokenId() - 1; emit PrelaunchEnded(); }
Bio.sol#L53 Bio.sol#L69 Bio.sol#L98 Namespace.sol#L35 Namespace.sol#L117 Namespace.sol#L126 Namespace.sol#L152 Tray.sol#L247 Utils.sol#L19-L49 Utils.sol#L69-L72 Utils.sol#L140 Utils.sol#L195 Utils.sol#L247
Recommendation Multiline output parameters and return statements should follow the same style recommended for wrapping long lines found in the Maximum Line Length section.
(https://docs.soliditylang.org/en/v0.8.17/style-guide.html#introduction)
Since this is a low level language that is more difficult to parse by readers, include extensive documentation, comments on the rationale behind its use, clearly explaining what each assembly instruction does.
This will make it easier for users to trust the code, for reviewers to validate the code, and for developers to build on or update the code.
Note that using Assembly removes several important security features of Solidity, which can make the code more insecure and more error-prone.
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
87: assembly {
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
165: assembly {
Lack of two-step procedure for critical operations leaves them error-prone. Consider adding two-step procedure on the critical functions.
Consider adding a two-steps pattern on critical changes to avoid mistakenly transferring ownership of roles or critical functionalities to the wrong address
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); }
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { address currentNoteAddress = address(note); note = ERC20(_newNoteAddress); emit NoteAddressUpdate(currentNoteAddress, _newNoteAddress); }
/// @notice Change the revenue address /// @param _newRevenueAddress New address to use function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { address currentRevenueAddress = revenueAddress; revenueAddress = _newRevenueAddress; emit RevenueAddressUpdated(currentRevenueAddress, _newRevenueAddress); } /// @notice End the prelaunch phase and start the public mint function endPrelaunchPhase() external onlyOwner { if (prelaunchMinted != type(uint256).max) revert PrelaunchAlreadyEnded(); prelaunchMinted = _nextTokenId() - 1; emit PrelaunchEnded(); }
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
ProfilePicture.sol#L40-L45 ProfilePicture.sol#L50-L52 Bio.sol#L23 Namespace.sol#L54-L67 Tray.sol#L78-L90
Group solmate/utils imports
import {ERC721A} from "erc721a/ERC721A.sol"; import {ERC20} from "solmate/tokens/ERC20.sol"; import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; import {Owned} from "solmate/auth/Owned.sol"; import {LibString} from "solmate/utils/LibString.sol"; import {Base64} from "solady/utils/Base64.sol"; import "./Utils.sol"; import "../interface/Turnstile.sol";
At the start of the project, the system may need to be stopped or upgraded, I suggest you have a script beforehand and add it to the documentation. This can also be called an ” EMERGENCY STOP (CIRCUIT BREAKER) PATTERN “.
(https://github.com/maxwoe/solidity_patterns/blob/master/security/EmergencyStop.sol)
#0 - c4-judge
2023-04-11T20:29:06Z
0xleastwood marked the issue as grade-a
#1 - c4-judge
2023-04-12T20:35:19Z
0xleastwood marked the issue as selected for report
#2 - 0xleastwood
2023-04-12T20:38:32Z
L-1 - Known issue.
L-3 - This is documented in Tray.buy()
.
L-6 - Known issue.
L-7 - Known issue.
NC-1 - Known issue.
NC-9 - Should be optimised out.
Rest of the issues are valid.
🌟 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
Use a Solidity version of at least 0.8.2 to get simple compiler automatic inlining.
Use a Solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads.
Use a Solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings.
Use a Solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value.
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
2: pragma solidity >=0.8.0;
public/external function names and public member variable names can be optimized to save gas. Below are the interfaces/abstract contracts that can be optimized so that the most frequently-called functions use the least amount of gas possible during method lookup. Method IDs that have two leading zero bytes can save 128 gas each during deployment, and renaming functions to have lower method IDs will save 22 gas per call, per sorted position shifted
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
///@audit mint(),getPFP() 8: contract ProfilePicture is ERC721 {
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
///@audit mint() 9: contract Bio is ERC721 {
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
///@audit fuse(),burn(),changeNoteAddress(),changeRevenueAddress() 11: contract Namespace is ERC721, Owned {
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
///@audit buy(),burn(),getTile(),getTiles(),changeNoteAddress(),changeRevenueAddress(),endPrelaunchPhase(), 13: contract Tray is ERC721A, Owned {
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
62: if (i != lengthInBytes - 1) {
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
if (prelaunchMinted != type(uint256).max) revert PrelaunchAlreadyEnded(); prelaunchMinted = _nextTokenId() - 1;
In Solidity, declaring a variable inside a loop can result in higher gas costs compared to declaring it outside the loop. This is because every time the loop runs, a new instance of the variable is created, which can lead to unnecessary memory allocation and increased gas costs
On the other hand, if you declare a variable outside the loop, the variable is only initialized once, and you can reuse the same instance of the variable inside the loop. This approach can save gas and optimize the execution of your code
GAS SAMPLE TEST IN remix ide(Without gas optimizations) :
Before Mitigation :
function testGas() public view { for(uint256 i = 0; i < 10; i++) { address collateral = msg.sender; address collateral1 = msg.sender; }
The execution Cost : 2176
After Mitigation :
function testGas() public view { address collateral; address collateral1; for(uint256 i = 0; i < 10; i++) { collateral = msg.sender; collateral1 = msg.sender; }
The execution Cost : 2086 .
Hence clearly after mitigation the gas cost is reduced. Approximately its possible to save 9 gas for every iterations
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
FILE: 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
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
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
mapping(string => uint256) public nameToToken;
/// @notice Maps NFT IDs to (ASCII) names mapping(uint256 => string) public tokenToName; /// @notice Stores the character data of an NFT mapping(uint256 => Tray.TileData[]) private nftCharacters;
Saves ~13 gas per instance
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
57: constructor(address _cidNFT, string memory _subprotocolName) ERC721("Profile Picture", "PFP") {
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
32: constructor() ERC721("Biography", "Bio") {
FIle: 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
73: constructor(
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
98: constructor(
FILE: 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size
(https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html)
Each operation involving a uint8 costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8) as compared to ones involving uint256, due to the compiler having to clear the higher bits of the memory word before operating on the uint8, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
131: uint8 asciiStartingIndex = 97; // Starting index for (lowercase) characters 138: uint8 asciiStartingIndex = 97; 272: uint8 lastByteVal = uint8(_startingSequence[3]); 273: uint8 lastByteSum = lastByteVal + _characterIndex;
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
73 : uint256 private prelaunchMinted = type(uint256).max;
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) {
Not inlining costs 20 to 40 gas because of two extra JUMP instructions and additional stack operations needed for function calls.
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Utils.sol
function characterToUnicodeBytes( uint8 _fontClass, uint16 _characterIndex, uint256 _characterModifier ) internal pure returns (bytes memory) {
function _getUtfSequence(bytes memory _startingSequence, uint8 _characterIndex) private pure returns (bytes memory)
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
245: function _drawing(uint256 _seed) private pure returns (TileData memory tileData) {
External functions do not require a context switch to the EVM, while public functions do.
Its possible to save 10-15 gas using external instead public for every function call
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
43: function tokenURI(uint256 _id) public view override returns (string memory) {
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
90: function tokenURI(uint256 _id) public view override returns (string memory) {
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
119: function tokenURI(uint256 _id) public view override returns (string memory) {
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
79: note = ERC20(_note); 80: revenueAddress = _revenueAddress;
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
107: revenueAddress = _revenueAddress; 108: note = ERC20(_note);
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
196: if (!_exists(_trayId)) revert TrayNotMinted(_trayId);
204: if (!_exists(_trayId)) revert TrayNotMinted(_trayId);
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
44: if (_ownerOf[_id] == address(0)) revert TokenNotMinted(_id);
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
91: if (_ownerOf[_id] == address(0)) revert TokenNotMinted(_id);
Recommendation You can consider adding a modifier like below.
modifier check() { require(!_exists(_trayId),""); _; }
If a function modifier or require such as onlyOwner-admin is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE(2), DUP1(3), ISZERO(3), PUSH2(3), JUMPI(10), PUSH1(3), DUP1(3), REVERT(0), JUMPDEST(1), POP(2) which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Namespace.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {
FILE : 2023-03-canto-identity/canto-namespace-protocol/src/Tray.sol
function changeNoteAddress(address _newNoteAddress) external onlyOwner { function changeRevenueAddress(address _newRevenueAddress) external onlyOwner { function endPrelaunchPhase() external onlyOwner {
Recommendation Functions guaranteed to revert when called by normal users can be marked payable (for only onlyOwner, mixedAuth, authorized functions)
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
bytes(_bio).length should be cached instead of re-calling the function
123: if (bytes(_bio).length == 0 || bytes(_bio).length > 200) revert InvalidBioLength(bytes(_bio).length);
Checks that involve constants should come before checks that involve state variables, function calls, and calculations. By doing these checks first, the function is able to revert before wasting a Gcoldsload (2100 gas*) in a function that may ultimately revert in the unhappy case
function mint(address _nftContract, uint256 _nftID) external { uint256 tokenId = ++numMinted; if (ERC721(_nftContract).ownerOf(_nftID) != msg.sender) revert PFPNotOwnedByCaller(msg.sender, _nftContract, _nftID); ProfilePictureData storage pictureData = pfp[tokenId]; pictureData.nftContract = _nftContract;
FILE : 2023-03-canto-identity/canto-pfp-protocol/src/ProfilePicture.sol
103: nftID = 0;
FILE : 2023-03-canto-identity/canto-bio-protocol/src/Bio.sol
93: bytesOffset = 0;
#0 - c4-judge
2023-04-11T05:35:59Z
0xleastwood marked the issue as grade-a