ENS Contest - Sathish9098's results

Decentralised naming for web3

General Information

Platform: Code4rena

Start Date: 14/04/2023

Pot Size: $90,500 USDC

Total HM: 7

Participants: 59

Period: 14 days

Judge: LSDan

Total Solo HM: 3

Id: 232

League: ETH

ENS

Findings Distribution

Researcher Performance

Rank: 11/59

Findings: 2

Award: $1,588.85

QA:
grade-a
Gas:
grade-a

🌟 Selected for report: 1

🚀 Solo Findings: 0

Awards

828.1889 USDC - $828.19

Labels

bug
grade-a
QA (Quality Assurance)
selected for report
edited-by-warden
Q-44

External Links

LOW FINDINGS

LOW COUNTISSUESINSTANCES
[L-1]Use abi.encode to convert safest way from uint values to bytes3
[L-2]Loss of precision due to rounding1
[L-3]Consider using OpenZeppelin’s SafeCast library to prevent unexpected overflows when casting from uint25611
[L-4]MIXING AND OUTDATED COMPILER7
[L-5]abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()7
[L-6]Lack of Sanity/Threshold/Limit Checks2
[L-7]Function Calls in Loop Could Lead to Denial of Service10
[L-8]Project Upgrade and Stop Scenario should be-
[L-9]Front running attacks by the onlyOwner3
[L-10]Use BytesLib.sol library to safely covert bytes to uint2562
[L-11]Avoid infinite loops whenever possible2
[L-12]In the constructor, there is no return of incorrect address identification6
[L-13]Even with the onlyOwner or owner_only modifier, it is best practice to use the re-entrancy pattern3

NON CRITICAL FINDINGS

NC COUNTISSUESINSTANCES
[NC-1]immutable should be uppercase4
[NC-2]Missing NATSPEC-
[NC-3]For functions, follow Solidity standard naming conventions (internal function style rule)28
[NC-4]Need Fuzzing test for unchecked5
[NC-5]Remove commented out code-
[NC-6]Inconsistent method of specifying a floating pragma8
[NC-7]NO SAME VALUE INPUT CONTROL1
[NC-8]Constant redefined elsewhere-
[NC-9]According to the syntax rules, use => mapping ( instead of => mapping( using spaces as keyword3
[NC-10]Use SMTChecker-
[NC-11]Constants on the left are better20
[NC-12]Assembly Codes Specific – Should Have Comments18
[NC-13]Take advantage of Custom Error’s return value property4
[NC-14]Use constants instead of using numbers directly without explanations6
[NC-15]Shorthand way to write if / else statement4
[NC-16]For critical changes should emit both old and new values1
[NC-17]Don't use named return variables its confusing8
[NC-18]NON-LIBRARY/INTERFACE FILES SHOULD USE FIXED COMPILER VERSIONS, NOT FLOATING ONES7
[NC-19]Constants should be in uppercase7
[NC-20]TYPOS4
[NC-21]Contracts should have full test coverage-
[NC-22]Use named parameters for mapping type declarations3
[NC-23]File does not contain an SPDX Identifier4
[NC-24]declaration shadows an existing declaration-
[NC-25]Event is missing indexed fields2

[L-1] Use abi.encode to convert safest way from uint values to bytes

DRAWBACKS

Now the protocol uses direct conversion way this is not safe. bytes(value) to convert a uint to bytes is not considered a safe way because it creates an uninitialized byte array of length. This means that the contents of the byte array are undefined and may contain sensitive data from previous memory usage, which could result in security vulnerabilities.

BENIFITS of abi.encode

In Solidity, the safest way to convert a uint to bytes is to use the abi.encode function. This function will encode the uint as a byte array using the ABI encoding rules, which ensures that the output is a deterministic and standardized representation of the uint value.

FILE: 2023-04-ens/contracts/utils/NameEncoder.sol

27:  dnsName[i + 1] = bytes1(labelLength);
49:  dnsName[0] = bytes1(labelLength);

NameEncoder.sol#L27

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

376: return bytes32(ret << (256 - bitlen));

BytesUtils.sol#L376

[L-2] Loss of precision due to rounding

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

52: q = r1 / r2;

EllipticCurve.sol#L52

[L-3] Consider using OpenZeppelin’s SafeCast library to prevent unexpected overflows when casting from uint256

Using the SafeCast library can help prevent unexpected errors in your Solidity code and make your contracts more secure

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

160: if (!RRUtils.serialNumberGte(rrset.expiration, uint32(now))) {
161: revert SignatureExpired(rrset.expiration, uint32(now));
166: if (!RRUtils.serialNumberGte(uint32(now), rrset.inception)) {
167: revert SignatureNotValidYet(rrset.inception, uint32(now));

DNSSECImpl.sol#L160

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

430: return uint16(ac1);

RRUtils.sol#L430

When ever we convert int256 to uint256 or uint256 to int256 we should use OpenZeppelin’s safecast to avoid unexpected errors

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

56: if (t1 < 0) return (m - uint256(-t1));
58: return uint256(t1);

EllipticCurve.sol#L56-L58

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

92: int256 diff = int256(a & mask) - int256(b & mask);
99: return int256(len) - int256(otherlen);
183: return uint8(self[idx]);
344: decoded = uint8(base32HexTable[uint256(uint8(char)) - 0x30]);

BytesUtils.sol#L92

Consider using OpenZeppelin’s SafeCast library to prevent unexpected overflows when casting from uint256.

[L-4] MIXING AND OUTDATED COMPILER

The pragma version used are: 0.8.0

The minimum required version must be 0.8.17; otherwise, contracts will be affected by the following important bug fixes:

0.8.14:

ABI Encoder: When ABI-encoding values from calldata that contain nested arrays, correctly validate the nested array length against calldatasize() in all cases. Override Checker: Allow changing data location for parameters only when overriding external functions.

0.8.15

Code Generation: Avoid writing dirty bytes to storage when copying bytes arrays. Yul Optimizer: Keep all memory side-effects of inline assembly blocks.

0.8.16

Code Generation: Fix data corruption that affected ABI-encoding of calldata values represented by tuples: structs at any nesting level; argument lists of external functions, events and errors; return value lists of external functions. The 32 leading bytes of the first dynamically-encoded value in the tuple would get zeroed when the last component contained a statically-encoded array.

0.8.17 Yul Optimizer: Prevent the incorrect removal of storage writes before calls to Yul functions that conditionally terminate the external EVM call. Apart from these, there are several minor bug fixes and improvements

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

2: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

1: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/SHA1.sol

1: pragma solidity >=0.8.4;

FILE: 2023-04-ens/contracts/dnsregistrar/DNSClaimChecker.sol

2: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnsregistrar/RecordParser.sol

2: pragma solidity ^0.8.11;

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/ModexpPrecompile.sol

1: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/RSAVerify.sol

1: pragma solidity ^0.8.4;

[L-5] abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256()

Use abi.encode() instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456) => 0x123456 => abi.encodePacked(0x1,0x23456), but abi.encode(0x123,0x456) => 0x0...1230...456). "Unless there is a compelling reason, abi.encode should be preferred". If there is only one argument to abi.encodePacked() it can often be cast to bytes() or bytes32() instead. If all arguments are strings and or bytes, bytes.concat() should be used instead

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

222: keccak256(
                abi.encodePacked(parentNode, name.keccak(idx, separator - idx))
            );

OffchainDNSResolver.sol#L222-L224

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

119: bytes32 node = keccak256(abi.encodePacked(rootNode, labelHash));
151: bytes32 node = keccak256(abi.encodePacked(parentNode, labelHash));
185: node = keccak256(abi.encodePacked(parentNode, label));

DNSRegistrar.sol#L119

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/ModexpPrecompile.sol

12: bytes memory input = abi.encodePacked(
            uint256(base.length),
            uint256(exponent.length),
            uint256(modulus.length),
            base,
            exponent,
            modulus
        );

ModexpPrecompile.sol#L12-L19`

FILE: 2023-04-ens/contracts/utils/NameEncoder.sol

28: node = keccak256(
                        abi.encodePacked(
                            node,
                            bytesName.keccak(i + 1, labelLength)
                        )
                    );

45: node = keccak256(
            abi.encodePacked(node, bytesName.keccak(0, labelLength))
        );

NameEncoder.sol#L28-L33

[L-6] Lack of Sanity/Threshold/Limit Checks

Devoid of sanity/threshold/limit checks, critical parameters can be configured to invalid values, causing a variety of issues and breaking expected interactions within/between contracts. Consider adding proper uint256 validation as well as zero address checks for critical changes. A worst case scenario would render the contract needing to be re-deployed in the event of human/accidental errors that involve value assignments to immutable variables. If the validation procedure is unclear or too complex to implement on-chain, document the potential issues that could produce invalid values

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

64: function setAlgorithm(uint8 id, Algorithm algo) public owner_only {
        algorithms[id] = algo;
        emit AlgorithmUpdated(id, address(algo));
    }

75: function setDigest(uint8 id, Digest digest) public owner_only {
        digests[id] = digest;
        emit DigestUpdated(id, address(digest));
    }

DNSSECImpl.sol#L64-L78

[L-7] Function Calls in Loop Could Lead to Denial of Service

Function calls made in unbounded loop are error-prone with potential resource exhaustion as it can trap the contract due to the gas limitations or failed transactions

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

while (counts > othercounts) {
            prevoff = off;
            off = progress(self, off);
            counts--;
        }

while (othercounts > counts) {
            otherprevoff = otheroff;
            otheroff = progress(other, otheroff);
            othercounts--;
        }

        // Compare the last nonequal labels to each other
        while (counts > 0 && !self.equals(off, other, otheroff)) {
            prevoff = off;
            off = progress(self, off);
            otherprevoff = otheroff;
            otheroff = progress(other, otheroff);
            counts -= 1;
        }

RRUtils.sol#L291-L295

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

118: for (uint256 i = 0; i < input.length; i++) {
            RRUtils.SignedSet memory rrset = validateSignedSet(
                input[i],
                proof,
                now
            );
            proof = rrset.data;
            inception = rrset.inception;
        }

260: for (; !proof.done(); proof.next()) {
            bytes memory proofName = proof.name();
            if (!proofName.equals(rrset.signerName)) {
                revert ProofNameMismatch(rrset.signerName, proofName);
            }

            bytes memory keyrdata = proof.rdata();
            RRUtils.DNSKEY memory dnskey = keyrdata.readDNSKEY(
                0,
                keyrdata.length
            );
            if (verifySignatureWithKey(dnskey, keyrdata, rrset, data)) {
                return;
            }


DNSSECImpl.sol#L118-L126

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/DNSSECImpl.sol#L336-L360

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/DNSSECImpl.sol#L380-L404

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/EllipticCurve.sol#L325-L327

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/EllipticCurve.sol#L361-L362

[L-8] Project Upgrade and Stop Scenario should be

At the start of the project, the system may need to be stopped or upgraded, I suggest you have a script beforehand and add it to the documentation. This can also be called an ” EMERGENCY STOP (CIRCUIT BREAKER) PATTERN “.

https://github.com/maxwoe/solidity_patterns/blob/master/security/EmergencyStop.sol

[L-9] Front running attacks by the onlyOwner

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

80: function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner {
        suffixes = _suffixes;
        emit NewPublicSuffixList(address(suffixes));
    }

DNSRegistrar.sol#L80-L83

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

64: function setAlgorithm(uint8 id, Algorithm algo) public owner_only {
        algorithms[id] = algo;
        emit AlgorithmUpdated(id, address(algo));
    }

75: function setDigest(uint8 id, Digest digest) public owner_only {
        digests[id] = digest;
        emit DigestUpdated(id, address(digest));
    }

DNSSECImpl.sol#L64-L67

[L-10] Use BytesLib.sol library to safely covert bytes to uint256

Use toUint256() safely convert bytes to uint256 instead of plain way of conversion

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/P256SHA256Algorithm.sol

34: return [uint256(data.readBytes32(0)), uint256(data.readBytes32(32))];
41: return [uint256(data.readBytes32(4)), uint256(data.readBytes32(36))];

P256SHA256Algorithm.sol#L34

[L-11] Avoid infinite loops whenever possible

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

> This r2 != 0 condition can be always true cause unbounded loop. This condition only fails if r2 is exactly equal to 0   

51: while (r2 != 0) {
                q = r1 / r2;
                (t1, t2, r1, r2) = (t2, t1 - int256(q) * t2, r2, r1 - q * r2);
            }

EllipticCurve.sol#L51-L54

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

while (true) {
            assert(idx < self.length);
            uint256 labelLen = self.readUint8(idx);
            idx += labelLen + 1;
            if (labelLen == 0) {
                break;
            }
        }

RRUtils.sol#L24-L31

[L-12] In the constructor, there is no return of incorrect address identification

In case of incorrect address definition in the constructor , there is no way to fix it because of the variables are immutable.

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

44: ens = _ens;
45: oracle = _oracle;

OffchainDNSResolver.sol#L44-L45

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

62: previousRegistrar = _previousRegistrar;
63: resolver = _resolver;
64: oracle = _dnssec;
67: ens = _ens;

DNSRegistrar.sol#L63-L65

require(_ens!=address(0), " zero address");

[L-13] Even with the onlyOwner or owner_only modifier, it is best practice to use the re-entrancy pattern

It's still good practice to apply the reentry model as a precautionary measure in case the code is changed in the future to remove the onlyOwner modifier or the contract is used as a base contract for other contracts.

Using the reentry modifier provides an additional layer of security and ensures that your code is protected from potential reentry attacks regardless of any other security measures you take.

So even if you followed the "check-effects-interactions" pattern correctly, it's still considered good practice to use the reentry modifier

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

80: function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner {
        suffixes = _suffixes;
        emit NewPublicSuffixList(address(suffixes));
    }

DNSRegistrar.sol#L80-L83

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

64: function setAlgorithm(uint8 id, Algorithm algo) public owner_only {
        algorithms[id] = algo;
        emit AlgorithmUpdated(id, address(algo));
    }

75: function setDigest(uint8 id, Digest digest) public owner_only {
        digests[id] = digest;
        emit DigestUpdated(id, address(digest));
    }

DNSSECImpl.sol#L64-L67

Recommended Mitigation:


  modifier noReentrant() {
        require(!locked, "Reentrant call");
        locked = true;
        _;
        locked = false;
    }

function setPublicSuffixList(PublicSuffixList _suffixes) public  onlyOwner noReentrant  {

NON CRITICAL FINDINGS

[NC-1] immutable should be uppercase

FILE : 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

37: ENS public immutable ens;
38: DNSSEC public immutable oracle;

OffchainDNSResolver.sol#L37-L38

- 38: DNSSEC public immutable oracle;
+ 38: DNSSEC public immutable ORACLE;

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/DNSRegistrar.sol#L26-L27

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/DNSRegistrar.sol#L29-L30

[NC-2] Missing NATSPEC

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/OffchainDNSResolver.sol#L22-L27

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/OffchainDNSResolver.sol#L49-L52

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/OffchainDNSResolver.sol#L65-L69

[NC-3] For functions, follow Solidity standard naming conventions (internal function style rule)

Description

The above codes don’t follow Solidity’s standard naming convention,

internal and private functions : the mixedCase format starting with an underscore (_mixedCase starting with an underscore)

File: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

136: function parseRR(
        bytes memory data,
        uint256 idx,
        uint256 lastIdx
    ) internal view returns (address, bytes memory) {

162: function readTXT(
        bytes memory data,
        uint256 startIdx,
        uint256 lastIdx
    ) internal pure returns (bytes memory) {

173: function parseAndResolve(
        bytes memory nameOrAddress,
        uint256 idx,
        uint256 lastIdx
    ) internal view returns (address) {

190: function resolveName(
        bytes memory name,
        uint256 idx,
        uint256 lastIdx
    ) internal view returns (address) {

209: function textNamehash(
        bytes memory name,
        uint256 idx,
        uint256 lastIdx
    ) internal view returns (bytes32) {

OffchainDNSResolver.sol#L136-L140

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/RSAVerify.sol#L14-L18

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/ModexpPrecompile.sol#L7-L11

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/P256SHA256Algorithm.sol#L30-L32

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/P256SHA256Algorithm.sol#L37-L39

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/utils/NameEncoder.sol#L9-L11

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/utils/HexUtils.sol#L11-L15

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/utils/HexUtils.sol#L68-L72

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/DNSClaimChecker.sol#L19-L22

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/DNSClaimChecker.sol#L46-L50

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/DNSClaimChecker.sol#L66-L70

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L13-L17

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L32-L35

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L52-L59

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L111-L117

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L129-L134

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L148-L152

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L164-L167

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L179-L182

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L332-L336

[NC-4] Need Fuzzing test for unchecked

FILE: 2023-04-ens/contracts/utils/NameEncoder.sol

24:  unchecked {

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

380:   unchecked {
336:   unchecked {

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

284:  unchecked {

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

41:  unchecked {

[NC-5] Remove commented out code

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

358: *     function computeKeytag(bytes memory data) internal pure returns (uint16) {
359: *         uint ac;
360: *         for (uint i = 0; i < data.length; i++) {
361: *             ac += i & 1 == 0 ? uint16(data.readUint8(i)) << 8 : data.readUint8(i);
362: *         }
363: *         return uint16(ac + (ac >> 16));
364: *     }

RRUtils.sol#L358-L364

[NC-6] Inconsistent method of specifying a floating pragma

Some files use >=, some use ^. The instances below are examples of the method that has the fewest instances for a specific version. Note that using >= without also specifying <= will lead to failures to compile, or external project incompatability, when the major version changes and there are breaking-changes, so ^ should be preferred regardless of the instance counts

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

2: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

1: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/SHA1.sol

1: pragma solidity >=0.8.4;

FILE: 2023-04-ens/contracts/dnsregistrar/DNSClaimChecker.sol

2: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnsregistrar/RecordParser.sol

2: pragma solidity ^0.8.11;

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/ModexpPrecompile.sol

1: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/RSAVerify.sol

1: pragma solidity ^0.8.4;

[NC-7] NO SAME VALUE INPUT CONTROL

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

80: function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner {
        suffixes = _suffixes;
        emit NewPublicSuffixList(address(suffixes));
    }

DNSRegistrar.sol#L80-L83

[NC-8] Constant redefined elsewhere

Consider defining in only one contract so that values cannot become out of sync when only one location is updated.

A cheap way to store constants in a single location is to create an internal constant in a library. If the variable is a local cache of another contract’s value, consider making the cache variable internal or private, which will require external users to query the contract with the source of truth, so that callers don’t get out of sync.

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

29: uint16 constant CLASS_INET = 1;
30: uint16 constant TYPE_TXT = 16;

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

72: uint256 constant RRSIG_TYPE = 0;
73: uint256 constant RRSIG_ALGORITHM = 2;
74: uint256 constant RRSIG_LABELS = 3;
75: uint256 constant RRSIG_TTL = 4;
76: uint256 constant RRSIG_EXPIRATION = 8;
77: uint256 constant RRSIG_INCEPTION = 12;
78: uint256 constant RRSIG_KEY_TAG = 16;
79: uint256 constant RRSIG_SIGNER_NAME = 18;
210: uint256 constant DNSKEY_FLAGS = 0;
211: uint256 constant DNSKEY_PROTOCOL = 2;
212: uint256 constant DNSKEY_ALGORITHM = 3;
213: uint256 constant DNSKEY_PUBKEY = 4;

236: uint256 constant DS_KEY_TAG = 0;
237: uint256 constant DS_ALGORITHM = 2;
238: uint256 constant DS_DIGEST_TYPE = 3;
239: uint256 constant DS_DIGEST = 4;

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

21: uint256 constant a =
        0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC;
23: uint256 constant b =
        0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B;
25: uint256 constant gx =
        0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296;
27: uint256 constant gy =
        0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5;
29: uint256 constant p =
        0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF;
30: uint256 constant n =
        0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551;

32: uint256 constant lowSmax =
        0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0;

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

27: uint16 constant DNSCLASS_IN = 1;
29: uint16 constant DNSTYPE_DS = 43;
30: uint16 constant DNSTYPE_DNSKEY = 48;
32: uint256 constant DNSKEY_FLAG_ZONEKEY = 0x100;

[NC-9] According to the syntax rules, use => mapping ( instead of => mapping( using spaces as keyword

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

45: mapping(uint8 => Algorithm) public algorithms;
46: mapping(uint8 => Digest) public digests;

DNSSECImpl.sol#L45-L46

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

32: mapping(bytes32 => uint32) public inceptions;

DNSRegistrar.sol#L32

[NC-10] Use SMTChecker

The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification

https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19

[NC-11] Constants on the left are better

If you use the constant first you support structures that veil programming errors. And one should only produce code either to add functionality, fix an programming error or trying to support structures to avoid programming errors (like design patterns).

https://www.moserware.com/2008/01/constants-on-left-are-better-but-this.html

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/OffchainDNSResolver.sol#L178

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/DNSRegistrar.sol#L179

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/DNSSECImpl.sol#L294

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/EllipticCurve.sol#L51

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/EllipticCurve.sol#L128

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

145:  if (a != 0) {
148:  if (b != 0) {
392:  if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) {
410:  if (P[2] == 0) {
340:  if (scalar == 0) {
342:  } else if (scalar == 1) {
344:  } else if (scalar == 2) {
355:  if (scalar % 2 == 0) {
364:  if (scalar % 2 == 1) {

EllipticCurve.sol#L145

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

28:  if (labelLen == 0) {
64:  if (labelLen == 0) {
312: if (off == 0) {
315: if (otheroff == 0) {

RRUtils.sol#L28

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

353: if (len % 8 == 0) {
356: } else if (len % 8 == 2) {
360: } else if (len % 8 == 4) {
364: } else if (len % 8 == 5) {
368: } else if (len % 8 == 7) {

BytesUtils.sol#L353

[NC-12] Assembly Codes Specific – Should Have Comments

Since this is a low level language that is more difficult to parse by readers, include extensive documentation, comments on the rationale behind its use, clearly explaining what each assembly instruction does.

This will make it easier for users to trust the code, for reviewers to validate the code, and for developers to build on or update the code.

Note that using Assembly removes several important security features of Solidity, which can make the code more insecure and more error-prone

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/ModexpPrecompile.sol

23: assembly {

ModexpPrecompile.sol#L23

FILE: 2023-04-ens/contracts/utils/HexUtils.sol

17:  assembly {

HexUtils.sol#L17

FILE: FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

386: assembly {

RRUtils.sol#L386

FILE: 2023-04-ens/contracts/dnssec-oracle/SHA1.sol

7: assembly {

SHA1.sol#L7

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

19:  assembly {
73:  assembly {
80:  assembly {
197: assembly {
213: assembly {
229: assembly {
245: assembly {
267: assembly {
276: assembly {
286: assembly {
311: assembly {

BytesUtils.sol#L19

[NC-13] Take advantage of Custom Error’s return value property

An important feature of Custom Error is that values such as address, tokenID, msg.value can be written inside the () sign, this kind of approach provides a serious advantage in debugging and examining the revert details of dapps such as tenderly

2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

34: error NoOwnerRecordFound();
35: error PreconditionNotMet();
36: error StaleProof();

DNSRegistrar.sol#L36-L37

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

38:  error InvalidRRSet();

DNSSECImpl.sol#L38

[NC-14] Use constants instead of using numbers directly without explanations

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

144: if (txt.length < 5 || !txt.equals(0, "ENS1 ", 0, 5)) {
149: uint256 lastTxtIdx = txt.find(5, txt.length - 5, " ");
151: address dnsResolver = parseAndResolve(txt, 5, txt.length);
154: address dnsResolver = parseAndResolve(txt, 5, lastTxtIdx);

OffchainDNSResolver.sol#L144

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/P256SHA256Algorithm.sol

33: require(data.length == 64, "Invalid p256 signature length");
40: require(data.length == 68, "Invalid p256 key length");

P256SHA256Algorithm.sol#L33

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/utils/HexUtils.sol#L25-L36

[NC-15] Shorthand way to write if / else statement

The normal if / else statement can be refactored in a shorthand way to write it:

Increases readability Shortens the overall SLOC

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

if (separator < lastIdx) {
            parentNode = textNamehash(name, separator + 1, lastIdx);
        } else {
            separator = lastIdx;
        }

OffchainDNSResolver.sol#L216-L220

FILE : 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

if (shortest - idx >= 32) {
                    mask = type(uint256).max;
                } else {
                    mask = ~(2 ** (8 * (idx + 32 - shortest)) - 1);
                }

BytesUtils.sol#L87-L91

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

221: if (isZeroCurve(x0, y0)) {
            return (x1, y1, z1);
        } else if (isZeroCurve(x1, y1)) {
            return (x0, y0, z0);
        }

234: if (t0 == t1) {
                return twiceProj(x0, y0, z0);
            } else {
                return zeroProj();
            }

EllipticCurve.sol#L221-L225


if (separator < lastIdx) {
            parentNode = textNamehash(name, separator + 1, lastIdx);
        } else {
            separator = lastIdx;
        }

separator < lastIdx ? parentNode = textNamehash(name, separator + 1, lastIdx); : separator = lastIdx ;

[NC-16] For critical changes should emit both old and new values

Emitting both old and new values for critical changes is a good practice in Solidity, as it provides a way for external parties (such as other smart contracts or off-chain applications) to track and verify the changes made to a smart contract state.

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

80: function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner {
        suffixes = _suffixes;
        emit NewPublicSuffixList(address(suffixes));
    }

DNSRegistrar.sol#L80-L83

[NC-17] Don't use named return variables its confusing

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

113: function _claim(
        bytes memory name,
        DNSSEC.RRSetWithSignature[] memory input
    ) internal returns (bytes32 parentNode, bytes32 labelHash, address addr) {

166: function enableNode(bytes memory domain) public returns (bytes32 node) {

174: function _enableNode(
        bytes memory domain,
        uint256 offset
    ) internal returns (bytes32 node) {

DNSRegistrar.sol#L133-L136

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnsregistrar/RecordParser.sol#L14-L21

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/ModexpPrecompile.sol#L7-L11

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/utils/HexUtils.sol#L11-L15

[18] NON-LIBRARY/INTERFACE FILES SHOULD USE FIXED COMPILER VERSIONS, NOT FLOATING ONES

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

2: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

1: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/SHA1.sol

1: pragma solidity >=0.8.4;

FILE: 2023-04-ens/contracts/dnsregistrar/DNSClaimChecker.sol

2: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnsregistrar/RecordParser.sol

2: pragma solidity ^0.8.11;

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/ModexpPrecompile.sol

1: pragma solidity ^0.8.4;

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/RSAVerify.sol

1: pragma solidity ^0.8.4;

[NC-19] Constants should be in uppercase

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

21: uint256 constant a =
        0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC;
23: uint256 constant b =
        0x5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B;
25: uint256 constant gx =
        0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296;
27: uint256 constant gy =
        0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5;
29: uint256 constant p =
        0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF;
30: uint256 constant n =
        0xFFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551;

32: uint256 constant lowSmax =
        0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0;

EllipticCurve.sol#L21-L35

[NC-20] TYPOS

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

/// @audit codepoints => code points

- 43: *      on unicode codepoints.
+ 43: *      on unicode code points.

FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

/// @audit bitshifting=> bit shifting
/// @audit Naive => Native 

- 356: * from the input string, with some mild bitshifting. Here's a Naive solidity implementation:
- 356: * from the input string, with some mild bit shifting. Here's a Native solidity implementation:

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

/// @audit canonicalised => MEANING LESS WORD 

135: *        data, followed by a series of canonicalised RR records that the signature

[NC-21] Contracts should have full test coverage

While 100% code coverage does not guarantee that there are no bugs, it often will catch easy-to-find bugs, and will ensure that there are fewer regressions when the code invariably has to be modified. Furthermore, in order to get full coverage, code authors will often have to re-organize their code so that it is more modular, so that each component can be tested separately, which reduces interdependencies between modules and layers, and makes for code that is easier to reason about and audit

What is the overall line coverage percentage provided by your tests?: 90

[NC-22] Use named parameters for mapping type declarations

Consider using named parameters in mappings (e.g. mapping(address account => uint256 balance)) to improve readability. This feature is present since Solidity 0.8.18.

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

45: mapping(uint8 => Algorithm) public algorithms;
46: mapping(uint8 => Digest) public digests;

DNSSECImpl.sol#L45-L46

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

32: mapping(bytes32 => uint32) public inceptions;

DNSRegistrar.sol#L32

[NC-23] File does not contain an SPDX Identifier

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/SHA1.sol#L1-L3

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L1-L3

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/RRUtils.sol#L1-L9

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/algorithms/EllipticCurve.sol#L1-L19

[NC-24] declaration shadows an existing declaration

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

30:   address public immutable resolver;
104:  address resolver,
159:  function resolver(

192: address owner,
143: function owner(

[NC-25] Event is missing indexed fields

Index event fields make the field more quickly accessible to off-chain tools that parse events. However, note that each index field costs extra gas during emission, so it’s not necessarily best to index the maximum allowed per event (threefields). Each event should use three indexed fields if there are three or more fields, and gas usage is not particularly of concern for the events in question. If there are fewer than three fields, all of the fields should be indexed.

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

47: event Claim(
        bytes32 indexed node,
        address indexed owner,
        bytes dnsname,
        uint32 inception
    );
53: event NewPublicSuffixList(address suffixes);

DNSRegistrar.sol#L47-L53

#0 - c4-judge

2023-05-08T09:41:20Z

dmvt marked the issue as grade-a

#1 - c4-judge

2023-05-10T09:36:50Z

dmvt marked the issue as selected for report

#2 - dmvt

2023-05-10T09:57:46Z

L-4, L-5, NC-2, NC-11, NC-16, NC-18, & NC-21 are out of scope (bot race)

L-11 should be NC

Findings Information

🌟 Selected for report: JCN

Also found by: Sathish9098, d3e4, descharre, naman1778, niser93, openwide, saneryee

Labels

bug
G (Gas Optimization)
grade-a
high quality report
sponsor acknowledged
edited-by-warden
G-07

Awards

760.662 USDC - $760.66

External Links

GAS OPTIMIZATION

Gas CountIssuesInstancesGas Saved
[G-1]State variable value only set in the constructor can be declared as a constants to save large volume of the gas190000
[G-2]State variables only set in the constructor should be declared immutable120000
[G-3]Using storage instead of memory for structs/arrays saves gas36300
[G-4]Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate1-
[G-5]For events use 3 indexed rule to save gas3-
[G-6]Lack of input value checks cause a redeployment if any human/accidental errors4-
[G-7]Use nested if and, avoid multiple check combinations218
[G-8]Unnecessary look up in if condition7-
[G-9]Don't declare the variable inside the loops3-
[G-10]Functions should be used instead of modifiers to save gas1-
[G-11]Sort Solidity operations using short-circuit mode5-
[G-12]Use assembly to check for address(0)742
[G-13]Shorthand way to write if / else statement can reduce the deployment cost4-
[G-14]The Less gas consuming condition checks should be on top1-
[G-15]nternal functions not called by the contract should be removed to save deployment gas3-
[G-16]Modifiers or private functions only called once can be inlined to save gas280
[G-17]NOT USING THE NAMED RETURN VARIABLES WHEN A FUNCTION RETURNS, WASTES DEPLOYMENT GAS12-
[G-18]Use constants instead of type(uintx).max5-
[G-19]Instead of calculating bytes32(0) every time inside the contract its possible to use constants to reduce the gas cost1-
[G-20]Structs can be packed into fewer storage slots140000

TOTAL INSTANCES : 67

APROXIMATE GAS SAVED: 156000 gas

[G-1] State variable value only set in the constructor can be declared as a constants to save large volume of the gas

Instances (1)

Approximate Gas Saved : (4000 gas) (As per my remix test 90000 gas)

gatewayURL string variable value is only set inside the constructor. The value is not changed anywhere inside the contract. This gatewayURL can be declared as constant to save large volume of gas

As per Remix gas test approximately possible to saves 90000 gas

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

39:  string public gatewayURL;

43: constructor(ENS _ens, DNSSEC _oracle, string memory _gatewayURL) {
        ens = _ens;
        oracle = _oracle;
        gatewayURL = _gatewayURL;
    }

OffchainDNSResolver.sol#L39-L47

[G-2] State variables only set in the constructor should be declared immutable

Instances(1)

Approximate Gas saved : 20000 gas

Avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).

While strings are not value types, and therefore cannot be immutable/constant if not hard-coded outside of the constructor, the same behavior can be achieved by making the current contract abstract with virtual functions for the string accessors, and having a child contract override the functions with the hard-coded implementation-specific values

In parent contract DNSSEC.sol the variable "anchors" should be declared immutable . The anchors value is only set inside the DNSSECImpl contract constructor

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

55:  anchors = _anchors;

DNSSECImpl.sol#L55

[G-3] Using storage instead of memory for structs/arrays saves gas

Instances(3)

Approximate gas saved: 6300 gas

When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array. If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read. Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read. The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

53: string[] memory urls = new string[](1);

OffchainDNSResolver.sol#L53

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

307: bytes memory ret = new bytes(len);

BytesUtils.sol#L307

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

408: uint256[3] memory P = addAndReturnProjectivePoint(x1, y1, x2, y2);

EllipticCurve.sol#L408

[G-4] Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate

Instances(1)

Saves a storage slot for the mapping. Depending on the circumstances and sizes of types, can avoid a Gsset (20000 gas) per mapping combined. Reads and subsequent writes can also be cheaper when a function requires both values and they both fit in the same storage slot. Finally, if both fields are accessed in the same function, can save ~42 gas per access due to not having to recalculate the key’s keccak256 hash (Gkeccak256 - 30 gas) and that calculation’s associated stack operations.

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

45: mapping(uint8 => Algorithm) public algorithms;
46: mapping(uint8 => Digest) public digests;

DNSSECImpl.sol#L45-L46

[G-5] For events use 3 indexed rule to save gas

Instances(2)

Need to declare 3 indexed fields for event parameters. If the event parameter is less than 3 should declare all event parameters indexed

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

47: event Claim(
        bytes32 indexed node,
        address indexed owner,
        bytes dnsname,
        uint32 inception
    );
53: event NewPublicSuffixList(address suffixes);

DNSRegistrar.sol#L47-L53

[G-6] Lack of input value checks cause a redeployment if any human/accidental errors

Instances(4)

Devoid of sanity/threshold/limit checks, critical parameters can be configured to invalid values, causing a variety of issues and breaking expected interactions within/between contracts. Consider adding proper uint256 validation. A worst case scenario would render the contract needing to be re-deployed in the event of human/accidental errors that involve value assignments to immutable variables.

If any human/accidental errors happen need to redeploy the contract so this create the huge gas lose

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

constructor(ENS _ens, DNSSEC _oracle, string memory _gatewayURL) {
        ens = _ens;
        oracle = _oracle;
        gatewayURL = _gatewayURL;
    }

OffchainDNSResolver.sol#L43-L47

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

62: previousRegistrar = _previousRegistrar;
63: resolver = _resolver;
64: oracle = _dnssec;
65: suffixes = _suffixes;
67: ens = _ens;

DNSRegistrar.sol#L62-L67

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

80: function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner {
        suffixes = _suffixes;
        emit NewPublicSuffixList(address(suffixes));
    }

DNSRegistrar.sol#L80-L83

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

55: anchors = _anchors;

DNSSECImpl.sol#L55

[G-7] Use nested if and, avoid multiple check combinations

Instances(2)

Using nested is cheaper than using && multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.

As per Solidity reports possible to save 9 gas

FILE: FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

178: if (nameOrAddress[idx] == "0" && nameOrAddress[idx + 1] == "x") {

OffchainDNSResolver.sol#L178

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

128: if (x0 == 0 && y0 == 0) {

EllipticCurve.sol#L128

[G-8] Unnecessary look up in if condition

Instances(7)

If the || condition isn’t required, the second condition will have been looked up unnecessarily.

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

86:  if (
                !rrname.equals(name) ||
                iter.class != CLASS_INET ||
                iter.dnstype != TYPE_TXT
            ) {

144:  if (txt.length < 5 || !txt.equals(0, "ENS1 ", 0, 5)) {

OffchainDNSResolver.sol#L86-L90

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

187: if (owner == address(0) || owner == previousRegistrar) {

DNSRegistrar.sol#L187

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

138: if (0 == x || x == p || 0 == y || y == p) {
392: if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) {
41:  if (u == 0 || u == m || m == 0) return 0;

EllipticCurve.sol#L138

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

201: if (
                    name.length != iter.data.nameLength(iter.offset) ||
                    !name.equals(0, iter.data, iter.offset, name.length)
                ) {

DNSSECImpl.sol#L201-L204

[G-9] Don't declare the variable inside the loops

Instances(3)

In every iterations the new variables instance created this will consumes more gas . So just declare variables outside the loop and only use inside to save gas

FILE: 2023-04-ens/contracts/dnsregistrar/DNSClaimChecker.sol

29: for (
            RRUtils.RRIterator memory iter = data.iterateRRs(0);
            !iter.done();
            iter.next()
        ) {
            if (iter.name().compareNames(buf.buf) != 0) continue;
            bool found;  /// @audit found inside loop
            address addr;  /// @audit addr inside loop
            (addr, found) = parseRR(data, iter.rdataOffset, iter.nextOffset);
            if (found) {
                return (addr, true);
            }
        }

51: while (idx < endIdx) {
            uint256 len = rdata.readUint8(idx);
            idx += 1;

            bool found;
            address addr;

DNSClaimChecker.sol#L29-L40

https://github.com/code-423n4/2023-04-ens/blob/45ea10bacb2a398e14d711fe28d1738271cd7640/contracts/dnssec-oracle/BytesUtils.sol#L341-L342

[G-10] Functions should be used instead of modifiers to save gas

Instances(1)

File; 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

modifier onlyOwner() {
        Root root = Root(ens.owner(bytes32(0)));
        address owner = root.owner();
        require(msg.sender == owner);
        _;
    }

DNSRegistrar.sol#L73-L78

[G-11] Sort Solidity operations using short-circuit mode

Instances(5)

Short-circuiting is a solidity contract development model that uses OR/AND logic to sequence different cost operations. It puts low gas cost operations in the front and high gas cost operations in the back, so that if the front is low If the cost operation is feasible, you can skip (short-circuit) the subsequent high-cost Ethereum virtual machine operation.

//f(x) is a low gas cost operation //g(y) is a high gas cost operation //Sort operations with different gas costs as follows f(x) || g(y) f(x) && g(y)
FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

42:   if (u == 0 || u == m || m == 0) return 0;
138:  if (0 == x || x == p || 0 == y || y == p) {
392:  if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) {

EllipticCurve.sol#L42

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

187:  if (owner == address(0) || owner == previousRegistrar) {

DNSRegistrar.sol#L187

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

if (
                !rrname.equals(name) ||
                iter.class != CLASS_INET ||
                iter.dnstype != TYPE_TXT
            ) {

OffchainDNSResolver.sol#L86-L90

[G-12] Use assembly to check for address(0)

Instances(7)

Saves 6 gas per instance

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

102: if (dnsresolver != address(0)) 
197: if (resolver == address(0)) {

OffchainDNSResolver.sol#L102

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

115: if (addr != address(0)) {
116: if (resolver == address(0)) {
187: if (owner == address(0) || owner == previousRegistrar) {

DNSRegistrar.sol#L115-L116

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

317:  if (address(algorithm) == address(0)) {
420:  if (address(digests[digesttype]) == address(0)) {

DNSSECImpl.sol#L317

[G-13] Shorthand way to write if / else statement can reduce the deployment cost

Instances(4)

FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol

if (separator < lastIdx) {
            parentNode = textNamehash(name, separator + 1, lastIdx);
        } else {
            separator = lastIdx;
        }

OffchainDNSResolver.sol#L216-L220

FILE : 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

if (shortest - idx >= 32) {
                    mask = type(uint256).max;
                } else {
                    mask = ~(2 ** (8 * (idx + 32 - shortest)) - 1);
                }

BytesUtils.sol#L87-L91

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

221: if (isZeroCurve(x0, y0)) {
            return (x1, y1, z1);
        } else if (isZeroCurve(x1, y1)) {
            return (x0, y0, z0);
        }

234: if (t0 == t1) {
                return twiceProj(x0, y0, z0);
            } else {
                return zeroProj();
            }

EllipticCurve.sol#L221-L225


if (separator < lastIdx) {
            parentNode = textNamehash(name, separator + 1, lastIdx);
        } else {
            separator = lastIdx;
        }

separator < lastIdx ? parentNode = textNamehash(name, separator + 1, lastIdx); : separator = lastIdx ;

[G-14] The Less gas consuming condition checks should be on top

Instances(1)

When writing conditional statements in smart contracts, it is generally best practice to order the conditions so that the less gas-consuming checks are performed first. This can help to optimize the gas usage of the contract and improve its overall efficiency

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

> "if (!isOnCurve(Q[0], Q[1])) {" check should come top then "if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) { " condition 

  if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) {
            // || rs[1] > lowSmax)
            return false;
        }
        if (!isOnCurve(Q[0], Q[1])) {
            return false;
        }

EllipticCurve.sol#L392-L396

[G-15] internal functions not called by the contract should be removed to save deployment gas

Instances(3)

If the functions are required by an interface, the contract should inherit from that interface and use the override keyword

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

386: function validateSignature(
        bytes32 message,
        uint256[2] memory rs,
        uint256[2] memory Q
    ) internal pure returns (bool) {

377: function multipleGeneratorByScalar(
        uint256 scalar
    ) internal pure returns (uint256, uint256) {

316: function multiplyPowerBase2(
        uint256 x0,
        uint256 y0,
        uint256 exp
    ) internal pure returns (uint256, uint256) {

EllipticCurve.sol#L386-L390

[G-16] Modifiers or private functions only called once can be inlined to save gas

Instances (2)

ITs possible to save 40-50 gas

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

73: modifier onlyOwner() {

DNSRegistrar.sol#L73-L78

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

273: function memcpy(uint256 dest, uint256 src, uint256 len) private pure {

BytesUtils.sol#L273

[G-17] NOT USING THE NAMED RETURN VARIABLES WHEN A FUNCTION RETURNS, WASTES DEPLOYMENT GAS

Instances (12)

FILE:  2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

166: function enableNode(bytes memory domain) public returns (bytes32 node) {

174: function _enableNode(
        bytes memory domain,
        uint256 offset
    ) internal returns (bytes32 node) {

DNSRegistrar.sol#L166

FILE: 2023-04-ens/contracts/utils/NameEncoder.sol

9: function dnsEncodeName(
        string memory name
    ) internal pure returns (bytes memory dnsName, bytes32 node) {

NameEncoder.sol#L9-L11

FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol

106: function zeroProj()
        internal
        pure
        returns (uint256 x, uint256 y, uint256 z)
    {

117:  function zeroAffine() internal pure returns (uint256 x, uint256 y) {

124: function isZeroCurve(
        uint256 x0,
        uint256 y0
    ) internal pure returns (bool isZero) {

208: function addProj(
        uint256 x0,
        uint256 y0,
        uint256 z0,
        uint256 x1,
        uint256 y1,
        uint256 z1
    ) internal pure returns (uint256 x2, uint256 y2, uint256 z2) {

335: function multiplyScalar(
        uint256 x0,
        uint256 y0,
        uint256 scalar
    ) internal pure returns (uint256 x1, uint256 y1) {

EllipticCurve.sol#L106-L110

FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol

87: function verifyRRSet(
        RRSetWithSignature[] memory input
    )
        external
        view
        virtual
        override
        returns (bytes memory rrs, uint32 inception)

107: function verifyRRSet(
        RRSetWithSignature[] memory input,
        uint256 now
    )
        public
        view
        virtual
        override
        returns (bytes memory rrs, uint32 inception)

140: function validateSignedSet(
        RRSetWithSignature memory input,
        bytes memory proof,
        uint256 now
    ) internal view returns (RRUtils.SignedSet memory rrset) {

181: function validateRRs(
        RRUtils.SignedSet memory rrset,
        uint16 typecovered
    ) internal pure returns (bytes memory name) {


DNSSECImpl.sol#L87-L94

[G-18] Use constants instead of type(uintx).max

Instances (5):

type(uint256).max it uses more gas in the distribution process and also for each transaction than constant usage

FILE: 2023-04-ens/contracts/dnsregistrar/RecordParser.sol

24: if (separator == type(uint256).max) {
25: return ("", "", type(uint256).max);

33: if (terminator == type(uint256).max) {

RecordParser.sol#L24-L25

FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol

398: return type(uint256).max;
88:  mask = type(uint256).max;

BytesUtils.sol#L398

[G-19] Instead of calculating bytes32(0) every time inside the contract its possible to use constants to reduce the gas cost

Instances (1)

By defining a constant variable, you can avoid computing the value of bytes32(0) every time it is used in your contract. Instead, you can simply reference the constant variable, which will have the same value as bytes32(0)

FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol

74:  Root root = Root(ens.owner(bytes32(0)));
180: return bytes32(0);
188: if (parentNode == bytes32(0)) {
189: Root root = Root(ens.owner(bytes32(0)));

DNSRegistrar.sol#L74

[G-20] Structs can be packed into fewer storage slots

Instances (1)

Aproximate Gas Saved: 40000 gas (2 Slots)

As the solidity EVM works with 32 bytes, variables less than 32 bytes should be packed inside a struct so that they can be stored in the same slot, this saves gas when writing to storage ~20000 gas

For offset,rdataOffset,nextOffset variables uint128 alone enough. So we can avoid 2 slots (40000 gas )

2023-04-ens/contracts/dnssec-oracle/RRUtils.sol

  120: struct RRIterator {
  121:        bytes data;
+ 122:        uint128 offset;
- 122:        uint256 offset;
  123:        uint16 dnstype;
  124:        uint16 class;
  125:        uint32 ttl;
+ 126:        uint128 rdataOffset;
+ 127:        uint128 nextOffset;
- 126:        uint256 rdataOffset;
- 127:        uint256 nextOffset;
  128:    }

#0 - c4-pre-sort

2023-05-02T08:10:26Z

thereksfour marked the issue as high quality report

#1 - c4-sponsor

2023-05-05T14:20:26Z

Arachnid marked the issue as sponsor acknowledged

#2 - c4-judge

2023-05-08T09:40:27Z

dmvt marked the issue as grade-b

#3 - c4-judge

2023-05-08T09:41:00Z

dmvt marked the issue as grade-a

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