1.1. Critical known vulnerabilities exist in currently used @openzeppelin/contracts version
As several known critical vulnerabilities exist in the current @openzeppelin/contracts version, consider updating package.json with at least @openzeppelin/contracts@4.4.1 here:
SafeMath and Solidity 0.8.* handles overflows for basic math operations but not for casting.
Consider using OpenZeppelin's SafeCast library to prevent unexpected overflows when casting from uint256 here:
1.5. _safeMint() should be used rather than _mint() wherever possible
_mint() is discouraged in favor of _safeMint() which ensures that the recipient is either an EOA or implements ERC1155TokenReceiver's onERC1155Received.
Be careful however to respect the CEI pattern or add a re-entrancy guard as _safeMint adds a callback-check and a malicious onERC1155Received could be exploited if not careful (the CEIP is respected here).
Contracts inheriting from OpenZeppelin's libraries have the default transferOwnership() function (a one-step process). It's possible that the onlyOwner role mistakenly transfers ownership to a wrong address, resulting in a loss of the onlyOwner role.
Consider overriding the default transferOwnership() function to first nominate an address as the pendingOwner and implementing an acceptOwnership() function which is called by the pendingOwner to confirm the transfer.
ETHRegistrarController.sol
ethregistrar/ETHRegistrarController.sol:9:import "@openzeppelin/contracts/access/Ownable.sol";
ethregistrar/ETHRegistrarController.sol:17:contract ETHRegistrarController is Ownable, IETHRegistrarController {
ReverseRegistrar.sol
registry/ReverseRegistrar.sol:5:import "@openzeppelin/contracts/access/Ownable.sol";
registry/ReverseRegistrar.sol:18:contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar {
Controllable.sol
wrapper/Controllable.sol:4:import "@openzeppelin/contracts/access/Ownable.sol";
wrapper/Controllable.sol:6:contract Controllable is Ownable {
1.7. require() should be used for checking error conditions on inputs and return values while assert() should be used for invariant checking
Properly functioning code should never reach a failing assert statement, unless there is a bug in your contract you should fix. Here, I believe the assert should be a require or a revert:
As the Solidity version is > 0.8.* the remaining gas would still be refunded in case of failure.
2. Non-Critical Issues
2.1. Avoid using deprecated keywords (now) as variable names
now is a deprecated keyword that was used before block.timestamp. Here, a variable is named as "now", which introduces some misunderstanding from the IDE:
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). If there is only one argument to abi.encodePacked() it can often be cast to bytes() or bytes32() instead.
dnssec-oracle/DNSSECImpl.sol:105: * data, followed by a series of canonicalised RR records that the signature
optimised vs optimized
registry/ReverseRegistrar.sol:156: * @dev An optimised function to compute the sha3 of the lower-case
Authorises vs Authorizes
ethregistrar/IBaseRegistrar.sol:20: // Authorises a controller, who can register and renew domains.
authorised vs authorized
registry/ReverseRegistrar.sol:46: "ReverseRegistrar: Caller is not a controller or authorised by address or the address itself"
registry/ReverseRegistrar.sol:126: * Only callable by controllers and authorised users
wrapper/NameWrapper.sol:202: * @dev Can be called by the owner of the name on the .eth registrar or an authorised caller on the registrar
wrapper/NameWrapper.sol:241: * Only callable by authorised controllers.
wrapper/NameWrapper.sol:266: * Only callable by authorised controllers.
2.5. Use a constant instead of duplicating the same string
wrapper/ERC1155Fuse.sol:176: require(to != address(0), "ERC1155: transfer to the zero address");
wrapper/ERC1155Fuse.sol:199: require(to != address(0), "ERC1155: transfer to the zero address");
wrapper/ERC1155Fuse.sol:327: revert("ERC1155: transfer to non ERC1155Receiver implementer");
wrapper/ERC1155Fuse.sol:359: revert("ERC1155: transfer to non ERC1155Receiver implementer");
2.8. Adding a return statement when the function defines a named return variable, is redundant
While not consuming more gas with the Optimizer enabled: using both named returns and a return statement isn't necessary. Removing one of those can improve code clarity.
Affected code:
BytesUtils.sol#readUint8()
135: function readUint8(bytes memory self, uint idx) internal pure returns (uint8 ret) {
136: return uint8(self[idx]);
137: }
Table of Contents
@openzeppelin/contracts
versionpragma experimental ABIEncoderV2
is deprecated_safeMint()
should be used rather than_mint()
wherever possiblerequire()
should be used for checking error conditions on inputs and return values whileassert()
should be used for invariant checkingnow
) as variable namesabi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such askeccak256()
constant
instead of duplicating the same stringstring.concat()
orbytes.concat()
return
statement when the function defines a named return variable, is redundant1. Low Risk Issues
1.1. Critical known vulnerabilities exist in currently used
@openzeppelin/contracts
versionAs several known critical vulnerabilities exist in the current
@openzeppelin/contracts
version, consider updatingpackage.json
with at least@openzeppelin/contracts@4.4.1
here:While vulnerabilities are known, the current scope isn't affected (this might not hold true for the whole solution or a future update)
1.2.
pragma experimental ABIEncoderV2
is deprecatedUse
pragma abicoder v2
instead1.3. Unsafe casting may overflow
SafeMath and Solidity 0.8.* handles overflows for basic math operations but not for casting. Consider using OpenZeppelin's SafeCast library to prevent unexpected overflows when casting from uint256 here:
1.4. Missing address(0) checks
Consider adding an
address(0)
check for immutable variables:1.5.
_safeMint()
should be used rather than_mint()
wherever possible_mint()
is discouraged in favor of_safeMint()
which ensures that the recipient is either an EOA or implementsERC1155TokenReceiver
'sonERC1155Received
.Be careful however to respect the CEI pattern or add a re-entrancy guard as
_safeMint
adds a callback-check and a maliciousonERC1155Received
could be exploited if not careful (the CEIP is respected here).Reading material:
1.6. Use a 2-step ownership transfer pattern
Contracts inheriting from OpenZeppelin's libraries have the default
transferOwnership()
function (a one-step process). It's possible that theonlyOwner
role mistakenly transfers ownership to a wrong address, resulting in a loss of theonlyOwner
role. Consider overriding the defaulttransferOwnership()
function to first nominate an address as thependingOwner
and implementing anacceptOwnership()
function which is called by thependingOwner
to confirm the transfer.1.7.
require()
should be used for checking error conditions on inputs and return values whileassert()
should be used for invariant checkingProperly functioning code should never reach a failing assert statement, unless there is a bug in your contract you should fix. Here, I believe the assert should be a require or a revert:
As the Solidity version is > 0.8.* the remaining gas would still be refunded in case of failure.
2. Non-Critical Issues
2.1. Avoid using deprecated keywords (
now
) as variable namesnow
is a deprecated keyword that was used beforeblock.timestamp
. Here, a variable is named as "now", which introduces some misunderstanding from the IDE:Consider changing the variable's name.
2.2.
abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such askeccak256()
Similar issue in the past: here Original issue: Hash collisions when using abi.encodePacked() with multiple variable length arguments
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)
, butabi.encode(0x123,0x456)
=>0x0...1230...456
). If there is only one argument toabi.encodePacked()
it can often be cast tobytes()
orbytes32()
instead.Here, no attack vector can be thought of. It's a simple mispractice, hence the NC severity.
2.3. It's better to emit after all processing is done
2.4. Typos
2.5. Use a
constant
instead of duplicating the same string2.6. Open TODOS
Consider resolving the TODOs before deploying.
2.7. Use
string.concat()
orbytes.concat()
Solidity version 0.8.4 introduces
bytes.concat()
(vsabi.encodePacked(<bytes>,<bytes>)
) Solidity version 0.8.12 introducesstring.concat()
(vsabi.encodePacked(<str>,<str>)
)2.8. Adding a
return
statement when the function defines a named return variable, is redundantWhile not consuming more gas with the Optimizer enabled: using both named returns and a return statement isn't necessary. Removing one of those can improve code clarity.
Affected code:
2.9. Non-library/interface files should use fixed compiler versions, not floating ones