Lazy Accumulated Funding

v0.8.6 stops ordinary quote-driven flows from rolling the shared symbol/PartyB accumulated funding state. Per-quote funding is settled lazily from persisted state. That shared state changes only during explicit funding configuration or the automatic pause and rebase around symbol restatement.

Summary

The accumulated funding system keeps mutable funding state per (symbolId, partyB). It stores a weighted-average rate over completed history plus the rate currently in force. In earlier versions, position open, close, accumulated funding charge, liquidation sync, and migration initialization first rolled this shared state forward (re-folding the weighted average and advancing lastUpdatedEpoch) so that a simplified two-term formula could write the quote's accumulatedPaidFunding baseline.

The roll was never required for correctness. The persisted state fully determines the cumulative fee at any time, as described under Lazy evaluation model. The roll fed the two-term shortcut, changed shared state during ordinary quote operations, forced storage writes and logs onto those operations, and added one integer-division rounding per roll.

v0.8.6 removes that roll from ordinary quote processing. Quote-driven flows now compute the quote's baseline with the same lazy formula every read path already used, and do not mutate the symbol/PartyB state. The weighted average is re-folded during solver configuration and at symbol-restatement boundaries. Both emit the state event after their final storage writes. Between those explicit transitions the persisted funding state is byte-stable.

Lazy evaluation model

The persisted state stores the weighted-average rate over completed history (accumulatedRate, valid through lastUpdatedEpoch) and the rate in force since then (currentRate). The cumulative fee per unit of position at any epoch is fully determined without rolling storage:

Choose the long or short fields according to the quote's positionType. In the formula, snapshotFee means snapshotLongFee or snapshotShortFee, the cumulative per-unit fee preserved when a duration change rebases the epoch schedule. startEpoch is the first epoch of the current funding history, while lastUpdatedEpoch is the last epoch already represented by accumulatedRate. epochDuration is the configured number of seconds in one funding epoch.

currentEpoch  = floor(block.timestamp / epochDuration)
cumulativeFee = snapshotFee
              + accumulatedRate * (lastUpdatedEpoch - startEpoch)
              + currentRate     * (currentEpoch - lastUpdatedEpoch)

Funding charges, aggregate funding debt views, and the per-quote accumulatedPaidFunding baseline written at open, close, charge, liquidation, or migration all use this formula. Because the fee charged and the baseline written now come from the same expression, the amount a quote pays equals the change in its recorded baseline exactly, with no re-rounding of the weighted average in between.

What changed on-chain

  • LibQuoteFunding.updateAccumulatedPaidFunding no longer calls the accumulated-rate updater. It writes the quote's baseline using the three-term lazy formula above.
  • MigrationFacetImpl._initializeQuoteFunding initializes migrated quotes with the same lazy computation instead of rolling state.
  • There is no ordinary quote-flow fold-and-emit wrapper. The fold followed by emitAccumulatedFundingStateUpdated occurs inside solver configuration paths and when Core pauses or resumes funding for a symbol restatement.
  • Quote flows no longer pay for symbol-state storage writes or the state log. The cumulative funding formula is unchanged, but exact integer results can differ from v0.8.5 because the lazy path avoids the weighted-average division that each roll introduced.

State event

event AccumulatedFundingStateUpdated(
    uint256 indexed symbolId,
    address indexed partyB,
    int256 currentLongRate,
    int256 currentShortRate,
    int256 accumulatedLongRate,
    int256 accumulatedShortRate,
    uint256 lastUpdatedEpoch,
    uint256 lastUpdatedTimeStamp,
    uint256 startEpochTimeStamp,
    uint256 startEpoch,
    uint256 epochDuration,
    int256 snapshotLongFee,
    int256 snapshotShortFee
);

The payload mirrors the persisted FundingFee struct after the state transition completes. The contract emits it after final storage writes in these paths:

  • setEpochDurations
  • updateAccumulatedFundingFee
  • setFundingFee, setLongFundingFee, and setShortFundingFee
  • the funding pause, abort restore, and final rate rebase around symbol restatement