Platform: Code4rena
Start Date: 12/07/2022
Pot Size: $75,000 USDC
Total HM: 16
Participants: 100
Period: 7 days
Judge: LSDan
Total Solo HM: 7
Id: 145
League: ETH
Rank: 68/100
Findings: 2
Award: $118.73
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 8olidity, Aussie_Battlers, Bnke0x0, Ch_301, Critical, Deivitto, Dravee, ElKu, Funen, GimelSec, JC, JohnSmith, Lambda, MiloTruck, PwnedNoMore, ReyAdmirado, Rohan16, Rolezn, Ruhum, RustyRabbit, Sm4rty, TomJ, Waze, _Adam, __141345__, alan724, asutorufos, benbaessler, berndartmueller, bin2chen, brgltd, bulej93, c3phas, cRat1st0s, cryptonue, cryptphi, csanuragjain, delfin454000, dxdv, exd0tpy, fatherOfBlocks, gogo, hake, hyh, joestakey, kyteg, lcfr_eth, minhtrng, p_crypt0, pashov, pedr02b2, philogy, rajatbeladiya, rbserver, rishabh, robee, rokinot, sach1r0, sashik_eth, seyni, simon135, svskaushik, zuhaibmohd, zzzitron
78.8739 USDC - $78.87
At least these but possibly any which users interact with.
ETHRegistrarController BaseRegistrarImplementation Resolver* NameWrapper
Suggestion is mostly for IERC20 but ERC721's have been sent to the contracts as well.
In the past there has been significant loss from users sending the wrong tokens to ENS contracts. Including over 500k USD of ENS tokens directly.
These tokens can never be rescued as the current contracts do not include an IERC20 withdraw function. Implementing this would allow the ENS DAO to work with the senders of the tokens to rescue funds from future mistakes like these listed below.
17WETH in the ETHRegistrarController (25k usd) https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2?a=0x283af0b28c62c092c9727f1ee09c02ca627eb7f5
4400 APE in the ETHRegistrarController (25k usd) https://etherscan.io/token/0x4d224452801aced8b2f0aebe155379bb5d594381?a=0x283af0b28c62c092c9727f1ee09c02ca627eb7f5
125 ENS tokens to the BaseRegistrar https://etherscan.io/token/0xc18360217d8f7ab5e7c516566761ea12ce7f9d72?a=0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85
46000 ENS tokens sent to the PublicResolver (500k USD) this also affects voting weights of the ENS token. https://etherscan.io/token/0xc18360217d8f7ab5e7c516566761ea12ce7f9d72?a=0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41
The function(s) should be protected by onlyOwner, onlyController or some form of access control.
Function example for IERC20:
// withdraw any ERC20 tokens sent by mistake. function _adminWithdrawERC20(address _token, address _where) external onlyOwner() { IERC20(_token).transfer(_where, IERC20(_token).balanceOf(address(this))); }
🌟 Selected for report: 0xKitsune
Also found by: 0x040, 0x1f8b, 0x29A, 0xNazgul, 0xNineDec, 0xsam, 8olidity, Aussie_Battlers, Aymen0909, Bnke0x0, CRYP70, Ch_301, Chom, Deivitto, Dravee, ElKu, Fitraldys, Funen, GimelSec, IllIllI, JC, JohnSmith, Lambda, MiloTruck, Noah3o6, RedOneN, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, TomJ, Tomio, Waze, _Adam, __141345__, ajtra, ak1, arcoun, asutorufos, benbaessler, brgltd, bulej93, c3phas, cRat1st0s, cryptonue, delfin454000, durianSausage, fatherOfBlocks, gogo, hake, hyh, joestakey, karanctf, kyteg, lcfr_eth, lucacez, m_Rassska, rajatbeladiya, rbserver, robee, rokinot, sach1r0, sahar, samruna, sashik_eth, seyni, simon135, zuhaibmohd
39.8564 USDC - $39.86
https://github.com/code-423n4/2022-07-ens/blob/main/contracts/ethregistrar/ETHRegistrarController.sol#L249 Function: _setRecords
function _setRecords( address resolver, bytes32 label, bytes[] calldata data ) internal { // use hardcoded .eth namehash bytes32 nodehash = keccak256(abi.encodePacked(ETH_NODE, label)); for (uint256 i = 0; i < data.length; i++) { // check first few bytes are namehash bytes32 txNamehash = bytes32(data[i][4:36]); require( txNamehash == nodehash, "ETHRegistrarController: Namehash on record do not match the name being registered" ); resolver.functionCall( data[i], "ETHRegistrarController: Failed to set Record" ); } }
Place the length outside of the array and use unchecked{}, ++i
New _setRecords function:
function _setRecords( address resolver, bytes32 label, bytes[] calldata data ) internal { // use hardcoded .eth namehash bytes32 nodehash = keccak256(abi.encodePacked(ETH_NODE, label)); uint256 length = data.lenth; for (uint256 i = 0; i < length; ) { // check first few bytes are namehash bytes32 txNamehash = bytes32(data[i][4:36]); require( txNamehash == nodehash, "ETHRegistrarController: Namehash on record does not match name being registered" ); resolver.functionCall( data[i], "ETHRegistrarController: Failed to set Record" ); unchecked{ ++i; } } }
https://github.com/code-423n4/2022-07-ens/blob/main/contracts/wrapper/ERC1155Fuse.sol#L78 function: balanceOfBatch
function balanceOfBatch(address[] memory accounts, uint256[] memory ids) public view virtual override returns (uint256[] memory) { require( accounts.length == ids.length, "ERC1155: accounts and ids length mismatch" ); uint256[] memory batchBalances = new uint256[](accounts.length); for (uint256 i = 0; i < accounts.length; ++i) { batchBalances[i] = balanceOf(accounts[i], ids[i]); } return batchBalances; }
Place the length outside of the array and use unchecked{}, ++i
new balanceOfBatch function
function balanceOfBatch(address[] memory accounts, uint256[] memory ids) public view virtual override returns (uint256[] memory) { require( accounts.length == ids.length, "ERC1155: accounts and ids length mismatch" ); uint256[] memory batchBalances = new uint256[](accounts.length); uint256 length = accounts.length; for (uint256 i = 0; i < length;) { batchBalances[i] = balanceOf(accounts[i], ids[i]); unchecked{ ++i; } } return batchBalances; }
https://github.com/code-423n4/2022-07-ens/blob/main/contracts/wrapper/ERC1155Fuse.sol#L188 function: safeBatchTransferFrom
function safeBatchTransferFrom( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) public virtual override { require( ids.length == amounts.length, "ERC1155: ids and amounts length mismatch" ); require(to != address(0), "ERC1155: transfer to the zero address"); require( from == msg.sender || isApprovedForAll(from, msg.sender), "ERC1155: transfer caller is not owner nor approved" ); for (uint256 i = 0; i < ids.length; ++i) { uint256 id = ids[i]; uint256 amount = amounts[i]; (address oldOwner, uint32 fuses, uint64 expiration) = getData(id); if (!_canTransfer(fuses)) { revert OperationProhibited(bytes32(id)); } require( amount == 1 && oldOwner == from, "ERC1155: insufficient balance for transfer" ); _setData(id, to, fuses, expiration); } emit TransferBatch(msg.sender, from, to, ids, amounts); _doSafeBatchTransferAcceptanceCheck( msg.sender, from, to, ids, amounts, data ); }
Place the length outside of the array and use unchecked{}, ++i new safeBatchTransferFrom function:
function safeBatchTransferFrom( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) public virtual override { require( ids.length == amounts.length, "ERC1155: ids and amounts length mismatch" ); require(to != address(0), "ERC1155: transfer to the zero address"); require( from == msg.sender || isApprovedForAll(from, msg.sender), "ERC1155: transfer caller is not owner nor approved" ); uint256 length = ids.length; for (uint256 i = 0; i < length;) { uint256 id = ids[i]; uint256 amount = amounts[i]; (address oldOwner, uint32 fuses, uint64 expiration) = getData(id); if (!_canTransfer(fuses)) { revert OperationProhibited(bytes32(id)); } require( amount == 1 && oldOwner == from, "ERC1155: insufficient balance for transfer" ); _setData(id, to, fuses, expiration); unchecked{++i;} } emit TransferBatch(msg.sender, from, to, ids, amounts); _doSafeBatchTransferAcceptanceCheck( msg.sender, from, to, ids, amounts, data ); }
https://github.com/code-423n4/2022-07-ens/blob/main/contracts/ethregistrar/BulkRenewal.sol#L50 function: renewAll
function renewAll(string[] calldata names, uint256 duration) external payable override { ETHRegistrarController controller = getController(); for (uint256 i = 0; i < names.length; i++) { IPriceOracle.Price memory price = controller.rentPrice( names[i], duration ); controller.renew{value: price.base + price.premium}( names[i], duration ); } // Send any excess funds back payable(msg.sender).transfer(address(this).balance); }
Place the length outside of the array and use unchecked{}, ++i
New renewAll function:
function renewAll(string[] calldata names, uint256 duration) external payable override { ETHRegistrarController controller = getController(); uint256 length = names.length; for (uint256 i = 0; i < length;) { IPriceOracle.Price memory price = controller.rentPrice( names[i], duration ); controller.renew{value: price.base + price.premium}( names[i], duration ); } // Send any excess funds back payable(msg.sender).transfer(address(this).balance); unchecked{ ++i; } }