Platform: Code4rena
Start Date: 12/08/2022
Pot Size: $35,000 USDC
Total HM: 10
Participants: 126
Period: 3 days
Judge: Justin Goro
Total Solo HM: 3
Id: 154
League: ETH
Rank: 35/126
Findings: 2
Award: $107.67
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Aymen0909
Also found by: 0xSky, 0xf15ers, CertoraInc, JohnSmith, auditor0517, bin2chen, csanuragjain, scaraven, tabish, wagmi, yixxas
VotingEscrow.increaseUnlockTime() uses wrong unlock time for old lock. The user's voting power might be calculated wrongly.
As we can see from CheckpointMath, the oldLock.end
should be original value but it uses the increased unlock_time
here.
So inside the _checkpoint()
function, several calculations will be wrong including userOldPoint.bias.
Manual Review
This line should be changed like this.
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 513: oldLocked.end = oldUnlockTime;
#0 - lacoop6tu
2022-08-16T09:27:40Z
Duplicate of #217
🌟 Selected for report: oyc_109
Also found by: 0x1f8b, 0x52, 0xDjango, 0xLovesleep, 0xNazgul, 0xNineDec, 0xbepresent, 0xmatt, 0xsolstars, Aymen0909, Bahurum, Bnke0x0, CertoraInc, Chom, CodingNameKiki, DecorativePineapple, Deivitto, Dravee, ElKu, Funen, GalloDaSballo, IllIllI, JC, JohnSmith, Junnon, KIntern_NA, Lambda, LeoS, MiloTruck, Noah3o6, PaludoX0, RedOneN, Respx, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, Ruhum, Sm4rty, TomJ, Vexjon, Waze, Yiko, __141345__, a12jmx, ajtra, ak1, apostle0x01, asutorufos, auditor0517, bin2chen, bobirichman, brgltd, bulej93, byndooa, c3phas, cRat1st0s, cryptphi, csanuragjain, d3e4, defsec, delfin454000, djxploit, durianSausage, ellahi, erictee, exd0tpy, fatherOfBlocks, gogo, jonatascm, ladboy233, medikko, mics, natzuu, neumo, p_crypt0, paribus, pfapostol, rbserver, reassor, ret2basic, robee, rokinot, rvierdiiev, sach1r0, saneryee, seyni, sikorico, simon135, sseefried, wagmi, wastewa
29.9451 USDC - $29.95
Currently it doesn't validate the new address when change the owner and recommend using two-phase ownership transfers for safety.
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 120: owner = _owner;
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 139: function transferOwnership(address _addr) external { 140: require(msg.sender == owner, "Only owner"); 141: owner = _addr; 142: emit TransferOwnership(_addr); 143: }
There is no address(0) validation for penaltyRecipient
and penalty might be transferred to zero address here.
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 121: penaltyRecipient = _penaltyRecipient;
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 153: function updatePenaltyRecipient(address _addr) external { 154: require(msg.sender == owner, "Only owner"); 155: penaltyRecipient = _addr; 156: emit UpdatePenaltyRecipient(_addr); 157: }
In the CheckpointMath, it requires T>owner.end
but it's implemented as unlock_time >= locked_.end
.
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 414: require(unlock_time >= locked_.end, "Only increase lock end"); // from using quitLock, user should increaseAmount instead
In the CheckpointMath, it requires to.end > from.end
but it's implemented as toLocked.end >= fromLocked.end
.
After I confirm with sponsor, >=
should be used instead of >
. Otherwise, a delegator can't change the delegatee when the previous delegatee keeps updating fromLocked.end = the latest week before block.timestamp + MAXTIME.
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 589: require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
This comment is wrong because we check locked_.end > block.timestamp
here.
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 647: // oldLocked can have either expired <= timestamp or zero end
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 25: event Deposit( 26: address indexed provider, 27: uint256 value, 28: uint256 locktime, 29: LockAction indexed action, 30: uint256 ts 31: );
File: 2022-08-fiatdao\contracts\VotingEscrow.sol 32: event Withdraw( 33: address indexed provider, 34: uint256 value, 35: LockAction indexed action, 36: uint256 ts 37: );
manager
and ve
can be declared as immutable variables because they are set only once in the constructor and they should be 0-checked.
File: 2022-08-fiatdao\contracts\features\Blocklist.sol 15: manager = _manager; 16: ve = _ve;