Lucene search

K
code423n4Code4renaCODE423N4:2023-03-WENWIN-FINDINGS-ISSUES-387
HistoryMar 09, 2023 - 12:00 a.m.

Minimum referral requirement is incorrectly computed

2023-03-0900:00:00
Code4rena
github.com
5
referrer rewards
draw execution
tickets sold
rewards loss
vulnerability impact
mitigation step

Lines of code

Vulnerability details

As per the docs:

Referrer rewards are allocated on a per draw basis



Referrers who meet the minimum referral requirement according to the following table will be eligible for the Individual Referrer Allocation

The minimum referral requirement for a draw N + 1 is computed based on the number of tickets sold for the draw N.

In the code, this is computed upon draw execution of the draw N.
In receiveRandomNumber():

src/Lottery.sol
224:         winningTicket[drawFinalized] = _winningTicket;
225:         drawExecutionInProgress = false;
226: 
227:         uint256 ticketsSoldDuringDraw = nextTicketId - lastDrawFinalTicketId;
228:         lastDrawFinalTicketId = nextTicketId;
229:         // solhint-disable-next-line max-line-length
230:         referralDrawFinalize(drawFinalized, ticketsSoldDuringDraw);

The number used as β€œnumber of tickets sold for the draw N” is ticketsSoldDuringDraw. It is computed as nextTicketId - lastDrawFinalTicketId.

The issue is that nextTicketId is the id of the last Ticket minted. But tickets are minted for every draw, and ticketId is incremented on mint(), regardless of the drawId

src/Ticket.sol
23:     function mint(address to, uint128 drawId, uint120 combination) internal returns (uint256 ticketId) {
24:         ticketId = nextTicketId++; //@audit - not a function of drawId
25:         ticketsInfo[ticketId] = TicketInfo(drawId, combination, false);
26:         _mint(to, ticketId);
27:     }

This means ticketsSoldDuringDraw accounts for tickets sold during the draw N, notfor it.

Impact

The minimum referral requirement maths is completely broken, and will lead to a potential loss of rewards for referrers.

Proof of Concept

let us look at a draw N.
Before execution, lastDrawFinalTicketId == X.
Execution now happens.
Between the last draw execution and this one, there was a specifically high number of ticket sales, for draws ranging from N to M: assume 5,000 were sold for draw N, but 50,000 were sold in total.

ticketsSoldDuringDraw = nextTicketId - lastDrawFinalTicketId = 50,000

The minimum referral amount is computed:

src/ReferralSystem.sol
93:         minimumEligibleReferrals[drawFinalized + 1] =
94:             getMinimumEligibleReferralsFactorCalculation(ticketsSoldDuringDraw);

Because 50,000 tickets were sold, the minimum number of referrals for draw N + 1 will be 0,75% * 50,000 = 375, while it should have been 5,000 * 1% = 50.
Referrers for draw N + 1 will need to find 325 more referrers than what they should.

Some referrers will lose on the reward they were entitled to (if their referral amount is between 50 and 375).

Tools Used

Manual Analysis

Recommended Mitigation Steps

Use a mapping for nextTicketId in Ticket, so that it accounts for the drawId


The text was updated successfully, but these errors were encountered:

All reactions