Winning tickets can settle for less than their face value once the recipient’s reserve is exhausted
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 155
- Forks
- 50
- PR merge metrics
- No merged PRs in 30d
Description
### Summary
The function to redeem winning tickets ([redeemWinningTicket](https://github.com/livepeer/protocol/blob/e02052a04bde9ba1b3bb187d84209e4592e74cad/contracts/pm/mixins/MixinTicketBrokerCore.sol#L107-L174)) on the `TicketBroker` contract can settle for less than the ticket’s face value or even 0 payout while consuming the ticket.
While this might look unexpected, it’s actually clearly documented in [the specification](https://github.com/livepeer/wiki/blob/b1ea1b05e0959c1d0dbe2bcaa7e13b68850b949b/spec/streamflow/pm.md), so the Orchestrator’s client should always ensure sufficient Broadcaster’s deposits and reserves before taking on a job, as the protocol doesn’t enforce that.
### Root cause
The [redeemWinningTicket](https://github.com/livepeer/protocol/blob/2221f58a61ad9ddc23e696bc63223f9fbf1107f4/contracts/pm/mixins/MixinTicketBrokerCore.sol#L107-L169) function first ensures [that the broadcaster’s deposit and reserve are greater than the face value](https://github.com/livepeer/protocol/blob/e02052a04bde9ba1b3bb187d84209e4592e74cad/contracts/pm/mixins/MixinTicketBrokerCore.sol#L132), while using `remainingReserve` for the reserve calculation. However, later, the payout is calculated using a different function `claimableReserve` which is that recipient's own share of the reserve for the round. The ticket is marked as used before that payout is computed, and nothing checks that the amount paid actually matches the face value.
Since anyone can top up the sender's balances (as [fundDepositAndReserveFor](https://github.com/livepeer/protocol/blob/e02052a04bde9ba1b3bb187d84209e4592e74cad/contracts/pm/mixins/MixinTicketBrokerCore.sol#L87-L105) doesn’t restrict it), a third party can add just enough ETH to satisfy the check, and the ticket can be consumed for a “partial payout” or even zero payment. The recipient can also reach the same outcome by redeeming its own ticket while its claimable reserve is short or completely exhausted in that specific round.
### Why this is expected
1. The code follows the [specification](https://github.com/livepeer/wiki/blob/b1ea1b05e0959c1d0dbe2bcaa7e13b68850b949b/spec/streamflow/pm.md), which documents known edge cases under which a partial or full face value can be lost:
- Precondition A: Deposit short, reserve allocation covers part of the remainder. Outcome: Partial payment, ticket consumed.
- Precondition B: Deposit short; reserve allocation already spent this round for another ticket. Outcome: Partial payout from the deposit only; ticket consumed.
- Precondition C: Zero deposit, reserve allocation already spent this round. Outcome: Zero payment, ticket consumed.
2. The client that follows the same specification has to protect against possible attacks. The protocol doesn’t enforce that the Broadcaster holds enough ETH to cover all tickets that they sign.
### Out of scope for the bug bounty
Reports describing a winning ticket settling below its face value in `TicketBroker.redeemWinningTicket`, including variants driven by the ticket broadcaster, by a third-party deposit top-up, or by no attacker at all, will be deemed invalid and are not eligible for rewards under the Livepeer Immunefi bug bounty program, unless the report shows how the severity can be escalated beyond what is described above.
### References
- [0xa8bB618B1520E284046F3dFc448851A1Ff26e41B](https://arbiscan.io/address/0xa8bB618B1520E284046F3dFc448851A1Ff26e41B#code): `TicketBroker` proxy address on Arbitrum One.
- [pm.md](https://github.com/livepeer/wiki/blob/b1ea1b05e0959c1d0dbe2bcaa7e13b68850b949b/spec/streamflow/pm.md?plain=1#L157): “Probabilistic Micropayments” documentation page.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with redeemWinningTicket in contracts/pm/mixins/MixinTicketBrokerCore.sol and compare its remainingReserve and claimableReserve paths with the referenced pm.md specification. The report describes the behavior as documented and out of scope, so it does not identify a patch target or a concrete implementation-level definition of done.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- solidity
- Domain
- blockchain, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100