This method will use _transferDelegateBalance() ot transfer the current delegateBalance to _toDelegate
function _transferDelegateBalance(
address _vault,
address _fromDelegate,
address _toDelegate,
uint96 _amount
) internal {
// If we are transferring tokens from a delegated account to an undelegated account
if (_fromDelegate != address(0) && _fromDelegate != SPONSORSHIP_ADDRESS) {
_decreaseBalances(_vault, _fromDelegate, 0, _amount);
// If we are delegating to the zero address, decrease total supply
// If we are delegating to the sponsorship address, decrease total supply
if (_toDelegate == address(0) || _toDelegate == SPONSORSHIP_ADDRESS) {
_decreaseTotalSupplyBalances(_vault, 0, _amount);
}
}
// If we are transferring tokens from an undelegated account to a delegated account
@> if (_toDelegate != address(0) && _toDelegate != SPONSORSHIP_ADDRESS) {
_increaseBalances(_vault, _toDelegate, 0, _amount);
// If we are removing delegation from the zero address, increase total supply
// If we are removing delegation from the sponsorship address, increase total supply
if (_fromDelegate == address(0) || _fromDelegate == SPONSORSHIP_ADDRESS) {
_increaseTotalSupplyBalances(_vault, 0, _amount);
}
}
}
We know from the above method that to will not increase delegateBalance if to==address(0).
But the current protocol should default the delegate to itself if current delegate is address(0).
function _delegateOf(address _vault, address _user) internal view returns (address) {
address _userDelegate;
if (_user != address(0)) {
_userDelegate = delegates[_vault][_user];
// If the user has not delegated, then the user is the delegate
@> if (_userDelegate == address(0)) {
_userDelegate = _user;
}
}
return _userDelegate;
}
So there is a problem, if the user wants to set the delegate to himself, he may execute delegate(to=address(0)).
The result: the delegate becomes himself, but the delegateBalance is lost!
Malicious users may use this to prevent other users from transferring delegates.
Example.
Suppose: Alice's balance= 100 and the delegate is Bob, so Alice's delegateBalance==0 and Bob's delegateBalance==100.
Alice executes delegate(to=address(0))
At this point, Alice's delegates become himself, but delegateBalance is still 0 (due to the problem mentioned above)
Jack executes delegate(to=alice) (assuming jack's balance == 100)
At this point, Alice's delegateBalance will become 100.
Alice executes delegate(to=bob).
At this point, Alice's delegateBalance will become 0 (delegateBalance = 100-100)
after that Jack can't reset the delegate anymore, because now Alice's delegateBalance is 0, and reset delegate will execute Alice.delegateBalance = 0 - 100 resulting in underflow
Lines of code
https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/0145eeac23301ee5338c659422dd6d69234f5d50/src/TwabController.sol#L648
Vulnerability details
Impact
delegate()
doesn't handle theto=address(0)
case very well, and could be used to maliciously prevent others user to reset thedelegate
Proof of Concept
We can set our own
delegates[_vault][_from]
to_to
by using thedelegate()
methodThis method will use
_transferDelegateBalance()
ot transfer the currentdelegateBalance
to_toDelegate
We know from the above method that
to
will not increasedelegateBalance
ifto==address(0)
.But the current protocol should default the delegate to itself if current delegate is address(0).
So there is a problem, if the user wants to set the delegate to himself, he may execute
delegate(to=address(0))
. The result: the delegate becomes himself, but thedelegateBalance
is lost!Malicious users may use this to prevent other users from transferring delegates.
Example. Suppose: Alice's
balance= 100
and the delegate is Bob, so Alice'sdelegateBalance==0
and Bob'sdelegateBalance==100
.Alice executes
delegate(to=address(0))
At this point, Alice's delegates become himself, butdelegateBalance
is still 0 (due to the problem mentioned above)Jack executes
delegate(to=alice)
(assuming jack's balance == 100) At this point, Alice'sdelegateBalance
will become100
.Alice executes
delegate(to=bob)
. At this point, Alice'sdelegateBalance
will become0
(delegateBalance = 100-100)after that Jack can't reset the delegate anymore, because now
Alice
'sdelegateBalance
is 0, and reset delegate will executeAlice.delegateBalance = 0 - 100
resulting inunderflow
Tools Used
Recommended Mitigation Steps
Assessed type
Context