Platform: Code4rena
Start Date: 12/07/2022
Pot Size: $75,000 USDC
Total HM: 16
Participants: 100
Period: 7 days
Judge: LSDan
Total Solo HM: 7
Id: 145
League: ETH
Rank: 8/100
Findings: 3
Award: $1,709.39
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: 0x1f8b
1562.0489 USDC - $1,562.05
equals
with offset might return true when equals
without offset returns false.
The problem is that self.length
could be greater than other.length + offset
, it should be ==
, or it should contain a length argument.
Here you have an example of the failure:
equals(0x0102030000, 0, 0x010203)
=> return true
decoded input { "bytes self": "0x0102030000", "uint256 offset": "0", "bytes other": "0x010203" } decoded output { "0": "bool: true" }
function equals(bytes memory self, uint offset, bytes memory other) internal pure returns (bool) { - return self.length >= offset + other.length && equals(self, offset, other, 0, other.length); + return self.length == offset + other.length && equals(self, offset, other, 0, other.length); }
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0xDjango, 0xNazgul, 0xNineDec, 0xf15ers, 8olidity, Aussie_Battlers, Bnke0x0, Ch_301, Critical, Deivitto, Dravee, ElKu, Funen, GimelSec, JC, JohnSmith, Lambda, MiloTruck, PwnedNoMore, ReyAdmirado, Rohan16, Rolezn, Ruhum, RustyRabbit, Sm4rty, TomJ, Waze, _Adam, __141345__, alan724, asutorufos, benbaessler, berndartmueller, bin2chen, brgltd, bulej93, c3phas, cRat1st0s, cryptonue, cryptphi, csanuragjain, delfin454000, dxdv, exd0tpy, fatherOfBlocks, gogo, hake, hyh, joestakey, kyteg, lcfr_eth, minhtrng, p_crypt0, pashov, pedr02b2, philogy, rajatbeladiya, rbserver, rishabh, robee, rokinot, sach1r0, sashik_eth, seyni, simon135, svskaushik, zuhaibmohd, zzzitron
103.0422 USDC - $103.04
Some used packages are out of date, it is good practice to use the latest version of these packages:
"@ensdomains/buffer": "^0.0.13" => 0.1.0 "@openzeppelin/contracts": "^4.1.0" => 4.7.0
The pragma version used is:
pragma solidity ^0.8.4; pragma solidity ^0.8.13;
But recently solidity released a new version with important Bugfixes:
The first one is related to ABI-encoding nested arrays directly from calldata. You can find more information here.
The second bug is triggered in certain inheritance structures and can cause a memory pointer to be interpreted as a calldata pointer or vice-versa. We also have a dedicated blog post about this bug.
Apart from these, there are several minor bug fixes and improvements.
The minimum required version should be 0.8.15
It's possible to lose the ownership under specific circumstances.
Because an human error it's possible to set a new invalid owner. When you want to change the owner's address it's better to propose a new owner, and then accept this ownership with the new wallet.
Affected source code:
The following lines requires a valid address as an argument, in some of them, once the value is set, it cannot be changed again, so it is mandatory to check if these values are as expected.
Affected source code for address(0)
:
The user's ether could be locked and unrecoverable.
Because to transfer ether the .transfer
method (which is capped at 2300 gas) is used instead of .call
which is limited to the gas provided by the user. If a contract that has a fallback
method more expensive than 2300 gas, creates an offer, it will be impossible for this contract cancel the offer or receive funds back to that address.
Reference:
Affected lines:
High impact:
Low impact:
Recommended Mitigation Steps:
.call
instead of .transfer
.encode
instead of encodePacked
for hashigUse of abi.encodePacked
is safe, but unnecessary and not recommended. abi.encodePacked
can result in hash collisions when used with two dynamic arguments (string/bytes).
There is also discussion of removing abi.encodePacked
from future versions of Solidity (ethereum/solidity#11593), so using abi.encode
now will ensure compatibility in the future.
Affected source code:
ERC1155Fuse
In the contract ERC1155Fuse
the methods safeTransferFrom
and safeBatchTransferFrom
has different logic when the to is the same owner. if the user use safeTransferFrom
to transfer a not owned item to the same owner, nothing will happend, and no-revert will ocurr, but if you use safeBatchTransferFrom
a revert will happend.
In the ERC1155Fuse
contract, the safeTransferFrom
and safeBatchTransferFrom
methods have different logic when the destination (to
) is the owner. if the user uses safeTransferFrom
to transfer an item that they don't own to the same owner, nothing will happen and it won't be reverted, but if they use safeBatchTransferFrom
, it will be reverted.
Affected source code:
now
alias should be avoidedThere is an argument named now
, this is an alias that could work in a different way with different versions of solidity because it's a reserved word. As the documentation said is not critical, but should be avoided.
In version 0.7.0, the alias now (for block.timestamp) was removed.
Reference:
Affected source code:
The code that contains "open todos" reflects that the development is not finished and that the code can change a posteriori, prior release, with or without audit.
Affected source code:
abstract
for base contractsAbstract contracts are contracts that have at least one function without its implementation. An instance of an abstract cannot be created.
Reference:
Affected source code:
The following methods do not have any authentication and the states can be modified by anyone:
https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/ethregistrar/TestResolver.sol#L21 https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/ethregistrar/DummyOracle.sol#L11 https://github.com/code-423n4/2022-07-ens/blob/ff6e59b9415d0ead7daf31c2ed06e86d9061ae22/contracts/registry/TestRegistrar.sol#L31
🌟 Selected for report: 0xKitsune
Also found by: 0x040, 0x1f8b, 0x29A, 0xNazgul, 0xNineDec, 0xsam, 8olidity, Aussie_Battlers, Aymen0909, Bnke0x0, CRYP70, Ch_301, Chom, Deivitto, Dravee, ElKu, Fitraldys, Funen, GimelSec, IllIllI, JC, JohnSmith, Lambda, MiloTruck, Noah3o6, RedOneN, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, TomJ, Tomio, Waze, _Adam, __141345__, ajtra, ak1, arcoun, asutorufos, benbaessler, brgltd, bulej93, c3phas, cRat1st0s, cryptonue, delfin454000, durianSausage, fatherOfBlocks, gogo, hake, hyh, joestakey, karanctf, kyteg, lcfr_eth, lucacez, m_Rassska, rajatbeladiya, rbserver, robee, rokinot, sach1r0, sahar, samruna, sashik_eth, seyni, simon135, zuhaibmohd
44.3038 USDC - $44.30
delete
optimizationShortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition is met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
I suggest shortening the revert strings to fit in 32 bytes, or that using custom errors as described next (require pragma upgrade).
Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)
Source Custom Errors in Solidity:
Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.
Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).
Affected source code:
immutable
It's possible to avoid storage access a save gas using immutable
keyword for the following variables:
It's also better to remove the initial values, because they will be set during the constructor.
Affected source code:
constants
expressions are expressions, not constants
Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.
If the variable was immutable instead: the calculation would only be done once at deploy time (in the constructor), and then the result would be saved and read directly at runtime rather than being recalculated.
Reference:
Consequences: each usage of a "constant" costs ~100gas more on each access (it is still a little better than storing the result in storage, but not much..). since these are not real constants, they can't be referenced from a real constant environment (e.g. from assembly, or from another library )
Affected source code:
++i
costs less gas compared to i++
or i += 1
++i
costs less gas compared to i++
or i += 1
for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.
i++
increments i
and returns the initial value of i
. Which means:
uint i = 1; i++; // == 1 but i == 2
But ++i
returns the actual incremented value:
uint i = 1; ++i; // == 2 and i == 2 too, so no need for a temporary variable
In the first case, the compiler has to create a temporary variable (when used) for returning 1
instead of 2
I suggest using ++i
instead of i++
to increment the value of an uint variable. Same thing for --i
and i--
Affected source code:
It's cheaper to store the length of the array inside a local variable and iterate over it.
Affected source code:
If a variable is not set/initialized, the default value is assumed (0, false
, 0x0 ... depending on the data type). You are simply wasting gas if you directly initialize it with its default value.
Affected source code:
abi.encodeWithSelector
instead of abi.encodeWithSignature
abi.encodeWithSelector
is much cheaper than abi.encodeWithSignature
because it doesn't require to compute the selector from the string.
Reference:
Affected source code:
encodepacked
Use encodepacked
with one unique dynamic value will return the same output and input, so it can be avoided.
Affected source code:
delete
optimizationUse delete
instead of set to default value (false
or 0
).
5 gas could be saved per entry in the following affected lines:
function _burn(uint256 tokenId) internal virtual { address owner = ownerOf(tokenId); // Clear fuses and set owner to 0 - _setData(tokenId, address(0x0), 0, 0); + delete _tokens[tokenId]; emit TransferSingle(msg.sender, owner, address(0x0), tokenId, 1); }
#0 - jefflau
2022-08-01T09:53:20Z
- Gas saving using immutable
Most of these contracts are out of scope and are effectively unchangeable
#1 - jefflau
2022-08-01T09:53:57Z
- delete optimization
An optimisation I haven't seen anyone else so far report! (Although i haven't confirmed the gas reduction)