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: 41/100
Findings: 3
Award: $124.89
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rajatbeladiya
Also found by: 0x29A, 0xNineDec, Amithuddar, Aussie_Battlers, Ch_301, Dravee, GimelSec, IllIllI, Jujic, Limbooo, RedOneN, Ruhum, TomJ, _Adam, __141345__, alan724, asutorufos, berndartmueller, c3phas, cccz, cryptphi, durianSausage, fatherOfBlocks, hake, hyh, pashov, scaraven, zzzitron
5.45 USDC - $5.45
https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/ethregistrar/ETHRegistrarController.sol#L183 https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/ethregistrar/ETHRegistrarController.sol#L204 https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/ethregistrar/ETHRegistrarController.sol#L211
As transfer has a limit of 2,300 gas, when refunding excess Eth to smart contract accounts the transfer can fail. This can be due to:
The functions register, renew & withdraw will be limited to only EOA accounts or contract accounts with a very basic receive payable callback.
Manual Review
I recommend adding reentrant guards and using call() instead of transfer().
#0 - jefflau
2022-07-22T09:49:03Z
Duplicate of #133
🌟 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
79.4817 USDC - $79.48
Critical changes such as ownership changes should be a 2 step process to protect against human error. While the errors are unlikely important parts of the contract would become unusable if they occured.
Consider changing the following functions to 2 step procedures: Owned.sol#L18-L20
When modifying critical state variables such as changing owner, I recommend emiting an event to allow monitoring with off chain tools.
Consider adding an event to the setOwner function: Owned.sol#L19
For non library/interface contracts I recommend not using a floating pragma version and deploying with the version that was used for testing.
Also the following is missing a pragma version: IBaseRegistrar.sol#L1
The following functions are using both named returns and return statements. I recommend only using one of them per function. BytesUtils.sol#L135-L136 DNSSECImpl.sol#L110-L139 RRUtils.sol#L38-L40 NameWrapper.sol#L744-L752 NameWrapper.sol#L832-L843 NameWrapper.sol#L853-L864
Recommend resolving and removing the following TODO before deployment. DNSSECImpl.sol#L238
The following functions are never called within their given contract can be changed from public to external: DNSSECImpl.sol#L58 DNSSECImpl.sol#L69 Owned.sol#L18 NameWrapper.sol#L105 NameWrapper.sol#L128 NameWrapper.sol#L456-L461 Controllable.sol#L11
Natspec is missing in the following locations: BytesUtils.sol#L232 - missing @return DNSSECImpl.sol#L108 - missing @return DNSSECImpl.sol#L145 - missing @return DNSSECImpl.sol#L180 - missing @param rrset DNSSECImpl.sol#L229 - missing @param keyrdata ReverseRegistrar.sol#L73 - missing @param resolver ReverseRegistrar.sol#L129 - missing @param resolver NameWrapper.sol#L114 - missing @param tokenId NameWrapper.sol#L207 - missing @return NameWrapper.sol#L268 - missing @param expiry NameWrapper.sol#L371 - missing @return NameWrapper.sol#L398 - missing @param resolver
SPDX-License-Identifier is missing in the following files: BytesUtils.sol RRUtils.sol SHA1.sol Owned.sol Algorithm.sol Digest.sol ETHRegistrarController.sol IETHRegistrarController.sol StringUtils.sol IBaseRegistrar.sol ReverseRegistrar.sol IReverseRegistrar.sol IMetadataService.sol ENS.sol
🌟 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.9648 USDC - $39.96
When incrementing i in for loops there is no chance of overflow so unchecked can be used to save gas. I ran a simple test in remix and found deployment savings of 31,653 gas and on each function call saved ~141 gas per iteration.
contract Test { function loopTest() external { for (uint256 i; i < 1; ++i) { Deployment Cost: 125,637, Cost on function call: 24,601 vs for (uint256 i; i < 1; ) { // for loop body unchecked { ++i } Deployment Cost: 93,984, Cost on function call: 24,460 } } }
For loops that can use unchecked increments: BytesUtils.sol#L266 BytesUtils.sol#L313 DNSSECImpl.sol#L93 ETHRegistrarController.sol#L256 ERC1155Fuse.sol#L92 ERC1155Fuse.sol#L205
In for loops pre increments can be used to save a small amount of gas per iteration. I ran a test in remix using a for loop and found the deployment savings of 497 gas and ~5 gas per iteration.
contract Test { function loopTest() external { for (uint256 i; i < 1; i++) { (Deployment cost: 118,408, Cost on function call: 24,532) vs for (uint256 i; i < 1; ++i) { (Deployment cost: 117,911, Cost on function call: 24,527) } } }
For loops that can use pre increments: BytesUtils.sol#L266 BytesUtils.sol#L313 DNSSECImpl.sol#L93 ETHRegistrarController.sol#L256
As your using a solidity version > 0.8.4 you can replace revert strings with custom errors. This will save in deployment costs and runtime costs. Based on a test in remix, replacing a single revert string with a custom error saved 12,404 gas in deployment cost and 86 gas on each function call.
contract Test { uint256 a; function check() external { require(a != 0, "check failed"); } } (Deployment cost: 114,703, Cost on Function call: 23,392) vs contract Test { uint256 a; error checkFailed(); function check() external { if (a != 0) revert checkFailed(); } } (Deployment cost: 102,299, Cost on Function call: 23,306)
Instances where custom errors can be implemented: RRUtils.sol#L307 ETHRegistrarController.sol#L99-L102 ETHRegistrarController.sol#L137-L139 ETHRegistrarController.sol#L196-L199 ETHRegistrarController.sol#L232-L242 ETHRegistrarController.sol#L259-L261 ReverseRegistrar.sol#L41-L54 BytesUtil.sol#L28 BytesUtil.sol#L42 ERC1155Fuse.sol#L60-L62 ERC1155Fuse.sol#L85-L87 ERC1155Fuse.sol#L107-L109 ERC1155Fuse.sol#L176-L179 ERC1155Fuse.sol#L195-L203 ERC1155Fuse.sol#L215-L217 ERC1155Fuse.sol#L248-L252 ERC1155Fuse.sol#L290-L293 Controllable.sol#L17
If you opt not to use custom errors keeping revert strings <= 32 bytes in length will save gas. I ran a test in remix and found the savings for a single short revert string vs long string to be 9,377 gas in deployment cost and 18 gas on function call.
contract Test { uint256 a; function check() external { require(a != 0, "short error message"); (Deployment cost: 114,799, Cost on function call: 23,392) vs require(a != 0, "A longer Error Message over 32 bytes in length"); (Deployment cost: 124,176, Cost on function call: 23,410) } }
I recommend shortenning the following revert strings to < 32 bytes in length: ETHRegistrarController.sol#L99-L102 ETHRegistrarController.sol#L137-L139 ETHRegistrarController.sol#L198 ETHRegistrarController.sol#L232-L242 ETHRegistrarController.sol#L259-L261 ReverseRegistrar.sol#L41-L54 ERC1155Fuse.sol#L60-L62 ERC1155Fuse.sol#L85-L87 ERC1155Fuse.sol#L107-L109 ERC1155Fuse.sol#L176-L179 ERC1155Fuse.sol#L195-L203 ERC1155Fuse.sol#L215-L217 ERC1155Fuse.sol#L248-L252 ERC1155Fuse.sol#L290-L293 Controllable.sol#L17
If optimising for runtime costs over deployment costs you can seperate && in require functions into 2 parts. I ran a basic test in remix and it cost an extra 234 gas to deploy but will save ~9 gas everytime the require function is called. (note: If you upgrade to solidity version > 0.8.13 this is no longer true)
contract Test { uint256 a = 0; uint256 b = 1; function test() external { require(a == 0 && b > a) (Deployment cost: 123,291, Cost on function call: 29,371) vs require(a == 0); require(b > a); (Deployment cost: 123,525, Cost on function call: 29,362) } }
Require statements that can be split up: BytesUtils.sol#L268 ERC1155Fuse.sol#L215-L218 ERC1155Fuse.sol#L290-L293