ENS contest - lcfr_eth's results

Decentralised naming for wallets, websites, & more.

General Information

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

ENS

Findings Distribution

Researcher Performance

Rank: 68/100

Findings: 2

Award: $118.73

🌟 Selected for report: 0

🚀 Solo Findings: 0

QA - Add ERC20 Interface -> withdraw function on new contracts.

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.

Background / Why

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))); }

Minor debatable gas optimizations

ETHRegistrarController.sol

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; } } }

ERC1155Fuse.sol

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 ); }

BulkRenewal.sol

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; } }
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