HELIX// security home risk tokenomics docs paper
PRE-AUDIT full report ↗
scope HelixPerps.sol + HelixHook.sol · tools slither 0.11 · forge 34/34 · manual · critical 0 · high 0 · medium 1 (fixed) · external audit Trail of Bits · Spearbit · Cantina (TBD)
SECURITY

pre-audit triage. — public before mainnet · 0 crit, 0 high, 1 medium fixed, all lows acknowledged or fixed. external audits pending.

full INTERNAL_AUDIT.md in the repo · slither + forge + manual review
01 FINDINGS BY SEVERITY // internal triage results · pre-external-audit

critical

0
none found

high

0
none found

medium

1
1 fixed · 0 open

low

4
2 fixed · 2 acknowledged

info

~12
style / docs only
02 FINDINGS DETAIL // every finding, status, remediation · one-paragraph summary per
M-01 FIXED slither
Unchecked ERC20 transfers (10 call sites)

USDC and a vanilla $HELIX revert on failure so the immediate deploy is safe. But the engine is positioned as general-purpose — a future market backed by a non-reverting ERC20 (USDT-style) would silently swallow failed transfers and corrupt internal accounting. Remediation: added _safeTransfer + _safeTransferFrom wrappers in HelixPerps.sol L266-280 that decode optional bool returns and revert on either failure mode. All 10 sites refactored; 34/34 forge tests still passing.

L-01 ACKNOWLEDGED slither
Divide-before-multiply scaling

Standard guidance is "multiply before divide" to preserve precision. In our case WAD / 1e6 = 1e12 is exact (both powers of ten, no truncation) so result is identical to amount * WAD / 1e6. The bonus-emissions math already does multiplication first. No code change. Documented so external auditors don't re-flag.

L-02 ACKNOWLEDGED slither
receive() reverts on ETH

Slither flags "contract has payable but no withdraw." The receive() exists specifically to revert as a defensive guard against accidental ETH transfers. No ether can accumulate. No code change.

L-03 ACKNOWLEDGED slither
bytes32(0) strict equality on market existence

Market existence is gated by markets[id].id != bytes32(0). Since id == keccak256(symbol) for valid markets, a zero-hash collision is computationally impossible. No code change.

L-04 FIXED slither
Timestamp-dependent comparisons

Bootstrap window relies on block.timestamp < bootstrapEndTs. A miner could nudge timestamps a few seconds. Remediation: bootstrap end set with 1-block grace (HelixPerps.sol L189) so a single block of miner clock-skew can't accidentally tip the window. Multi-block manipulation requires miner economic incentive larger than the 2× emission delta — out of scope.

03 THREAT MODEL // what an attacker could try · why each path is closed

🔒 reentrancy on settle

Every external function that touches state uses OpenZeppelin's nonReentrant. The hook's beforeSwap path is checks-effects-interactions; liquidate() sweeps before any token transfer.

🎯 oracle manipulation

There is no oracle. Mark price = AMM pool price after the swap completes. To manipulate the mark you have to move the same pool everyone arbs against — impact equals normal swap impact, no advantage.

🌪 sandwich on perp open

Sandwiching a perp open requires moving the pool. The bot pays the same slippage as a normal trader, then HELIX uses the post-swap tick as the mark. Sandwich profit = 0.

🧊 funding rate spike

FUNDING_CLAMP_PP1E8 = 5000 caps per-block funding accrual at ±0.005%/hr (≈ ±43.8% APR). Even at 100% one-sided OI, the rate cannot exceed that. Manipulation surface is bounded.

💀 HLP drawdown spiral

The 8% drawdown gate (HLP_DRAWDOWN_GATE_BPS = 800) pauses withdrawals once HLP falls 8% from peak. Stops a panic-run loop where deficit → withdrawals → realized losses → more deficit.

👤 admin / governance attack

None exists. Contract has no Ownable, no proxy, no upgrade path. Every parameter is a public constant. There is nothing for governance to compromise because there is no governance.

🐛 math invariant break

34/34 forge tests assert: hlpAssets ≥ 0, sum(margin) == totalCollateral, fundingPaid + fundingReceived == 0 across every fuzz run.

🔨 DoS / griefing

Per-block work bounded: SWEEP_MAX_PER_SWAP = 8 liquidations per hook call. New positions can't push the queue above the cap. Gas spike on a single swap is bounded.

04 CONTRACT CONSTANTS · IMMUTABLE // every safety threshold, hardcoded in HelixPerps.sol · no governance can change them
constantvaluepurposesource
LIQ_BOUNTY_BPS100 (1.00%)paid to keeper that triggers liqHelixPerps.sol L55
MAINT_MARGIN_BPS_MAJOR200 (2.00%)health floor for ETH/BTC/SOL/HYPEL70
MAINT_MARGIN_BPS_ALT500 (5.00%)health floor for all other marketsL72
HLP_DRAWDOWN_GATE_BPS800 (8.00%)HLP withdraws pause above thisL75
FUNDING_CLAMP_PP1E8±5_000cap on per-block funding accrualL66
SWEEP_MAX_PER_SWAP8max liquidations per hook callL78
FEE_BPS45 (0.45%)taker fee on every open / closeL60
FEE_SPLIT_HLP_BPS5000 (50%)portion of fees routed to HLPL62
FEE_SPLIT_BUYBACK_BPS5000 (50%)portion routed to $HELIX buybackL64
BOOTSTRAP_MULTIPLIER_BPS20000 (2×)bonus emissions during 28d windowL85
BOOTSTRAP_CAP_USDC1,000,000first $1M of HLP deposits gets 2×L88
FOUNDER_SEED_LOCK90 daysfounder USDC seed lockedL92
every value above is declared public constant. there is no setter, no governance vote, no timelock that can change them. the only way to ship new params is a fresh deployment to a new address.
05 BUG BOUNTY // active from mainnet deploy · scope = HelixPerps.sol + HelixHook.sol · payouts via Immunefi or direct

// payout tiers

CRITICALloss of funds · HLP drainage · supply inflation$250,000
HIGHstuck funds · unauthorized state change · funding manipulation > clamp$50,000
MEDIUMgriefing · DoS · partial state corruption$10,000
LOWnon-blocking issue · style / docs / minor edge case$1,000
contact: security@helix.trade · PGP key at /brand/security-pubkey.asc (TBA post-deploy) · response within 24h