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: 70/179
Findings: 5
Award: $159.74
🌟 Selected for report: 0
🚀 Solo Findings: 0
26.7695 USDC - $26.77
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L71-L89 https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L168-L175
The delegate
function in VoteEscrowDelegation.sol
is used to delegate voting power from one tokenId to another. The tokenId is added to the toTokenId's delegatedTokenIds array which contains all tokenIds that have delegated to the toTokenId. The amount of votes a token has for a checkpoint is determined by its own balance and the balances of all tokens in delegatedTokenIds.
Since delegatedTokenIds is an array, it may contain the same tokenId more than once. This means that the voting power of a tokenId can be multiplied by just adding the same tokenId to the delegatedTokenIds array.
This issue would allow anyone to increase their voting power without staking more tokens or having legitimate delegations.
A malicous user may also fill the delegatedTokenIds of any tokenId so that when a user wants to delegate to that tokenId the delegate function will revert. This happens when the delegate funtion calls the _writeCheckpoint function which reverts if the delegatedTokenIds array contains 500 or more tokenIds.
The code below shows how a tokenId delegates to a toTokenId in the delegate
function. If nCheckpoints > 0, then the tokenId will be added to the toTokenId's delegatedTokenIds array for that checkpoint. The function does not check if the tokenId is currently delegating to the toTokenId or another tokenId which allows a user to repeatedly call the function for the same toTokenId
if (nCheckpoints > 0) { Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1]; checkpoint.delegatedTokenIds.push(tokenId); _writeCheckpoint(toTokenId, nCheckpoints, checkpoint.delegatedTokenIds);
After the tokenId is added, _writeCheckpoint is called which will either update the current checkpoint of the toTokenId or create a new checkpoint with the updated delegatedTokenIds array.
The malicious user could use a tokenId with very little voting power to fill up the delegatedTokenIds array so that other tokens are unable to delegate to that toTokenId. They could also delegate to a tokenId they own with the same tokenId and multiply its voting power without actually adding more votes. The getVotes function will then just return voting power of the tokenId * 500 (the delegatedTokenIds array cannot contain more than 500 tokenIds).
Prevent duplicate tokenIds from being entered in the delegatedTokenIds array when the delegate
function is called. This could be done by checking ```delegates[tokenId] == address(0), which if false then remove the tokenId from the delegatedTokenIds array of the toTokenId currently being delegated to.
#0 - KenzoAgada
2022-08-04T12:50:53Z
Duplicate of #169
93.2805 USDC - $93.28
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L210-L216 https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L242
The removeDelegation
function in VoteEscrowDelegation.sol
is used to revoke the delegation given to another tokenId in the delegate
function. In other words, removeDelegation
is supposed to remove tokenId
from the delegatedTokenIds
of the toTokenId
.
When used in the _transferFrom
function, the tokenId
being transferred should be delegating to any other tokenIds when transferred to the new owner.
Currently, the removeDelegation
function only removes the tokenId
given as an argument from the delegatedTokenIds
array.
This issue effectively means that users are unable to revoke delegations.
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); }
Consider adding a toTokenId
parameter to the removeDelegation
function which could be used to find the toTokenId
to which the tokenId
delegated to. Then the tokenId
should be removed from the toTokenId
's delegatedTokenIds array. The delegates[tokenId]
should return the toTokenId
delegate of the tokenId
and can be used to remove the delegate from tokenId.
#0 - KenzoAgada
2022-08-02T08:22:24Z
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
Judge has assessed an item in Issue #921 as Medium risk. The relevant finding follows:
Impact The payable(address).transfer function has a limit of 2300 gas (source). If the receiver has a fallback/receive function that requires more gas then the transaction would revert. If this is the case with the signer of the order, the orders of ordertype 0 might not be fillable.
Recommended Mitigation Steps Consider using the call function instead of transfer to send ETH.
#0 - dmvt
2022-10-21T14:08:51Z
Duplicate of #343
🌟 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
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/core/GolomTrader.sol#L217
The fillAsk
function in GolomTrader.sol
is used to fill a signed order using ETH. The line 217 checks that the amount of ETH given is >= the expected amount that the caller has to pay according to the total amount of the order, the amount of times to fill the order and the amount the caller wants to send as an extra payment.
Allowing the msg.value
to be more than the expected amount to be paid may lead to the user losing the amount that was overspent. Since there does not seem to be any functionality that would allow the operator of the contract to reimburse funds mistakenly sent to the contract, the user may lose the overspent ETH.
Affected code:
217: require(msg.value >= o.totalAmt * amount + p.paymentAmt, 'mgmtm');
A quick fix solution would be to change the >=
to ==
on line 217 which would mean the user would have to provide exactly the amount needed to fill an order. Another solution would be to introduce additional functionality that would allow the operator of the contract to refund any funds sent by mistake to the contract.
#0 - KenzoAgada
2022-08-04T02:50:07Z
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
The payable(address).transfer function has a limit of 2300 gas (source). If the receiver has a fallback/receive function that requires more gas then the transaction would revert. If this is the case with the signer of the order, the orders of ordertype 0 might not be fillable.
Consider using the call
function instead of transfer
to send ETH.
getVotes
and getPriorVotes
may not return the votes of the tokenId, only its delegatorsVoteEscrowDelegation.sol#L168-L175
VoteEscrowDelegation.sol#L185-L193
The getVotes
function in VoteEscrowDelegation.sol
should return the total amount of votes that a tokenId has. Similarly, the getPriorVotes
should return the total amount of votes that a tokenId has as of a block number. If no other tokenIds have delegated to the tokenId and it isn't delegated to itself then the getVotes
and getPriorVotes
functions will return 0 even if the tokenId has a non-zero balance.
The voting power of tokenIds will not be accurate due to the incorrect return values of these functions.
tokenIds should not be able to delegate to themselves and the getVotes
and getPriorVotes
functions should set the base votes amount to the balance of the tokenId given as input instead of 0.
A floating pragma might allow the contract to be deployed with an outdated compiler which might introduce bugs to the contracts deployed.
Lock the pragma version to the same version as used by the other contracts in the project.
On line 68 in VoteEscrowDelegation.sol
in VoteEscrowDelegation.sol
, the comments to do not explain what the delegate
function does.
On line 164 in VoteEscrowDelegation.sol
in VoteEscrowDelegation.sol
, the comment references account
which might be misleading if by account
it means user since a user may hold multiple tokenIds.
On line 68, a brief description of what the function does should be added and on line 164 it might be better to replace the reference to account
with tokenId
.