Platform: Code4rena
Start Date: 26/07/2022
Pot Size: $75,000 USDC
Total HM: 29
Participants: 179
Period: 6 days
Judge: LSDan
Total Solo HM: 6
Id: 148
League: ETH
Rank: 27/179
Findings: 6
Award: $457.14
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: berndartmueller
Also found by: 0x1f8b, 0x52, 0xA5DF, 0xsanson, CRYP70, GimelSec, Krow10, TrungOre, auditor0517, hansfriese, hyh, panprog, rajatbeladiya, rbserver, teddav
93.2805 USDC - $93.28
The addVoteEscrow
method is poorly implemented and denies service..
The addVoteEscrow
method sets the value of ve
to pendingVoteEscrow
as long as the ve
is empty, instead of using the received argument (_voteEscrow
) since pendingVoteEscrow
is empty, it becomes impossible modify ve
mediate that method.
function addVoteEscrow(address _voteEscrow) external onlyOwner { if (address(ve) == address(0)) { ve = VE(_voteEscrow); ve = VE(pendingVoteEscrow); } ...
Fix the method like following:
function addVoteEscrow(address _voteEscrow) external onlyOwner { if (address(ve) == address(0)) { + ve = VE(_voteEscrow); - ve = VE(pendingVoteEscrow); } else { voteEscrowEnableDate = block.timestamp + 1 days; pendingVoteEscrow = _voteEscrow; } }
#0 - KenzoAgada
2022-08-02T09:24:52Z
Duplicate of #611
🌟 Selected for report: cloudjunky
Also found by: 0x1f8b, 0x4non, 0x52, 0xDjango, 0xHarry, 0xNazgul, 0xNineDec, 0xf15ers, 0xsanson, 0xsolstars, 8olidity, Bnke0x0, CertoraInc, Chom, Deivitto, Dravee, GalloDaSballo, GimelSec, IllIllI, Jmaxmanblue, JohnSmith, Jujic, Kenshin, Krow10, Lambda, MEP, Noah3o6, RedOneN, Ruhum, StErMi, StyxRave, TomJ, Treasure-Seeker, TrungOre, _Adam, __141345__, arcoun, asutorufos, bardamu, bearonbike, bin2chen, brgltd, bulej93, c3phas, cRat1st0s, carlitox477, cccz, codexploder, cryptonue, cryptphi, cthulhu_cult, dharma09, dipp, djxploit, durianSausage, ellahi, giovannidisiena, hansfriese, horsefacts, hyh, immeas, indijanc, jayjonah8, jayphbee, joestakey, kenzo, kyteg, ladboy233, minhquanym, navinavu, obront, oyc_109, peritoflores, rbserver, reassor, rokinot, rotcivegaf, saian, scaraven, shenwilly, simon135, sseefried, teddav, zzzitron
0.0037 USDC - $0.00
Judge has assessed an item in Issue #622 as Medium risk. The relevant finding follows:
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 source code:
#0 - dmvt
2022-10-21T11:59:59Z
Duplicate of #343
🌟 Selected for report: codexploder
Also found by: 0x1f8b, 0xNineDec, 0xsanson, RustyRabbit, Treasure-Seeker, berndartmueller, chatch, teddav
104.014 USDC - $104.01
Judge has assessed an item in Issue #622 as Medium risk. The relevant finding follows:
If there is a hard fork, the EIP712_DOMAIN_TYPEHASH
is not computed. After initialization, the variable EIP712_DOMAIN_TYPEHASH
in contract GolomTrader
is cached in the contract storage and does not change. However, if a hard fork takes place after the contract deployment, the block would cause the domain to become invalid on one of the forked chains. chainid
is now different.
Affected source code:
#0 - dmvt
2022-10-21T12:01:43Z
Duplicate of #391
130.0175 USDC - $130.02
Judge has assessed an item in Issue #622 as Medium risk. The relevant finding follows:
#0 - dmvt
2022-10-21T11:59:45Z
Duplicate of #357
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0x52, 0xA5DF, 0xDjango, 0xLovesleep, 0xNazgul, 0xNineDec, 0xSmartContract, 0xackermann, 0xc0ffEE, 0xf15ers, 0xmatt, 0xsanson, 0xsolstars, 8olidity, AuditsAreUS, Bahurum, Bnke0x0, CRYP70, CertoraInc, Ch_301, Chom, CryptoMartian, Deivitto, DevABDee, Dravee, ElKu, Franfran, Funen, GalloDaSballo, GimelSec, GiveMeTestEther, Green, JC, Jmaxmanblue, JohnSmith, Jujic, Junnon, Kenshin, Krow10, Kumpa, Lambda, MEP, Maxime, MiloTruck, Mohandes, NoamYakov, Picodes, RedOneN, Rohan16, Rolezn, Ruhum, RustyRabbit, Sm4rty, Soosh, StErMi, StyxRave, Tadashi, TomJ, Treasure-Seeker, TrungOre, Waze, _Adam, __141345__, ajtra, ak1, apostle0x01, arcoun, asutorufos, async, benbaessler, berndartmueller, bin2chen, brgltd, c3phas, cRat1st0s, carlitox477, chatch, codetilda, codexploder, cryptonue, cryptphi, csanuragjain, cthulhu_cult, delfin454000, dipp, dirk_y, djxploit, ellahi, exd0tpy, fatherOfBlocks, giovannidisiena, hansfriese, horsefacts, hyh, idkwhatimdoing, indijanc, jayfromthe13th, jayphbee, joestakey, kenzo, kyteg, lucacez, luckypanda, mics, minhquanym, obront, oyc_109, pedr02b2, rajatbeladiya, rbserver, reassor, robee, rokinot, rotcivegaf, sach1r0, saian, saneryee, sashik_eth, scaraven, shenwilly, simon135, sseefried, supernova, teddav, ych18, zuhaibmohd, zzzitron
35.1687 USDC - $35.17
The pragma version used is:
pragma solidity 0.8.11;
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
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)
:
Integer range:
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:
ecrecover
returns is not address(0)
The method ecrecover
returns address(0)
when the signature is wrong, so if a user use address(0)
as a o.signer
the return will be true
.
Affected source code:
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 source code:
If there is a hard fork, the EIP712_DOMAIN_TYPEHASH
is not computed. After initialization, the variable EIP712_DOMAIN_TYPEHASH
in contract GolomTrader
is cached in the contract storage and does not change. However, if a hard fork takes place after the contract deployment, the block would cause the domain to become invalid on one of the forked chains. chainid
is now different.
Affected source code:
Although it has not been possible to exploit the reentrancy attack, the logic of the methods named below, make the changes of the validation flags after a method that allows reentrancy, it is convenient to modify the flags before the external calls to contracts.
For example, you could call merge
or similar functions that don't have nonreentrant
, so it's better to apply the following changes:
- assert(IERC20(token).transfer(msg.sender, value)); // Burn the NFT _burn(_tokenId); + require(IERC20(token).transfer(msg.sender, value));
Affected source code:
Use 1 days
instead of a multiplication:
The following line is not required so it's better to remove the last return
:
🌟 Selected for report: JohnSmith
Also found by: 0x1f8b, 0xA5DF, 0xDjango, 0xKitsune, 0xLovesleep, 0xNazgul, 0xSmartContract, 0xmatt, 0xsam, Aymen0909, Bnke0x0, CRYP70, Chandr, Chinmay, CodingNameKiki, Deivitto, Dravee, ElKu, Fitraldys, Funen, GalloDaSballo, Green, IllIllI, JC, Jmaxmanblue, Junnon, Kaiziron, Kenshin, Krow10, Maxime, Migue, MiloTruck, Noah3o6, NoamYakov, Randyyy, RedOneN, ReyAdmirado, Rohan16, Rolezn, Ruhum, Sm4rty, StyxRave, TomJ, Tomio, _Adam, __141345__, ajtra, ak1, apostle0x01, asutorufos, async, benbaessler, brgltd, c3phas, cRat1st0s, carlitox477, delfin454000, djxploit, durianSausage, ellahi, erictee, fatherOfBlocks, gerdusx, gogo, hyh, jayfromthe13th, jayphbee, joestakey, kaden, kenzo, kyteg, ladboy233, lucacez, m_Rassska, mics, minhquanym, oyc_109, pfapostol, rbserver, reassor, rfa, robee, rokinot, sach1r0, saian, samruna, sashik_eth, simon135, supernova, tofunmi, zuhaibmohd
94.6571 USDC - $94.66
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).
Reduce the size of error messages (Long revert Strings)
Shortening 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 above.
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:
It's cheaper to store the length of the array inside a local variable and iterate over it.
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:
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:
Use constant instead of storage/computation values in:
Cache keccak256
results:
delete
optimizationUse delete
instead of set to default value (false
or 0
).
5 gas could be saved per entry in the following affected lines:
The following structs could be optimized moving the position of certains values in order to save slot storages:
struct Order { address collection; // NFT contract address uint256 tokenId; // order for which tokenId of the collection address signer; // maker of order address uint256 orderType; // 0 if selling nft for eth , 1 if offering weth for nft,2 if offering weth for collection with special criteria root uint256 totalAmt; // price value of the trade // total amt maker is willing to give up per unit of amount Payment exchange; // payment agreed by maker of the order to pay on succesful filling of trade this amt is subtracted from totalamt Payment prePayment; // another payment , can be used for royalty, facilating trades - bool isERC721; // standard of the collection , if 721 then true , if 1155 then false uint256 tokenAmt; // token amt useful if standard is 1155 if >1 means whole order can be filled tokenAmt times uint256 refererrAmt; // amt to pay to the address that helps in filling your order bytes32 root; // A merkle root derived from each valid tokenId — set to 0 to indicate a collection-level or tokenId-specific order. address reservedAddress; // if not address(0) , only this address can fill the order + bool isERC721; // standard of the collection , if 721 then true , if 1155 then false + uint8 v; uint256 nonce; // nonce of order usefull for cancelling in bulk uint256 deadline; // timestamp till order is valid epoch timestamp in secs - uint8 v; bytes32 r; bytes32 s; }
The following clauses are OR closures, but due to their implementation, all possible paths are validated.
function _isApprovedOrOwner(address _spender, uint256 _tokenId) internal view returns (bool) { address owner = idToOwner[_tokenId]; - bool spenderIsOwner = owner == _spender; - bool spenderIsApproved = _spender == idToApprovals[_tokenId]; - bool spenderIsApprovedForAll = (ownerToOperators[owner])[_spender]; - return spenderIsOwner || spenderIsApproved || spenderIsApprovedForAll; + return (owner == _spender) || (_spender == idToApprovals[_tokenId]) || (ownerToOperators[owner])[_spender]; }
function approve(address _approved, uint256 _tokenId) public { address owner = idToOwner[_tokenId]; // Throws if `_tokenId` is not a valid NFT require(owner != address(0)); // Throws if `_approved` is the current owner require(_approved != owner); // Check requirements - bool senderIsOwner = (idToOwner[_tokenId] == msg.sender); - bool senderIsApprovedForAll = (ownerToOperators[owner])[msg.sender]; - require(senderIsOwner || senderIsApprovedForAll); + require((idToOwner[_tokenId] == msg.sender) || (ownerToOperators[owner])[msg.sender]); // Set the approval idToApprovals[_tokenId] = _approved; emit Approval(owner, _approved, _tokenId); }
require
instead of assert
The assert()
and require()
functions are a part of the error handling aspect in Solidity. Solidity makes use of state-reverting error handling exceptions. This means all changes made to the contract on that call or any sub-calls are undone if an error is thrown. It also flags an error.
They are quite similar as both check for conditions and if they are not met, would throw an error.
The big difference between the two is that the assert()
function when false, uses up all the remaining gas and reverts all the changes made.
Meanwhile, a require()
function when false, also reverts back all the changes made to the contract but does refund all the remaining gas fees we offered to pay. This is the most common Solidity function used by developers for debugging and error handling.
Affected source code:
Having a method that always returns the same value is not correct in terms of consumption, if you want to modify a value, and the method will perform a revert
in case of failure, it is not necessary to return a true
, since it will never be false
. It is less expensive not to return anything, and it also eliminates the need to double-check the returned value by the caller.
Affected source code:
Method creation has an extra cost, a method to return the height of the block is not required.