Quantillon Protocol

ISlippageStorage

Git Source

Title: ISlippageStorage

Author: Quantillon Labs - Nicolas Bellengé - @chewbaccoin

Interface for the Quantillon SlippageStorage contract

Stores on-chain slippage data published by an off-chain service. Provides rate-limited writes via WRITER_ROLE and config management via MANAGER_ROLE.

Note: security-contact: team@quantillon.money

Functions

initialize

Initialize the SlippageStorage contract

Sets up roles, rate-limit parameters, and treasury. Admin receives DEFAULT_ADMIN_ROLE, MANAGER_ROLE, EMERGENCY_ROLE, and UPGRADER_ROLE. Writer receives WRITER_ROLE. Callable only once via proxy deployment.

Notes:

  • security: Validates admin, writer, and treasury are non-zero; enforces config bounds

  • validation: Validates admin/writer/treasury != address(0); interval and threshold within max

  • state-changes: Grants roles, sets minUpdateInterval, deviationThresholdBps, treasury

  • events: No events emitted

  • errors: Reverts with ZeroAddress if admin/writer/treasury is zero; reverts with ConfigValueTooHigh if interval or threshold exceeds max

  • reentrancy: Protected by initializer modifier (callable only once)

  • access: Public - only callable once during proxy deployment

  • oracle: No oracle dependencies

function initialize(
    address admin,
    address writer,
    uint48 minInterval,
    uint16 deviationThreshold,
    address treasury,
    uint8 initialEnabledSources
) external;

Parameters

NameTypeDescription
adminaddressAddress receiving DEFAULT_ADMIN_ROLE and all management roles
writeraddressAddress receiving WRITER_ROLE (the off-chain publisher service wallet)
minIntervaluint48Minimum seconds between successive writes (0..MAX_UPDATE_INTERVAL)
deviationThresholduint16Deviation in bps that bypasses rate limit (0..MAX_DEVIATION_THRESHOLD)
treasuryaddressTreasury address for token/ETH recovery
initialEnabledSourcesuint8Bitmask of initially enabled sources (0x01=Lighter, 0x02=Hyperliquid, 0x03=both)

updateSlippage

Publish a new slippage snapshot on-chain

Rate-limited: if within minUpdateInterval since last update, only allows the write when |newWorstCaseBps - lastWorstCaseBps| > deviationThresholdBps. First update always succeeds (timestamp == 0 means no prior data).

Notes:

  • security: Requires WRITER_ROLE; blocked when paused; rate-limited by minUpdateInterval

  • validation: Checks elapsed time since last update; validates deviation if within interval

  • state-changes: Overwrites _snapshot with new values, timestamp, and block number

  • events: Emits SlippageUpdated(midPrice, worstCaseBps, spreadBps, depthEur, timestamp)

  • errors: Reverts with RateLimitTooHigh if within interval and deviation is below threshold

  • reentrancy: Not protected - no external calls made during execution

  • access: Restricted to WRITER_ROLE; blocked when contract is paused

  • oracle: No on-chain oracle dependency; data is pushed by the off-chain Slippage Monitor

function updateSlippage(
    uint128 midPrice,
    uint128 depthEur,
    uint16 worstCaseBps,
    uint16 spreadBps,
    uint16[5] calldata bucketBps
) external;

Parameters

NameTypeDescription
midPriceuint128EUR/USD mid price (18 decimals)
depthEuruint128Total ask depth in EUR (18 decimals)
worstCaseBpsuint16Worst-case slippage across buckets (bps)
spreadBpsuint16Bid-ask spread (bps)
bucketBpsuint16[5]Per-size slippage in bps, fixed order: [10k, 50k, 100k, 250k, 1M]

updateSlippageBatch

Publish slippage snapshots for multiple sources in a single transaction

Sources disabled in enabledSources bitmask are silently skipped (not reverted). Rate-limited per source: within-interval updates are skipped unless deviation > threshold. Lighter source (sourceId=0) writes to the legacy _snapshot slot for backward compat.

Notes:

  • security: Requires WRITER_ROLE; blocked when paused

  • validation: Per-source rate limit: skips (does not revert) if within interval and deviation <= threshold

  • state-changes: Writes each enabled source's snapshot; Lighter updates _snapshot for backward compat

  • events: Emits SlippageSourceUpdated for each source actually written

  • errors: No explicit reverts for rate-limited sources (silently skipped)

  • reentrancy: Not protected - no external calls made during execution

  • oracle: No on-chain oracle dependency; data is pushed by the off-chain Slippage Monitor

  • access: Restricted to WRITER_ROLE; blocked when contract is paused

function updateSlippageBatch(SourceUpdate[] calldata updates) external;

Parameters

NameTypeDescription
updatesSourceUpdate[]Array of per-source snapshot inputs

setMinUpdateInterval

Update the minimum interval between successive slippage writes

Setting to 0 disables the rate limit; MAX_UPDATE_INTERVAL caps at 1 hour.

Notes:

  • security: Requires MANAGER_ROLE; enforces upper bound MAX_UPDATE_INTERVAL

  • validation: Validates newInterval <= MAX_UPDATE_INTERVAL

  • state-changes: Updates minUpdateInterval state variable

  • events: Emits ConfigUpdated("minUpdateInterval", oldValue, newValue)

  • errors: Reverts with ConfigValueTooHigh if newInterval > MAX_UPDATE_INTERVAL

  • reentrancy: Not protected - no external calls made

  • access: Restricted to MANAGER_ROLE

  • oracle: No oracle dependencies

function setMinUpdateInterval(uint48 newInterval) external;

Parameters

NameTypeDescription
newIntervaluint48New minimum interval in seconds (0..MAX_UPDATE_INTERVAL)

setDeviationThreshold

Update the worst-case bps deviation threshold that bypasses the rate limit

When |newWorstCaseBps - lastWorstCaseBps| > threshold, rate limit is bypassed.

Notes:

  • security: Requires MANAGER_ROLE; enforces upper bound MAX_DEVIATION_THRESHOLD (500 bps)

  • validation: Validates newThreshold <= MAX_DEVIATION_THRESHOLD

  • state-changes: Updates deviationThresholdBps state variable

  • events: Emits ConfigUpdated("deviationThresholdBps", oldValue, newValue)

  • errors: Reverts with ConfigValueTooHigh if newThreshold > MAX_DEVIATION_THRESHOLD

  • reentrancy: Not protected - no external calls made

  • access: Restricted to MANAGER_ROLE

  • oracle: No oracle dependencies

function setDeviationThreshold(uint16 newThreshold) external;

Parameters

NameTypeDescription
newThresholduint16New deviation threshold in bps (0..MAX_DEVIATION_THRESHOLD)

pause

Pause the contract, blocking all slippage updates

Once paused, updateSlippage reverts until unpaused.

Notes:

  • security: Requires EMERGENCY_ROLE; prevents unauthorized pausing

  • validation: No input validation required

  • state-changes: Sets OpenZeppelin Pausable internal paused flag to true

  • events: Emits Paused(account) from OpenZeppelin PausableUpgradeable

  • errors: No errors thrown

  • reentrancy: Not protected - no external calls made

  • access: Restricted to EMERGENCY_ROLE

  • oracle: No oracle dependencies

function pause() external;

unpause

Unpause the contract, resuming slippage updates

Restores normal operation; WRITER_ROLE can immediately publish again.

Notes:

  • security: Requires EMERGENCY_ROLE; prevents unauthorized unpausing

  • validation: No input validation required

  • state-changes: Sets OpenZeppelin Pausable internal paused flag to false

  • events: Emits Unpaused(account) from OpenZeppelin PausableUpgradeable

  • errors: No errors thrown

  • reentrancy: Not protected - no external calls made

  • access: Restricted to EMERGENCY_ROLE

  • oracle: No oracle dependencies

function unpause() external;

getSlippage

Get the full current slippage snapshot

Returns a zero-valued struct if updateSlippage has never been called.

Notes:

  • security: No security concerns - read-only view function

  • validation: No input validation required

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown

  • reentrancy: Not applicable - view function

  • access: Public - no restrictions

  • oracle: No oracle dependencies - reads stored state only

function getSlippage() external view returns (SlippageSnapshot memory snapshot);

Returns

NameTypeDescription
snapshotSlippageSnapshotThe latest SlippageSnapshot stored on-chain

getBucketBps

Get per-bucket slippage in bps in canonical size order

Returns buckets in fixed order: [10k EUR, 50k EUR, 100k EUR, 250k EUR, 1M EUR]. All values are zero if updateSlippage has never been called.

Notes:

  • security: No security concerns - read-only view function

  • validation: No input validation required

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown

  • reentrancy: Not applicable - view function

  • access: Public - no restrictions

  • oracle: No oracle dependencies - reads stored state only

function getBucketBps() external view returns (uint16[5] memory bucketBps);

Returns

NameTypeDescription
bucketBpsuint16[5]Array of 5 slippage values in bps for each order size bucket

getSlippageAge

Get seconds elapsed since the last on-chain slippage update

Returns 0 if no update has ever been published (timestamp == 0).

Notes:

  • security: No security concerns - read-only view function

  • validation: No input validation required

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown

  • reentrancy: Not applicable - view function

  • access: Public - no restrictions

  • oracle: No oracle dependencies - reads stored timestamp only

function getSlippageAge() external view returns (uint256 age);

Returns

NameTypeDescription
ageuint256Seconds since last updateSlippage call, or 0 if never updated

getSlippageBySource

Get the full slippage snapshot for a specific source

sourceId=0 (SOURCE_LIGHTER) reads from the legacy _snapshot slot. Other sourceIds read from _sourceSnapshots mapping. Returns a zero-valued struct if no data has been published for that source.

Notes:

  • security: No security concerns - read-only view function

  • validation: No input validation required

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown

  • reentrancy: Not applicable - view function

  • oracle: No oracle dependencies - reads stored state only

  • access: Public - no restrictions

function getSlippageBySource(uint8 sourceId) external view returns (SlippageSnapshot memory snapshot);

Parameters

NameTypeDescription
sourceIduint8Source identifier (SOURCE_LIGHTER=0, SOURCE_HYPERLIQUID=1)

Returns

NameTypeDescription
snapshotSlippageSnapshotThe latest SlippageSnapshot for the given source

getSlippageAgeBySource

Get seconds elapsed since the last on-chain update for a specific source

Returns 0 if no update has ever been published for the source (timestamp == 0).

Notes:

  • security: No security concerns - read-only view function

  • validation: No input validation required

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown

  • reentrancy: Not applicable - view function

  • oracle: No oracle dependencies - reads stored timestamp only

  • access: Public - no restrictions

function getSlippageAgeBySource(uint8 sourceId) external view returns (uint256 age);

Parameters

NameTypeDescription
sourceIduint8Source identifier (SOURCE_LIGHTER=0, SOURCE_HYPERLIQUID=1)

Returns

NameTypeDescription
ageuint256Seconds since last update for that source, or 0 if never updated

enabledSources

Get the bitmask of enabled sources (bit N = source N enabled)

Bit 0 = SOURCE_LIGHTER, Bit 1 = SOURCE_HYPERLIQUID. 0x03 = both enabled.

Notes:

  • security: No security concerns - read-only view function

  • validation: No input validation required

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown

  • reentrancy: Not applicable - view function

  • oracle: No oracle dependencies

  • access: Public - no restrictions

function enabledSources() external view returns (uint8 mask);

Returns

NameTypeDescription
maskuint8Current enabled sources bitmask

setEnabledSources

Update which sources are enabled for storage in updateSlippageBatch

Bit 0 = SOURCE_LIGHTER, Bit 1 = SOURCE_HYPERLIQUID. 0x03 = both enabled. Disabled sources are silently skipped in batch writes without reverting.

Notes:

  • security: Requires MANAGER_ROLE

  • validation: No additional validation; all uint8 values accepted

  • state-changes: Updates enabledSources state variable

  • events: Emits EnabledSourcesUpdated(oldMask, newMask)

  • errors: No errors thrown

  • reentrancy: Not protected - no external calls made

  • oracle: No oracle dependencies

  • access: Restricted to MANAGER_ROLE

function setEnabledSources(uint8 mask) external;

Parameters

NameTypeDescription
maskuint8New bitmask (0x01=Lighter only, 0x02=Hyperliquid only, 0x03=both)

minUpdateInterval

Get the current minimum update interval

Rate limit applied to consecutive updateSlippage calls.

Notes:

  • security: No security concerns - read-only view function

  • validation: No input validation required

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown

  • reentrancy: Not applicable - view function

  • access: Public - no restrictions

  • oracle: No oracle dependencies

function minUpdateInterval() external view returns (uint48 interval);

Returns

NameTypeDescription
intervaluint48Minimum seconds required between successive writes

deviationThresholdBps

Get the current deviation threshold that bypasses the rate limit

When |newWorstCaseBps - lastWorstCaseBps| exceeds this, rate limit is bypassed.

Notes:

  • security: No security concerns - read-only view function

  • validation: No input validation required

  • state-changes: No state changes - view function

  • events: No events emitted

  • errors: No errors thrown

  • reentrancy: Not applicable - view function

  • access: Public - no restrictions

  • oracle: No oracle dependencies

function deviationThresholdBps() external view returns (uint16 threshold);

Returns

NameTypeDescription
thresholduint16Current deviation threshold in bps

recoverToken

Recover ERC20 tokens accidentally sent to this contract

Transfers the specified amount to the treasury address.

Notes:

  • security: Requires DEFAULT_ADMIN_ROLE; prevents unauthorized token withdrawals

  • validation: Implicitly validated via SafeERC20 transfer

  • state-changes: No internal state changes; transfers token balance externally

  • events: No events emitted from this contract

  • errors: Reverts if ERC20 transfer fails (SafeERC20 revert)

  • reentrancy: Not protected - external ERC20 call; admin-only mitigates risk

  • access: Restricted to DEFAULT_ADMIN_ROLE

  • oracle: No oracle dependencies

function recoverToken(address token, uint256 amount) external;

Parameters

NameTypeDescription
tokenaddressERC20 token contract address to recover
amountuint256Amount of tokens to transfer to treasury (token decimals)

recoverETH

Recover ETH accidentally sent to this contract

Transfers the entire ETH balance to the treasury address.

Notes:

  • security: Requires DEFAULT_ADMIN_ROLE; prevents unauthorized ETH withdrawals

  • validation: No input validation required; uses address(this).balance

  • state-changes: No internal state changes; transfers ETH balance externally

  • events: Emits ETHRecovered(treasury, amount)

  • errors: Reverts if ETH transfer fails

  • reentrancy: Not protected - external ETH transfer; admin-only mitigates risk

  • access: Restricted to DEFAULT_ADMIN_ROLE

  • oracle: No oracle dependencies

function recoverETH() external;

Events

SlippageUpdated

Emitted when slippage data is updated on-chain (Lighter legacy single-source path)

event SlippageUpdated(uint128 midPrice, uint16 worstCaseBps, uint16 spreadBps, uint128 depthEur, uint48 timestamp);

SlippageSourceUpdated

Emitted once per source written in updateSlippageBatch

event SlippageSourceUpdated(
    uint8 indexed sourceId,
    uint128 midPrice,
    uint16 worstCaseBps,
    uint16 spreadBps,
    uint128 depthEur,
    uint48 timestamp
);

EnabledSourcesUpdated

Emitted when the enabledSources bitmask is changed by MANAGER_ROLE

event EnabledSourcesUpdated(uint8 oldMask, uint8 newMask);

ConfigUpdated

Emitted when a config parameter is changed

event ConfigUpdated(string indexed param, uint256 oldValue, uint256 newValue);

TreasuryUpdated

Emitted when treasury address is updated

event TreasuryUpdated(address indexed newTreasury);

ETHRecovered

Emitted when ETH is recovered from the contract

event ETHRecovered(address indexed to, uint256 amount);

Structs

SourceUpdate

Input for one source in a batch update

struct SourceUpdate {
    uint8 sourceId; // SOURCE_LIGHTER=0, SOURCE_HYPERLIQUID=1
    uint128 midPrice; // EUR/USD mid price (18 decimals)
    uint128 depthEur; // Total ask depth in EUR (18 decimals)
    uint16 worstCaseBps;
    uint16 spreadBps;
    uint16[5] bucketBps; // [10k, 50k, 100k, 250k, 1M]
}

SlippageSnapshot

Packed on-chain slippage snapshot (2 storage slots)

Storage layout (must not be reordered — UUPS upgrade-safe): Slot 0 (32 bytes): midPrice (uint128) + depthEur (uint128) Slot 1 (26/32 bytes): worstCaseBps (2) + spreadBps (2) + timestamp (6) + blockNumber (6) + bps10k (2) + bps50k (2) + bps100k (2) + bps250k (2) + bps1M (2) Individual uint16 fields are used instead of uint16[5] because Solidity arrays always start a new storage slot, which would waste a full slot.

struct SlippageSnapshot {
    uint128 midPrice; // EUR/USD mid price (18 decimals)
    uint128 depthEur; // Total ask depth in EUR (18 decimals)
    uint16 worstCaseBps; // Worst-case slippage across buckets (bps)
    uint16 spreadBps; // Bid-ask spread (bps)
    uint48 timestamp; // Block timestamp of update
    uint48 blockNumber; // Block number of update
    uint16 bps10k; // Slippage bps for 10k EUR bucket
    uint16 bps50k; // Slippage bps for 50k EUR bucket
    uint16 bps100k; // Slippage bps for 100k EUR bucket
    uint16 bps250k; // Slippage bps for 250k EUR bucket
    uint16 bps1M; // Slippage bps for 1M EUR bucket
}