Platform: Code4rena
Start Date: 31/01/2023
Pot Size: $36,500 CANTO
Total HM: 5
Participants: 38
Period: 3 days
Judge: berndartmueller
Total Solo HM: 2
Id: 212
League: ETH
Rank: 19/38
Findings: 2
Award: $72.33
π Selected for report: 0
π Solo Findings: 0
π Selected for report: HardlyDifficult
Also found by: 0xAgro, 0xSmartContract, Aymen0909, DevABDee, JC, Matin, Rolezn, SleepingBugs, adriro, brevis, btk, chaduke, d3e4, enckrish, hihen, joestakey, libratus, merlin, nicobevi, rotcivegaf, shark, sorrynotsorry
149.2473 CANTO - $44.97
Issue | Risk | Instances | |
---|---|---|---|
1 | Immutable state variables lack zero address checks | Low | 3 |
2 | Use Openzeppelin EnumerableSet.UintSet for managing SubprotocolData.active | NC | 1 |
3 | Duplicated require() checks should be refactored to a modifier | NC | 2 |
4 | Use scientific notation | NC | 3 |
Constructors should check the values written in an immutable state variables(address) is not the zero value (address(0))
Instances include:
File: SubprotocolRegistry.sol Line 66
note = ERC20(_noteContract);
File: CidNFT.sol Line 129-130
note = ERC20(_noteContract); subprotocolRegistry = SubprotocolRegistry(_subprotocolRegistry);
Add non-zero address checks in the constructors for the instances aforementioned.
EnumerableSet.UintSet
for managing SubprotocolData.active
:In the CidNFT
contract you should consider using openzeppelin's EnumerableSet.UintSet
library for handling the SubprotocolData.active
values, this library has the functions add, remove, contains already pre-built and thus can be used immediatly in a single line of code by the contract instead of writing many lines to do so. The use of this library will improve the readability of the code.
The library can be used as follows :
using EnumerableSet for EnumerableSet.UintSet; struct SubprotocolData { /// @notice Mapping for ordered type mapping(uint256 => uint256) ordered; /// @notice Value for primary type uint256 primary; /// @notice List for active type EnumerableSet.UintSet active; }
And the contract add/remove logic should be replace by the inbuilt add/remove of the library and existance checks can be perfomed by the contains method.
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 statement is repeated in both add
and remove
functions :
address cidNFTOwner = ownerOf[_cidNFTID]; if ( cidNFTOwner != msg.sender && getApproved[_cidNFTID] != msg.sender && !isApprovedForAll[cidNFTOwner][msg.sender] ) revert NotAuthorizedForCIDNFT(msg.sender, _cidNFTID, cidNFTOwner);
Because this check is done on the caller (msg.sender) it can be placed at the beginning of the function and thus can be refactored into a modifier, it should be replaced by a isAllowed
modifier as follow :
modifier isAllowed(uint256 _cidNFTID){ address cidNFTOwner = ownerOf[_cidNFTID]; if ( cidNFTOwner != msg.sender && getApproved[_cidNFTID] != msg.sender && !isApprovedForAll[cidNFTOwner][msg.sender] ) revert NotAuthorizedForCIDNFT(msg.sender, _cidNFTID, cidNFTOwner); _; }
When using multiples of 10 you shouldn't use decimal literals or exponentiation (e.g. 1000000, 10**18) but use scientific notation for better readability.
Instances include:
File: CidNFT.sol Line 17
uint256 public constant CID_FEE_BPS = 1_000;
File: CidNFT.sol Line 191
uint256 cidFee = (subprotocolFee * CID_FEE_BPS) / 10_000;
File: SubprotocolRegistry.sol Line 17
uint256 public constant REGISTER_FEE = 100 * 10**18;
Use scientific notation for the instances aforementioned.
#0 - c4-judge
2023-02-18T12:52:05Z
berndartmueller marked the issue as grade-b
π Selected for report: HardlyDifficult
Also found by: 0xAgro, 0xSmartContract, 0xackermann, Aymen0909, Dravee, Matin, MiniGlome, NoamYakov, ReyAdmirado, Rolezn, Ruhum, c3phas, descharre, joestakey
90.8172 CANTO - $27.36
Issue | Instances | |
---|---|---|
1 | Optimize the register function (saves ~1800 Gas) | 1 |
2 | Use unchecked blocks to save gas | 2 |
3 | Duplicated input check statements should be refactored to a modifier | 2 |
4 | public functions not called by the contract should be declared external instead | 1 |
5 | ++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 | 1 |
register
function (saves ~1800 Gas) :The function register in the SubprotocolRegistry can be optimized to save ~1800 Gas (as shown in the table below) on each call by modifying the following :
subprotocolData
data directly to storage
instead of using memory
first.Savings :
min | avg | median | max | |
---|---|---|---|---|
Before | 5690 | 63110 | 54258 | 85453 |
After | 1180 | 61268 | 53434 | 84629 |
-1842 |
Thus the function should be modified as follow :
function register( bool _ordered, bool _primary, bool _active, address _nftAddress, string calldata _name, uint96 _fee ) external { if (!(_ordered || _primary || _active)) revert NoTypeSpecified(_name); if ( !ERC721(_nftAddress).supportsInterface( type(CidSubprotocolNFT).interfaceId ) ) revert NotASubprotocolNFT(_nftAddress); SubprotocolData storage subprotocolData = subprotocols[_name]; if (subprotocolData.owner != address(0)) revert SubprotocolAlreadyExists(_name, subprotocolData.owner); SafeTransferLib.safeTransferFrom( note, msg.sender, cidFeeWallet, REGISTER_FEE ); subprotocolData.owner = msg.sender; subprotocolData.fee = _fee; subprotocolData.nftAddress = _nftAddress; subprotocolData.ordered = _ordered; subprotocolData.primary = _primary; subprotocolData.active = _active; emit SubprotocolRegistered( msg.sender, _name, _nftAddress, _ordered, _primary, _active, _fee ); }
unchecked
blocks to save gas (saves ~40 gas) :Solidity version 0.8+ comes with implicit overflow and underflow checks on unsigned integers. When an overflow or an underflow isnβt possible (as an example, when a comparison is made before the arithmetic operation), some gas can be saved by using an unchecked block.
There are 2 instances of this issue:
File: CidNFT.sol Line 191
uint256 cidFee = (subprotocolFee * CID_FEE_BPS) / 10_000;
The above operation cannot overflow 2256 because the value of subprotocolFee
is always less than 296 and CID_FEE_BPS == 1000
, so the operation should be marked as unchecked
.
File: CidNFT.sol Line 225
activeData.positions[_nftIDToAdd] = lengthBeforeAddition + 1;
The above operation cannot realistically overflow as this whould lead to a very big tokens array (which is not really possible), so the operation should be marked as unchecked
.
The following check statement is repeated in both add
and remove
functions :
address cidNFTOwner = ownerOf[_cidNFTID]; if ( cidNFTOwner != msg.sender && getApproved[_cidNFTID] != msg.sender && !isApprovedForAll[cidNFTOwner][msg.sender] ) revert NotAuthorizedForCIDNFT(msg.sender, _cidNFTID, cidNFTOwner);
Because this check is done on the function caller (msg.sender) it can be placed at the beginning of the function and thus can be refactored into a modifier to save gas, it should be replaced by a isAllowed
modifier as follow :
modifier isAllowed(uint256 _cidNFTID){ address cidNFTOwner = ownerOf[_cidNFTID]; if ( cidNFTOwner != msg.sender && getApproved[_cidNFTID] != msg.sender && !isApprovedForAll[cidNFTOwner][msg.sender] ) revert NotAuthorizedForCIDNFT(msg.sender, _cidNFTID, cidNFTOwner); _; }
Savings (saves in total ~400 gas) :
min | avg | median | max | |
---|---|---|---|---|
Before | 8378 | 50503 | 52935 | 129892 |
After | 5460 | 50207 | 52941 | 129906 |
-296 |
min | avg | median | max | |
---|---|---|---|---|
Before | 6367 | 14082 | 9372 | 26324 |
After | 3460 | 13986 | 9378 | 26330 |
-96 |
public
functions not called by the contract should be declared external
instead :The public
functions that are not called inside the contract should be declared external
instead to save gas.
There is 1 instance of this issue:
File: CidNFT.sol Line 136
function tokenURI(uint256 _id) public view override returns (string memory)
++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 is 1 instance of this issue:
File: CidNFT.sol Line 150
for (uint256 i = 0; i < _addList.length; ++i)
#0 - c4-judge
2023-02-18T12:42:22Z
berndartmueller marked the issue as grade-b