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: 5/179
Findings: 8
Award: $3,168.07
π Selected for report: 2
π Solo Findings: 1
26.7695 USDC - $26.77
The delegate function of the VoteEscrow contract allows users to reuse votes to vote for multiple tokenIDs Consider the following scenario, where user A has 100 votes. User A calls the delegate function to vote for tokenID 2 and 3 respectively Calling the getVotes function for tokenID 2 and 3 will return 100 That is, user A uses 100 votes to get the effect of 200 votes
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L71-L89 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L168-L175 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L116-L120
None
Remove vote on previous delegates[tokenId] in delegate function
#0 - KenzoAgada
2022-08-02T12:00:16Z
Duplicate of #169
93.2805 USDC - $93.28
In the removeDelegation function of the VoteEscrow contract, the user is only allowed to remove their own votes against themselves, which makes it impossible for the user to remove their votes against other users.
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L210-L216 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L233-L242
None
change
function removeDelegation(uint256 tokenId) external { require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed'); uint256 nCheckpoints = numCheckpoints[tokenId]; Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1]; removeElement(checkpoint.delegatedTokenIds, tokenId); _writeCheckpoint(tokenId, nCheckpoints, checkpoint.delegatedTokenIds); }
to
function removeDelegation(uint256 tokenId) external { require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed'); uint256 toTokenId = delegates[tokenId]; uint256 nCheckpoints = numCheckpoints[toTokenId]; Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1]; removeElement(checkpoint.delegatedTokenIds, tokenId); _writeCheckpoint(toTokenId, nCheckpoints, checkpoint.delegatedTokenIds); }
#0 - KenzoAgada
2022-08-02T08:23:40Z
Duplicate of #751
π 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
In GolomTrader contract, payEther function calls native payable.transfer. This is unsafe as transfer has hard coded gas budget and can fail when the user is a smart contract.
Whenever the user either fails to implement the payable fallback function or cumulative gas cost of the function sequence invoked on a native token transfer exceeds 2300 gas consumption limit the native tokens sent end up undelivered and the corresponding user funds return functionality will fail each time.
None
Using low-level call.value(amount) with the corresponding result check or using the OpenZeppelin Address.sendValue is advised: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol#L60
#0 - KenzoAgada
2022-08-03T14:06:19Z
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
When using the transferFrom function of an ERC721 contract to send an NFT, if the receiving address is a smart contract and does not support ERC721, the NFT can be frozen in the contract.
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L236-L237 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L301-L302 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L361-L362
None
Use the ERC721 contract's safeTransferFrom function to send NFTs
#0 - KenzoAgada
2022-08-03T15:20:30Z
Duplicate of #342
π Selected for report: sseefried
Also found by: 0x4non, IllIllI, Jmaxmanblue, JohnSmith, Lambda, arcoun, berndartmueller, cccz, csanuragjain, minhquanym, rbserver, rotcivegaf
According to https://eips.ethereum.org/EIPS/eip-721#specification, the safeTransferFrom function needs to check that the return value of onERC721Received is bytes4(keccak256("onERC721Received(address,address,uint,bytes)"))
None
function safeTransferFrom( address _from, address _to, uint256 _tokenId, bytes memory _data ) public { _transferFrom(_from, _to, _tokenId, msg.sender); if (_isContract(_to)) { // Throws if transfer destination is a contract which does not implement 'onERC721Received' try IERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) returns (bytes4 retval) { + return retval == IERC721Receiver.onERC721Received.selector; } catch ( bytes memory reason
#0 - KenzoAgada
2022-08-04T02:04:41Z
Duplicate of #577
π Selected for report: cccz
2827.0776 USDC - $2,827.08
When MIN_VOTING_POWER_REQUIRED is changed, tokenIDs with votes lower than MIN_VOTING_POWER_REQUIRED will not be able to vote through the delegate function, but previous votes will not be affected. Since MIN_VOTING_POWER_REQUIRED is mainly used to reduce the influence of spam users, changing this value should affect previous votes.
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L168-L194 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L260-L262 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L73-L74
None
In the getPriorVotes and getVotes functions, when the balance corresponding to tokenId is less than MIN_VOTING_POWER_REQUIRED, the value of votes will not be increased
function getVotes(uint256 tokenId) external view returns (uint256) { uint256[] memory delegated = _getCurrentDelegated(tokenId); uint256 votes = 0; for (uint256 index = 0; index < delegated.length; index++) { + if(this.balanceOfNFT(delegated[index]) >= MIN_VOTING_POWER_REQUIRED){ votes = votes + this.balanceOfNFT(delegated[index]); + } } return votes; } /** * @notice Determine the prior number of votes for an account as of a block number * @dev Block number must be a finalized block or else this function will revert to prevent misinformation. * @param tokenId The address of the account to check * @param blockNumber The block number to get the vote balance at * @return The number of votes the account had as of the given block */ function getPriorVotes(uint256 tokenId, uint256 blockNumber) public view returns (uint256) { require(blockNumber < block.number, 'VEDelegation: not yet determined'); uint256[] memory delegatednft = _getPriorDelegated(tokenId, blockNumber); uint256 votes = 0; for (uint256 index = 0; index < delegatednft.length; index++) { + if(this.balanceOfAtNFT(delegatednft[index], blockNumber) >= MIN_VOTING_POWER_REQUIRED){ votes = votes + this.balanceOfAtNFT(delegatednft[index], blockNumber); + } } return votes; }
#0 - zeroexdead
2022-09-03T19:03:12Z
When calling we getVotes()
and getPriorVotes()
we're considering MIN_VOTING_POWER_REQUIRED
.
Reference: https://github.com/golom-protocol/contracts/commit/db650729b0805ec19906a0ea11de6af7a53ac382
#1 - dmvt
2022-10-14T15:55:35Z
Downgrading this to medium. Assets are not a direct risk.
169.0228 USDC - $169.02
The validateOrder function of GolomTrader calls the Solidity ecrecover function directly to verify the given signatures. The return value of ecrecover may be 0, which means the signature is invalid, but the check can be bypassed when signer is 0.
None
Use the recover function from OpenZeppelin's ECDSA library for signature verification.
#0 - KenzoAgada
2022-08-05T01:59:18Z
Seems invalid or QA at best. No impact on protocol as far as I see, invalid orders from "address 0" will revert.
In fillAsk if the o.signer
is address 0, the function will try to pull tokens from address 0 and will fail.
in fillBid/criteria, function will try to transfer msg.sender's tokens to address 0 and pull weth from address 0. So will fail.
#1 - dmvt
2022-10-13T13:22:26Z
This is valid as a medium risk. It opens a griefing attack where a bad actor spams any system that relies on this function. The fact that the fill will fail while the order appears valid is specifically what makes this griefing attack possible.
π 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
In the fillAsk function of the GolomTrader contract, when msg.value > o.totalAmt * amount + p.paymentAmt, the excess ETH is not refunded
None
Refund the excess ETH to the caller
#0 - KenzoAgada
2022-08-04T02:51:42Z
Duplicate of #75