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: 61/141
Findings: 2
Award: $108.61
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: xiaoming90
Also found by: 0x1f8b, 0x29A, 0x52, 0xA5DF, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 0xsanson, 0xsolstars, 242, 8olidity, Amithuddar, Aymen0909, Bnke0x0, BowTiedWardens, David_, Deivitto, ElKu, Funen, Hawkeye, IllIllI, JC, Kaiziron, Keen_Sheen, Kthere, Kulk0, Kumpa, Lambda, MEP, ReyAdmirado, Rohan16, Ruhum, Sm4rty, TomJ, Tomio, Treasure-Seeker, TrungOre, Tutturu, Viksaa39, Waze, _Adam, __141345__, ak1, apostle0x01, asutorufos, async, ayeslick, aysha, bbrho, benbaessler, berndartmueller, c3phas, cccz, chatch, cloudjunky, codexploder, cryptphi, delfin454000, dipp, durianSausage, dy, exd0tpy, fatherOfBlocks, hake, hansfriese, horsefacts, hubble, joestakey, jonatascm, kebabsec, kenzo, kyteg, mektigboy, neumo, oyc_109, pashov, pedr02b2, peritoflores, rajatbeladiya, rbserver, robee, rokinot, s3cunda, sach1r0, sahar, sashik_eth, scaraven, shenwilly, simon135, sorrynotsorry, sseefried, svskaushik, unforgiven, z3s, zzzitron
71.1389 USDC - $71.14
Information : L001 - Unsafe ERC20 Operation(s)
utils/SafeSend.sol:33: WETH(WETH_ADDRESS).transfer(_to, _value); references/TransferReference.sol:22: IERC20(_token).transfer(_to, _value); modules/Migration.sol:175: payable(msg.sender).transfer(ethAmount); modules/Migration.sol:328: payable(msg.sender).transfer(userEth); modules/protoforms/BaseVault.sol:65: IERC20(_tokens[i]).transferFrom(_from, _to, _amounts[i]);
It is recommended to always use OpenZeppelin's SafeERC20
library, for example :
references/TransferReference.sol:22: IERC20(_token).safeTransfer(_to, _value);
_safeMint
should be used instead of _mint
wherever possibleIt is encouraged to use _safeMint
instead of _mint
, as _safeMint
ensures that the recipient is an EOA (Externally Owned Account).
FERC1155.sol:85: _mint(_to, _id, _amount, _data);
It is recommended to use _safeMint
instead of _mint
wherever possible, for example :
FERC1155.sol:85: _safeMint(_to, _id, _amount, _data);
Contracts that implement access control, e.g. owner
, should consider implementing a two-step transfer pattern.
Otherwise, it is possible that the role mistakenly transfers ownership to the wrong address, resulting in losing the role.
https://github.com/code-423n4/2022-07-fractional/blob/main/src/Vault.sol#L93-L97
Information : Block timestamp
ERC20.permit(address,address,uint256,uint256,uint8,bytes32,bytes32) (node_modules/@rari-capital/solmate/src/tokens/ERC20.sol#116-160) uses timestamp for comparisons Dangerous comparisons: - require(bool,string)(deadline >= block.timestamp,PERMIT_DEADLINE_EXPIRED) (node_modules/@rari-capital/solmate/src/tokens/ERC20.sol#125) FERC1155.permit(address,address,uint256,bool,uint256,uint8,bytes32,bytes32) (src/FERC1155.sol#98-135) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > _deadline (src/FERC1155.sol#108) FERC1155.permitAll(address,address,bool,uint256,uint8,bytes32,bytes32) (src/FERC1155.sol#145-180) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > _deadline (src/FERC1155.sol#154) Buyout.sellFractions(address,uint256) (src/modules/Buyout.sol#112-144) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > endTime (src/modules/Buyout.sol#125) Buyout.buyFractions(address,uint256) (src/modules/Buyout.sol#149-179) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > endTime (src/modules/Buyout.sol#162) Buyout.end(address,bytes32[]) (src/modules/Buyout.sol#184-239) uses timestamp for comparisons Dangerous comparisons: - block.timestamp <= endTime (src/modules/Buyout.sol#203) Migration.commit(address,uint256) (src/modules/Migration.sol#182-217) uses timestamp for comparisons Dangerous comparisons: - block.timestamp > proposal.startTime + PROPOSAL_PERIOD (src/modules/Migration.sol#197) - currentPrice > proposal.targetPrice (src/modules/Migration.sol#209) Migration.withdrawContribution(address,uint256) (src/modules/Migration.sol#295-329) uses timestamp for comparisons Dangerous comparisons: - current != State.INACTIVE || migrationInfo[_vault][_proposalId].newVault != address(0) (src/modules/Migration.sol#306-307)
Avoid relying on block.timestamp
.
Information : Calls inside a loop
Multicall.multicall(bytes[]) (src/utils/Multicall.sol#11-35) has external calls inside a loop: (success,result) = address(this).delegatecall(_data[i]) (src/utils/Multicall.sol#21) Migration.generateMerkleTree(address[]) (src/modules/Migration.sol#490-515) has external calls inside a loop: treeLength += IModule(_modules[i]).getLeafNodes().length (src/modules/Migration.sol#500) Migration.generateMerkleTree(address[]) (src/modules/Migration.sol#490-515) has external calls inside a loop: leaves = IModule(_modules[i_scope_0]).getLeafNodes() (src/modules/Migration.sol#508) BaseVault.batchDepositERC20(address,address,address[],uint256[]) (src/modules/protoforms/BaseVault.sol#58-70) has external calls inside a loop: IERC20(_tokens[i]).transferFrom(_from,_to,_amounts[i]) (src/modules/protoforms/BaseVault.sol#65) BaseVault.batchDepositERC721(address,address,address[],uint256[]) (src/modules/protoforms/BaseVault.sol#77-89) has external calls inside a loop: IERC721(_tokens[i]).safeTransferFrom(_from,_to,_ids[i]) (src/modules/protoforms/BaseVault.sol#84) BaseVault.batchDepositERC1155(address,address,address[],uint256[],uint256[],bytes[]) (src/modules/protoforms/BaseVault.sol#98-117) has external calls inside a loop: IERC1155(_tokens[i]).safeTransferFrom(_from,_to,_ids[i],_amounts[i],_datas[i]) (src/modules/protoforms/BaseVault.sol#108-114) BaseVault.generateMerkleTree(address[]) (src/modules/protoforms/BaseVault.sol#122-137) has external calls inside a loop: leaves = IModule(_modules[i]).getLeafNodes() (src/modules/protoforms/BaseVault.sol#131)
Favor pull over push strategy for external calls.
Information : Unused return
Buyout.end(address,bytes32[]) (src/modules/Buyout.sol#184-239) ignores return value by IVault(address(_vault)).execute(supply,data,_burnProof) (src/modules/Buyout.sol#219) Buyout.cash(address,bytes32[]) (src/modules/Buyout.sol#244-273) ignores return value by IVault(address(_vault)).execute(supply,data,_burnProof) (src/modules/Buyout.sol#264) Buyout.redeem(address,bytes32[]) (src/modules/Buyout.sol#278-303) ignores return value by IVault(address(_vault)).execute(supply,data,_burnProof) (src/modules/Buyout.sol#294) Buyout.withdrawERC20(address,address,address,uint256,bytes32[]) (src/modules/Buyout.sol#311-335) ignores return value by IVault(address(_vault)).execute(transfer,data,_erc20TransferProof) (src/modules/Buyout.sol#334) Buyout.withdrawERC721(address,address,address,uint256,bytes32[]) (src/modules/Buyout.sol#343-367) ignores return value by IVault(address(_vault)).execute(transfer,data,_erc721TransferProof) (src/modules/Buyout.sol#366) Buyout.withdrawERC1155(address,address,address,uint256,uint256,bytes32[]) (src/modules/Buyout.sol#376-404) ignores return value by IVault(address(_vault)).execute(transfer,data,_erc1155TransferProof) (src/modules/Buyout.sol#403) Buyout.batchWithdrawERC1155(address,address,address,uint256[],uint256[],bytes32[]) (src/modules/Buyout.sol#413-445) ignores return value by IVault(address(_vault)).execute(transfer,data,_erc1155BatchTransferProof) (src/modules/Buyout.sol#440-444) Minter._mintFractions(address,address,uint256,bytes32[]) (src/modules/Minter.sol#50-61) ignores return value by IVault(address(_vault)).execute(supply,data,_mintProof) (src/modules/Minter.sol#60)
Ensure that all the return values of the function calls are used.
Information : Divide before multiply
Supply.mint(address,uint256) (src/targets/Supply.sol#23-103) performs a multiplication on the result of a division: -returnDataWords_mint_asm_0 = returndatasize()() / 0x20 (src/targets/Supply.sol#49) -cost_mint_asm_0 = 3 * returnDataWords_mint_asm_0 (src/targets/Supply.sol#57) Supply.mint(address,uint256) (src/targets/Supply.sol#23-103) performs a multiplication on the result of a division: -msizeWords_mint_asm_0 = memPointer_mint_asm_0 / 0x20 (src/targets/Supply.sol#54) -cost_mint_asm_0 = cost_mint_asm_0 + returnDataWords_mint_asm_0 - msizeWords_mint_asm_0 * 3 + returnDataWords_mint_asm_0 * returnDataWords_mint_asm_0 - msizeWords_mint_asm_0 * msizeWords_mint_asm_0 / 0x200 (src/targets/Supply.sol#61-73) Supply.burn(address,uint256) (src/targets/Supply.sol#108-188) performs a multiplication on the result of a division: -returnDataWords_burn_asm_0 = returndatasize()() / 0x20 (src/targets/Supply.sol#134) -cost_burn_asm_0 = 3 * returnDataWords_burn_asm_0 (src/targets/Supply.sol#142) Supply.burn(address,uint256) (src/targets/Supply.sol#108-188) performs a multiplication on the result of a division: -msizeWords_burn_asm_0 = memPointer_burn_asm_0 / 0x20 (src/targets/Supply.sol#139) -cost_burn_asm_0 = cost_burn_asm_0 + returnDataWords_burn_asm_0 - msizeWords_burn_asm_0 * 3 + returnDataWords_burn_asm_0 * returnDataWords_burn_asm_0 - msizeWords_burn_asm_0 * msizeWords_burn_asm_0 / 0x200 (src/targets/Supply.sol#146-158) Transfer.ERC20Transfer(address,address,uint256) (src/targets/Transfer.sol#18-177) performs a multiplication on the result of a division: -returnDataWords_ERC20Transfer_asm_0 = returndatasize()() + 0x1f / 0x20 (src/targets/Transfer.sol#75-78) -cost_ERC20Transfer_asm_0 = 3 * returnDataWords_ERC20Transfer_asm_0 (src/targets/Transfer.sol#87) Transfer.ERC20Transfer(address,address,uint256) (src/targets/Transfer.sol#18-177) performs a multiplication on the result of a division: -msizeWords_ERC20Transfer_asm_0 = memPointer_ERC20Transfer_asm_0 / 0x20 (src/targets/Transfer.sol#84) -cost_ERC20Transfer_asm_0 = cost_ERC20Transfer_asm_0 + returnDataWords_ERC20Transfer_asm_0 - msizeWords_ERC20Transfer_asm_0 * 3 + returnDataWords_ERC20Transfer_asm_0 * returnDataWords_ERC20Transfer_asm_0 - msizeWords_ERC20Transfer_asm_0 * msizeWords_ERC20Transfer_asm_0 / 0x200 (src/targets/Transfer.sol#91-112) Transfer.ERC721TransferFrom(address,address,address,uint256) (src/targets/Transfer.sol#184-287) performs a multiplication on the result of a division: -returnDataWords_ERC721TransferFrom_asm_0 = returndatasize()() + 0x20 / 0x20 (src/targets/Transfer.sol#226-229) -cost_ERC721TransferFrom_asm_0 = 3 * returnDataWords_ERC721TransferFrom_asm_0 (src/targets/Transfer.sol#237) Transfer.ERC721TransferFrom(address,address,address,uint256) (src/targets/Transfer.sol#184-287) performs a multiplication on the result of a division: -msizeWords_ERC721TransferFrom_asm_0 = memPointer_ERC721TransferFrom_asm_0 / 0x20 (src/targets/Transfer.sol#234) -cost_ERC721TransferFrom_asm_0 = cost_ERC721TransferFrom_asm_0 + returnDataWords_ERC721TransferFrom_asm_0 - msizeWords_ERC721TransferFrom_asm_0 * 3 + returnDataWords_ERC721TransferFrom_asm_0 * returnDataWords_ERC721TransferFrom_asm_0 - msizeWords_ERC721TransferFrom_asm_0 * msizeWords_ERC721TransferFrom_asm_0 / 0x200 (src/targets/Transfer.sol#241-253) Transfer.ERC1155TransferFrom(address,address,address,uint256,uint256) (src/targets/Transfer.sol#295-408) performs a multiplication on the result of a division: -returnDataWords_ERC1155TransferFrom_asm_0 = returndatasize()() + 0x20 / 0x20 (src/targets/Transfer.sol#343-346) -cost_ERC1155TransferFrom_asm_0 = 3 * returnDataWords_ERC1155TransferFrom_asm_0 (src/targets/Transfer.sol#354) Transfer.ERC1155TransferFrom(address,address,address,uint256,uint256) (src/targets/Transfer.sol#295-408) performs a multiplication on the result of a division: -msizeWords_ERC1155TransferFrom_asm_0 = memPointer_ERC1155TransferFrom_asm_0 / 0x20 (src/targets/Transfer.sol#351) -cost_ERC1155TransferFrom_asm_0 = cost_ERC1155TransferFrom_asm_0 + returnDataWords_ERC1155TransferFrom_asm_0 - msizeWords_ERC1155TransferFrom_asm_0 * 3 + returnDataWords_ERC1155TransferFrom_asm_0 * returnDataWords_ERC1155TransferFrom_asm_0 - msizeWords_ERC1155TransferFrom_asm_0 * msizeWords_ERC1155TransferFrom_asm_0 / 0x200 (src/targets/Transfer.sol#358-370) Transfer.ERC1155BatchTransferFrom(address,address,address,uint256[],uint256[]) (src/targets/Transfer.sol#411-580) performs a multiplication on the result of a division: -returnDataWords_ERC1155BatchTransferFrom_asm_0 = returndatasize()() + 0x20 / 0x20 (src/targets/Transfer.sol#512-515) -cost_ERC1155BatchTransferFrom_asm_0 = 3 * returnDataWords_ERC1155BatchTransferFrom_asm_0 (src/targets/Transfer.sol#528) Transfer.ERC1155BatchTransferFrom(address,address,address,uint256[],uint256[]) (src/targets/Transfer.sol#411-580) performs a multiplication on the result of a division: -msizeWords_ERC1155BatchTransferFrom_asm_0 = transferDataSize_ERC1155BatchTransferFrom_asm_0 / 0x20 (src/targets/Transfer.sol#525) -cost_ERC1155BatchTransferFrom_asm_0 = cost_ERC1155BatchTransferFrom_asm_0 + returnDataWords_ERC1155BatchTransferFrom_asm_0 - msizeWords_ERC1155BatchTransferFrom_asm_0 * 3 + returnDataWords_ERC1155BatchTransferFrom_asm_0 * returnDataWords_ERC1155BatchTransferFrom_asm_0 - msizeWords_ERC1155BatchTransferFrom_asm_0 * msizeWords_ERC1155BatchTransferFrom_asm_0 / 0x200 (src/targets/Transfer.sol#532-544)
Consider ordering multiplication before division.
Information : reentrancy-events reentrancy-benign reentrancy-no-eth reentrancy-eth check-effects-interactions pattern
Reentrancy in Migration.commit(address,uint256) (src/modules/Migration.sol#182-217): External calls: - IFERC1155(token).setApprovalFor(address(buyout),id,true) (src/modules/Migration.sol#211) - IBuyout(buyout).start{value: proposal.totalEth}(_vault) (src/modules/Migration.sol#213) External calls sending eth: - IBuyout(buyout).start{value: proposal.totalEth}(_vault) (src/modules/Migration.sol#213) State variables written after the call(s): - proposal.isCommited = true (src/modules/Migration.sol#214) Reentrancy in VaultFactory.deployFor(address) (src/VaultFactory.sol#62-89): External calls: - Vault(vault).init() (src/VaultFactory.sol#70) - Vault(vault).transferOwnership(_owner) (src/VaultFactory.sol#73) State variables written after the call(s): - nextSeeds[tx.origin] = bytes32(uint256(seed) + 1) (src/VaultFactory.sol#77) Reentrancy in Migration.join(address,uint256,uint256) (src/modules/Migration.sol#108-139): External calls: - IFERC1155(token).safeTransferFrom(msg.sender,address(this),id,_amount,) (src/modules/Migration.sol#129-135) State variables written after the call(s): - proposal.totalFractions += _amount (src/modules/Migration.sol#137) Reentrancy in Migration.settleFractions(address,uint256,bytes32[]) (src/modules/Migration.sol#260-290): External calls: - _mintFractions(proposal.newVault,address(this),proposal.newFractionSupply,_mintProof) (src/modules/Migration.sol#275-280) - data = abi.encodeCall(ISupply.mint,(_to,_fractionSupply)) (src/modules/Minter.sol#56-59) - IVault(address(_vault)).execute(supply,data,_mintProof) (src/modules/Minter.sol#60) State variables written after the call(s): - migrationInfo[_vault][_proposalId].fractionsMigrated = true (src/modules/Migration.sol#282) Reentrancy in Migration.settleVault(address,uint256) (src/modules/Migration.sol#223-254): External calls: - newVault = IVaultRegistry(registry).create(merkleRoot,proposal.plugins,proposal.selectors) (src/modules/Migration.sol#238-242) State variables written after the call(s): - proposal.newVault = newVault (src/modules/Migration.sol#244)
Apply the check-effects-interactions pattern.
#0 - HardlyDifficult
2022-07-26T17:42:18Z
Merging with #643
🌟 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
37.4674 USDC - $37.47
Uninitialized variables are assigned with the default value of their type, initializing a variable with its default value costs unnecessary gas.
utils/MerkleBase.sol:51: for (uint256 i = 0; i < _proof.length; ++i) { modules/protoforms/BaseVault.sol:64: for (uint256 i = 0; i < _tokens.length; ) { modules/protoforms/BaseVault.sol:83: for (uint256 i = 0; i < _tokens.length; ) { modules/protoforms/BaseVault.sol:107: for (uint256 i = 0; i < _tokens.length; ++i) { Vault.sol:78: for (uint256 i = 0; i < length; i++) { Vault.sol:104: for (uint256 i = 0; i < length; i++) {
It is recommended to initialize variables without assigning them the default value, for example :
utils/MerkleBase.sol:51: for (uint256 i; i < _proof.length; ++i) {
Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.
utils/MerkleBase.sol:51: for (uint256 i = 0; i < _proof.length; ++i) { utils/MerkleBase.sol:62: require(_data.length > 1, "wont generate root for single leaf"); utils/MerkleBase.sol:78: require(_data.length > 1, "wont generate proof for single leaf"); utils/MerkleBase.sol:110: for (uint256 i; i < result.length; ++i) { modules/protoforms/BaseVault.sol:64: for (uint256 i = 0; i < _tokens.length; ) { modules/protoforms/BaseVault.sol:83: for (uint256 i = 0; i < _tokens.length; ) { modules/protoforms/BaseVault.sol:107: for (uint256 i = 0; i < _tokens.length; ++i) { modules/protoforms/BaseVault.sol:130: for (uint256 i; i < _modules.length; ++i) { modules/protoforms/BaseVault.sol:132: for (uint256 j; j < leaves.length; ++j) { modules/Buyout.sol:454: for (uint256 i; i < permissions.length; ) {
It is recommended to cache the array length on a variable before running the loop, then it doesn't need to read the length on every iteration, which cost gas, for example :
uint256 len = _proof.length; utils/MerkleBase.sol:51: for (uint256 i = 0; i < len; ++i) {
If the divisor/multiplier x
is a power of 2, it can be calculated by shifting log2(x)
to the right/left. Division with /
cost more gas compared to bitwise shifting.
utils/MerkleBase.sol:100: _node = _node / 2; utils/MerkleBase.sol:136: result = new bytes32[](length / 2 + 1); utils/MerkleBase.sol:142: result = new bytes32[](length / 2);
It is recommended to replace /
and *
with >>
and <<
respectively and divisor/multiplier x
to log2(x)
, for division/multiplication where the divisor/multiplier is a power of 2, for example :
utils/MerkleBase.sol:100: _node = _node >> 1;
Prefix increment ++i
returns the updated value after it's incremented and postfix increment i++
returns the original value then increments it. Prefix increment costs less gas compared to postfix increment.
utils/MerkleBase.sol:188: ceil++; FERC1155.sol:339: nonces[_owner]++, FERC1155.sol:363: nonces[_owner]++, Vault.sol:78: for (uint256 i = 0; i < length; i++) { Vault.sol:104: for (uint256 i = 0; i < length; i++) {
It is recommended to use prefix increment instead of postfix one when the return value is not needed, as both of them will give the same result and prefix increment costs less gas.
For example :
utils/MerkleBase.sol:188: ++ceil;
Shortening revert strings to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met.
utils/MerkleBase.sol:62: require(_data.length > 1, "wont generate root for single leaf"); utils/MerkleBase.sol:78: require(_data.length > 1, "wont generate proof for single leaf");
It is recommended to use error code and providing a reference to the error code instead of a long revert string., for example :
// Link to the reference of error codes utils/MerkleBase.sol:78: require(_data.length > 1, "ERR");
Public functions that are never called by the contract should be declared external to save gas.
verifyProof(bytes32,bytes32[],bytes32) should be declared external: - MerkleBase.verifyProof(bytes32,bytes32[],bytes32) (src/utils/MerkleBase.sol#43-56) uri(uint256) should be declared external: - Metadata.uri(uint256) (src/utils/Metadata.sol#39-41) selfPermit(address,uint256,bool,uint256,uint8,bytes32,bytes32) should be declared external: - SelfPermit.selfPermit(address,uint256,bool,uint256,uint8,bytes32,bytes32) (src/utils/SelfPermit.sol#18-37) selfPermitAll(address,bool,uint256,uint8,bytes32,bytes32) should be declared external: - SelfPermit.selfPermitAll(address,bool,uint256,uint8,bytes32,bytes32) (src/utils/SelfPermit.sol#46-63)
Use the external attribute for functions never called from the contract.