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 globalupnlValidTime.
// 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
enabledfield is needed because it is exactly equivalent toupnlValidTime != 0.
Read the effective validity
function getMuonFunctionUpnlValidTime(
MuonFunction func
) external view returns (uint256 upnlValidTime, bool isOverridden);
Return semantics:
- If an override is set,
upnlValidTimeis the override andisOverridden == true. -
If no override is set,
upnlValidTimeis the global value fromgetMuonConfig()andisOverridden == 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.