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: 115/179
Findings: 2
Award: $56.49
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L459-L461 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L313-L316
In the event someone wrongfully sends ETH to both contract GolomTrader or Reward Distributor (for example through a low level call with the wrong parameters) fallback()
or receive()
functions will receive the call with the ETH in it. However ETH will be stuck in the contract since there are no withdraw functions
In order to avoid that, I suggest:
revert()
inside them.function withdraw(address payable _receiver) external onlyOwner { _receiver.transfer(address(this).balance); }
#0 - 0xsaruman
2022-08-22T11:36:58Z
#1 - dmvt
2022-10-14T15:30:55Z
Lack of a recovery function is low risk. Downgrading to QA.
🌟 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
Require error message is over 32 bytes and will cost most more VEDelegation: Need more voting power
. I suggest making it shorter by changing it to Need more voting power
File: contracts/vote-escrow/VoteEscrowDelegation.sol #73 require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power');
Works also :
File: contracts/rewards/RewardDistributor.sol #181 require(tokenowner == ve.ownerOf(tokenids[tindex]), 'Can only claim for a single Address together');
Since the _delegatedTokenIds
is not modified we could use it as an calldata parameter instead of memory inside the _writeCheckpoint
function.
File: contracts/vote-escrow/VoteEscrowDelegation.sol #97 function _writeCheckpoint( uint256 toTokenId, uint256 nCheckpoints, uint256[] memory _delegatedTokenIds ) internal {
The checkpoints[toTokenId]
is potentially called 3 times within the same _writeCheckpoint
so I believe we could potentially store it inside a memory variable in order to reduce gas cost every time it is called and then update the variable in storage accordingly.
File: contracts/vote-escrow/VoteEscrowDelegation.sol #101 Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];
File: contracts/vote-escrow/VoteEscrowDelegation.sol #106 checkpoints[toTokenId][nCheckpoints]
File: contracts/vote-escrow/VoteEscrowDelegation.sol #107 checkpoints[toTokenId][nCheckpoints] numCheckpoints[toTokenId] = nCheckpoints + 1
In order to optimise for loops, I would advise storing .length
method's return inside a memory variable. Also we can assume that the iterator i
will not overflow so it is safe to use the uncheck
attribute and replace it by ++i
in order to reduce useless opcodes and reduce gas cost.
File: contracts/vote-escrow/VoteEscrowDelegation.sol #171 for (uint256 index = 0; index < delegated.length; index++) { votes = votes + this.balanceOfNFT(delegated[index]); }
could be replaced by
uint256 delegatedLength = delegated.length; for (uint256 index = 0; index < delegatedLength;) { votes = votes + this.balanceOfNFT(delegated[index]); unchecked {++i} }
Same for:
File: contracts/vote-escrow/VoteEscrowDelegation.sol #189 for (uint256 index = 0; index < delegatednft.length; index++) { votes = votes + this.balanceOfAtNFT(delegatednft[index], blockNumber); }
could be replaced by
uint256 delegatedNftLength = delegatednft.length; for (uint256 index = 0; index < delegatedNftLength;) { votes = votes + this.balanceOfAtNFT(delegatednft[index], blockNumber); unchecked { index++} }
File: contracts/vote-escrow/VoteEscrowDelegation.sol #199 for (uint256 i; i < _array.length; i++) { if (_array[i] == _element) { _array[i] = _array[_array.length - 1]; _array.pop(); break; } }
could be replaced by
uint256 arrayLength = _array.length; for (uint256 i; i < arrayLength;) { if (_array[i] == _element) { _array[i] = _array[arrayLength - 1]; _array.pop(); break; } uncheck { +ii} }
File: contracts/rewards/RewardDistributor.sol #156 uint256 reward = 0; for (uint256 index = 0; index < epochs.length; index++) { require(epochs[index] < epoch); reward = reward + (rewardExchange[epochs[index]] * feesExchange[addr][epochs[index]]) / epochTotalFee[epochs[index]]; feesExchange[addr][epochs[index]] = 0; }
could be replace by
uint256 epochsLength = epochs.length; for (uint256 index = 0; index < epochsLength;) { require(epochs[index] < epoch); reward = reward + (rewardExchange[epochs[index]] * feesExchange[addr][epochs[index]]) / epochTotalFee[epochs[index]]; feesExchange[addr][epochs[index]] = 0; uncheck {++index} }
In order to reduce gas cost for users that will not pass the following require : require(_isApprovedOrOwner(_sender, _tokenId))
I believe we should move up this require before the this.removeDelegation(_tokenId);
since this function is pretty costly and has no impact on the next require.
File: contracts/vote-escrow/VoteEscrowDelegation.sol #245 require(_isApprovedOrOwner(_sender, _tokenId))
should be before
File: contracts/vote-escrow/VoteEscrowDelegation.sol #242 this.removeDelegation(_tokenId)
It cheaper to not instantiate variable by 0 and let the EVM automatically set it to 0
File: contracts/rewards/RewardDistributor.sol #45 uint256 public epoch = 0;
Order
struct in order to use less storage slotsIn order to make the Order
struct more gas efficient we should rethink the way it is packed. For that we have the move the uint8 v
next to a 20 bytes address like address collection
or reservedAddress
so that instead of respectively take 2 slots they will only use one.
The initial
File: contracts/core/GolomTrader.sol #47 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; }
could be changed to:
struct Order { address collection; // NFT contract address uint8 v; uint256 tokenId; // order for which tokenId of the collection address signer; // maker of order address uint256 orderType; uint256 totalAmt; // price value of the trade // total amt maker is willing to give up per unit of amount Payment exchange; 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; 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 bytes32 r; bytes32 s; }
abi.encodePacked
over abi.encode
It is more efficient to use the abi.encodePacked
over the abi.encode
method and should be used in this case since we will then hash the result of this method into a 32 bytes variable using the keccak256
function
File: contracts/core/GolomTrader.sol #102 EIP712_DOMAIN_TYPEHASH = keccak256( abi.encode( keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), keccak256(bytes('GOLOM.IO')), keccak256(bytes('1')), chainId, address(this) ) );
Same for:
File: contracts/core/GolomTrader.sol #114 keccak256( abi.encode( keccak256('payment(uint256 paymentAmt,address paymentAddress)'), p.paymentAmt, p.paymentAddress ) );
File: contracts/core/GolomTrader.sol #129 keccak256( abi.encode( keccak256( 'order(address collection,uint256 tokenId,address signer,uint256 orderType,uint256 totalAmt,payment exchange,payment prePayment,bool isERC721,uint256 tokenAmt,uint256 refererrAmt,bytes32 root,address reservedAddress,uint256 nonce,uint256 deadline)payment(uint256 paymentAmt,address paymentAddress)' ), o.collection, o.tokenId, o.signer, o.orderType, o.totalAmt, hashPayment(o.exchange), hashPayment(o.prePayment), o.isERC721, o.tokenAmt, o.refererrAmt, o.root, o.reservedAddress, extra ) );