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: 37/179
Findings: 6
Award: $325.18
π Selected for report: 0
π Solo Findings: 0
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L100-L103 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L242 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L384
Contract GolomToken
uses addFee
function of RewardDistributor
contract to add rewards for all trades that are being done by users. Function addFees
rewards trader and exchange with fees and in addition it converts ether
to weth
that can be later claimed by VeNFT
holders via multiStakerClaim
function. The issue is that once rewardToken
supply reaches 1 billion of tokens it stops converting ether
to weth
:
if (rewardToken.totalSupply() > 1000000000 * 10**18) { // if supply is greater then a billion dont mint anything, dont add trades return; }
This means that new protocol fees will get locked in RewardDistributor
contract in form of ether
and it will be not possible to claim them by VeNFT
holders through multiStakerClaim
.
rewards/RewardDistributor.sol
:
governance/GolomToken.sol
:
Manual Review / VSCode
It is recommended to keep converting ether
to weth
in addFee
function even when minting rewards has finished.
#0 - KenzoAgada
2022-08-03T12:16:35Z
Duplicate of #320
π 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
The Istanbul hardfork increases the gas cost of the SLOAD
operation and therefore breaks some existing smart contracts.
Function GolomTrader.payEther
is using legacy transfer
function, which has a fixed gas stipend and can fail.
The reason behind this is that, after the Istanbul hardfork, any smart contract that uses transfer()
or send()
is taking a hard dependency on a gas costs by forwarding a fixed amount of gas (2300). This forwards 2300 gas, which may not be enough if the recipient is a contract and the cost of gas changes.
core/GolomTrader.sol
:
It is recommended to use safe wrapper library, such as the OpenZeppelin Address libraryβs and its sendValue
function that forwards sufficient gas for the transfer regardless of the underlying OPCODE gas costs.
#0 - KenzoAgada
2022-08-03T14:07:12Z
Duplicate of #343
π Selected for report: TomJ
Also found by: 0x4non, 0x52, 0xDjango, 0xNazgul, 0xf15ers, 0xsanson, 8olidity, Bnke0x0, CertoraInc, Ch_301, Chom, Dravee, GalloDaSballo, GimelSec, JC, Jujic, Kenshin, Kumpa, Lambda, M0ndoHEHE, PaludoX0, RedOneN, Ruhum, Sm4rty, Treasure-Seeker, TrungOre, Twpony, Waze, _Adam, __141345__, apostle0x01, arcoun, benbaessler, bin2chen, brgltd, cccz, cloudjunky, cryptonue, djxploit, ellahi, erictee, hansfriese, i0001, minhquanym, oyc_109, peritoflores, rbserver, reassor, rokinot, rotcivegaf, saian, shenwilly, sseefried
0.1513 USDC - $0.15
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L236 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L301 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L361
Contract GolomTrader
uses transferFrom
function for transferring NFT between parties. The issue is that NFT transferred to contracts that do not handle ERC721 tokens will be lost. In addition there are a few NFT's example that have logic in the onERC721Received()
function, which is only trigged in the safeTransferFrom
function.
OpenZeppelin also discourage usage of transferFrom()
.
core/GolomTrader.sol
:
fillAsk
- https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L236fillBid
- https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L301fillCriteriaBid
- https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L361Manual Review / VSCode
It is recommended to use safeTransferFrom
function instead of transferFrom
for ERC721 tokens.
#0 - KenzoAgada
2022-08-03T15:09:57Z
Duplicate of #342
π Selected for report: AuditsAreUS
Also found by: 0xSky, CertoraInc, GimelSec, GiveMeTestEther, Green, Lambda, Ruhum, RustyRabbit, Treasure-Seeker, Twpony, arcoun, bin2chen, cccz, codexploder, cryptonue, dipp, horsefacts, jayphbee, joestakey, minhquanym, obront, peritoflores, rbserver, reassor, rotcivegaf, scaraven, ych18
4.5163 USDC - $4.52
Contract GolomTrader
requires sending ether
to function fillAsk
in order to settle ask order on-chain. Following check is done:
// attached ETH value should be greater than total value of one NFT * total number of NFTs + any extra payment to be given require(msg.value >= o.totalAmt * amount + p.paymentAmt, 'mgmtm');
After that the payment in ether
is properly distributed across all parties (such as seller, exchange, distributor etc.). The issue is that if the user sends more ether
than o.totalAmt * amount + p.paymentAmt
the exceeding part will get locked in the contract forever and cannot be withdrawn since the contract does not implement any administrative functionality for withdrawing ether
.
core/GolomTrader.sol
:
Manual Review / VSCode
It is recommended to add administrative functionality for withdrawing ether
.
#0 - KenzoAgada
2022-08-04T02:53:44Z
Duplicate of #75 Probably not the best mitigation - contract can send excess back to user, or make sure only required is being sent
π 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
167.5763 USDC - $167.58
Low
Multiple functions of GolomToken
contract do not follow checks-effects-interactions pattern that might lead to reentrancy attacks.
GolomToken.mintAirdrop
:
function mintAirdrop(address _airdrop) external onlyOwner { require(!isAirdropMinted, 'already minted'); _mint(_airdrop, 150_000_000 * 1e18); isAirdropMinted = true; }
GolomToken.mintGenesisReward
:
function mintGenesisReward(address _rewardDistributor) external onlyOwner { require(!isGenesisRewardMinted, 'already minted'); _mint(_rewardDistributor, 62_500_000 * 1e18); isGenesisRewardMinted = true; }
governance/GolomToken.sol
:
Manual Review / VSCode
It is recommended to first set the effects such as setting isAirdropMinted
and isGenesisRewardMinted = true
then perform interactions such as _mint
.
Low
Function GolomTrader.cancelOrder
does not check for return value of validateOrder
function which might lead to serious vulnerability if the logic of validateOrder
change and it will not throw error when the signature is invalid.
core/GolomTrader.sol
:
Manual Review / VSCode
It is recommended to add check of status of validateOrder
:
(uint256 status, bytes32 hashStruct, ) = validateOrder(o); require(status != 0, 'order not valid'); filled[hashStruct] = o.tokenAmt + 1;
Low
Multiple require statements do not contain error message which makes it difficult to understand why the transaction has reverted.
core/GolomTrader.sol:220: require(msg.sender == o.reservedAddress); core/GolomTrader.sol:291: require(msg.sender == o.reservedAddress); core/GolomTrader.sol:293: require(o.orderType == 1); core/GolomTrader.sol:295: require(status == 3); core/GolomTrader.sol:296: require(amountRemaining >= amount); core/GolomTrader.sol:313: require(o.signer == msg.sender); core/GolomTrader.sol:342: require(o.totalAmt >= o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt); core/GolomTrader.sol:345: require(msg.sender == o.reservedAddress); core/GolomTrader.sol:347: require(o.orderType == 2); core/GolomTrader.sol:349: require(status == 3); core/GolomTrader.sol:350: require(amountRemaining >= amount); rewards/RewardDistributor.sol:88: require(msg.sender == trader); rewards/RewardDistributor.sol:144: require(epochs[index] < epoch); rewards/RewardDistributor.sol:158: require(epochs[index] < epoch); vote-escrow/VoteEscrowDelegation.sol:245: require(_isApprovedOrOwner(_sender, _tokenId)); vote-escrow/VoteEscrowCore.sol:360: require(_entered_state == _not_entered); vote-escrow/VoteEscrowCore.sol:540: require(_isApprovedOrOwner(_sender, _tokenId)); vote-escrow/VoteEscrowCore.sol:646: require(owner != address(0)); vote-escrow/VoteEscrowCore.sol:648: require(_approved != owner); vote-escrow/VoteEscrowCore.sol:652: require(senderIsOwner || senderIsApprovedForAll); vote-escrow/VoteEscrowCore.sol:869: require(msg.sender == voter); vote-escrow/VoteEscrowCore.sol:874: require(msg.sender == voter); vote-escrow/VoteEscrowCore.sol:879: require(msg.sender == voter); vote-escrow/VoteEscrowCore.sol:884: require(msg.sender == voter); vote-escrow/VoteEscrowCore.sol:889: require(msg.sender == voter); vote-escrow/VoteEscrowCore.sol:895: require(_from != _to); vote-escrow/VoteEscrowCore.sol:896: require(_isApprovedOrOwner(msg.sender, _from)); vote-escrow/VoteEscrowCore.sol:897: require(_isApprovedOrOwner(msg.sender, _to)); vote-escrow/VoteEscrowCore.sol:927: require(_value > 0); // dev: need non-zero value vote-escrow/VoteEscrowCore.sol:944: require(_value > 0); // dev: need non-zero value
Manual Review / VSCode
It is recommended to add require error messages.
Low
Multiple contracts are not implementing events for critical functions. Lack of events makes it difficult for off-chain applications to monitor the protocol.
governance/GolomToken.sol
:
mintAirdrop
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L42-L46mintGenesisReward
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L50-L54setMinter
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L57-L61executeSetMinter
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L65-L72rewards/RewardDistributor.sol
:
traderClaim
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L141exchangeClaim
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L155multiStakerClaim
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L172changeTrader
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L285executeChangeTrader
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L291addVoteEscrow
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L298executeAddVoteEscrow
function event - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L308vote-escrow/VoteEscrowDelegation.sol
:
removeDelegation
function event - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L210changeMinVotingPower
function event - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L260core/GolomTrader.sol
:
setDistributor
function event - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L444executeSetDistributor
function event - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L454Manual Review / VSCode
It is recommended to add missing events to listed functions.
Low
Multiple contracts of Golom protocol do not check for zero addresses which might lead to loss of funds, failed transactions and can break the protocol functionality.
governance/GolomToken.sol
:
_governance
address check - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L28_minter
address check - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L58rewards/RewardDistributor.sol
:
_weth
, _trader
, _token
, _governance
addresses checks - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L75-L78addr
address check - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L141addr
address check - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L155_trader
address check - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L285_voteEscrow
address check - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L298vote-escrow/VoteEscrowDelegation.sol
:
_token
address check - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L52core/GolomTrader.sol
:
_governance
address check - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L92_distributor
address check - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L444Manual Review / VSCode
It is recommended to add zero address checks for listed parameters.
Low
Changing critical addresses such as ownership should be a two-step process where the first transaction (from the old/current address) registers the new address (i.e. grants ownership) and the second transaction (from the new address) replaces the old address with the new one. This gives an opportunity to recover from incorrect addresses mistakenly used in the first step. If not, contract functionality might become inaccessible.
governance/GolomToken.sol
:
Ownable
- https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L14rewards/RewardDistributor.sol
:
Ownable
- https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L43vote-escrow/VoteEscrowDelegation.sol
:
Ownable
- https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L20core/GolomTrader.sol
:
Ownable
- https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L40Manual Review / VSCode
It is recommended to implement two-step process for changing ownership.
Non-Critical
As different compiler versions have critical behavior specifics if the contract gets accidentally deployed using another compiler version compared to one they tested with, various types of undesired behavior can be introduced.
governance/GolomToken.sol:pragma solidity ^0.8.11
;Manual Review / VSCode
Consider locking compiler version, for example pragma solidity 0.8.11
.
Non-Critical
Contracts are missing pause functionality that could be used in case of security incidents and would block executing selected functions while the contract is paused.
governance/GolomToken.sol vote-escrow/VoteEscrowDelegation.sol vote-escrow/VoteEscrowCore.sol rewards/RewardDistributor.sol core/GolomTrader.sol
Manual Review / VSCode
It is recommended to add functionality for pausing contracts and go through all publicly/externally accessible functions to decide which one should be forbidden from running while the contracts are paused.
Non-Critical
Multiple contracts are missing natspec comments which makes code more difficult to read and prone to errors.
governance/GolomToken.sol
:
rewards/RewardDistributor.sol
:
@return
natspec - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L213-L215@return
natspec - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L252-L254@return
natspec - https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L267-L269vote-escrow/VoteEscrowDelegation.sol
:
@param
natspec - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L94@param
natspec - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L233core/GolomTrader.sol
:
@return
natspec - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L164@param tokenId
and @param proof
natspec - https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L328-L334Manual Review / VSCode
It is recommended to add missing natspec comments.
Non-Critical
Multiple require statements contain typos in their error messages.
rewards/RewardDistributor.sol
:
Manual Review / VSCode
It is recommended to fix typos.
π 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
21.3211 USDC - $21.32
Contract GolomToken
has multiple require statements that are obsolete since the same check is already being done in validateOrder
function.
Check in validateOrder
that checks if order is already filled:
if (filled[hashStruct] >= o.tokenAmt) { // handles erc1155 return (2, hashStruct, 0); } return (3, hashStruct, o.tokenAmt - filled[hashStruct]);
Obsolete require statement checking if order is already filled:
(uint256 status, bytes32 hashStruct, uint256 amountRemaining) = validateOrder(o); require(status == 3, 'order not valid'); require(amountRemaining >= amount, 'order already filled'); // @audit - obsolete require statement
core/GolomTrader.sol
:
It is recommended to remove listed require statement.
Function GolomTrader.validateOrder
implements obsolete code that checks for the same thing. Statement require
checks if signaturesigner
is equal to o.signer
and then if
statement is doing the same thing.
address signaturesigner = ecrecover(hash, o.v, o.r, o.s); require(signaturesigner == o.signer, 'invalid signature'); if (signaturesigner != o.signer) { return (0, hashStruct, 0); }
core/GolomTrader.sol
:
It is recommended to remove if
statement that always passes since there is require
statement before.
Contract RewardDistributor
is using math exponent calculation to express big numbers. This consumes additional gas and it is better to use scientific notation.
rewards/RewardDistributor.sol:48: uint256 constant dailyEmission = 600000 * 10**18; rewards/RewardDistributor.sol:100: if (rewardToken.totalSupply() > 1000000000 * 10**18) {
It is recommended to use scientific notation, for example: 1e3
.
Caching the array length outside a loop saves reading it on each iteration, as long as the array's length is not changed during the loop.
core/GolomTrader.sol:415: for (uint256 i = 0; i < proof.length; i++) { rewards/RewardDistributor.sol:143: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:157: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:180: for (uint256 tindex = 0; tindex < tokenids.length; tindex++) { rewards/RewardDistributor.sol:183: for (uint256 index = 0; index < epochs.length; index++) { vote-escrow/VoteEscrowDelegation.sol:171: for (uint256 index = 0; index < delegated.length; index++) { vote-escrow/VoteEscrowDelegation.sol:189: for (uint256 index = 0; index < delegatednft.length; index++) { vote-escrow/VoteEscrowDelegation.sol:199: for (uint256 i; i < _array.length; i++) {
It is recommended to cache the array length outside of for loop.
Shortening revert error messages to fit in 32 bytes will decrease gas costs for deployment and gas costs when the revert condition has been met.
governance/GolomToken.sol:24: require(msg.sender == minter, 'GolomToken: only reward distributor can enable'); rewards/RewardDistributor.sol:181: require(tokenowner == ve.ownerOf(tokenids[tindex]), 'Can only claim for a single Address together'); rewards/RewardDistributor.sol:292: require(traderEnableDate <= block.timestamp, 'RewardDistributor: time not over yet'); rewards/RewardDistributor.sol:309: require(voteEscrowEnableDate <= block.timestamp, 'RewardDistributor: time not over yet'); vote-escrow/VoteEscrowDelegation.sol:73: require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power'); vote-escrow/VoteEscrowCore.sol:929: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw'); vote-escrow/VoteEscrowCore.sol:945: require(unlock_time > block.timestamp, 'Can only lock until time in the future'); vote-escrow/VoteEscrowCore.sol:983: require(_locked.end > block.timestamp, 'Cannot add to expired lock. Withdraw');
Manual Review / VSCode
It is recommended to decrease revert messages to maximum 32 bytes.
Usage of custom errors reduces the gas cost.
Contracts that should be using custom errors:
contracts/governance/GolomToken.sol
contracts/vote-escrow/VoteEscrowDelegation.sol
contracts/rewards/RewardDistributor.sol
contracts/core/GolomTrader.sol
contracts/vote-escrow/VoteEscrowCore.sol
contracts/vote-escrow/TokenUriHelper.sol
It is recommended to add custom errors to listed contracts.
++i
or --i
costs less gas compared to i++
, i += 1
, i--
or i -= 1
for unsigned integer as pre-increment/pre-decrement is cheaper (about 5 gas per iteration).
core/GolomTrader.sol:415: for (uint256 i = 0; i < proof.length; i++) { rewards/RewardDistributor.sol:143: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:157: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:180: for (uint256 tindex = 0; tindex < tokenids.length; tindex++) { rewards/RewardDistributor.sol:183: for (uint256 index = 0; index < epochs.length; index++) { vote-escrow/VoteEscrowDelegation.sol:171: for (uint256 index = 0; index < delegated.length; index++) { vote-escrow/VoteEscrowDelegation.sol:189: for (uint256 index = 0; index < delegatednft.length; index++) { vote-escrow/VoteEscrowDelegation.sol:199: for (uint256 i; i < _array.length; i++) {
It is recommended to use ++i
or --i
instead of i++
, i += 1
, i--
or i -= 1
to increment value of an unsigned integer variable.
Starting from solidity 0.8.0
there is built-in check for overflows/underflows. This mechanism automatically checks if the variable overflows or underflows and throws an error. Multiple contracts use increments that cannot overflow but consume additional gas for checks.
core/GolomTrader.sol:415: for (uint256 i = 0; i < proof.length; i++) { rewards/RewardDistributor.sol:143: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:157: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:180: for (uint256 tindex = 0; tindex < tokenids.length; tindex++) { rewards/RewardDistributor.sol:183: for (uint256 index = 0; index < epochs.length; index++) { vote-escrow/VoteEscrowDelegation.sol:171: for (uint256 index = 0; index < delegated.length; index++) { vote-escrow/VoteEscrowDelegation.sol:189: for (uint256 index = 0; index < delegatednft.length; index++) { vote-escrow/VoteEscrowDelegation.sol:199: for (uint256 i; i < _array.length; i++) {
It is recommended to wrap incrementing with unchecked
block, for example: unchecked { ++i }
or unchecked { --i }
If a variable is not set/initialized, it is assumed to have the default value (0
for uint, false
for bool, address(0)
for addresses). Explicitly initializing it with its default value is an anti-pattern and waste of gas.
core/GolomTrader.sol:415: for (uint256 i = 0; i < proof.length; i++) { rewards/RewardDistributor.sol:45: uint256 public epoch = 0; rewards/RewardDistributor.sol:142: uint256 reward = 0; rewards/RewardDistributor.sol:143: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:156: uint256 reward = 0; rewards/RewardDistributor.sol:157: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:175: uint256 reward = 0; rewards/RewardDistributor.sol:176: uint256 rewardEth = 0; rewards/RewardDistributor.sol:180: for (uint256 tindex = 0; tindex < tokenids.length; tindex++) { rewards/RewardDistributor.sol:183: for (uint256 index = 0; index < epochs.length; index++) { rewards/RewardDistributor.sol:222: uint256 reward = 0; rewards/RewardDistributor.sol:223: uint256 rewardEth = 0; rewards/RewardDistributor.sol:226: for (uint256 index = 0; index < epoch; index++) { rewards/RewardDistributor.sol:257: uint256 reward = 0; rewards/RewardDistributor.sol:258: for (uint256 index = 0; index < epoch; index++) { rewards/RewardDistributor.sol:272: uint256 reward = 0; rewards/RewardDistributor.sol:273: for (uint256 index = 0; index < epoch; index++) { vote-escrow/VoteEscrowDelegation.sol:50: uint256 public MIN_VOTING_POWER_REQUIRED = 0; vote-escrow/VoteEscrowDelegation.sol:147: uint256 lower = 0; vote-escrow/VoteEscrowDelegation.sol:170: uint256 votes = 0; vote-escrow/VoteEscrowDelegation.sol:171: for (uint256 index = 0; index < delegated.length; index++) { vote-escrow/VoteEscrowDelegation.sol:188: uint256 votes = 0; vote-escrow/VoteEscrowDelegation.sol:189: for (uint256 index = 0; index < delegatednft.length; index++) { vote-escrow/VoteEscrowCore.sol:735: uint256 block_slope = 0; // dblock/dt vote-escrow/VoteEscrowCore.sol:745: for (uint256 i = 0; i < 255; ++i) { vote-escrow/VoteEscrowCore.sol:1042: uint256 _min = 0; vote-escrow/VoteEscrowCore.sol:1044: for (uint256 i = 0; i < 128; ++i) { vote-escrow/VoteEscrowCore.sol:1113: uint256 _min = 0; vote-escrow/VoteEscrowCore.sol:1115: for (uint256 i = 0; i < 128; ++i) { vote-escrow/VoteEscrowCore.sol:1133: uint256 d_block = 0; vote-escrow/VoteEscrowCore.sol:1134: uint256 d_t = 0; vote-escrow/VoteEscrowCore.sol:1167: for (uint256 i = 0; i < 255; ++i) { vote-escrow/VoteEscrowCore.sol:1211: uint256 dt = 0;
It is recommended to remove explicit initializations with default values.
When dealing with unsigned integer types, comparisons with != 0
are cheaper than with > 0
.
core/GolomTrader.sol:152: if (payAmt > 0) { core/GolomTrader.sol:250: if (o.refererrAmt > 0 && referrer != address(0)) { core/GolomTrader.sol:387: if (o.refererrAmt > 0 && referrer != address(0)) { rewards/RewardDistributor.sol:124: if (previousEpochFee > 0) { vote-escrow/VoteEscrowDelegation.sol:78: if (nCheckpoints > 0) { vote-escrow/VoteEscrowDelegation.sol:103: if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) { vote-escrow/VoteEscrowDelegation.sol:119: return nCheckpoints > 0 ? checkpoints[tokenId][nCheckpoints - 1].delegatedTokenIds : myArray; vote-escrow/VoteEscrowCore.sol:579: return size > 0; vote-escrow/VoteEscrowCore.sol:704: if (old_locked.end > block.timestamp && old_locked.amount > 0) { vote-escrow/VoteEscrowCore.sol:708: if (new_locked.end > block.timestamp && new_locked.amount > 0) { vote-escrow/VoteEscrowCore.sol:727: if (_epoch > 0) { vote-escrow/VoteEscrowCore.sol:855: // value == 0 (extend lock) or value > 0 (add to lock or extend lock) vote-escrow/VoteEscrowCore.sol:927: require(_value > 0); // dev: need non-zero value vote-escrow/VoteEscrowCore.sol:928: require(_locked.amount > 0, 'No existing lock found'); vote-escrow/VoteEscrowCore.sol:944: require(_value > 0); // dev: need non-zero value vote-escrow/VoteEscrowCore.sol:981: assert(_value > 0); // dev: need non-zero value vote-escrow/VoteEscrowCore.sol:982: require(_locked.amount > 0, 'No existing lock found'); vote-escrow/VoteEscrowCore.sol:997: require(_locked.amount > 0, 'Nothing is locked');
It is recommended to use != 0
instead of > 0
.
Packing variables into storage slots saves gas.
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 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; }
In order to save gas following variables can be packed:
uint256 orderType -> uint8 orderType uint256 deadline -> uint32 deadline
In addition it is recommended to reorder struct variables:
struct Order { address collection; // NFT contract address uint256 tokenId; // order for which tokenId of the collection address signer; // maker of order address 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 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 uint256 nonce; // nonce of order usefull for cancelling in bulk uint32 deadline; // timestamp till order is valid epoch timestamp in secs bool isERC721; // standard of the collection , if 721 then true , if 1155 then false uint8 orderType; // 0 if selling nft for eth , 1 if offering weth for nft,2 if offering weth for collection with special criteria root uint8 v; bytes32 r; bytes32 s; }
core/GolomTrader.sol
:
Manual Review / VSCode
It is recommended to pack listed integer variables and reorder struct variables.