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: 15/38
Findings: 2
Award: $135.96
π Selected for report: 0
π Solo Findings: 0
360.4223 CANTO - $108.60
https://github.com/code-423n4/2023-01-canto-identity/blob/main/src/AddressRegistry.sol#L43
Multiple people can register the same cidNFT in a way that the same "canonical on-chain identity" can be shared accross multiple real-life identities.
cidNFT
s can be transfered as any ERC721 token. After each transfer the new owner can register
the cidNFT
as his own within the AddressRegistry
contract. Thus, several addresses can be registered under the same cidNFTID
.
We can even imagine a malicious smart contract serving as a NFT flash loan reserve for anyone to register the same cidNFTID
. This goes against the basic concept of the protocol since it allows a shared identity.
Furthermore, people sharing the same registered cidNFTID
do not have to pay several times the fee to add
traits from Subprotocols
to there shared CidNFT
.
Here is an example of a flash loan pool allowing anyone to share the same cidNFTID
for free:
// SPDX-License-Identifier: GPL-3.0-only pragma solidity >=0.8.0; import "./CidNFT.sol"; contract NFTFlahLoan { CidNFT nft; constructor(address _cidNFT) { nft = CidNFT(_cidNFT); } function flashLoan(uint _id, address _to) external payable { require(nft.ownerOf(_id) == address(this), "Wrong id"); nft.transferFrom(address(this), _to, _id); (bool success, ) = address(_to).call{value:msg.value}(abi.encodeWithSignature("receiveFlashLoan(uint256)", _id)); require(success, "call Failed"); require(nft.ownerOf(_id) == address(this), "NFT must be returned"); } }
Inspection.
Remove the ability to register the same CidNFTID twice.
In addition to the cidNFTs
mapping that links addresses to cidNFTID
, there could be a mapping that links cidNFTID
s to owners
File: AddressRegistry.sol mapping(address => uint256) private cidNFTs; mapping(uint256 => address) private cidNFTsOwners; // Add this //... function register(uint256 _cidNFTID) external { if (ERC721(cidNFT).ownerOf(_cidNFTID) != msg.sender) revert NFTNotOwnedByUser(_cidNFTID, msg.sender); if (cidNFTsOwners[_cidNFTID] != address(0)) // Add this revert NFTAlreadyRegistered(); // Add this cidNFTsOwners[_cidNFTID] = msg.sender; // Add this cidNFTs[msg.sender] = _cidNFTID; emit CIDNFTAdded(msg.sender, _cidNFTID); }
#0 - c4-judge
2023-02-09T12:03:19Z
berndartmueller marked the issue as primary issue
#1 - c4-judge
2023-02-09T12:44:13Z
berndartmueller marked the issue as duplicate of #177
#2 - c4-judge
2023-02-17T21:36:07Z
berndartmueller changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-02-17T21:37:15Z
berndartmueller marked the issue as satisfactory
π 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
This report does not include publicly known issues
Issue | Instances | |
---|---|---|
[GAS-01] | ++i/i++ Should Be unchecked{++i} /unchecked{i++} When It Is Not Possible For Them To Overflow, As Is The Case When Used In For- And While-loops | 2 |
[GAS-02] | Using fixed bytes is cheaper than using string | 25 |
[GAS-03] | Setting the constructor to payable | 3 |
[GAS-04] | Using unchecked blocks to save gas | 5 |
[GAS-05] | require() /revert() statements that check input arguments should be at the top of the function | 5 |
Total: 40 instances over 5 issues
++i/i++
Should Be unchecked{++i}
/unchecked{i++}
When It Is Not Possible For Them To Overflow, As Is The Case When Used In For- And While-loopsThe unchecked keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP
File: CidNFT.sol L148: _mint(msg.sender, ++numMinted); L150: for (uint256 i = 0; i < _addList.length; ++i) {
string
Use bytes
for arbitrary-length raw byte data and string for arbitrary-length string
(UTF-8) data. If you can limit the length to a certain number of bytes, always use one of bytes1
to bytes32
because they are much cheaper.
// Before string a; function add(string str) { a = str; } // After bytes32 a; function add(bytes32 str) public { a = str; }
Consider limiting Subprotocols name to 32 bytes and replace name string
to bytes32
File: SubprotocolRegistry.sol L40: mapping(string => SubprotocolData) private subprotocols; L47: string indexed name, L58: error SubprotocolAlreadyExists(string name, address owner); L59: error NoTypeSpecified(string name); L84: string calldata _name, L106: function getSubprotocol(string calldata _name) external view returns (SubprotocolData memory) {
File: CidNFT.sol L70: mapping(uint256 => mapping(string => SubprotocolData)) internal cidData; L77: string indexed subprotocolName, L80: event PrimaryDataAdded(uint256 indexed cidNFTID, string indexed subprotocolName, uint256 subprotocolNFTID); L84: string indexed subprotocolName, L90: string indexed subprotocolName, L96: event PrimaryDataRemoved(uint256 indexed cidNFTID, string indexed subprotocolName, uint256 subprotocolNFTID); L95: event ActiveDataRemoved(uint256 indexed cidNFTID, string indexed subprotocolName, uint256 subprotocolNFTID); L102: error SubprotocolDoesNotExist(string subprotocolName); L104: error AssociationTypeNotSupportedForSubprotocol(AssociationType associationType, string subprotocolName); L107: error ActiveArrayAlreadyContainsID(uint256 cidNFTID, string subprotocolName, uint256 nftIDToAdd); L108: error OrderedValueNotSet(uint256 cidNFTID, string subprotocolName, uint256 key); L109: error PrimaryValueNotSet(uint256 cidNFTID, string subprotocolName); L110: error ActiveArrayDoesNotContainID(uint256 cidNFTID, string subprotocolName, uint256 nftIDToRemove); L167: string calldata _subprotocolName, L239: string calldata _subprotocolName, L297: string calldata _subprotocolName, L307: function getPrimaryData(uint256 _cidNFTID, string calldata _subprotocolName) L319: function getActiveData(uint256 _cidNFTID, string calldata _subprotocolName) L333: string calldata _subprotocolName,
constructor
to payable
Saves ~13 gas per instance
File: AddressRegistry.sol L36: constructor(address _cidNFT) {
File: CidNFT.sol L119: constructor( string memory _name, string memory _symbol, string memory _baseURI, address _cidFeeWallet, address _noteContract, address _subprotocolRegistry ) ERC721(_name, _symbol) {
File: SubprotocolRegistry.sol L65: constructor(address _noteContract, address _cidFeeWallet) {
unchecked
blocks to save gasSolidity 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.
File: CidNFT.sol L148: _mint(msg.sender, ++numMinted); L150: for (uint256 i = 0; i < _addList.length; ++i) { L179: uint256 befSwapLastNFTID = activeData.values[arrayLength - 1]; L180: activeData.values[arrayPosition - 1] = befSwapLastNFTID; L225: activeData.positions[_nftIDToAdd] = lengthBeforeAddition + 1;
require()
/revert()
statements that check input arguments should be at the top of the functionChecks 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.
File: CidNFT.sol L178: if ( cidNFTOwner != msg.sender && getApproved[_cidNFTID] != msg.sender && !isApprovedForAll[cidNFTOwner][msg.sender] ) revert NotAuthorizedForCIDNFT(msg.sender, _cidNFTID, cidNFTOwner); L183: if (_nftIDToAdd == 0) revert NFTIDZeroDisallowedForSubprotocols(); L250: if ( cidNFTOwner != msg.sender && getApproved[_cidNFTID] != msg.sender && !isApprovedForAll[cidNFTOwner][msg.sender] ) revert NotAuthorizedForCIDNFT(msg.sender, _cidNFTID, cidNFTOwner);
File: SubprotocolRegistry.sol L88: if (!(_ordered || _primary || _active)) revert NoTypeSpecified(_name); L94: if (!ERC721(_nftAddress).supportsInterface(type(CidSubprotocolNFT).interfaceId)) revert NotASubprotocolNFT(_nftAddress);
#0 - c4-judge
2023-02-18T12:42:48Z
berndartmueller marked the issue as grade-b