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: 4/179
Findings: 10
Award: $3,428.05
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: GimelSec
Also found by: 0x52, 0xA5DF, 0xSky, 0xsanson, Bahurum, CertoraInc, GalloDaSballo, JohnSmith, Lambda, MEP, Twpony, arcoun, berndartmueller, cryptphi, hansfriese, kenzo, kyteg, panprog, rajatbeladiya, scaraven, simon135, zzzitron
26.7695 USDC - $26.77
In _writeCheckpoint
in VoteEscrowDelegate, the contract tries to access the previous checkpoint at index nCheckpoints - 1
. However, initially, when a user is delegated to this value is 0
thereby creating an arithmetic underflow and reverts the transaction. This prevents anyone from being delegated and because GovernerBravo.sol only counts delegated votes (and does not include the token vote itself), it would be impossible for anyone to create or vote on proposals.
1
delegates votes to token 2
by calling delegate()
nCheckpoints == 0
, a new dynamic array is created and passed to _writeCheckpoints
nCheckpoints - 1
which reverts with an arithmetic underflowVS Code
Change _writeCheckpoint
from
function _writeCheckpoint( uint256 toTokenId, uint256 nCheckpoints, uint256[] memory _delegatedTokenIds ) internal { require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more'); Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1]; if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) { oldCheckpoint.delegatedTokenIds = _delegatedTokenIds; } else { checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds); numCheckpoints[toTokenId] = nCheckpoints + 1; } }
to
function _writeCheckpoint( uint256 toTokenId, uint256 nCheckpoints, uint256[] memory _delegatedTokenIds ) internal { require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more'); if (nCheckpoints > 0) { Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1]; if (oldCheckpoint.fromBlock == block.number) { oldCheckpoint.delegatedTokenIds = _delegatedTokenIds; } else { checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds); numCheckpoints[toTokenId] = nCheckpoints + 1; } } else { checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds); numCheckpoints[toTokenId] = nCheckpoints + 1; } }
#0 - KenzoAgada
2022-08-02T09:05:46Z
Duplicate of #630
93.2805 USDC - $93.28
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L381 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L389-L394 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L396-L399
When calculating protocolFee
in _settleBalances()
in GolomTrader, the protocolFee is multiplied by amount
twice which can drastically increase the fees that a user receives.
This issue counts as loss of funds and breaks a major selling point of the protocol (low fees) which I believe is high severity.
fillBid
and _settleBalances
is calledprotocolFee
is calculated as 50 * 50 / 10000 * 10 = 2.5 ETH
paymentAmt
are zero, the sender receives (50 - 2.5)*10 = 475 ETH
5%
instead of 0.5%
with 22.5
ETH being left in the contractVS Code
Change the payments in _settleBalances
from
payEther( (o.totalAmt - protocolfee - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount - p.paymentAmt, msg.sender );
to
payEther( (o.totalAmt - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount - p.paymentAmt - protocolfee, msg.sender );
#0 - KenzoAgada
2022-08-02T06:33:30Z
Duplicate of #240
26.7695 USDC - $26.77
In delegate()
of VoteEscrowDelegate, no check is made that the token has been delegated to another token or delegated multiple times. This allows a user with a token to delegate to itself an arbitrary number of times so that the user in effect controls over 50% of votes. The user can then create a new malicious proposal on GovernerBravo to upgrade to a malicious contract and empty all funds from the protocol etc...
1
with voting power of 0.2% of the entire voting powerVS Code
Add a check which makes sure that a token has not already been delegated e.g.
In delegate()
require(delegates[tokenId] == 0, "Already delegated");
Make sure that when you remove delegations that you reset this value
#0 - KenzoAgada
2022-08-02T12:15:11Z
Duplicate of #169
When the 1 billion supply has been reached for the GOLOM token, addFee()
in RewardDistributor will automatically return to the start without updating any trades, however will still accept fees being sent from GolomTrader. These fees however cannot be claimed by anyone, thereby permanently locking these fees into the contract.
VS Code
Consider preventing fees from being sent to the reward distributor after the max supply has been reached or allowing fees to be sent to governance or some other privileged role.
#0 - KenzoAgada
2022-08-03T13:01:06Z
Duplicate of #320
In _writeCheckpoint
, when the oldCheckpoint
is accessed, it is stored in memory so any changes to that variable is not permanently stored. This means that if a user transfers a token to someone else, if a delegation is made in the same block then the delegations are not removed. This allows the new owner to essentially keep all the delegations (without the delegators consent) and use them for malicious purposes.
I believe this issue is medium severity (as it requires frontrunning) however can cause a severe breach in trust between delegators and the delegated.
if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) {
passes
4. The delegatedIds
in storage is unchanged thereby allowing alice to keep all of bob's delegations
VS Code
In _withdrawCheckpoint
change
Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];
to
Checkpoint storage oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];
#0 - KenzoAgada
2022-08-02T08:20:52Z
Duplicate of #455
93.2805 USDC - $93.28
As of now, removeDelegation
removes the delegation of a token to itself, however leaves all delegators in place. This can be very dangerous because if this token is transferred to a malicious user (for example the malicious user buys the token from the original user on a secondary market) then the token can be used to propose and vote for malicious upgrades/proposals which can hijack the entire protocol. This essentially allows an attacker to buyout votes without the explicit consent of the original delegator.
VS Code
Consider changing removeDelegation
so that it removes all delegations. This can be done by passing an empty dynamic array for _writeCheckpoint()
#0 - zeroexdead
2022-08-20T18:52:27Z
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
An issue from a past Code4Arena contest here describes the issue with transfer()
very well.
VS Code
Use call()
instead of transfer()
and make sure to check that the call did succeed
#0 - KenzoAgada
2022-08-03T14:03:28Z
Duplicate of #343
🌟 Selected for report: scaraven
2827.0776 USDC - $2,827.08
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L873-L876 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L883-L886 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L894 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L538 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L1008
A malicious voter can arbitrarily increase the number of attachments
or set the voted
status of a token to true. This prevents the token from being withdrawn, merged or transfered thereby locking the tokens into the contract for as long as the voter would like.
I submitted this is as a medium severity because it has external circumstances (a malicious voter) however has a very high impact if it does occur.
voting()
or attach()
thereby preventing the user withdrawing their token after the locked time bypassesVS Code
I have not seen any use of voting()
or attach()
in any of the other contracts so it may be sensible to remove those functions altogether. On the other hand, setting voter to be smart contract which is not malicious offsets this problem.
#0 - zeroexdead
2022-09-03T18:57:27Z
🌟 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
Currently, in fillAsk
the check to make sure that sufficient ETH has been supplied uses >=
however any excess ethereum is not sent back to the user. Therefore, if a user sends too much ETH to the contract when buying an NFT will permanently lose those funds.
I believe this is medium severity as it requires external circumstances (users sends too much ETH) but causes permanent locking and loss of funds. Additionally, this problem can be very easily avoided.
VS Code
In fillAsk()
change
require(msg.value >= o.totalAmt * amount + p.paymentAmt, 'mgmtm');
to
require(msg.value == o.totalAmt * amount + p.paymentAmt, 'mgmtm');
#0 - KenzoAgada
2022-08-04T02:49:59Z
Duplicate of #75
🌟 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
Magic values make the code less clear and can be especially annoying when you want to update them during development. Consider storing magic values as variables e.g.
uint256 public constant GENESIS_REWARD = 62_500_000 * 1e18; ... _mint(_rewardDistributor, GENESIS_REWARD);
instead of
_mint(_rewardDistributor, 62_500_000 * 1e18);
Occurrences: https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L44 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L52 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L60 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L100 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L120-L121 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L286 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L302 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L212 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#L254-L255 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L263 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L269 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L381 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L449 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L99
increase_amount()
in VoteEscrowCore is obsoleteincrease_amount()
makes a check to make sure that the sender is the owner of the token. Other than this check, increase_amount()
performs the exact same operations as deposit_for
which does not have the authentication check.
Consider removing increase_amount()
Occurrences:
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowCore.sol#L977
validateOrder()
from GolomTradervalidateOrder()
checks whether the signer of the order hash is the same as o.signer
twice in the same function, thereby rendering the second check obsolete. Consider removing one of these checks
addFees
makes sure that current supply is less than 1 billion however does not account for new reward tokens minted later on. This can cause the supply to become larger than 1 billion
Consider adding a check which makes sure that the supply is less than 1 billion after minting reward tokens
removeDelegationByOwner()
in VoteEscrowDelegateCurrently, this code is commented out, so I feel like low severity is suitable however it may be a medium as the sponsor has told me that it is in scope.
removeDelegationByOwner()
is supposed to remove the owner from delegation to another token however instead would remove the delegated token from own delegation
Consider changing
removeElement(checkpoint.delegatedTokenIds, delegatedTokenId);
to
removeElement(checkpoint.delegatedTokenIds, ownerTokenId);
Currently, if governance update an address to address(0)
then after the initial time period, governance can instantly set a new address which may be malicious.
Consider adding a check to make sure that the new address is non zero e.g.
require(_addr != 0, "Non zero address");
Occurrences: https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GolomToken.sol#L66-L67 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L445-L446 https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/rewards/RewardDistributor.sol#L299-L300
totalAmt
made in fillBid
and fillCriteriaBid
Currently fillBid()
executes:
require( o.totalAmt * amount > (o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt) * amount + p.paymentAmt ); // cause bidder eth is paying for seller payment p , dont take anything extra from seller
while fillCriteriaBid()
executes:
require(o.totalAmt >= o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt);
None of these checks account for the protocol fee and therefore if totalAmt
is too low then it can cause either the transaction to revert suddenly or for any excess ethereum stored in the contract to be funnelled away.
Consider changing the checks to the following:
require( o.totalAmt * amount > (o.exchange.paymentAmt + o.prePayment.paymentAmt + o.refererrAmt + o.totalAmt * 50 / 10000) * amount + p.paymentAmt );