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: 80/141
Findings: 2
Award: $99.42
🌟 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
61.9486 USDC - $61.95
https://github.com/code-423n4/2022-07-fractional/blob/main/src/Vault.sol#L73
The install function in vault does not detect the value of plugins. As a result, address(0) can be passed in, which conflicts with uninstall
File: src\Vault.sol function install(bytes4[] memory _selectors, address[] memory _plugins) external { if (owner != msg.sender) revert NotOwner(owner, msg.sender); uint256 length = _selectors.length; for (uint256 i = 0; i < length; i++) { methods[_selectors[i]] = _plugins[i]; } emit InstallPlugin(_selectors, _plugins); } function uninstall(bytes4[] memory _selectors) external { if (owner != msg.sender) revert NotOwner(owner, msg.sender); uint256 length = _selectors.length; for (uint256 i = 0; i < length; i++) { methods[_selectors[i]] = address(0); } emit UninstallPlugin(_selectors); }
forge test
function initializeNFTETH() public view returns (bytes4[] memory, address[] memory) { address[] memory plugins = new address[](3); bytes4[] memory selectors = new bytes4[](3); (plugins[0], plugins[1], plugins[2]) = ( address(0),// this is address(0) address(0), address(0) ); (selectors[0], selectors[1], selectors[2]) = ( receiver.onERC1155Received.selector, receiver.onERC1155BatchReceived.selector, receiver.onERC721Received.selector ); return (selectors, plugins); } function testReceiveERC721() public { vaultProxy.init(); vaultProxy.install(nftReceiverSelectors1, nftReceiverPlugins1); MockERC721(erc721).mint(vault, 2); assertEq(IERC721(erc721).balanceOf(vault), 1); }
In this way, the plugins in _selectors are all 0
#0 - mehtaculous
2022-07-19T16:42:28Z
Duplicate of #495
#1 - HardlyDifficult
2022-08-03T21:45:46Z
By replacing the previous plugin using the same selector, this seems to result in a silent upgrade to the latest plugin. Agree this may lead to unintentional changes, but in that case the owner should be able to install again correcting the problem. Adding a check in this flow to make upgrades more explicit seems like a nice to have. Lowering severity and converting this into a QA report for the warden.
🌟 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.4667 USDC - $37.47
If the intention is for the Ether to be used, the function should call another function, otherwise it should revert
File: src\Vault.sol: 31 /// @dev Callback for receiving Ether when the calldata is empty 32: receive() external payable {} 33 File: src\mocks\MockEthReceiver.sol: 4 contract MockEthReceiver { 5: receive() external payable {} 6 } 8 contract MockEthNotReceiver { 9: receive() external payable { 10 revert(); File: src\mocks\MockSender.sol: 10 11: receive() external payable {} 12 } File: src\modules\Buyout.sol: 52 /// @dev Callback for receiving ether when the calldata is empty 53: receive() external payable {} 54 File: src\modules\Migration.sol: 62 /// @dev Callback for receiving ether when the calldata is empty 63: receive() external payable {}
keccak256()
, should use immutable
 rather than constant
File: src\constants\Permit.sol: 4 /// @dev The EIP-712 typehash for the contract's domain 5: bytes32 constant DOMAIN_TYPEHASH = keccak256( 6 "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" 9 /// @dev The EIP-712 typehash for the permit struct used by the contract 10: bytes32 constant PERMIT_TYPEHASH = keccak256( 11 "Permit(address owner,address operator,uint256 tokenId,bool approved,uint256 nonce,uint256 deadline)" 14 /// @dev The EIP-712 typehash for the permit all struct used by the contract 15: bytes32 constant PERMIT_ALL_TYPEHASH = keccak256( 16 "PermitAll(address owner,address operator,bool approved,uint256 nonce,uint256 deadline)"
File: src\Vault.sol: 77 uint256 length = _selectors.length; 78: for (uint256 i = 0; i < length; i++) { 79 methods[_selectors[i]] = _plugins[i]; 103 uint256 length = _selectors.length; 104: for (uint256 i = 0; i < length; i++) { 105 methods[_selectors[i]] = address(0); File: src\modules\protoforms\BaseVault.sol: 63 ) external { 64: for (uint256 i = 0; i < _tokens.length; ) { 65 IERC20(_tokens[i]).transferFrom(_from, _to, _amounts[i]); 82 ) external { 83: for (uint256 i = 0; i < _tokens.length; ) { 84 IERC721(_tokens[i]).safeTransferFrom(_from, _to, _ids[i]); 106 unchecked { 107: for (uint256 i = 0; i < _tokens.length; ++i) { 108 IERC1155(_tokens[i]).safeTransferFrom( File: src\utils\MerkleBase.sol: 50 unchecked { 51: for (uint256 i = 0; i < _proof.length; ++i) { 52 rollingHash = hashLeafPairs(rollingHash, _proof[i]);
Even memory arrays incur the overhead of bit tests and bit shifts to calculate the array length. Storage array length checks incur an extra Gwarmaccess (100 gas) PER-LOOP.
File: src\modules\protoforms\BaseVault.sol: 63 ) external { 64: for (uint256 i = 0; i < _tokens.length; ) { 65 IERC20(_tokens[i]).transferFrom(_from, _to, _amounts[i]); 82 ) external { 83: for (uint256 i = 0; i < _tokens.length; ) { 84 IERC721(_tokens[i]).safeTransferFrom(_from, _to, _ids[i]); 106 unchecked { 107: for (uint256 i = 0; i < _tokens.length; ++i) { 108 IERC1155(_tokens[i]).safeTransferFrom( 129 unchecked { 130: for (uint256 i; i < _modules.length; ++i) { 131 bytes32[] memory leaves = IModule(_modules[i]).getLeafNodes(); 132: for (uint256 j; j < leaves.length; ++j) { 133 hashes[counter++] = leaves[j]; File: src\utils\MerkleBase.sol: 50 unchecked { 51: for (uint256 i = 0; i < _proof.length; ++i) { 52 rollingHash = hashLeafPairs(rollingHash, _proof[i]); 109 uint256 offset; 110: for (uint256 i; i < result.length; ++i) { 111 if (result[i] != bytes32(0)) {