code-423n4 / 2023-08-pooltogether-mitigation-findings

0 stars 0 forks source link

New undelegation logic is broken, it won't work if the currentDelegate is already the SPONSORSHIP_ADDRESS #39

Open code423n4 opened 1 year ago

code423n4 commented 1 year ago

Lines of code

https://github.com/GenerationSoftware/pt-v5-twab-controller/blob/main/src/TwabController.sol#L645-L651

Vulnerability details

Original Issue

H-06 - Resetting delegation will result in user funds being lost forever

Details

The previous implementation to reset the delegation was causing that the users lost their own delegate balance if they tried to reset the delegation to themselves by setting the _to parameter as the address(0), which that is the value used to determine if a user is the delegate or not.

Mitigation

The implemented mitigation is attempting to treat the value of the _to parameter to be used as indicative of an undelegation operation.

Conclusion of the Mitigation and Proof of Concept of the new Bug

Coded PoC

function testFirstSponsorshipAndThenUndelegate() external {
  address _sponsorshipAddress = SPONSORSHIP_ADDRESS;

  assertEq(twabController.delegateOf(mockVault, alice), alice);

  vm.startPrank(mockVault);
  //@audit => Alice's funds in the mockVault are delegated to the sponsor
  twabController.sponsor(alice);

  assertEq(twabController.delegateOf(mockVault, alice), _sponsorshipAddress);

  uint96 _amount = 1000e18;
  twabController.mint(alice, _amount);

  assertEq(twabController.balanceOf(mockVault, alice), _amount);
  assertEq(twabController.delegateBalanceOf(mockVault, alice), 0);

  assertEq(twabController.balanceOf(mockVault, _sponsorshipAddress), 0);
  assertEq(twabController.delegateBalanceOf(mockVault, _sponsorshipAddress), 0);
  vm.stopPrank();

  //@audit => Now alice want to undelegate the SPONSORSHIP_ADDRESS using the new logic to perform undelegations
  vm.startPrank(alice);
  twabController.delegate(mockVault,address(0));
  vm.stopPrank();
}

Test result: FAILED. 0 passed; 1 failed; finished in 2.36ms

Failing tests: Encountered 1 failing test in test/TwabController.t.sol:TwabControllerTest [FAIL. Reason: SameDelegateAlreadySet(0x0000000000000000000000000000000000000001)] testFirstSponsorshipAndThenUndelegate() (gas: 109875)


## Recommended Mitigation Steps
- The recommendation would be to set the `to` address to be the user's address instead of the SPONSORSHIP_ADDRESS, in this way the new undelegation process won't fail if the `_currentDelegate` has already been set to the SPONSORSHIP_ADDRESS.

```solidity
function _delegate(address _vault, address _from, address _to) internal {
  address _currentDelegate = _delegateOf(_vault, _from);
  ...

- address to = _to == address(0) ? SPONSORSHIP_ADDRESS : _to;
+ address to = _to == address(0) ? _from : _to;

  if (to == _currentDelegate) {
    revert SameDelegateAlreadySet(to);
  }

  ...
}

Assessed type

Context

c4-sponsor commented 1 year ago

asselstine marked the issue as sponsor confirmed

djb15 commented 1 year ago

I think this report is incorrect no @asselstine?

It's intended behaviour to revert if you're trying to delegate to the same address that's already delegated to. In this case you're trying to delegate from SPONSORSHIP_ADDRESS to SPONSORSHIP_ADDRESS (because 0 is now arbitrarily mapped to SPONSORSHIP_ADDRESS).

The new implementation has changed the "reset" behaviour to specify the user address rather than address(0) to undelegate (i.e. delegate back to yourself).

stalinMacias commented 1 year ago

The new implementation has changed the "reset" behaviour to specify the user address rather than address(0) to undelegate (i.e. delegate back to yourself).

Actually, the new implementation attempted to undelegate by delegating to the SPONSORSHIP_ADDRESS if the address in the delegate() was passed as the 0x address. See the comment in the TwabController::_delegate()

djb15 commented 1 year ago

Exactly my point! Why is forcing the delegation to the SPONSORSHIP_ADDRESS any different to forcing the delegation to the user address when calling with address(0)?

With your suggested change, If I had delegated to myself and then tried to delegate to address(0) then the transaction would also revert.

The sponsor has decided to map address(0) to the SPONSORSHIP_ADDRESS and there isn't anything invalid about that imo

stalinMacias commented 1 year ago

Because setting the current_delegate to the SPONSORSHIP_ADDRESS can be done not only by the new undelegation logic.

asselstine commented 1 year ago

Hmm the reproduction didn't work; this test failed as it should.

Need to dig in.

stalinMacias commented 1 year ago

@asselstine The test is expected to fail with the message "Reason: SameDelegateAlreadySet(0x0000000000000000000000000000000000000001)] "

djb15 commented 1 year ago

Ok let me phrase this another way. Why does delegating to address(0) have to be considered "undelegating"? Why can't it be considered as "sponsoring"? I agree with you it's more logical to sponsor back to yourself, but just because it doesn't I don't think that's a bug.

The worst case scenario is:

  1. A user has delegated to SPONSORSHIP_ADDRESS
  2. A user tries to delegate to address(0) to reset their delegation but the transaction reverts because delegating to address(0) is the same as delegating to SPONSORSHIP_ADDRESS
  3. The user then delegates to their address (which they could have done instead of step 2 anyway)

The end result is 1 reverted transaction with no funds lost, so surely QA at best?

I'm sure we could go round in circles with this anyway haha

asselstine commented 1 year ago

I believe I see the confusion: the implementation doesn't match the intent. It does work, however. It needs clarification!

I believe this is comment is the problem:

L646: // Treat address(0) as un-delegating

Because the comments also say:

L646: ... this lets
// them do so without knowing the sponsorship special value address.

The "this" is referring to address(0).

So in the same breath it's saying address(0) is used to un-delegate but is also used as an alias for the SPONSORSHIP_ADDRESS. So which is it?

I do like having address(0) interpreted as sponsorship. They are essentially burning their chance; this intuitive to me.

I also think removing the erroneous comment and adding some additional natspec on undelegating by delegating to yourself.

stalinMacias commented 1 year ago

okaay, so, looks like @asselstine has cleared all the doubts, so, I think you are right @djb15 , it could be a QA.

Thanks @asselstine for summarizing and explaining what was the intention of the address(0).

Picodes commented 1 year ago

Thanks for your explanations everyone. Will downgrade this to QA.

c4-judge commented 1 year ago

Picodes changed the severity to QA (Quality Assurance)

c4-judge commented 1 year ago

Picodes marked the issue as grade-b