Livepeer Onchain Treasury Upgrade - Vagner's results

Decentralized video infrastructure protocol powering video in web3's leading social and media applications.

General Information

Platform: Code4rena

Start Date: 31/08/2023

Pot Size: $55,000 USDC

Total HM: 5

Participants: 30

Period: 6 days

Judge: hickuphh3

Total Solo HM: 2

Id: 282

League: ETH

Livepeer

Findings Distribution

Researcher Performance

Rank: 7/30

Findings: 1

Award: $2,232.82

🌟 Selected for report: 0

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: ladboy233

Also found by: Vagner

Labels

bug
2 (Med Risk)
satisfactory
sponsor acknowledged
sufficient quality report
duplicate-194

Awards

2232.8225 USDC - $2,232.82

External Links

Lines of code

https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L413-L418

Vulnerability details

Impact

Slashed transcoders can still become active transcorders by bonding an amount again to increase the total stake, which can inflate the actual delegatedAmount, giving those transcorders more power voting power than it should.

Proof of Concept

Every time someone bonds or unbonds tokens in the system, it will modify different storage values, by passing trough two important functions, increaseTotalStake and decreaseTotalStake. Each of these functions modify the delegatedAmount by adding or subtracting the specific amount a delegator or a transcoder bounded or unbounded from the protocol https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L1344 https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L1383 Delegated amount is an important variable that can be used later, after the checkpoints in BondingVotes.sol trough _checkpointBondingState function, by transcoders to weight their voting power in LivepeerGovernor.sol. The problem relies in the case of a slashed transcoder, since the delegated amount that he has is not updated in every case, which can mess up calculations in the long run. As you can see slashTranscoder can be called to slash a specific bonded amount of a transcoder and resign him from the transcoderPool https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L394-L408 amount which is calculated as a percentage of the total amount bonded and then subtracted from his total amount bonded https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L403 https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L411 In the next if statement , if there is still a bonded amount, then it will subtract the penalty also from his delegatedAmount https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L414-L417 and then burns/transfers the penalty and fees to the finder, if that is the case. The problem relies in the case where a transcoder gets all of his bonded amount slashed, because in that case the if statement here https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L414 will not pass since the delegator status will be unbounded https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L959-L961 which will not make any subtraction from the total delegatedAmount, leaving the slashed amount of the transcoder still in the delegatedAmount since it doesn't get subtracted anywhere else. Now even if the transcoder gets resign here https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L407 he can still bond any amount which will increaseTotalStake and will check if isRegisteredTranscoder https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L1318 which will be the case here since isRegisteredTranscoder checks for these 2 conditions https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L1158 which will both be true, since the delegateAddress is not getting deleted anywhere in the slashTranscoder or any other modifier, and bondedAmount will be greater than 0 since the bonding happens before increasing the total stake amount. Because of that the slashed transcoder will tryToJoinActiveSet and he can become a transcoder again. When the delegatedAmount gets updated after https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L1344 all of his slashed amount will still be existing into the variable, even though the amount got burn, and the new amount will be added, which will inflate his actual delegatedAmount, since his first share should not be there anymore because it got burn. That will checkpoint the transcoder with and inflated amount, which will give him more voting power in LivepeerGovernor.sol than he should have.

Tools Used

Manual review

Consider subtracting the amount burned from the delegatedAmount all the time, since any amount he bond/undbond will increase/decrease the delegatedAmount every time beside this special case where the actual value of delegatedAmount can get inflated.

Assessed type

Other

#0 - c4-pre-sort

2023-09-09T14:46:59Z

141345 marked the issue as sufficient quality report

#1 - victorges

2023-09-15T17:18:27Z

Even though we don't really use slashTranscoder today, the report is valid given the invalid state that the transcoder would be into. Not sure if we're gonna worry fixing it, but acknowledging as an issue to the least

#2 - c4-sponsor

2023-09-15T17:18:30Z

victorges (sponsor) acknowledged

#3 - HickupHH3

2023-09-18T10:40:20Z

i'm grouping this together with #194 because it is a case of not handling fully slashed transcoders correctly - and that is intended because slashing has been deprecated.

#4 - c4-judge

2023-09-18T10:40:50Z

HickupHH3 marked the issue as duplicate of #194

#5 - c4-judge

2023-09-18T10:41:12Z

HickupHH3 marked the issue as satisfactory

Findings Information

🌟 Selected for report: ladboy233

Also found by: Vagner

Labels

bug
2 (Med Risk)
disagree with severity
downgraded by judge
primary issue
satisfactory
selected for report
sponsor confirmed
sufficient quality report
M-02

Awards

2232.8225 USDC - $2,232.82

External Links

Lines of code

https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/treasury/GovernorCountingOverridable.sol#L181-L189

Vulnerability details

Impact

If a transcoder gets slashed fully he can still vote with 0 amount of weight making any other delegated user that wants to change his vote to subtract their weight amount from other delegators/transcoders.

Proof of Concept

In BondingManager.sol any transcoder can gets slashed by a specific percentage, and that specific transcoder gets resigned and that specific percentage gets deducted from his bondedAmount https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L394-L411 If any bondedAmount will remain then the penalty will also gets subtracted from the delegatedAmount, if not, nothing happens https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L412-L417 After that the penalty gets burned and the fees gets paid to the finder, if that is the case https://github.com/code-423n4/2023-08-livepeer/blob/a3d801fa4690119b6f96aeb5508e58d752bda5bc/contracts/bonding/BondingManager.sol#L420-L440 The problem relies in the fact that a fully slashed transcoder, even if it gets resigned, he is still seen as an active transcoder in the case of voting. Let's assume this case :

Tools Used

Manual review

If a transcoder gets fully slashed and resigned, delete his address from delegateAddress so he will not appear as a transcoder in the mechanism of counting the votes. If he still wants to participate in the system he can act as a delegator to another transcoder. Another solution would be to not let 0 weight votes happen anyways, since they don't modify the vote state at all.

Assessed type

Governance

#0 - c4-pre-sort

2023-09-09T14:49:13Z

141345 marked the issue as sufficient quality report

#1 - c4-sponsor

2023-09-14T20:44:51Z

victorges marked the issue as disagree with severity

#2 - victorges

2023-09-14T20:49:06Z

Slashing is turned off in the protocol, so this specific issue is not that important. It did point towards an issue in BondingVotes tho in the way that the getBondingState function checks if transcoder is a transcoder or not and defaults to 0 when bondedAmount is 0. There are other ways that this logic can be triggered (not through slashTranscoder) that can show inconsistencies as well, so I'm flagging this as valid for the underlying issue that it hints to, even though it does not describe a realistic scenario about how this issue could happen.

#3 - c4-sponsor

2023-09-14T20:49:11Z

victorges (sponsor) confirmed

#4 - HickupHH3

2023-09-18T04:20:23Z

I found the POC hard to follow. A coded instance would have tremendously helped, but what I gather is:

  • fully slashed transcoder that votes will do so with 0 weight
  • any delegator that delegated their votes to this specific transcoder intending to change their vote may face an issue with sub overflow because _handleVoteOverrides() uses the slashed transcoder's bondedAmount instead of the 0 weight. so this issue seems valid, although its premise is that the slashing functionality is in place.

The README did not indicate that slashing was turned off, though I see it's mentioned a couple of times in the Discord channel. Should've been added in the "Known caveats / limitations" section.

Hence, IMO it isn't entirely fair to wardens if I invalidate their issue because they weren't aware of this fact.

Leaning towards a downgrade to M because of the external requirement of the active slashing mechanism, but am open to discussion.

#5 - c4-judge

2023-09-18T10:38:52Z

HickupHH3 changed the severity to 2 (Med Risk)

#6 - c4-judge

2023-09-18T10:40:38Z

HickupHH3 marked the issue as primary issue

#7 - c4-judge

2023-09-18T10:41:00Z

HickupHH3 marked the issue as selected for report

#8 - c4-judge

2023-09-18T10:41:04Z

HickupHH3 marked the issue as satisfactory

#9 - HickupHH3

2023-09-24T09:30:14Z

Revisiting this issue, IMO it's unreasonable to expect the sponsor to explicitly list out all the gotchas / limitations of the protocol. Some will only pop into mind through discussions with wardens, which seemed to be the case here when the slashing deprecation was mentioned a couple of times in the Discord channel.

On the flip side, the fact remains that the slashing mechanism is part of the audit scope, and was counted towards the sLOC, plus the points I raised above. There wasn't an indication in the code that slashing had been deprecated.

It's a difficult decision here, considering the different parties' POV. There could be some wardens that didn't submit issues related to slashing because of what was brought up in the Discord channel, and those that did (as one can see referenced above) because they didn't monitor the channel.

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax Β© 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter