Platform: Code4rena
Start Date: 07/08/2023
Pot Size: $36,500 USDC
Total HM: 11
Participants: 125
Period: 3 days
Judge: alcueca
Total Solo HM: 4
Id: 274
League: ETH
Rank: 51/125
Findings: 2
Award: $25.65
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ADM
Also found by: 0xDING99YA, 3docSec, BenRai, Jorgect, Kow, MrPotatoMagic, QiuhaoLi, RandomUser, SpicyMeatball, Tendency, Topmark, Watermelon, Yanchuan, Yuki, bart1e, cducrest, kaden, lsaudit, nemveer, nonseodion
15.8333 USDC - $15.83
Couple of points to note,
delegate()
, which allows delegation to only longer lock, i.e. Delegator can only delegate to a longer lock, i.e. to.end>from.end
require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
delegatee == msg.sender
require(locked_.delegatee == msg.sender, "Lock delegated");
increaseAmount
resets the lock end to 5 yearsVotingEscrow.increaseAmount#L301
newLocked.end = _floorToWeek(block.timestamp + LOCKTIME);
delegate()
transaction with increaseAmount
and prevent him from delegating to someone else. Thus, keeping the voting power to himself.withdraw()
transaction with increaseAmount
and prevent him from withdrawing Canto
. Thus again, keeping the voting power to himself.Let's see how Bob(malicious user) can prevent Alice from withdrawing. For simplicity let's assume the Locking period of 10 days
delegate(Alice.address)
transaction.increaseAmount(1)
passing 1 wei of canto as msg.value
to.end > from.end == false
(1 day > 10 days == false)Manual Review
I am not 100% sure what's the purpose of this condition in the delegate
. According to me below condition is sufficient.
require(to.end > block.timestamp);
DoS
#0 - c4-pre-sort
2023-08-13T06:59:43Z
141345 marked the issue as duplicate of #116
#1 - c4-pre-sort
2023-08-13T14:35:11Z
141345 marked the issue as duplicate of #82
#2 - c4-judge
2023-08-24T07:20:39Z
alcueca changed the severity to 2 (Med Risk)
#3 - c4-judge
2023-08-24T07:24:41Z
alcueca marked the issue as satisfactory
#4 - c4-judge
2023-08-24T07:24:46Z
alcueca marked the issue as partial-50
#5 - c4-pre-sort
2023-08-24T08:21:47Z
141345 marked the issue as not a duplicate
#6 - c4-pre-sort
2023-08-24T08:24:20Z
141345 marked the issue as duplicate of #375
#7 - c4-judge
2023-08-24T21:12:20Z
alcueca marked the issue as partial-50
#8 - c4-judge
2023-08-29T06:37:11Z
alcueca marked the issue as duplicate of #182
#9 - c4-judge
2023-08-29T06:37:35Z
alcueca changed the severity to 3 (High Risk)
🌟 Selected for report: ADM
Also found by: 0xDING99YA, 3docSec, BenRai, Jorgect, Kow, MrPotatoMagic, QiuhaoLi, RandomUser, SpicyMeatball, Tendency, Topmark, Watermelon, Yanchuan, Yuki, bart1e, cducrest, kaden, lsaudit, nemveer, nonseodion
15.8333 USDC - $15.83
https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L383-L384 https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L331
Let's first understand how the process of delegation, withdraw and increaseAmount works
Rule 1
: To delegate to some user A, A.lock.end
needs to be longer than msg.sender.lock.end
and A.Lock.end > block.timestamp
(Lock mustn't be expired)VotingEscrow.delegate#L383-L384
require(toLocked.end > block.timestamp, "Delegatee lock expired"); require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
Rule 2
: To withdraw the locked Canto
, delegatee must be msg.sender
require(locked_.delegatee == msg.sender, "Lock delegated");
Rule 3
: Locking period is fixed, i.e. 5 years. You can't increase the lock.end
directly but calling increaseAmount
will reset it to block.timestamp + 5 years
VotingEscrow.increaseAmount#L301
newLocked.end = _floorToWeek(block.timestamp + LOCKTIME);
Now consider userA
and userB
.
userA
has delegated to userB
, which means userB.lock.end >= userA.lock.end
And userA wants to withdraw his Canto in 2 different scenarios.
userA.lock.end > block.timestamp
, so userA's lock is not expired yetuserA.lock.delegatee == userA.address
, so, userA
need to undelegate from userB
to himself.
userB.lock.end >= userA.lock.end
as per Rule2
, so userA
can't delegate to himself because of Rule 1
increaseAmount
with minimum amount possible and increase his lock time to another 5 years. So won't be able to withdraw until another 5 yearsuserA.lock.end < block.timestamp
, so userA
wants to withdraw after his lock is expired.userA.lock.delegatee == userA.address
as per Rule2
, so, userA
need to undelegate from userB
to himself.
A.Lock.end > block.timestamp
as per Rule1
. So againhe won't be able to call delegateincreaseAmount
, reset the lock.end and then call delegate(userA.address).After delegating to someone else, User must need to call increaseAmount and wait for another 5 years to withdraw
https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L383-L384 https://github.com/code-423n4/2023-08-verwa/blob/a693b4db05b9e202816346a6f9cada94f28a2698/src/VotingEscrow.sol#L331
Manual Review
I am not sure about the reasoning behind a fixed lock time of 5 years. It doesn't provide flexibility and convenience to users. But I am sure there must be reasons for choosing it. For now I think removing the below condition works.
require(toLocked.end >= fromLocked.end, "Only delegate to longer lock");
Other
#0 - c4-pre-sort
2023-08-11T12:00:53Z
141345 marked the issue as duplicate of #223
#1 - c4-pre-sort
2023-08-13T11:56:42Z
141345 marked the issue as not a duplicate
#2 - c4-pre-sort
2023-08-13T11:56:57Z
141345 marked the issue as duplicate of #178
#3 - c4-judge
2023-08-24T07:14:14Z
alcueca marked the issue as duplicate of #82
#4 - c4-judge
2023-08-24T07:20:39Z
alcueca changed the severity to 2 (Med Risk)
#5 - c4-judge
2023-08-24T07:22:39Z
alcueca marked the issue as satisfactory
#6 - c4-judge
2023-08-24T07:22:44Z
alcueca marked the issue as partial-50
#7 - c4-judge
2023-08-29T06:37:36Z
alcueca changed the severity to 3 (High Risk)
🌟 Selected for report: RED-LOTUS-REACH
Also found by: 0x3b, 0x4non, 0xCiphky, 0xDING99YA, 0xDetermination, 0xE1, 0xG0P1, 0xStalin, 0xWaitress, 0xbrett8571, 0xhacksmithh, 0xkazim, 0xmuxyz, 0xweb3boy, 14si2o_Flint, AlexCzm, Alhakista, Bube, Bughunter101, Deekshith99, Eeyore, Giorgio, HChang26, InAllHonesty, JP_Courses, KmanOfficial, MatricksDeCoder, Mike_Bello90, MrPotatoMagic, Naubit, QiuhaoLi, RHaO-sec, Raihan, Rolezn, SUPERMAN_I4G, Shubham, Silverskrrrt, Strausses, T1MOH, Topmark, Tripathi, Watermelon, _eperezok, aakansha, auditsea, audityourcontracts, ayden, carlos__alegre, castle_chain, cducrest, ch0bu, d23e, deadrxsezzz, deth, devival, erebus, fatherOfBlocks, halden, hassan-truscova, hpsb, hunter_w3b, imkapadia, immeas, jat, kaden, kaveyjoe, klau5, koxuan, kutugu, ladboy233, lanrebayode77, leasowillow, lsaudit, markus_ether, matrix_0wl, merlin, nemveer, ni8mare, nonseodion, oakcobalt, owadez, p_crypt0, pipidu83, piyushshukla, popular00, ppetrov, rjs, sandy, sl1, supervrijdag, tay054, thekmj, wahedtalash77, windhustler, zhaojie
9.8204 USDC - $9.82
In _checkpoint
, slope is calculated and then multiplied with time delta to calculate the bias
VotingEscrow._checkpoint#L130-L131
userOldPoint.slope = _oldLocked.delegated / int128(int256(LOCKTIME)); userOldPoint.bias = userOldPoint.slope * int128(int256(_oldLocked.end - block.timestamp));
Here, division before multiplication happens.
If _oldLocked.delegated
is less than LOCKTIME
then slope = 0
and then bias = 0
. Which lead to loss of user's voting power even if user has locked some Canto
Manual review
Multiply the _oldLocked.delegated
with some multipier to calculate slope and then divide the bias with that multiplier
Other
#0 - c4-pre-sort
2023-08-13T07:32:15Z
141345 marked the issue as duplicate of #210
#1 - c4-pre-sort
2023-08-13T08:51:12Z
141345 marked the issue as duplicate of #299
#2 - c4-judge
2023-08-24T05:37:15Z
alcueca changed the severity to QA (Quality Assurance)
#3 - c4-judge
2023-08-25T22:50:30Z
alcueca marked the issue as grade-a