Skip to main content

Settlement

Normal Operation

Due to the settlement of game tickets being non-linear, the protocol must have a way to track liabilities against LP positions across all stages of the tickets lifecycle. This is handled within the SettlementManager contract - deployed as a component of the SLPHub.

Settlement is tracked on a per-pair basis, with each created ticket being assigned an incremental nonce. The contract tracks settled tickets through sets of contiguous ranges which only allows unlocking of funds once a ticket can be traced back to the origin range.

Below is a worked example of how non-linear settlement is handled.

note

We have simplified hedge values for clarity; in production, the hedge is calculated to maintain a stable price on the pair.

Ticket #1

Settlement 1

A user win pulls stake and winnings from the pair. Only the hedge returns to the pool (denominated in token1). With no adjacent tickets, we create() a new range. Funds move back into reserves (via pending instant unlock) because they are part of the canonical range.

Ticket #2

Settlement 2

The ticket settles as a loss; the user loses their 50 token1 stake. We insert the stake and ringfenced funds (500) because they are returning to the pool. We also insert the hedge, which returns to the pool (token0). Funds move back into reserves (via pending instant unlock) because they are part of the canonical range.

With two adjacent tickets, we extend() the range and sum the values.

Settlement 3

warning

Tickets can be settled non-linearly (due to asynchronous VRF calls). In this example ticket #3 will fulfill at a later stage.

Ticket #4

Settlement 4

With no adjacent ranges, we create() a new one. Funds move to pending because the ticket is not part of the canonical range.

Ticket #5

Settlement 5

Ticket #4 and #5 can extend() because they are adjacent. Funds remain pending because this range is not part of the canonical range.

Ticket #3

Settlement 6

Funds move to pending before ticket insertion occurs. The delayed Ticket #3 is fulfilled and inserted into the settlement chain.

With adjacent ranges on both sides, we call merge(). This creates a contiguous range back to #1. Funds from tickets #3 to #5 are unlocked from the pending pot and returned to reserves.

Settlement 7

Withdrawal Blocks

A special case exists within settlement when an LP withdrawal is initiated with pending tickets. As we need to compute the real liability of a position, we must "block" the joining of a range until we can calculate this value correctly.

Withdrawal Started

Consider the setup below. We have a pending ticket (#3) when an LP wishes to withdraw. The funds from finalised tickets (#1 - #2) have been returned to the reserves, while the funds for #3 and #4 are outstanding.

Settlement 8

When an LP calls startBurn(), we mark ticket #2 as last settled (LS) and snapshot the cumulative values. Ticket #4 is marked as current (C), which adds a special property: it cannot be joined on its right side.

Ticket #5

Settlement 9

If ticket #5 is inserted at this stage, it is treated as a new range and cannot extend() to the left of ticket #4 due to the block.

Ticket #6

Settlement 10

Ticket #5 can, however, extend() on its right side when ticket #6 is inserted.

Ticket #3

Settlement 11

When ticket #3 is finally inserted, the typical merge() function will be called and settle up to ticket #4. This operation sets the last settled to be equal to withdrawal block and can now be safely settled.

info

Nonces marked with a withdrawal block can be left-joined.

Withdrawal Finalise

When a withdrawal is finalised, we snapshot the current settled value up to the withdrawal block and then subtract the values captured when the withdrawal was started. This gives us the outstanding liability for the withdrawal range so that LPs can be credited correctly for this period.

Token0Token1
Cumulative when started500650
Cumulative when finalised19301950
Liability for withdraw period14301300

warning

These values apply to all active LPs in the given pair. The values used within withdrawals are normalised to "per LP token" values on-chain.

Removal of Withdrawal Block

As part of the withdrawal process, and also callable manually once snapshotting is complete, the join() function is used to remove the withdrawal block and join the pending range (#5 - #6) so it can be fully settled.

Settlement 12

Calculating LP Liability

Consider the same set of independent tickets from the previous example without any range joining. The liability values are the total delta from a particular ticket and are applied to the entire pool.

Settlement 13

Due to the unpredictable nature of users minting and burning LP in a given pair, these total values would be difficult to apply retroactively unless we knew the supply at that point in time, which we do not know on-chain. To remedy this, we normalise the value based on the LP supply at ticket creation and any additional mints while the ticket was being processed.

info

Adding liquidity will expose positions to currently pending tickets.


LP Tokens
Total Supply when #2 Created:100
Cumulative Mints when #2 Created:2180
Cumulative Mints when #2 Inserted:2200

With these stored values, we calculate the liability as follows:

ΔTicket FundsLP supply at creation+ΔCumulative Mint\frac{\Delta \text{Ticket Funds}}{\text{LP supply at creation} + \Delta \text{Cumulative Mint}}
Ticket#2(0)=500100+(22002180)=4.1666Ticket \#2 (0) = \frac{500}{\text{100} + (2200 - 2180)} = 4.1666

With the resolved liabilities per LP token:

Settlement 14