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
Rank: 11/59
Findings: 2
Award: $1,588.85
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: Sathish9098
Also found by: 0x73696d616f, 0xAgro, 0xSmartContract, 0xTheC0der, ABA, ArbitraryExecution, Aymen0909, BRONZEDISC, Bauchibred, Dyear, Eurovickk, IceBear, Jerry0x, Jorgect, Josiah, MalfurionWhitehat, MohammedRizwan, RaymondFam, Recep, Rickard, SAAJ, Shubham, Udsen, auditor0517, brgltd, catellatech, chaduke, codeslide, eierina, favelanky, j4ld1na, lukris02, matrix_0wl, naman1778, pontifex, schrodinger, tnevler, urataps
828.1889 USDC - $828.19
LOW COUNT | ISSUES | INSTANCES |
---|---|---|
[L-1] | Use abi.encode to convert safest way from uint values to bytes | 3 |
[L-2] | Loss of precision due to rounding | 1 |
[L-3] | Consider using OpenZeppelin’s SafeCast library to prevent unexpected overflows when casting from uint256 | 11 |
[L-4] | MIXING AND OUTDATED COMPILER | 7 |
[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 Checks | 2 |
[L-7] | Function Calls in Loop Could Lead to Denial of Service | 10 |
[L-8] | Project Upgrade and Stop Scenario should be | - |
[L-9] | Front running attacks by the onlyOwner | 3 |
[L-10] | Use BytesLib.sol library to safely covert bytes to uint256 | 2 |
[L-11] | Avoid infinite loops whenever possible | 2 |
[L-12] | In the constructor, there is no return of incorrect address identification | 6 |
[L-13] | Even with the onlyOwner or owner_only modifier, it is best practice to use the re-entrancy pattern | 3 |
NC COUNT | ISSUES | INSTANCES |
---|---|---|
[NC-1] | immutable should be uppercase | 4 |
[NC-2] | Missing NATSPEC | - |
[NC-3] | For functions, follow Solidity standard naming conventions (internal function style rule) | 28 |
[NC-4] | Need Fuzzing test for unchecked | 5 |
[NC-5] | Remove commented out code | - |
[NC-6] | Inconsistent method of specifying a floating pragma | 8 |
[NC-7] | NO SAME VALUE INPUT CONTROL | 1 |
[NC-8] | Constant redefined elsewhere | - |
[NC-9] | According to the syntax rules, use => mapping ( instead of => mapping( using spaces as keyword | 3 |
[NC-10] | Use SMTChecker | - |
[NC-11] | Constants on the left are better | 20 |
[NC-12] | Assembly Codes Specific – Should Have Comments | 18 |
[NC-13] | Take advantage of Custom Error’s return value property | 4 |
[NC-14] | Use constants instead of using numbers directly without explanations | 6 |
[NC-15] | Shorthand way to write if / else statement | 4 |
[NC-16] | For critical changes should emit both old and new values | 1 |
[NC-17] | Don't use named return variables its confusing | 8 |
[NC-18] | NON-LIBRARY/INTERFACE FILES SHOULD USE FIXED COMPILER VERSIONS, NOT FLOATING ONES | 7 |
[NC-19] | Constants should be in uppercase | 7 |
[NC-20] | TYPOS | 4 |
[NC-21] | Contracts should have full test coverage | - |
[NC-22] | Use named parameters for mapping type declarations | 3 |
[NC-23] | File does not contain an SPDX Identifier | 4 |
[NC-24] | declaration shadows an existing declaration | - |
[NC-25] | Event is missing indexed fields | 2 |
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.
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);
FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol 376: return bytes32(ret << (256 - bitlen));
FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol 52: q = r1 / r2;
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));
FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol 430: return uint16(ac1);
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);
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]);
Consider using OpenZeppelin’s SafeCast library to prevent unexpected overflows when casting from uint256.
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;
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));
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 );
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)) );
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)); }
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; }
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; }
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
FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol 80: function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner { suffixes = _suffixes; emit NewPublicSuffixList(address(suffixes)); }
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)); }
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))];
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); }
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; } }
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;
require(_ens!=address(0), " zero address");
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)); }
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)); }
Recommended Mitigation:
modifier noReentrant() { require(!locked, "Reentrant call"); locked = true; _; locked = false; } function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner noReentrant {
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;
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
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 {
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: * }
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;
FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol 80: function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner { suffixes = _suffixes; emit NewPublicSuffixList(address(suffixes)); }
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;
FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol 45: mapping(uint8 => Algorithm) public algorithms; 46: mapping(uint8 => Digest) public digests;
FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol 32: mapping(bytes32 => uint32) public inceptions;
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
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
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) {
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) {
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) {
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 {
FILE: 2023-04-ens/contracts/utils/HexUtils.sol 17: assembly {
FILE: FILE: 2023-04-ens/contracts/dnssec-oracle/RRUtils.sol 386: assembly {
FILE: 2023-04-ens/contracts/dnssec-oracle/SHA1.sol 7: assembly {
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 {
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();
FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol 38: error InvalidRRSet();
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);
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");
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); }
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(); }
if (separator < lastIdx) { parentNode = textNamehash(name, separator + 1, lastIdx); } else { separator = lastIdx; }
separator < lastIdx ? parentNode = textNamehash(name, separator + 1, lastIdx); : separator = lastIdx ;
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)); }
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) {
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;
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/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
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
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;
FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol 32: mapping(bytes32 => uint32) public inceptions;
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(
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);
#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
760.662 USDC - $760.66
Gas Count | Issues | Instances | Gas Saved |
---|---|---|---|
[G-1] | State variable value only set in the constructor can be declared as a constants to save large volume of the gas | 1 | 90000 |
[G-2] | State variables only set in the constructor should be declared immutable | 1 | 20000 |
[G-3] | Using storage instead of memory for structs/arrays saves gas | 3 | 6300 |
[G-4] | Multiple address/ID mappings can be combined into a single mapping of an address/ID to a struct, where appropriate | 1 | - |
[G-5] | For events use 3 indexed rule to save gas | 3 | - |
[G-6] | Lack of input value checks cause a redeployment if any human/accidental errors | 4 | - |
[G-7] | Use nested if and, avoid multiple check combinations | 2 | 18 |
[G-8] | Unnecessary look up in if condition | 7 | - |
[G-9] | Don't declare the variable inside the loops | 3 | - |
[G-10] | Functions should be used instead of modifiers to save gas | 1 | - |
[G-11] | Sort Solidity operations using short-circuit mode | 5 | - |
[G-12] | Use assembly to check for address(0) | 7 | 42 |
[G-13] | Shorthand way to write if / else statement can reduce the deployment cost | 4 | - |
[G-14] | The Less gas consuming condition checks should be on top | 1 | - |
[G-15] | nternal functions not called by the contract should be removed to save deployment gas | 3 | - |
[G-16] | Modifiers or private functions only called once can be inlined to save gas | 2 | 80 |
[G-17] | NOT USING THE NAMED RETURN VARIABLES WHEN A FUNCTION RETURNS, WASTES DEPLOYMENT GAS | 12 | - |
[G-18] | Use constants instead of type(uintx).max | 5 | - |
[G-19] | Instead of calculating bytes32(0) every time inside the contract its possible to use constants to reduce the gas cost | 1 | - |
[G-20] | Structs can be packed into fewer storage slots | 1 | 40000 |
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
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;
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);
FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol 307: bytes memory ret = new bytes(len);
FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol 408: uint256[3] memory P = addAndReturnProjectivePoint(x1, y1, x2, y2);
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;
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);
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;
FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol 80: function setPublicSuffixList(PublicSuffixList _suffixes) public onlyOwner { suffixes = _suffixes; emit NewPublicSuffixList(address(suffixes)); }
FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol 55: anchors = _anchors;
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") {
FILE: 2023-04-ens/contracts/dnssec-oracle/algorithms/EllipticCurve.sol 128: if (x0 == 0 && y0 == 0) {
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) {
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;
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) ) {
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;
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); _; }
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) {
FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol 187: if (owner == address(0) || owner == previousRegistrar) {
FILE: 2023-04-ens/contracts/dnsregistrar/OffchainDNSResolver.sol if ( !rrname.equals(name) || iter.class != CLASS_INET || iter.dnstype != TYPE_TXT ) {
OffchainDNSResolver.sol#L86-L90
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)) {
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) {
FILE: 2023-04-ens/contracts/dnssec-oracle/DNSSECImpl.sol 317: if (address(algorithm) == address(0)) { 420: if (address(digests[digesttype]) == address(0)) {
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); }
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(); }
if (separator < lastIdx) { parentNode = textNamehash(name, separator + 1, lastIdx); } else { separator = lastIdx; }
separator < lastIdx ? parentNode = textNamehash(name, separator + 1, lastIdx); : separator = lastIdx ;
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; }
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) {
Instances (2)
ITs possible to save 40-50 gas
FILE: 2023-04-ens/contracts/dnsregistrar/DNSRegistrar.sol 73: modifier onlyOwner() {
FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol 273: function memcpy(uint256 dest, uint256 src, uint256 len) private pure {
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) {
FILE: 2023-04-ens/contracts/utils/NameEncoder.sol 9: function dnsEncodeName( string memory name ) internal pure returns (bytes memory dnsName, bytes32 node) {
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) {
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) {
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) {
FILE: 2023-04-ens/contracts/dnssec-oracle/BytesUtils.sol 398: return type(uint256).max; 88: mask = type(uint256).max;
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)));
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