The virtual balance of a user is calculated using 2 values - the amount that is delegated to that user, and his lock period. When calling the increaseUnlockTime function, we want to checkpoint the user's data as long as he doesn't have any funds. This is because if he has funds, the change to the lock time will effect his virtual balance.
The code checks that the user doesn't have any funds by checking that he is not delegating his funds to someone else. This check can be seen here:
However, this check is wrong, because a user can delegate his funds but still has funds delegated to him, so the change to the unlock time will effect his virtual balance and is needed to be accounted by the _checkpoint function.
Proof of Concept
Alice locked X tokens and delegated them to Bob, so locked[alice].delegatee == bob
Bob locked Y tokens and delegated them to Charlie, so locked[bob].delegatee == charlie
Now:
locked[alice].delegated is 0 because all of Alice's funds are delegated to Bob and she doesn't have any funds delegated to her.
locked[bob].delegated is X because all of Bob's funds are delegated to Charlie, but Bob has Alice's funds delegated to him.
locked[charlie].delegated is Y because Charlie doesn't have any funds locked, but he has Bob's funds delegated to him.
Bob wants to increase the unlock time of his locked funds. He calls increaseUnlockTime with the new unlock time he wants.
In that call, the virtual balance of Bob is changed, because although he delegated his funds to Charlie, he still has Alice's funds delegated to him and the change to the unlock time will effect it. However, because of the wrong condition shown before, the _checkpoint function won't be called and his virtual balance won't be updated - locked[bob].delegatee == charlie.
Tools Used
Manual auditing - VS Code and me :)
Recommended Mitigation Steps
The correct condition here needs to check whether the user has ANY funds in the system, not if the user has delegated his funds or not. This can be checked by this condition:
Lines of code
https://github.com/code-423n4/2022-08-fiatdao/blob/fece3bdb79ccacb501099c24b60312cd0b2e4bb2/contracts/VotingEscrow.sol#L509
Vulnerability details
Impact
The virtual balance of a user is calculated using 2 values - the amount that is delegated to that user, and his lock period. When calling the
increaseUnlockTime
function, we want to checkpoint the user's data as long as he doesn't have any funds. This is because if he has funds, the change to the lock time will effect his virtual balance.The code checks that the user doesn't have any funds by checking that he is not delegating his funds to someone else. This check can be seen here:
However, this check is wrong, because a user can delegate his funds but still has funds delegated to him, so the change to the unlock time will effect his virtual balance and is needed to be accounted by the
_checkpoint
function.Proof of Concept
locked[alice].delegatee == bob
locked[bob].delegatee == charlie
Now:locked[alice].delegated
is 0 because all of Alice's funds are delegated to Bob and she doesn't have any funds delegated to her.locked[bob].delegated
is X because all of Bob's funds are delegated to Charlie, but Bob has Alice's funds delegated to him.locked[charlie].delegated
is Y because Charlie doesn't have any funds locked, but he has Bob's funds delegated to him.increaseUnlockTime
with the new unlock time he wants. In that call, the virtual balance of Bob is changed, because although he delegated his funds to Charlie, he still has Alice's funds delegated to him and the change to the unlock time will effect it. However, because of the wrong condition shown before, the_checkpoint
function won't be called and his virtual balance won't be updated -locked[bob].delegatee == charlie
.Tools Used
Manual auditing - VS Code and me :)
Recommended Mitigation Steps
The correct condition here needs to check whether the user has ANY funds in the system, not if the user has delegated his funds or not. This can be checked by this condition: