Contracts
The contracts are in contracts/src/. They use Solidity 0.8.28 with the optimizer at 200 runs, IR compilation, and the cancun EVM version.
Test contracts
TestUSDC has a public mint function. TestOracle accepts prices from one test operator. Market.testOracle() always returns true. These contracts are not suitable for real funds. No external security audit is complete.
| Contract | File | Purpose |
|---|---|---|
Market | Market.sol | Book, ladders, collateral, funding, margin, and liquidation for one asset |
QueuedMarket | QueuedMarket.sol | Market plus an on-chain order queue and combined quote calls |
SessionRegistry | SessionRegistry.sol | Session permissions for each account, session key, and market |
TestOracle | TestOracle.sol | Test price records. Its owner is the oracle signer. |
TestUSDC | TestUSDC.sol | Test collateral token with 6 decimals and the symbol tUSDC |
VarianceMath | VarianceMath.sol | Fixed-point logarithm and squared return |
Build and test
forge build --root contracts
forge test --root contracts -vv
forge fmt --root contracts --checkThe test files are Market.t.sol, MarketBook.t.sol, MarketLadder.t.sol, MarketGas.t.sol, and QueuedMarket.t.sol in contracts/test/.
Units
Prices, quantities, cash, fees, and margin have 18 decimals. deposit and withdraw take raw token amounts with 6 decimals. One token unit becomes 1e12 accounting units. All token operations use the account of the caller. A session cannot name a different withdrawal account.
Constants of Market
| Constant | Value | Meaning |
|---|---|---|
MULTIPLIER | 100 | USDC for each contract and each unit of annualized variance |
HORIZON_SECONDS | 7 days | Funding horizon |
YEAR_SECONDS | 365 days | Year basis |
RETURN_CAP | 0.0025 | Largest paid squared log return for each accepted observation |
POSITION_LIMIT | 1,000 contracts | For each account and reserved direction |
MAX_QUOTE | 100 | Largest order price and quote center |
MAX_ORDERS | 128 | Largest number of real resting orders |
MAX_MATCHES | 32 | Largest number of match attempts for one order |
MAX_ORDER_AGE | 3,600 seconds | Largest order life and quote life |
MAX_ORACLE_DELAY | 5 seconds | Largest delay between the observation timestamp and the block time |
MAX_LADDERS | 4 | Ladder slots in one market |
MAX_LEVELS | 32 | Levels of one ladder |
SIZE_UNIT | 0.01 contracts | Unit of a ladder size |
MAX_DEPTH | 384 | Largest depth argument of getBook |
Values that a market gets at deployment:
| Value | Rule |
|---|---|
observationSeconds | Constructor input, from 1 to 600 seconds |
maxGap | 3 * observationSeconds |
maxMarkAge | 2 * observationSeconds |
quoter | Constructor input. Only this address can call quote. |
oracleSigner | The owner of the TestOracle at deployment |
Other rules:
| Rule | Value |
|---|---|
| Trade fee | 0.05% of size * 100 * execution price for each side |
| Initial mark reserve | 100 * (0.25 + 0.5 * mark) for each contract |
| Maintenance mark reserve | 60% of the initial reserve |
| Funding buffers, initial and maintenance | 3 and 1 observations |
| Liquidation price range | 25% from the current valid mark |
| Mark depth | 2 contracts for each side, from two or more owners, with a spread of at most 20% of the midpoint |
See Margin and liquidation and Order book and mark for the rules in context.
Functions of Market
Collateral
| Function | Caller | Notes |
|---|---|---|
deposit(amount) | Account | Raw token amount with 6 decimals. The account must approve the market first. |
withdraw(amount) | Account | Needs a valid mark, no due observation, no oracle halt, and initial margin after the withdrawal |
Orders
| Function | Caller | Notes |
|---|---|---|
placeOrder(isBuy, price, size, ioc, expiry, maxMatches) | Account | Returns the order ID and the filled, resting, and canceled sizes |
placeOrderWithSig(action, signature) | Any address, usually the relayer | The session key signs the OrderAction |
cancel(orderId) | Order owner | Stays available during an oracle halt |
cancelWithSig(action, signature) | Any address, usually the relayer | The session key signs the CancelAction |
Maker ladders and quotes
| Function | Caller | Notes |
|---|---|---|
setLadder(index, first, step, levels, base, sizeStep) | Maker | Registers or replaces the ladder in one slot. levels = 0 clears it. |
quote(center, tick, validUntil, updateData) | Quoter | Moves all ladders and updates the mark. With update data, it also submits the observation. |
Oracle, mark, and risk
| Function | Caller | Notes |
|---|---|---|
submitObservation(updateData) | Any address | The data must carry a valid signature of the oracle signer, or the timestamp of a stored record |
updateMark() | Any address | Calculates the mark from the current book |
syncAccount(account) | Any address | Stores the pending funding of an account |
liquidate(account, maxMatches) | Any address | IOC close attempt for an account below maintenance margin |
Reads
| Function | Result |
|---|---|
accountState(account) | Cash, positions, entry, funding, fees, equity, margins, free collateral, and the liquidation flag |
getBook(depth) | Bids and asks of the merged book, real orders and ladder levels |
markPrice(), charge() | The current mark and the charge of the current interval |
oraclePrice(), oracleTime() | The last accepted price and its timestamp |
nextBoundary(), observationDue() | The next observation boundary and whether it passed |
halted(), markFrozen() | The oracle halt state and the mark freeze state |
fundingIndex(), fundingIndices(epoch), epoch() | The funding index and its history |
varianceAccumulator(), rawAccumulator() | Sums of the capped and the raw squared returns |
ladders(index), ladderFills(index), quoteState() | Ladder shapes, used ladder size, and the last quote |
nonces(account, session) | The next nonce of a session |
feePool() | Recorded trade fees. There is no fee withdrawal function. |
Functions of QueuedMarket
| Function | Caller | Notes |
|---|---|---|
enqueue(requests) | Account | Up to 16 IOC requests for each call. At most 128 pending requests. Needs a valid mark and no oracle halt. |
cancelQueued(id) | Request owner | Releases the reservation |
clearQueue(maxOrders) | Any address | Processes up to 16 requests in queue sequence |
quoteAndClear(center, tick, validUntil, updateData, maxOrders) | Quoter | quote, then clearQueue |
quoteAndTake(center, tick, validUntil, updateData, isBuy, price, size, expiry, maxMatches) | Quoter | quote, then one IOC order for the caller |
queueHead(), queueTail(), pendingOrders(id) | Read | Queue state |
An expired queue entry keeps its reservation until a call cancels or processes it.
Events of Market
| Event | When |
|---|---|
Deposited, Withdrawn | Collateral moves |
OrderPlaced | The contract accepts an order for execution |
OrderResult | End of an order call: filled, resting, canceled |
OrderCanceled | A resting order or a ladder side leaves the book. Reasons: 0 owner, 1 expired, 2 maker margin, 3 liquidation. |
Trade | One fill between a maker and a taker. liquidation is true for a forced fill. |
MarkUpdated | Each mark calculation, with the freeze state |
Observation | Each accepted observation, with the raw and capped squared return, the payment, the charges, and the skipped and chargeFrozen flags |
AccountSynced | The contract stores funding for an account |
LiquidationAttempt | Each liquidate call, with the filled size, the remaining position, the equity, the deficit, and the waiting flag |
LadderSet, Quoted | Ladder registration and each quote |
QueuedMarket adds OrderQueued, QueueExecuted, and QueueCanceled. The reasons of QueueCanceled are 0 owner cancellation, 1 expired, and 2 rejected at execution.
SessionRegistry
| Function | Notes |
|---|---|
authorize(session, market, expiry, maxExposure) | The caller gives a session key permission for one market. maxExposure is at most 1,000,000 contracts in 18-decimal units. The market position limit still applies. |
revoke(session, market) | The caller removes the permission |
check(account, session, market) | Returns the exposure limit. Reverts when the permission is expired or revoked. |
Signed actions
EIP-712 uses the domain name VarianceMarket, the version 1, the current chain ID, and the market address.
OrderAction(address account,address session,bool isBuy,uint256 price,uint256 size,
bool ioc,uint64 expiry,uint32 maxMatches,uint256 nonce,uint64 deadline)
CancelAction(address account,address session,uint256 orderId,uint256 nonce,uint64 deadline)Nonces are sequential. They are scoped to the account and the session in each market. The contract checks the signature, the action deadline, the session expiry, the revocation, the market scope, and the reserved exposure limit.
A session expiry or a revocation blocks new signed actions. It does not cancel orders that the contract already accepted. The main wallet can still cancel them.
The test oracle signature uses this type:
Observation(uint256 chainId,address oracle,bytes32 feedId,uint256 price,uint64 timestamp)TestOracle
| Function | Notes |
|---|---|
owner() | The deployer. The market uses this address as the oracle signer. |
publish(feedId, price, timestamp) | Owner only. Stores one price record. A record cannot change. |
read(feedId, updateData) | Returns the stored price for a timestamp |
The feed IDs of the scripts are the ASCII symbols MON and ETH, padded with zeros to 32 bytes.
VarianceMath
logRatio normalizes the larger price ratio by powers of two. It evaluates the odd atanh series through denominator 59 on the interval from 1 to 2. Negative returns use the negative logarithm of the inverse ratio. The source price bounds are 1e6 through 1e30 in 18-decimal units. These bounds prevent division underflow and keep all intermediate products below 256 bits.
Rounding
Margin calculations round up. Fees, funding, and profit calculations round toward zero. Weighted entry prices round down. These small differences can accumulate. They are not an insurance fund.
Foundry deployment script
The applications use the deployment scripts in scripts/. A separate Foundry script is available:
forge script --root contracts contracts/script/Deploy.s.sol:Deploy \
--rpc-url http://127.0.0.1:8545 --broadcast --private-key "$LOCAL_DEPLOYER_KEY"This script requires the local chain ID 31337. It emits the addresses. It does not write the application deployment file. Use a local test key only.
Growth and limits
- The funding index history and the order records grow over time. They are not pruned.
- The recorded gas measurements are Foundry measurements. They are not a Monad benchmark. See Implementation status.
- No production oracle test and no default recovery test are complete.