Muon UPNL Validity Overrides

Operators can now set different UPNL freshness windows for different Muon operation types instead of forcing every action to share one global timeout.

Summary

Before v0.8.6, every UPNL-based Muon signature used the same global validity window:

setMuonConfig(upnlValidTime, priceValidTime)

That made short-window account operations and longer-window liquidation operations share one value, even though their operational needs are different. For example, deallocation can reasonably require a very fresh signature such as 10 seconds, while PartyA liquidation may need a larger submission window such as 120 seconds.

Core UPNL categories can now override the global UPNL validity window. A MuonFunction is also the operation category that selects the authorized Muon key and gateway signer. The available categories are:

enum MuonFunction {
    Trading,
    AccountManagement,
    Settlement,
    ForceClose,
    Funding,
    LiquidationPartyA,
    LiquidationPartyB,
    RemoveMargin,
    ExpressCredit
}

ExpressCredit is authorization-only here. Express credit validates freshness with the Express Provider's muonFreshnessWindow, so it cannot be configured through the Core UPNL-override setting.

Before setting an override, match the affected method to its category in MuonFunction categories. Some methods use the category only for signer authorization and enforce a different lifecycle-based timestamp rule, so the method table also identifies where this freshness override actually applies.

setMuonFunctionUpnlValidTime(func, upnlValidTime)

A nonzero value sets the category-specific window. Passing 0 stores the unset sentinel, so that category inherits the global upnlValidTime again.

There is no separate clear method and no separate hasOverride storage flag. In the per-function mapping, 0 is the unset value.


API changes

Set or clear a per-function override

function setMuonFunctionUpnlValidTime(
    MuonFunction func,
    uint256 upnlValidTime
) external;

Access control:

  • Caller must have MUON_SETTER_ROLE.

Behavior:

  • The supplied value is written directly to MuonStorage.upnlValidTimeByFunction[func].
  • If upnlValidTime > 0, that value is the category-specific override.
  • If upnlValidTime == 0, zero means unset and the category inherits the global upnlValidTime.
// Zero is the unset sentinel; the resolver then uses the global value.
upnlValidTimeByFunction[func] = upnlValidTime;

The setter emits:

event SetMuonFunctionUpnlValidTime(
    MuonFunction indexed func,
    uint256 upnlValidTime
);

Event semantics:

  • upnlValidTime > 0: a category-specific override was set.
  • upnlValidTime == 0: the override is unset and the global value applies.
  • No separate enabled field is needed because it is exactly equivalent to upnlValidTime != 0.

Read the effective validity

function getMuonFunctionUpnlValidTime(
    MuonFunction func
) external view returns (uint256 upnlValidTime, bool isOverridden);

Return semantics:

  • If an override is set, upnlValidTime is the override and isOverridden == true.
  • If no override is set, upnlValidTime is the global value from getMuonConfig() and isOverridden == false.

This view returns the effective value used by verification, not the raw mapping slot.


Storage model

The new storage field is:

mapping(MuonFunction => uint256) upnlValidTimeByFunction;

The mapping is optional configuration. It does not replace the global upnlValidTime.

The invariant is:

effectiveUpnlValidTime(func) =
  upnlValidTimeByFunction[func] == 0
    ? global upnlValidTime
    : upnlValidTimeByFunction[func]

0 means "unset" for per-function overrides. Do not use 0 as an intentional validity window. Operators should keep the global upnlValidTime configured to a nonzero value.


MuonFunction categories

The categories originate from the MuonFunction enum shown above. Core validity-window storage remains enum-keyed, while the signature verifier receives the corresponding numeric ID through its forward-compatible uint8 boundary. This table maps the current IDs to the affected methods.

The intended method grouping is:

Category Methods / flows
Trading sendQuote, lockQuote, openPosition, fillCloseRequest, fillCloseRequestToLiquidation, emergencyClosePosition, openPositions, fillCloseRequests
AccountManagement deallocate, deallocateForPartyB, transfer-allocation flows that verify account UPNL
RemoveMargin safeDeallocate and safeDeallocateForPartyB, the paths carrying the Muon-attested scaledLockedBalance. Configure a tight window here. Margin removal is the one flow where a stale signature is worth money. See Strict Deallocation.
Settlement settleUpnl, settleUpnlUnified, isolated and cross-PartyB settlement signatures
ForceClose forceClosePosition, deprecated settleAndForceClosePosition, initializeForceClose, settleUpnlForForceClose, finalizeForceClose, and forceCloseAndSettlePositionsUnified
Funding chargeFundingRate, chargeAccumulatedFundingFee
LiquidationPartyA liquidatePartyA, setSymbolsPrice, deferredLiquidatePartyA, deferredSetSymbolsPrice, liquidatePartyAWithSnapshot, setSymbolsPriceWithSnapshot, singleStepLiquidatePartyAWithSnapshot. Only the legacy liquidatePartyA freshness check consults this UPNL-validity override; the other calls use the category for signer authorization and apply their own historical or liquidation-lifecycle timestamp rules.
LiquidationPartyB liquidatePartyB consults this freshness override. liquidatePositionsPartyB uses the same category for signer authorization, but bounds its price timestamp against the stored liquidation start and liquidationTimeout instead.
ExpressCredit Express withdrawal reserveDebt during initial acceptance and acceleration. This category authorizes the Muon key and gateway only; freshness uses the Express Provider's muonFreshnessWindow.

This table maps operation-level authorization. The enum controls which Muon public keys and gateway signers may sign each category. It controls this configurable freshness window only for the calls identified above that compare a signature timestamp with block.timestamp through the UPNL-validity resolver.

The category is passed to the signature verifier for key and gateway authorization on all grouped calls, but the per-function validity window only applies where Core checks signature freshness against block.timestamp through its UPNL resolver. ExpressCredit deliberately does not use that resolver.


Effective validity resolution

Every UPNL freshness check now resolves the window for its operation category.

v0.8.6 routes those checks through:

LibMuon.verifyUpnlTimestamp(timestamp, func)

That helper resolves the effective validity with:

LibMuon.getUpnlValidTime(func)

LibMuon.getUpnlValidTime(func) first checks upnlValidTimeByFunction[func]. If the mapping value is zero, it falls back to the global MuonStorage.upnlValidTime.

The change was applied across the Muon helper libraries that validate UPNL-bearing signatures:

LibMuonAccount
LibMuonPartyA
LibMuonPartyB
LibMuonPartyBBatchActions
LibMuonFundingRate
LibMuonForceActions
LibMuonSettlement
LibMuonUnifiedSettlement
LibMuon

The legacy liquidatePartyA start path also uses LibMuon.getUpnlValidTime(MuonFunction.LiquidationPartyA) for its explicit signature freshness check, so that check no longer reads the global upnlValidTime directly.