forexiz
FIG. 09·6
Forexiz · Threat model
Trust · Threat model · v1

What we can do
to your funds.

What we can't.

Most products that call themselves “decentralized” refuse to publish a threat model because it forces them to be explicit about what the operator can still do. We are explicit. Below: the three invariants the contracts enforce by construction, the roles in scope, and the exact blast radius of compromise per role.

Specimenv1.0
Tests99 + 24,576
Surface~20 KB
Solidity0.8.27
OZ5.0.2
ChainArb One · 42161
Witnessed
2026-05-13 · UTC
§ 02 · Invariants
3 properties · 24,576 assertions / run
Invariant
I1
Solvency
Mechanical statement
IERC20(asset).balanceOf(this) ≥ totalCollateral() + accruedFees()

The vault's real on-chain USDC balance is always at least the sum of every user's recorded collateral plus accrued fees. The accounting can never claim more than the contract holds.

Enforcement —24,576 randomized call sequences per invariant run. Caught a real bug during development: the original accrueFee(amount) API broke I1 and was replaced with collectFeeFrom(user, amount).
Invariant
I2
Egress paths
Mechanical statement
Only withdraw / liquidate / emergencyExit can decrease the vault balance

Three paths can move USDC out. withdraw is callable by the account owner, gated on free margin. liquidate is public, requires the account is flagged + insolvent. emergencyExit is callable by the account owner after the operator silence window. There is no other path.

Enforcement —Verified by the test_invariant_noOperatorWithdrawal property. bookPnL, setMarginUsed, settleAndUpdate, flagLiquidatable, collectFeeFrom, accrueFee cannot move tokens to operator addresses.
Invariant
I3
Operator boundary
Mechanical statement
MARGIN_MANAGER_ROLE cannot transfer to itself, ever

The matching engine's on-chain role can read every user's collateral, apply realized P&L between accounts, lock/unlock margin, move user collateral into the protocol fee pool atomically, and flag accounts liquidatable. It cannot transfer to itself.

Enforcement —FEE_SWEEPER_ROLE is the only role that can sweep accrued fees, and it can only sweep the fee accumulator — never user collateral. The vault MARGIN_MANAGER role is held by ForexizMarginManager, not the matcher EOA directly.
§ 03 · Roles
3 tiers · 5 entries

Roles, capabilities, mitigations

Trusted
Protocol-admin Gnosis Safe
ADMIN_ROLE
Can

Full role-management capability. Can grant/revoke any other role.

Mitigation

3-of-5 multisig with hardware-keyed signers, geographically distributed. Timelock contract on every governance action (Phase 6b).

Trusted
OpenZeppelin v5.0.2
inherited libraries
Can

AccessControl, Pausable, ReentrancyGuard, SafeERC20, MerkleProof.

Mitigation

Industry-standard library, audited many times over. Pinned via foundry.toml; upgrades reviewed before bump.

Semi-trusted
Matching engine
MARGIN_MANAGER_ROLE
Can

Apply arbitrary P&L between accounts, flag accounts liquidatable, collect fees.

Mitigation

Compromise = wrong P&L applied; tokens stay in the vault (I2 + I3). Recovery: admin Safe revokes, deploys new manager EOA. Damage limited.

Semi-trusted
Commitment publisher
COMMITMENT_PUBLISHER_ROLE
Can

Publish bogus Merkle roots.

Mitigation

Off-chain commitment archives + post-mortem reconciliation against the signed-receipt log catch this; new publisher key issued.

Untrusted
EOAs / public callers
no privileged role
Can

Anyone reads on-chain events.

Mitigation

No information privileged from on-chain events. Liquidation is public; griefing-resistance is in the math.

§ 04 · Boundary
Mechanically enforced

What the operator
can never do.

Mechanically enforced by the contracts. Not policy, not promise — code. Each item below is testable in the open-source repo and verifiable on Arbiscan.

  • §01Transfer your USDC out of the vault
  • §02Block, queue, or delay your withdrawal
  • §03Take a loss on your behalf without you signing
  • §04Change protocol parameters past the curator-bounded ranges
  • §05Replay a signed fill receipt (each is bound to a fillId + timestamp)
  • §06Censor liquidations (they are public, anyone can call)
§ 05 · Emergency exit
48h silence window

If we go offline,
you still leave.

If the matching engine ever goes silent for the silence window (default 48h), any account owner can call emergencyExit(to) directly on the vault. Pause must never trap funds — test_emergencyExit_worksWhenPaused enforces this.

Take-home formulaSolidity 0.8.27
function emergencyExit(address to) external {
    require(silentFor(msg.sender) >= silenceSeconds);

    uint256 takeHome = clampToZero(
        equity(msg.sender) - marginUsed(msg.sender)
    );
    takeHome = min(takeHome, collateralOf(msg.sender));

    asset.safeTransfer(to, takeHome);
}
Min
0
Max
collateral
Cooldown
48 h
§ 06 · Out of scope
Operational, not in-contract

Honest disclosure of what this page does not cover

  • Multisig signer compromise (operational)
  • Backend service compromise (off-chain)
  • DNS / domain hijacking
  • Node-operator compromise (Alchemy, Infura)
  • Frontend XSS or hosting compromise
  • Phishing / social engineering

These warrant their own operational review but are not audit findings on the contracts.

Companion document

AUDIT-PREP.md

In-scope contracts, focus areas, prioritized findings-handling protocol — the full document we hand to auditors before they start.