Canto Identity Subprotocols contest - Madalad's results

Subprotocols for Canto Identity Protocol.

General Information

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

Canto Identity Subprotocols

Findings Distribution

Researcher Performance

Rank: 86/98

Findings: 1

Award: $12.03

Gas:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Gas Optimizations Summary

IssueInstances
[G-01]abi.encodePacked is more gas efficient than abi.encode1
[G-02]Functions guaranteed to revert when called by normal users can be marked payable5
[G-03]Use assembly to calculate hashes1
[G-04]Division/Multiplication by 2 should use bit shifting5
[G-05]Use constant, immutable where applicable13
[G-06]Use unchecked for operations that cannot overflow/underflow16
[G-07]Change public functions to external3
[G-08]Use a more recent version of Solidity4
[G-09]Use named return values8

Total issues: 9

Total instances: 56

 

Gas Optimizations

[G-01] abi.encodePacked is more gas efficient than abi.encode

abi.encode pads all elementary types to 32 bytes, whereas abi.encodePacked will only use the minimal required memory to encode the data. See here for more info.

Instances: 1

File: canto-namespace-protocol/src/Tray.sol

162:                 lastHash = keccak256(abi.encode(lastHash));

 

[G-02] Functions guaranteed to revert when called by normal users can be marked payable

If a function modifier such as onlyOwner 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 (2400 per instance).

Instances: 5

File: canto-namespace-protocol/src/Namespace.sol

196:     function changeNoteAddress(address _newNoteAddress) external onlyOwner {

204:     function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {
File: canto-namespace-protocol/src/Tray.sol

210:     function changeNoteAddress(address _newNoteAddress) external onlyOwner {

218:     function changeRevenueAddress(address _newRevenueAddress) external onlyOwner {

225:     function endPrelaunchPhase() external onlyOwner {

 

[G-03] Use assembly to calculate hashes

Saves 5000 deployment gas per instance and 80 runtime gas per instance.

Unoptimized

function solidityHash(uint256 a, uint256 b) public view {
	//unoptimized
	keccak256(abi.encodePacked(a, b));
}

Optimized

function assemblyHash(uint256 a, uint256 b) public view {
	//optimized
	assembly {
		mstore(0x00, a)
		mstore(0x20, b)
		let hashedVal := keccak256(0x00, 0x40)
	}
}

Instances: 1

File: canto-namespace-protocol/src/Tray.sol

162:                 lastHash = keccak256(abi.encode(lastHash));

 

[G-04] Division/Multiplication by 2 should use bit shifting

<x> * 2 is equivalent to <x> << 1 and <x> / 2 is the same as <x> >> 1. The MUL and DIV opcodes cost 5 gas, whereas SHL and SHR only cost 3 gas.

Instances: 5

File: canto-namespace-protocol/src/Tray.sol

263:                 tileData.fontClass = 7 + uint8((res - 104) / 2);
File: canto-namespace-protocol/src/Utils.sol

148:                 uint256 characterIndex = (_characterModifier % ZALGO_NUM_ABOVE) * 2;

157:                 uint256 characterIndex = (_characterModifier % ZALGO_NUM_OVER) * 2;

166:                 uint256 characterIndex = (_characterModifier % ZALGO_NUM_BELOW) * 2;

201:                     uint256 offset = (_characterIndex - 2) * 2;

 

[G-05] Use constant, immutable where applicable

Use immutable if you want to assign a permanent value at construction. Use constants if you already know the permanent value. Both get directly embedded in bytecode, saving SLOAD. Variables only set in the constructor and never edited afterwards should be marked as immutable, as it would avoid the expensive storage-writing operation in the constructor (around 20 000 gas per variable) and replace the expensive storage-reading operations (around 2100 gas per reading) to a less expensive value reading (3 gas).

Instances: 13

File: canto-pfp-protocol/src/ProfilePicture.sol

35:     string public subprotocolName;

41:         address indexed minter,

42:         uint256 indexed pfpNftID,

43:         address indexed referencedContract,

44:         uint256 referencedNftId
File: canto-namespace-protocol/src/Namespace.sol

74:         address _tray,

75:         address _note,

76:         address _revenueAddress
File: canto-namespace-protocol/src/Tray.sol

99:         bytes32 _initHash,

100:         uint256 _trayPrice,

101:         address _revenueAddress,

102:         address _note,

103:         address _namespaceNFT

 

[G-06] Use unchecked for operations that cannot overflow/underflow

By bypassing Solidity's built in overflow/underflow checks using unchecked, we can save gas. This is especially beneficial for the index variable within for loops (saves 120 gas per iteration).

Instances: 16

File: canto-bio-protocol/src/Bio.sol

56:         for (uint i; i < lengthInBytes; ++i) {

59:             bytesOffset++;

100:         for (uint i; i < lines; ++i) {
File: canto-namespace-protocol/src/Namespace.sol

122:         for (uint256 i; i < numCharacters; ++i) {

127:             for (uint256 j = i + 1; j < numCharacters; ++j) {

147:             for (uint256 j; j < numBytesChar; ++j) {

150:             numBytes += numBytesChar;

174:         for (uint256 i; i < numUniqueTrays; ++i) {
File: canto-namespace-protocol/src/Tray.sol

129:         for (uint256 i; i < TILES_PER_TRAY; ++i) {

159:         for (uint256 i; i < _amount; ++i) {

161:             for (uint256 j; j < TILES_PER_TRAY; ++j) {
File: canto-namespace-protocol/src/Utils.sol

109:             for (uint256 i = 3; i < numBytes; ++i) {

146:             for (uint256 i; i < numAbove; ++i) {

155:             for (uint256 i; i < numMiddle; ++i) {

164:             for (uint256 i; i < numBelow; ++i) {

225:         for (uint256 i; i < _tiles.length; ++i) {

 

[G-07] Change public functions to external

Functions marked as public that are not called internally should be set to external to save gas and improve code quality. External call cost is less expensive than of public functions.

Instances: 3

File: canto-bio-protocol/src/Bio.sol

43:     function tokenURI(uint256 _id) public view override returns (string memory) {
File: canto-namespace-protocol/src/Namespace.sol

90:     function tokenURI(uint256 _id) public view override returns (string memory) {
File: canto-namespace-protocol/src/Tray.sol

119:     function tokenURI(uint256 _id) public view override returns (string memory) {

 

[G-08] Use a more recent version of Solidity

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.

Use a Solidity version of at least 0.8.12 to get string.concat() to be used instead of abi.encodePacked(<str>,<str>).

Use a solidity version of at least 0.8.13 to get the ability to use using for with a list of free functions.

Instances: 4

File: canto-pfp-protocol/src/ProfilePicture.sol

2: pragma solidity >=0.8.0;
File: canto-bio-protocol/src/Bio.sol

2: pragma solidity >=0.8.0;
File: canto-namespace-protocol/src/Namespace.sol

2: pragma solidity >=0.8.0;
File: canto-namespace-protocol/src/Tray.sol

2: pragma solidity >=0.8.0;

 

[G-09] Use named return values

Using named return values instead of explicitly calling return saves gas.

Instances: 8

File: canto-pfp-protocol/src/ProfilePicture.sol

70:     function tokenURI(uint256 _id) public view override returns (string memory) {
File: canto-bio-protocol/src/Bio.sol

43:     function tokenURI(uint256 _id) public view override returns (string memory) {
File: canto-namespace-protocol/src/Namespace.sol

90:     function tokenURI(uint256 _id) public view override returns (string memory) {
File: canto-namespace-protocol/src/Tray.sol

119:     function tokenURI(uint256 _id) public view override returns (string memory) {

276:     function _startTokenId() internal pure override returns (uint256) {
File: canto-namespace-protocol/src/Utils.sol

77:     ) internal pure returns (bytes memory) {

222:     function generateSVG(Tray.TileData[] memory _tiles, bool _isTray) internal pure returns (string memory) {

270:         returns (bytes memory)

 

#0 - c4-judge

2023-04-11T05:29:43Z

0xleastwood 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