Platform: Code4rena
Start Date: 22/05/2024
Pot Size: $20,000 USDC
Total HM: 6
Participants: 126
Period: 5 days
Judge: 0xsomeone
Total Solo HM: 1
Id: 379
League: ETH
Rank: 44/126
Findings: 2
Award: $0.02
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: SpicyMeatball
Also found by: 0rpse, 0xMosh, 0xblack_bird, 0xdice91, 0xhacksmithh, 0xleadwizard, 0xmystery, Audinarey, AvantGard, Bigsam, Dots, EPSec, Eeyore, Janio, Limbooo, LinKenji, Mahmud, MrPotatoMagic, Myd, Oxsadeeq, Sabit, SovaSlava, Stefanov, Tychai0s, Utsav, Varun_05, Walter, adam-idarrha, ahmedaghadi, araj, aslanbek, ayden, bigtone, c0pp3rscr3w3r, carrotsmuggler, crypticdefense, dhank, fyamf, gajiknownnothing, gavfu, itsabinashb, jasonxiale, joaovwfreire, ke1caM, leegh, merlinboii, mitko1111, n4nika, pfapostol, prapandey031, rouhsamad, sandy, snakeeaterr, stakog, steadyman, swizz, tedox, th3l1ghtd3m0n, trachev, turvy_fuzz, xyz, yashgoel72, zhaojohnson
0.014 USDC - $0.01
Users can set new shorter duration times for their locked funds and emit false events.
The setLockDuration
function has the following if-statement to make sure that no user can set a new lock duration that is before the current unlockTime:
if ( uint32(block.timestamp) + uint32(_duration) < lockedTokens[msg.sender][tokenContract].unlockTime) { revert LockDurationReducedError(); }
Then the function updates the new unlockTime immediately after like so:
uint32 lastLockTime = lockedTokens[msg.sender][tokenContract].lastLockTime; lockedTokens[msg.sender][tokenContract].unlockTime = lastLockTime + uint32(_duration);
The problem here is that we are using uint32(block.timestamp)
, instead of the previous lock time that was set, to check if a user is setting shorter durations. This will lead to shorter durations being set.
Let's imagine a user has already locked funds for a certain duration and wants to set a new duration. For example let's say the user currently has:
Now let's say the current block.timestamp is at 1900, and the user calls this function again, but with _duration
being set to 150, lowered from 1500. Plugging in these numbers in the if-statement will not make it revert:
if(1900 + 150 < 2000) { revert LockDurationReducedError(); }
Now let's go ahead and update the unlockTime again by plugging in the new numbers and see what we get. The new unlock time is now:
lockedTokens[msg.sender][tokenContract].unlockTime = 500 + 150 = 650 650 < 2000
The event in this function will now emit a new duration of 150, and the new unlockTime is now reduced from 2000 to 650.
Manual Review
Consider changing the uint32(block.timestamp)
to lockedTokens[msg.sender][tokenContract].lastLockTime
correctly check the user isn't setting shorter duration periods:
- if ( uint32(block.timestamp) + uint32(_duration) < lockedTokens[msg.sender][tokenContract].unlockTime) { + if ( lockedTokens[msg.sender][tokenContract].lastLockTime + uint32(_duration) < lockedTokens[msg.sender][tokenContract].unlockTime) { revert LockDurationReducedError(); }
Timing
#0 - c4-judge
2024-06-04T12:40:59Z
alex-ppg marked the issue as duplicate of #89
#1 - c4-judge
2024-06-05T12:53:36Z
alex-ppg marked the issue as satisfactory
#2 - c4-judge
2024-06-05T12:54:34Z
alex-ppg changed the severity to 3 (High Risk)
🌟 Selected for report: robertodf99
Also found by: 0xAadi, 0xAkira, 0xdice91, 0xhacksmithh, 0xleadwizard, AgileJune, Bauchibred, Bbash, Beosin, Bigsam, Dots, EPSec, EaglesSecurity, Eeyore, Evo, John_Femi, Mahmud, MrPotatoMagic, RotiTelur, Rushkov_Boyan, Sabit, Sentryx, Stormreckson, Topmark, Tychai0s, Utsav, Walter, ZanyBonzy, ZdravkoHr, adam-idarrha, araj, aslanbek, avoloder, bigtone, brevis, brgltd, carrotsmuggler, crypticdefense, dd0x7e8, dhank, djanerch, falconhoof, iamandreiski, joaovwfreire, leegh, merlinboii, mitko1111, pamprikrumplikas, pfapostol, prapandey031, swizz, trachev, twcctop, typicalHuman, unique, xyz
0.0148 USDC - $0.01
A price feed role owner can possibly cast a disapprove vote and approve vote in the same USD proposal.
In the approveUSDPrice()
function, a price feed role owner can cast an approve vote on a USD price proposal even if they already set a disapprove previously because there is no check to see if the msg.sender
had previously disapproved the proposal. This will potentially cause both usdUpdateProposal.approvalsCount++
and usdUpdateProposal.disapprovalsCount++
to be incremented by the same user.
It's implied that a price feed role owner can only vote once and not have their vote changed because if we take a look at the disapproveUSDPrice()
function, it checks to see if the msg.sender previously approved or disapproved. If either option was already made, then the function reverts. See code below inside disapproveUSDPrice()
:
if (usdUpdateProposal.approvals[msg.sender] == _usdProposalId) revert ProposalAlreadyApprovedError(); if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId) revert ProposalAlreadyDisapprovedError();
The same must be done inside approveUSDPrice()
function so that a user who previously disapproved a proposal can not decide to approve the same proposal
Manual Review
Consider adding a check inside approveUSDPrice()
to make sure a user did not already disapprove before. Please add to the beginning of the function:
if (usdUpdateProposal.disapprovals[msg.sender] == _usdProposalId) revert ProposalAlreadyDisapprovedError();
Other
#0 - c4-judge
2024-06-05T12:42:42Z
alex-ppg marked the issue as satisfactory