Platform: Code4rena
Start Date: 07/07/2022
Pot Size: $75,000 USDC
Total HM: 32
Participants: 141
Period: 7 days
Judge: HardlyDifficult
Total Solo HM: 4
Id: 144
League: ETH
Rank: 94/141
Findings: 1
Award: $64.32
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: joestakey
Also found by: 0x1f8b, 0x29A, 0xA5DF, 0xKitsune, 0xNazgul, 0xNineDec, 0xalpharush, 0xkatana, 0xsanson, 0xsolstars, 8olidity, Avci, Bnke0x0, BowTiedWardens, Chom, Deivitto, ElKu, Fitraldys, Funen, IllIllI, JC, Kaiziron, Lambda, Limbooo, MEP, NoamYakov, PwnedNoMore, RedOneN, ReyAdmirado, Rohan16, Ruhum, Saintcode_, Sm4rty, TomJ, Tomio, TrungOre, Tutturu, Waze, _Adam, __141345__, ajtra, apostle0x01, asutorufos, benbaessler, brgltd, c3phas, codexploder, cryptphi, delfin454000, dharma09, djxploit, durianSausage, fatherOfBlocks, giovannidisiena, gogo, horsefacts, hrishibhat, hyh, ignacio, jocxyen, jonatascm, karanctf, kebabsec, kyteg, m_Rassska, mektigboy, oyc_109, pedr02b2, rbserver, robee, rokinot, sach1r0, sashik_eth, simon135, slywaters
64.3188 USDC - $64.32
unchecked{++i}
instead of i++
in loopsa = a + b
instead of a += b
a / 2^x, x > 0
MLOAD
<< SLOAD
immutable
instead of state variablesconstants/immutable/state
as private/internal
calldataloud
vs mload
Internal
functions can be inlinedContracts:
file: src/Vault.sol ............................... // Lines: [78-78] for (uint256 i = 0; i < length; i++) {} // Lines: [104-104] for (uint256 i = 0; i < length; i++) {}
unchecked{++i};
instead of i++;
in loops<a name="G-02"></a>Contracts:
file: src/Vault.sol ............................... // Lines: [78-78] for (uint256 i = 0; i < length; i++) {} // Lines: [104-104] for (uint256 i = 0; i < length; i++) {}
a = a + b
instead of a += b
<a name="G-03"></a>Contracts:
file: src/FERC1155.sol ............................... // Lines: [86-86] totalSupply[_id] += _amount; // Lines: [270-271] balanceOf[_from][_id] -= _amount; balanceOf[_to][_id] += _amount; file: src/Buyout.sol ............................... // Lines: [176-176] buyoutInfo[_vault].ethBalance += msg.value; // Lines: [139-139] buyoutInfo[_vault].ethBalance -= ethAmount; file: src/Migration.sol ............................... // Lines: [123-124] proposal.totalEth += msg.value; userProposalEth[_proposalId][msg.sender] += msg.value; // Lines: [134-135] proposal.totalFractions += _amount; userProposalFractions[_proposalId][msg.sender] += _amount; // Lines: [156-156] proposal.totalFractions -= amount; // Lines: [160-160] proposal.totalEth -= ethAmount; // Lines: [497-497] treeLength += IModule(_modules[i]).getLeafNodes().length; file: src/MerkleBase.sol ............................... // Lines: [147-147] for (uint256 i; i < length - 1; i += 2) {} // Lines: [190-190] ceil -= pOf2; // see above
Contracts:
file: src/FERC1155.sol ............................... // Lines: [56-63] function burn( address _from, uint256 _id, uint256 _amount ) external onlyRegistry {} // Lines: [79-87] function mint( address _to, uint256 _id, uint256 _amount, bytes memory _data ) external onlyRegistry {} // Lines: [198-200] function setContractURI(string calldata _uri) external onlyController {} // Lines: [205-211] function setMetadata(address _metadata, uint256 _id) external onlyController {} // Lines: [217-225] function setRoyalties( uint256 _id, address _receiver, uint256 _percentage ) external onlyController {} // Lines: [229-232] function transferController(address _newController) external onlyController {} ```
a / 2^x, x > 0
<a name="G-05"></a>Contracts:
file: src/MerkleBase.sol ............................... // Lines: [100-100] _node = _node / 2; // Lines: [136-136] result = new bytes32[](length / 2 + 1); // Lines: [142-142] result = new bytes32[](length / 2);
MLOAD
<< SLOAD
<a name="G-06"></a>MLOAD
costs only 3 units of gas, SLOAD
(warm access) is about 100 units. Therefore, cache, when it's possible.Contracts:
file: src/FERC1155.sol ............................... // Lines: [91-91] if (msg.sender != metadata[_id]) revert InvalidSender(metadata[_id], msg.sender); // Lines: [297-298] if (msg.sender != metadata[_id]) revert InvalidSender(metadata[_id], msg.sender); // Lines: [303-305] _controller == address(0) ? controllerAddress = INITIAL_CONTROLLER() : controllerAddress = _controller; file: src/Bayout.sol ............................... // Lines: [176-176] buyoutInfo[_vault].ethBalance += msg.value; file: src/Vault.sol ............................... // Lines: [25-25] if (nonce != 0) revert Initialized(owner, msg.sender, nonce); // Lines: [76, 87, 94, 102] if (owner != msg.sender) revert NotOwner(owner, msg.sender); // Lines: [132-132] if (owner_ != owner) revert OwnerChanged(owner_, owner); file: src/Migration.sol ............................... // Lines: [126-127; 137-138] // Comment: - proposal.totalEth += msg.value => proposal.totalEth = proposal.totalEth(SLOAD) + msg.value; - proposal.totalEth = proposal.totalEth(which you can store in memory to avoid SLOAD) + msg.value. proposal.totalEth += msg.value; userProposalEth[_proposalId][msg.sender] += msg.value; proposal.totalFractions += _amount; userProposalFractions[_proposalId][msg.sender] += _amount; file: src/Buyout.sol ............................... // Lines: [474-502] permissions[0] = Permission( address(this), supply, ISupply(supply).burn.selector ); permissions[1] = Permission( address(this), transfer, ITransfer(transfer).ERC20Transfer.selector ); permissions[2] = Permission( address(this), transfer, ITransfer(transfer).ERC721TransferFrom.selector ); permissions[3] = Permission( address(this), transfer, ITransfer(transfer).ERC1155TransferFrom.selector ); permissions[4] = Permission( address(this), transfer, ITransfer(transfer).ERC1155BatchTransferFrom.selector ); ```
immutable
instead of state variables<a name="G-07"></a>Contracts:
file: src/FERC1155.sol ............................... // Lines: [19-19] address internal _controller; // Lines: [21-21] string public contractURI; // Lines: [142-142] result = new bytes32[](length / 2); file: src/Vault.sol ............................... // Lines: [15-15] bytes32 public merkleRoot; // Lines: [17-17] uint256 public nonce; file: src/VaultFactory.sol ............................... // Lines: [15-15] address public implementation; file: src/Buyout.sol ............................... // Lines: [29-33] address public registry; address public supply; address public transfer;
constants/immutable/state
as private/internal
<a name="G-08"></a>Contracts:
file: src/FERC1155.sol ............................... // Lines: [15-17] string public constant NAME = "FERC1155"; string public constant VERSION = "1"; // Lines: [21-21] string public contractURI; // Lines: [23-30] mapping(address => mapping(address => mapping(uint256 => bool))) public isApproved; /// @notice Mapping of metadata contracts for token ID types => metadata address mapping(uint256 => address) public metadata; /// @notice Mapping to track account nonces for metadata txs owner => nonces mapping(address => uint256) public nonces; /// @notice Mapping to track total supply for token ID types => totalSupply mapping(uint256 => uint256) public totalSupply; file: src/Vault.sol ............................... // Lines: [21-21] mapping(bytes4 => address) public methods; file: src/VaultRegistry.sol ............................... // Lines: [17-21] address public immutable factory; address public immutable fNFT; address public immutable fNFTImplementation; // Lines: [23-25] mapping(address => uint256) public nextId; mapping(address => VaultInfo) public vaultToToken; file: src/Buyout.sol ............................... // Lines: [35-38] uint256 public constant PROPOSAL_PERIOD = 2 days; uint256 public constant REJECTION_PERIOD = 4 days; mapping(address => Auction) public buyoutInfo;
calldataloud
vs mload
<a name="G-09"></a>Contracts:
file: src/FERC1155.sol ............................... // Lines: [68-68] function emitSetURI(uint256 _id, string memory _uri) external {} // Lines: [79-79] // Comments: OZ marked `bytes memory _data`, it is because those functions are virtual, hence could be overrided. Therefore, allowing modifying args for those overrided versions. Here, we just have an external function, therefore it's better to read bytes directly from calldata, especially, if the `_data` is a massive flow. function mint( address _to, uint256 _id, uint256 _amount, bytes memory _data ) external onlyRegistry {} // Lines: [68-68] function emitSetURI(uint256 _id, string memory _uri) external {} file: src/Vault.sol ............................... // Lines: [73-75] function install(bytes4[] memory _selectors, address[] memory _plugins) external {} // Lines: [101-101] function uninstall(bytes4[] memory _selectors) external {} file: src/VaultRegistry.sol ............................... // Lines: [51-54] function create( bytes32 _merkleRoot, address[] memory _plugins, bytes4[] memory _selectors ) external returns (address vault) {} // Lines: [67-72] function createFor( bytes32 _merkleRoot, address _owner, address[] memory _plugins, bytes4[] memory _selectors ) external returns (address vault) {} // Lines: [67-72] function createCollection( bytes32 _merkleRoot, address[] memory _plugins, bytes4[] memory _selectors ) external returns (address vault, address token) {} // Lines: [102-107] function createInCollection( bytes32 _merkleRoot, address _token, address[] memory _plugins, bytes4[] memory _selectors ) external returns (address vault) {} // Lines: [147-152] function createCollectionFor( bytes32 _merkleRoot, address _controller, address[] memory _plugins, bytes4[] memory _selectors ) public returns (address vault, address token) {} // Lines: [165-170] function _deployVault( bytes32 _merkleRoot, address _token, address[] memory _plugins, bytes4[] memory _selectors ) private returns (address vault) {} file: src/Buyout.sol ............................... function batchWithdrawERC1155( address _vault, address _token, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes32[] calldata _erc1155BatchTransferProof ) external {} // Look, how beatufil it looks like with calldata. Thank you for that!!!!!!!
Internal
functions can be inlined<a name="G-10"></a>JUMP
s which costs around 12 gas uints for each JUMP
.Contracts:
file: src/FERC1155.sol ............................... // Lines: [324-330] function _computePermitStructHash( address _owner, address _operator, uint256 _id, bool _approved, uint256 _deadline ) internal returns (bytes32) {} // Lines: [350-355] function _computePermitAllStructHash( address _owner, address _operator, bool _approved, uint256 _deadline ) internal returns (bytes32) {} // Lines: [371-371] function _computeDomainSeparator() internal view returns (bytes32) {} // Lines: [388-392] function _computeDigest(bytes32 _domainSeparator, bytes32 _structHash) internal pure returns (bytes32) {}
Contracts:
file: src/FERC1155.sol ............................... // Lines: [309-316] function INITIAL_CONTROLLER() public pure returns (address) { return _getArgAddress(0); } function VAULT_REGISTRY() public pure returns (address) { return _getArgAddress(20); }
Contracts:
file: src/Vault.sol ............................... // Lines: [77-77] uint256 length = _selectors.length; // Lines: [103-103] uint256 length = _selectors.length;
address
to address
, etc...Contracts:
file: src/VaultRegistry.sol ............................... // Lines: [73-73] vault = _deployVault(_merkleRoot, address(fNFT), _plugins, _selectors); // Lines: [56-56] vault = _deployVault(_merkleRoot, address(fNFT), _plugins, _selectors); // Lines: [154-154] abi.encodePacked(_controller, address(this))