Canto Identity Protocol contest - Aymen0909's results

Protocol Aggregating Protocol (PAP) for standardizing on-chain identity.

General Information

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

Canto Identity Protocol

Findings Distribution

Researcher Performance

Rank: 19/38

Findings: 2

Award: $72.33

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Awards

149.2473 CANTO - $44.97

Labels

bug
grade-b
QA (Quality Assurance)
Q-06

External Links

QA Report

Summary

IssueRiskInstances
1Immutable state variables lack zero address checksLow3
2Use Openzeppelin EnumerableSet.UintSet for managing SubprotocolData.activeNC1
3Duplicated require() checks should be refactored to a modifierNC2
4Use scientific notationNC3

Findings

1- Immutable state variables lack zero address checks :

Constructors should check the values written in an immutable state variables(address) is not the zero value (address(0))

Risk : Low
Proof of Concept

Instances include:

File: SubprotocolRegistry.sol Line 66

note = ERC20(_noteContract);

File: CidNFT.sol Line 129-130

note = ERC20(_noteContract); subprotocolRegistry = SubprotocolRegistry(_subprotocolRegistry);
Mitigation

Add non-zero address checks in the constructors for the instances aforementioned.

2- Use Openzeppelin EnumerableSet.UintSet for managing SubprotocolData.active :

Risk : NON CRITICAL

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.

3- Duplicated input check statements should be refactored to a modifier :

Input check statements used multiple times inside a contract should be refactored to a modifier for better readability and also to save gas.

Risk : NON CRITICAL
Proof of Concept

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); _; }

4- Use scientific notation :

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.

Risk : NON CRITICAL
Proof of Concept

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;
Mitigation

Use scientific notation for the instances aforementioned.

#0 - c4-judge

2023-02-18T12:52:05Z

berndartmueller marked the issue as grade-b

Findings Information

Labels

bug
G (Gas Optimization)
grade-b
G-05

Awards

90.8172 CANTO - $27.36

External Links

Gas Optimizations

Summary

IssueInstances
1Optimize the register function (saves ~1800 Gas)1
2Use unchecked blocks to save gas2
3Duplicated input check statements should be refactored to a modifier2
4public functions not called by the contract should be declared external instead1
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 loops1

Findings

1- Optimize the 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 :

  • Place the input check statements at the beginning of the function.
  • Save subprotocolData data directly to storage instead of using memory first.

Savings :

minavgmedianmax
Before5690631105425885453
After1180612685343484629
-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 ); }

2- Use 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.

3- Duplicated input check statements should be refactored to a modifier (saves ~400 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 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) :

  • add function (saves ~300 gas) :
minavgmedianmax
Before83785050352935129892
After54605020752941129906
-296
  • remove function (saves ~100 gas) :
minavgmedianmax
Before636714082937226324
After346013986937826330
-96

4- 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)

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 :

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

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter