Testnet — this app is connected to Hedera testnet

For issues or feedback, join our Discord →

Security

Lynx Smart Contract Security Review

Hardened direct-deploy ERC-4626 stack on Hedera mainnet (June–July 2026). Reviewed against ERC-4626 exploit classes, exposure-cap NAV hardening, keeper/LP automation, governance stack, Slither, Aderyn, Echidna, Halmos, and 157 automated regression tests.

Reviewed by: Slither · Aderyn · Echidna · Halmos (Lynx + Keeper) · ERC-4626 exploit tests · unit/fuzz · keeper health CIReport date: July 2026 (exposure caps + NAV hardening)Network: Hedera Hashgraph
Hedera HashgraphHardened v4 MainnetERC-4626Offset 6 Inflation DefenseExposure Cap NAVKeeper + LP AutomationOn-Chain Governance
100%
triaged
Hardened mainnet deploy

157/157 tests · 6,000 fuzz ops · 2/2 Echidna · 7/7 Halmos · Slither 141 triaged · vault + bridge paused until migration verified

Critical
0
None found
High
4
Accepted: 4
Medium
68
False positive / accepted / resolved
Low
64
Accepted / ignored: 64
Informational
5
Resolved / informational: 5

Security summary

  • Direct-deploy ERC-4626 vault (no proxy). Asset: WHBAR. Cost-basis NAV (no price oracle).
  • ERC-4626 inflation defense: OpenZeppelin 5.x virtual shares with _decimalsOffset() = 6 (14-decimal shares).
  • HBAR entry slippage: depositHbar(receiver, minSharesOut) and depositHbarAndWrap(minSharesOut); minSharesOut = 0 skips check (migration only).
  • Exposure-cap NAV hardening: strategy exposureValue(), bookLossToExposure(), harvest maxStaleAge, and ManagerMismatch on registerStrategy.
  • Profit streaming: realized gains locked 7 days; losses immediate. Harvest sandwich mitigated.
  • Redeem buffer: maxRedeem / maxWithdraw capped to idle WHBAR only.
  • Pause: vault + bridge inflows pausable (guardian or owner); redeem/unwrap always open. Mainnet: paused until migration verified.
  • Bridge invariant: LYNX.totalSupply() == vault.balanceOf(bridge) (unit + fuzz + Echidna).
  • Legacy burn-reward / transferHbarTo drain path removed (not present on v4 stack).
  • 157 automated tests, 6,000 property-fuzz ops (vault-bridge + keeper-LP + lp-strategy), 2/2 Echidna properties, 7/7 Halmos checks.
  • Keeper stack (LynxKeeperRouter, LynxLpAllocator): MinSwapOutRequired, fail-fast LP deploy, principal vs exposure ops playbook.
  • On-chain governance (LynxGovernor, LynxTimelock, LynxVotes) with proposal-guard calldata classification.
  • Scheduled keeper health CI on testnet (diagnose + stack health + mirror log checks).
  • Static analysis: Slither 141 / Aderyn 5H+16L (2,382 nSLOC) — triaged; harvest events, LpAllocatorSet, H-4 cast, and Halmos harness fixes resolved in code.

ERC-4626 exploit review

Tested against known ERC-4626 attack classes

LynxVault inherits OpenZeppelin 5.x ERC-4626 mitigations and adds Lynx-specific controls. Each class below was reviewed with dedicated unit tests, property fuzz, and/or Echidna where applicable. The legacy signed reward drain path does not exist on the v4 stack.

Inflation / donation (first depositor)

Mitigations: _decimalsOffset = 6, cost-basis NAV, migration deposit while vault is paused.

Test evidence

  • first-depositor inflationtest/LynxVault.test.ts
    Pass
  • Property fuzztest/fuzz/vault-bridge.fuzz.test.ts
    Pass
  • Echidna echidna_backingEchidnaLynxHarness.sol
    Pass
  • Echidna echidna_principal_consistencyEchidnaLynxHarness.sol
    Pass

Lending oracle abuse

Mitigations: Not in scope in-contract. Do not use raw convertToAssets() as a lending collateral price without extra guards.

Test evidence

  • In-contract lending
    N/A

Fake yield / inflated NAV

Mitigations: Keeper-only reportGain; lockedProfit streaming over profitUnlockPeriod.

Test evidence

  • Profit streaming + manager gaintest/LynxVault.test.ts
    Pass

Bridge 1:1 semantics

Mitigations: 1 LYNX base unit = 1 vault share base unit escrowed in bridge (not 1 HBAR per deposit).

Test evidence

  • Wrap/unwrap backingtest/LynxHTSBridge.test.ts
    Pass

HBAR / bridge deposit slippage

Mitigations: depositHbar(receiver, minSharesOut) and depositHbarAndWrap(minSharesOut) revert SharesSlippage when floor not met.

Test evidence

  • depositHbar slippagetest/LynxVault.test.ts
    Pass
  • depositHbarAndWrap slippagetest/LynxHTSBridge.test.ts
    Pass
  • decimals offsettest/LynxVault.test.ts
    Pass

Legacy vs v4: The historical proxy vault allowed backend-signed rewards and transferHbarTo drains without burning shares. The new ERC-4626 stack removed that path entirely. Share-price manipulation is mitigated via virtual offset 6 plus on-chain minSharesOut on HBAR deposit paths.

Threat status

Known exploit classes reviewed for the hardened mainnet v4 stack. Status reflects code mitigations, automated tests, and current operational state.

Threat classApplicableMitigationTestedStatus
Legacy signed reward drainNo on v4Path removedN/ANot applicable
ERC-4626 inflation / donationYesOZ virtual shares + offset 6Unit + fuzzMitigated
First depositor on empty vaultYesOffset + paused until migrationUnit + opsPaused
Fake yield / instant redeemPartialLocked profit + manager-only gainUnitMitigated
Lending oracle (Venus-style)External onlyNo in-contract lendingN/APolicy: no raw oracle
Bridge backing shortfallYesOps: fund bridge before unpauseFuzz + EchidnaPending migration
HBAR deposit slippageYesminSharesOut on HBAR/bridge pathsUnitDeployed
Governance / keeper trustYesOperational + HalmosKeeper ACLUnit + HalmosTrusted roles
Silent LP deploy no-opYesSwapProducedZero / LiquidityIncreaseZero revertsLynxLpAllocator.test.tsMitigated
Keeper schedule / ops driftPartialScheduleFailed event + scheduled health CIOps + mirror logsMonitored
Principal accounting drift (keeper ops)YesI10 invariant + echidna_principal_consistencykeeper-lp fuzz + EchidnaMitigated
Principal exceeds on-chain exposureYesexposureValue() + bookLossToExposure() + ops book-loss playbookStrategyManager.test.ts + stack-healthMitigated
Stale harvest / NAV driftYesStrategyManager maxStaleAge freshness gate on harvestStrategyManager.test.tsMitigated
Swap with zero min-out (keeper LP)YesMinSwapOutRequired when swapBps > 0; LP MinOutRequired + sqrt price limitLynxLpAllocator.test.ts + HalmosKeeperMitigated
Vault migration drain misroutingPartialVaultMigrationStrategy successor lock + armMigration guardVaultMigrationStrategy.test.tsDeferred (pre-cutover)

June 2026 expansion

Keeper & LP Automation Review

Scope expanded to cover LynxKeeperRouter (HIP-1215 weekly ops) and LynxLpAllocator (LP deploy path). Hardening, fuzz invariants I10–I12, HalmosKeeper ACL checks, and scheduled keeper-health CI.

Phase 1 — Contract hardening

  • LynxKeeperRouter: LpAllocatorSet, HarvestCompleted events
  • LynxLpAllocator: fail-fast on zero swap output / zero liquidity increase
  • LynxKeeperRouter: DEFAULT_LP_DRAIN_DUST as uint128; explicit local inits in reconcileInactiveLp
  • LynxLpAllocator: cached targets.length in deployLpIdle
  • Threat Library follow-up: MinSwapOutRequired when swapBps > 0
  • SaucerSwapLiquidityStrategy: MinOutRequired on manual swaps + configurable swapSqrtPriceLimitX96
  • StrategyManager: exposureValue(), bookLossToExposure(), maxStaleAge harvest gate, ManagerMismatch

Phase 2 — Test coverage

  • test/LynxLpAllocator.test.ts — real allocator + mock SaucerSwap stack
  • test/LynxKeeperRouter.test.ts — allocator integration + event assertions
  • test/fuzz/keeper-lp.fuzz.test.ts — invariants I10–I12 + I1/I2
  • test/fuzz/lp-strategy.fuzz.test.ts — F-016 LP NPM ordering invariants
  • test/fuzz/vault-bridge.fuzz.test.ts — F-004 loss booking + fee-aware maxWithdraw
  • test/StrategyManager.test.ts — exposure caps, bookLossToExposure, maxStaleAge
  • test/VaultMigrationStrategy.test.ts — migration drain safety (I13)
  • test/halmos/HalmosKeeper.t.sol — router/allocator ACL + minSwapOutRequiredWhenSwapping
  • EchidnaLynxHarness.sol — echidna_principal_consistency added

Phase 3 — Monitoring

  • .github/workflows/keeper-health.yml — scheduled testnet health (6h + dispatch)
  • keeper-diagnose.ts — strict mode, RESULT: PASS/FAIL
  • stack-health.ts — keeper warnings fail-hard; principal-vs-exposure checks; mirror log checks
  • npm run ops:testnet:keeper-health

LP / Keeper Coverage Matrix

RiskUnit testsOps monitoringFuzz / formal
Allocator ACL / configYes — LynxLpAllocator.test.tsDiagnose wiring checksHalmosKeeper
Router weekly ops / scheduleYes — LynxKeeperRouter.test.tsMirror ScheduleFailed, stale opskeeper-lp.fuzz.test.ts
Silent LP deploy no-opYes — SwapProducedZero, LiquidityIncreaseZerolp-strategy.fuzz.test.ts
Bad keeper swap / sandwichYes — MinSwapOutRequiredHalmosKeeper minSwapOut
Principal exceeds exposureYes — bookLossToExposurestack-health + book-loss script
Principal accounting driftYes — StrategyManager testsI10 fuzz + Echidna
Wrong call orderPartialPlaybook scriptsNo
Stranded capital in NFTPartialliquidPreview checksNo
SaucerSwap external bugsNoNoNo

Remaining Accepted Risks (Keeper / LP)

  • _findAvailableSecond prevrandao jitter — schedule slot probing; non-deterministic but bounded
  • HSS _scheduleNext CEI order — schedule failure non-reverting + ScheduleFailed event
  • LP admin reentrancy ordering — guarded by roles + nonReentrant (see triage H-01)
  • setSplitRouter(address(0)) — disables router alignment check by design (same as setGuardian(0))
  • FullMath caret operator (Aderyn H-2) — false positive; Uniswap V3 library uses bitwise XOR, not exponentiation
  • SaucerSwap external protocol — out of scope for Lynx contract audit

Security layers

Layer 1 inherits OpenZeppelin ERC-4626 defenses. Layer 2 adds Lynx-specific economic and operational controls. June 2026 redeploy required new bytecode (contracts are not upgradeable).

Layer 1 — OpenZeppelin ERC-4626

ItemImplementation
Virtual shares
Industry-standard post–v4.9 mitigation
shares = assets × (supply + 10^offset) / (assets + 1)
_decimalsOffset()
Donation/inflation attacks orders of magnitude more expensive than offset = 0
Constant 6 in LynxVault.sol
Share decimals()
Display only; bridge 1:1 is raw base units (share wei ↔ LYNX wei)
8 (WHBAR) + 6 = 14
Standard deposit()
Off-chain previewDeposit + UI slippage for WHBAR ERC-20 path
Unmodified IERC-4626 signature

June 2026 hardened redeploy

ChangeBefore → After
_decimalsOffset()0 (OZ default)6
HBAR slippagedepositHbar(receiver) onlydepositHbar(receiver, minSharesOut)
Bridge mintdepositHbarAndWrap()depositHbarAndWrap(minSharesOut)
Post-deploy pauseManual (ops:mainnet:pause-stack)Automatic in deploy scripts
Contract IDs10509714 / 10509719 / 10509723 / 1050974910510261 / 10510268 / 10510271 (+ LP 10510145)
Keeper / LP stackNot in prior audit scopeLynxKeeperRouter + LynxLpAllocator + expanded LP tests
Test + fuzz count78 tests · 4,000 fuzz ops157 tests · 6,000 fuzz ops · 7/7 Halmos
Static analysis scope1,563 nSLOC · 76 Slither · 4H+10L Aderyn2,382 nSLOC · 141 Slither · 5H+16L Aderyn
Exposure-cap NAV hardeningPrincipal-only strategy accountingexposureValue(), bookLossToExposure(), maxStaleAge, ManagerMismatch
Keeper swap guardsNo min-out on allocator swap pathMinSwapOutRequired + LP MinOutRequired / sqrt price limit
Audit scope expansionVault + bridge + LP + keeper+ HederaStakingStrategy, VaultMigrationStrategy, governance, MTM math libs
LP strategy mainnet ID0.0.105101450.0.10510275 (current stack)

Layer 2 — Lynx-specific controls

ControlWhat it doesRisk reducedEvidence
Cost-basis totalAssets()idle + deployedPrincipal - lockedProfit; no mark-to-marketOracle / manipulated NAVUnit + fuzz I2
lockedProfit + 7d unlockGains stream in; not instantly withdrawableHarvest sandwich, fake yield redeemLynxVault.test.ts
Buffer maxRedeemRedeem ≤ idle WHBAR onlyForced LP unwind in user txLynxVault.test.ts
reportGain / reportLossStrategyManager onlyPublic NAV inflationUnit + Halmos
No transferHbarTo / rewardsRemoved vs legacy architectureHistorical ~25k HBAR drain classNot in codebase
depositHbar minSharesOutRevert SharesSlippage if minted shares < floorDonation before HBAR mintdepositHbar slippage tests
depositHbarAndWrapForwards slippage check to vaultBad bridge mint UXLynxHTSBridge.test.ts
Pausable inflowsBlocks deposit/mint/wrap; not redeem/unwrapEmpty vault incident windowHardening.test.ts
Auto-pause on deploypause() after vault + bridge createPost-deploy exposureDeploy scripts
Ownable2Step + guardianTwo-step ownership; optional fast pauseGovernance hijack / incident responseHardening.test.ts
Exit fee capMAX_EXIT_FEE_BPS = 10%Fee governance mistakeHalmos
HtsOps._toInt64Revert on HTS amount overflowSilent truncationHardening.test.ts
Strategy manager CEIPrincipal before external calls on pushReentrancy accounting driftCode review + unit
setStrategyManager guardNo swap with outstanding principalNAV desyncHardening.test.ts
LP position capMAX_ACTIVE_LP_POSITIONS = 32Unbounded harvest gasSaucerSwapLiquidityStrategy.sol
LP deploy fail-fastRevert SwapProducedZero / LiquidityIncreaseZero on active deploySilent misconfigured LP deployLynxLpAllocator.test.ts
Keeper observabilityLpAllocatorSet, HarvestCompleted, ScheduleFailed eventsOps blind spotsLynxKeeperRouter.test.ts
Bridge backing1 LYNX wei = 1 share wei escrowedUnder-backed unwrapUnit + fuzz + Echidna
exposureValue()On-chain strategy exposure for NAV reconciliationPrincipal > real exposure driftStrategyManager + LP tests
bookLossToExposure()Keeper books loss when principal exceeds exposureOverstated NAV after LP impairmentStrategyManager.test.ts + ops book-loss
maxStaleAge harvest gateReject stale strategy harvests when configuredStale PnL inflating NAVStrategyManager.test.ts
ManagerMismatch guardregisterStrategy rejects wrong strategyManager on vaultMiswired strategy registrationStrategyManager.test.ts
MinSwapOutRequiredAllocator reverts when swapBps > 0 and minSwapOut = 0Sandwich / bad keeper swapLynxLpAllocator.test.ts + HalmosKeeper
LP swap min-out + price limitSaucerSwapLiquidityStrategy MinOutRequired + swapSqrtPriceLimitX96Admin swap MEV / bad fillSaucerSwapLiquidityStrategy.test.ts

Mainnet contracts (current)

  • LynxVault
    0.0.10510261
    0x0000000000000000000000000000000000a05fb5
  • StrategyManager
    0.0.10510268
    0x0000000000000000000000000000000000a05fbc
  • LynxHTSBridge
    0.0.10510271
    0x0000000000000000000000000000000000a05fbf
  • SaucerSwapLiquidityStrategy
    0.0.10510275
    0x0000000000000000000000000000000000a05fc3
  • LYNX (HTS, reused)
    0.0.9633601
    Supply key on legacy proxy until TokenUpdate

Mainnet operational state

  • Vault paused()Yes (inflows blocked)
  • Bridge paused()Yes (wrap/mint blocked)
  • Vault totalSupply444,842,000,000 (= LYNX totalSupply)
  • Bridge backingShares444,842,000,000 (1:1 with LYNX)
  • LYNX supply keyLegacy proxy 0.0.9766114 until cutover
  • Bridge lynxTokenSet to 0.0.9633601

Before public use: migrate backing → verify backingShares → TokenUpdate → unpause. Vault pause blocks all inflows including direct depositHbar.

Integrator / frontend requirements

  1. Mint / wrap: call previewDeposit(hbarAmount) then depositHbarAndWrap(minShares) with tolerance (e.g. 99.5% of preview). Use 0 only for trusted migration scripts.
  2. Display: 1 LYNX = 1 vault share base unit, not 1 HBAR per deposit.
  3. Do not use convertToAssets() as a lending collateral price without extra guards.
  4. Redeem: respect maxWithdraw / buffer; large exits need keeper pull from strategies.

Findings by Severity

Critical
0
High
4
Medium
68
Low
64
Info
5
Total Slither findings141
Triaged / no open exploits141 / 141
Aderyn themes (High / Low)5 / 16

Security Checklist

Compile (Cancun + viaIR)
Hardhat 3, solc 0.8.28
Passed
Unit + hardening tests
157 / 157 Mocha tests (exposure caps, governance, keeper/LP, migration)
Passed
Hardhat property fuzz
6,000 ops (vault-bridge + keeper-LP + lp-strategy), seed 42
Passed
Echidna invariant fuzz
echidna_backing + echidna_principal_consistency @ 50k
Passed
Formal verification (Halmos)
7 / 7 symbolic (HalmosLynx + HalmosKeeper incl. minSwapOut)
Passed
Static analysis (Slither + Aderyn)
141 Slither + 5H/16L Aderyn — all triaged
Passed
Keeper health CI
Scheduled testnet diagnose + stack health + mirror logs
Passed

Core Invariants

I1
Bridge backing (1:1)LYNX.totalSupply() == vault.balanceOf(bridge) — unit, fuzz, Echidna
I2
Cost-basis NAVtotalAssets() == idle + deployedPrincipal - lockedProfit — unit + fuzz
I3
Buffer-capped redemptionmaxRedeem / maxWithdraw capped to idle WHBAR — unit tests
I4
Strategy capital accountingPrincipal/cap/PnL consistent on push/pull/harvest — unit tests
I5
Access controlManager-only vault mutations; fee ceiling; Ownable2Step — unit + Halmos
I6
HTS amount safetyAmountExceedsInt64 guard prevents int64 truncation — hardening tests
I7
Emergency pause (inflows only)Deposits/wrap pausable; redeem/unwrap stay open — hardening tests
I8
Strategy manager migration safetysetStrategyManager rejects zero address and non-zero principal swap — hardening tests
I9
ERC-4626 inflation + HBAR slippage_decimalsOffset = 6; depositHbar/depositHbarAndWrap minSharesOut — first-depositor inflation, slippage, decimals offset tests
I10
Strategy principal sum (keeper fuzz)totalPrincipal() == sum(principalOf) — keeper-lp fuzz + Echidna echidna_principal_consistency
I11
Bridge backing (keeper context)Same as I1; re-checked after keeper ops in keeper-lp.fuzz.test.ts
I12
NAV identity (keeper context)Same as I2; re-checked after keeper ops in keeper-lp.fuzz.test.ts
I13
Vault migration drain safetyVaultMigrationStrategy relays WHBAR only to locked successor vault — VaultMigrationStrategy.test.ts

Audit Scope

ContractScope
LynxVault.sol
ERC-4626 vault (WHBAR); cost-basis NAV; profit streaming; exit fee
Production
LynxHTSBridge.sol
1:1 wrap of vault shares ↔ HTS LYNX; bridge is treasury + supply key
Production
StrategyManager.sol
Allocates vault WHBAR to strategies; cost-basis principal accounting
Production
SaucerSwapLiquidityStrategy.sol
Production LP strategy (SaucerSwap V2); admin-driven position ops
Production
LynxKeeperRouter.sol
Weekly ops keeper; surplus push; HIP-1215 schedule loop
Production
LynxLpAllocator.sol
LP strategy owner; swap idle WHBAR + increase LP position
Production
HederaStakingStrategy.sol
Production HBAR staking strategy; exposureValue for NAV reconciliation
Production
VaultMigrationStrategy.sol
Governed v4→v4 vault upgrade drain relay (pre-cutover)
Production
LynxGovernor.sol
OZ Governor — proposals, voting, timelock execution
Production
LynxTimelock.sol
Timelock controller; self-admin guard on role/delay changes
Production
LynxVotes.sol
ERC20Votes wrapper for vault shares
Production
libraries/FullMath.sol
Uniswap V3 fixed-point math (LP MTM helpers)
Production
libraries/TickMath.sol
Uniswap V3 tick math (LP MTM helpers)
Production
libraries/LiquidityAmounts.sol
Uniswap V3 liquidity amount math (LP MTM helpers)
Production
libraries/HtsOps.sol
HTS precompile (0x167) wrapper with uniform revert on failure
Production
Total nSLOC reviewed2,382

Out of scope

  • · mocks/ — test doubles only
  • · HarnessStrategy.sol — testnet scripting, not mainnet LP
  • · echidna/EchidnaLynxHarness.sol — fuzz harness
  • · HederaResponseCodes.sol — vendored Hedera constants

Seven-Layer Review Stack

1
CompileHardhat 3, solc 0.8.28, Cancun, viaIR
2
Unit + hardening regressionMocha — 157 tests (vault, bridge, exposure caps, governance, keeper/LP, migration)
3
Hardhat property fuzz6,000 ops (3×2,000 vault-bridge + keeper-LP + lp-strategy), seed 42
4
Echidna (Trail of Bits)echidna_backing + echidna_principal_consistency @ 50k
5
Halmos7 symbolic checks (HalmosLynx + HalmosKeeper incl. minSwapOut)
6
Slither (Trail of Bits)Static analysis — 141 findings triaged
7
Aderyn (Cyfrin)AST static analysis — 5H + 16L themes (2,382 nSLOC)
8
Keeper health CIScheduled testnet ops + mirror log checks (6h)
CI Pipeline (GitHub Actions)All green

Wake is intentionally not used (scope control; Slither + Aderyn + Echidna + Halmos suffice).

Hardening Applied

Real mitigations implemented in code — not triage waivers.

  • _decimalsOffset() = 6 — OpenZeppelin ERC-4626 virtual-share inflation defense (June 2026 redeploy)
  • depositHbar(receiver, minSharesOut) + depositHbarAndWrap(minSharesOut) — on-chain HBAR slippage
  • Auto-pause on vault + bridge deploy (DEPLOY_SKIP_AUTO_PAUSE for testnet smoke only)
  • Cost-basis NAV, locked profit streaming (7d), buffer-capped redeem
  • Ownable2Step on vault, bridge, manager, LP strategy, keeper router, and allocator
  • Pausable on vault/bridge inflows; guardian + owner pause; redeem/unwrap never paused
  • No legacy transferHbarTo / signed reward drain path
  • HtsOps._toInt64 guard (AmountExceedsInt64) prevents silent HTS truncation
  • StrategyManager.push uses CEI — principal booked before external calls
  • setStrategyManager rejects zero address; requires zero outstanding principal to swap
  • SaucerSwapLiquidityStrategy: MAX_ACTIVE_LP_POSITIONS = 32; bounded harvest loop
  • LynxLpAllocator: SwapProducedZero / LiquidityIncreaseZero fail-fast on deploy path
  • LynxLpAllocator: MinSwapOutRequired when swapBps > 0 (HalmosKeeper symbolic check)
  • SaucerSwapLiquidityStrategy: MinOutRequired + configurable swapSqrtPriceLimitX96 on admin swaps
  • StrategyManager: exposureValue reconciliation via bookLossToExposure(); maxStaleAge harvest freshness
  • StrategyManager: ManagerMismatch guard on registerStrategy
  • LynxKeeperRouter: HarvestCompleted events on harvestLp / harvestStaking (Slither unused-return fix)
  • LynxKeeperRouter: LpAllocatorSet event on setLpAllocator (Aderyn L-7 fix)
  • LynxKeeperRouter: DEFAULT_LP_DRAIN_DUST as uint128 (Aderyn H-4 cast fix)
  • Keeper health CI: strict diagnose + stack health + principal-vs-exposure + mirror ScheduleFailed checks

Accepted Residual Behavior

Centralized governance

Owner/keeper can change fees, strategies, caps, pause inflows, and move capital. Accepted under Ownable2Step with expected multisig governance.

Accepted by design

Why this is safe

  • Production governance is expected to be a multisig; keeper and governance must be honest and competent.
  • Optional guardian enables fast pause; exit/redemption paths stay open when inflows are paused.
  • MAX_EXIT_FEE_BPS ceiling, strategy registration checks, and per-strategy caps limit parameter risk.

Impact by role

Depositors
Trust governance for fee/cap/strategy changes; exits remain available when inflows paused
Keeper
Authorized to push/pull/harvest; nonReentrant and CEI on push reduce reentrancy surface
Governance
Ownable2Step two-step transfer; guardian can be cleared via setGuardian(0)

Accepted Residual Behavior

Cost-basis NAV and profit streaming

block.timestamp used in lockedProfit() and totalAssets(). Accepted — Yearn-style linear unlock prevents harvest sandwiching.

Accepted by design

Why this is safe

  • NAV uses cost basis (no oracle). Realized gains stream over profitUnlockPeriod; losses apply immediately.
  • Timestamp manipulation only shifts unlock speed within miner skew bounds (~10–15s on Hedera).

Impact by role

Depositors
Cannot front-run harvests; unlock period is a governance-set economic parameter
Strategies
Realized PnL reported by keeper; principal accounting enforced in unit tests
Protocol
No oracle dependency; cost-basis model is deliberate product choice

Accepted Residual Behavior

Payable contracts / native HBAR

LynxVault, LynxHTSBridge, and SaucerSwapLiquidityStrategy accept native value without a generic withdraw() sweep.

Accepted by design

Why this is safe

  • Vault and bridge must receive HBAR for depositHbar / depositHbarAndWrap.
  • LP strategy holds HBAR for SaucerSwap V2 NFT mint/increase fees (mintFeeHbar).
  • Residual dust is an operational concern; governance can add a sweep if needed — not a user-fund lock.

Impact by role

Users
HBAR deposits work as designed; funds are not locked by missing sweep
LP strategy
Native HBAR required for SaucerSwap V2 fee payments
Governance
Optional future sweep for operational dust cleanup

Accepted Residual Behavior

Buffer-capped instant redemption

Users redeem only from idle WHBAR in the vault — not forced strategy unwind in the user transaction.

Accepted by design

Why this is safe

  • Instant liquidity comes from the redemption buffer; keeper/governance replenishes via pull.
  • Users needing full withdrawal may wait for buffer refill or redeem the available portion.

Impact by role

Depositors
Instant exit up to idle buffer; larger exits require keeper pull
Keeper
Responsible for replenishing buffer from strategies
Strategies
No forced unwind on user redeem — capital pulled asynchronously

Accepted Residual Behavior

Trusted strategies and HTS precompile

Registered IStrategy implementations receive WHBAR; all LYNX/WHBAR movement depends on HTS at 0x167.

Accepted by design

Why this is safe

  • Only governance registers strategies. Production strategy is SaucerSwapLiquidityStrategy.
  • HtsOps wraps the precompile and reverts on non-success codes; AmountExceedsInt64 guard prevents truncation.
  • Mainnet assumes Hedera HTS behaves as documented.

Impact by role

Depositors
Strategy risk gated by governance registration; bridge 1:1 backing enforced in fuzz
Bridge
HTS mint/burn/transfer via HtsOps with uniform revert semantics
Platform
HTS precompile correctness is a Hedera platform assumption

Accepted Residual Behavior

Keeper schedule probing + external SaucerSwap

HIP-1215 schedule slot probing uses prevrandao jitter; SaucerSwap router/NFT manager behavior is an external protocol assumption.

Accepted by design

Why this is safe

  • _findAvailableSecond uses bounded prevrandao jitter for schedule slot discovery — non-deterministic but safe.
  • Schedule failures are non-reverting with ScheduleFailed events; health CI monitors mirror logs.
  • SaucerSwap V2 contracts are trusted external dependencies — not audited as part of Lynx bytecode.

Impact by role

Keeper
Schedule probing may need retries; monitored via keeper-health CI
Depositors
LP strategy risk gated by governance; allocator fail-fast on misconfigured deploys
Protocol
SaucerSwap bugs are out of scope; liquidPreview + ops playbooks mitigate ops risk

Findings & Disposition

Slither and Aderyn flag code patterns that may indicate bugs. After human review, each alert is assigned a disposition. “Accepted” does not mean an open vulnerability — it means the pattern is intentional design or an acceptable tradeoff with no exploitable path for users.

Accepted

The tool flagged something real, but it is intentional design or an acceptable tradeoff — not an exploitable bug.

False positive

The tool misread the code (common with HTS precompile wrappers and Solidity overrides).

Resolved

We changed the code to address the finding.

Ignored

Style or readability nit with no demonstrated security impact.

IDFindingSeverityDisposition
H-01
Slither + Aderyn
SaucerSwap LP admin reentrancy ordering
createLPPosition / increaseLPPosition update storage after external SaucerSwap calls. Slither reentrancy-eth; Aderyn H-2.
Why accepted

These functions are onlyOwner and nonReentrant. The external caller is the configured SaucerSwap NFT manager — a trusted protocol contract, not arbitrary users. There is no practical reentrancy attack path for depositors; reordering to strict checks-effects-interactions would be a style improvement, not a security fix.

SaucerSwapLiquidityStrategy.sol
highAccepted
H-02
Aderyn
Payable contracts lock native HBAR
LynxVault, LynxHTSBridge, and SaucerSwapLiquidityStrategy accept native value without a generic withdraw() sweep. Aderyn H-1.
Why accepted

The vault and bridge must receive HBAR for depositHbar and depositHbarAndWrap. The LP strategy holds HBAR for SaucerSwap V2 NFT mint/increase fees. User funds are not trapped — the missing sweep only affects residual operational dust, which governance can recover later if needed.

LynxVault.sol · LynxHTSBridge.sol · SaucerSwapLiquidityStrategy.sol
highAccepted
H-03
Slither
Arbitrary-send-eth on staking wrap
Slither arbitrary-send-eth: LynxKeeperRouter sends native HBAR to configured WHBAR helper during staking harvest wrap.
Why accepted

The recipient is a governance-configured WHBAR helper contract, not an arbitrary user address. This is required for the staking harvest unwrap/wrap path. Access is keeper-only on the harvest functions.

LynxKeeperRouter.sol
highAccepted
H-04
Aderyn
FullMath caret operator (Aderyn H-2)
Aderyn flags Uniswap V3 FullMath.sol for incorrect use of the caret operator.
Why false positive

False positive. FullMath is the standard Uniswap V3 library; the flagged operator is bitwise XOR in fixed-point math, not Solidity exponentiation. Included in scope for LP mark-to-market helpers only.

libraries/FullMath.sol
highFalse positive
H-05
Aderyn
Weak randomness on schedule probing
Aderyn H-5 flags prevrandao / pseudo-random slot probing in LynxKeeperRouter schedule discovery.
Why accepted

Accepted. _findAvailableSecond uses bounded prevrandao jitter to discover HIP-1215 schedule slots. Failure is non-reverting with ScheduleFailed events; health CI monitors mirror logs.

LynxKeeperRouter.sol
highAccepted
R-01
Slither
Harvest return values unused (Slither)
Slither unused-return on harvestLp / harvestStaking manager return values before fix.
Why resolved

Resolved by emitting HarvestCompleted events that include the manager return values. Closes the unused-return finding; +2 Low reentrancy-events on the same paths are accepted (observability events post-call).

LynxKeeperRouter.sol — harvestLp / harvestStaking
mediumResolved
R-02
Aderyn
LpAllocatorSet event missing (Aderyn L-7)
Aderyn flagged setLpAllocator state change without an event.
Why resolved

Resolved by adding LpAllocatorSet event on setLpAllocator. Improves ops observability and closes the Aderyn L-7 theme.

LynxKeeperRouter.setLpAllocator()
lowResolved
R-03
Slither + Aderyn
Silent LP deploy no-ops (manual review)
Misconfigured swap pools or zero-liquidity increases could complete without effect, leaving ops blind.
Why resolved

Resolved by LynxLpAllocator fail-fast reverts: SwapProducedZero when swap output is zero, LiquidityIncreaseZero when liquidity increase is zero on the active deploy path.

LynxLpAllocator.sol
mediumResolved
R-04
Aderyn
Unsafe uint128 cast on lpDrainDust (Aderyn H-4)
Aderyn flagged an unsafe downcast on lpDrainDustThreshold in LynxKeeperRouter.
Why resolved

Resolved by typing DEFAULT_LP_DRAIN_DUST as uint128 at declaration. Closes the H-4 unsafe-cast theme from the prior snapshot.

LynxKeeperRouter.sol
lowResolved
R-05
Slither
Uninitialized-local / cache-array-length (Slither)
Slither flagged uninitialized locals in reconcileInactiveLp and uncached array length in deployLpIdle.
Why resolved

Resolved with explicit local initializers in LynxKeeperRouter.reconcileInactiveLp and cached targets.length in LynxLpAllocator.deployLpIdle.

LynxKeeperRouter.sol · LynxLpAllocator.sol
lowResolved
M-01
Slither
HTS return values ignored at call sites
Slither unused-return on HtsOps.mint, transfer, associate, approve.
Why false positive

HtsOps checks HederaResponseCodes.SUCCESS internally and reverts with HTSCallFailed on failure. Callers do not need to re-check the int64 return value — the library already enforces success or revert.

libraries/HtsOps.sol
mediumFalse positive
M-02
Slither
Cost-basis NAV uses block.timestamp
block.timestamp used in lockedProfit(), totalAssets(), setProfitUnlockPeriod.
Why accepted

Profit streaming (Yearn-style linear unlock) deliberately uses timestamps so depositors cannot sandwich harvests. On Hedera, consensus timestamps have narrow skew (~10–15s), which is negligible for hour/day unlock periods set by governance.

LynxVault.sol
mediumAccepted
M-03
Aderyn
StrategyManager pull/harvest interaction order
State updates after withdraw() / harvest() external calls.
Why accepted

The manager must observe strategy return values before adjusting principal or reporting PnL — reversing the order would break accounting. Access is keeper-only with nonReentrant; push was reordered to CEI, but pull/harvest order is intentional.

StrategyManager.sol
mediumAccepted
M-04
Aderyn
Centralized governance
Owner/keeper can change fees, strategies, caps, pause inflows, and move capital.
Why accepted

This is the protocol trust model, not a code bug. Mitigations include Ownable2Step (two-step ownership transfer), optional guardian for fast pause, exit/redemption paths that stay open when inflows are paused, MAX_EXIT_FEE_BPS ceiling, and per-strategy caps. Production governance is expected to be a multisig.

Core contracts
mediumAccepted
M-05
Slither + Aderyn
registerStrategy ManagerMismatch validation order
Aderyn H-3 / Slither: registerStrategy performs external view reads before writing strategy state.
Why accepted

Accepted. Leading calls validate strategyManager matches the vault before registration — a hardening guard against miswired strategies. No user funds at risk; only governance can register.

StrategyManager.sol — registerStrategy
mediumAccepted
M-06
Slither
bookLossToExposure reentrancy-benign
Slither reentrancy-benign on StrategyManager.bookLossToExposure after external strategy calls.
Why accepted

Accepted. Same pattern as bookLoss — keeper-only, nonReentrant manager path. Books NAV loss when principal exceeds on-chain exposureValue(); required for exposure-cap hardening.

StrategyManager.sol — bookLossToExposure
mediumAccepted
L-01
Slither
configure() allows zero addresses
SaucerSwapLiquidityStrategy.configure allows zero addresses for SaucerSwap dependencies.
Why accepted

Zero means “not configured yet.” All mutating LP functions revert with NotConfigured() until the admin sets real SaucerSwap router and NFT manager addresses. This is an intentional two-phase setup pattern.

SaucerSwapLiquidityStrategy.configure()
lowAccepted
L-02
Slither
setGuardian(address(0)) allowed
Guardian can be set to the zero address.
Why accepted

Clearing the guardian is intentional — it disables guardian-only pause so only the owner can pause. Documented in NatSpec. Useful when rotating or removing the guardian role without transferring ownership.

LynxVault.sol · LynxHTSBridge.sol
lowAccepted
L-03
Aderyn
Unsafe ERC20 on HTS paths
HtsOps.transfer / approve flagged as unsafe ERC20 by Aderyn.
Why false positive

These are Hedera HTS precompile calls at 0x167, not standard ERC20 transfer/approve with boolean return values. Aderyn’s ERC20 checker does not apply to HTS semantics.

libraries/HtsOps.sol
lowFalse positive
L-04
Aderyn
Cancun / PUSH0 bytecode
Bytecode may include PUSH0 (Cancun opcode).
Why accepted

evmVersion: cancun is pinned in Hardhat and Foundry profiles to match Hedera mainnet (Besu EVM, release 0.50.0+). PUSH0 is expected and required for Cancun-targeted deployments.

All production contracts
lowAccepted
L-05
Slither
LynxVault asset() unimplemented
Slither claims ILynxVault.asset() is not implemented.
Why false positive

asset() is implemented via override resolving both ERC4626 and ILynxVault in LynxVault.sol. Slither’s inheritance analysis missed the dual-interface override.

LynxVault.sol
lowFalse positive
L-06
Aderyn
Large numeric literal BASIS = 10_000
Aderyn L-2 flags the BASIS constant as a large numeric literal.
Why ignored

10_000 is the standard basis-points denominator (100.00%). Named constant improves readability; no security impact.

Core contracts
lowIgnored
L-07
Aderyn
nonReentrant not first modifier
Aderyn L-3: nonReentrant is not the first modifier on some LP admin functions.
Why ignored

Modifier ordering on owner-only admin functions has no demonstrated security impact. nonReentrant is still applied; reordering would be cosmetic.

SaucerSwapLiquidityStrategy.sol
lowIgnored
L-08
Aderyn
Unspecific pragma ^0.8.20
Aderyn L-8: broad ^0.8.20 pragma on production contracts.
Why ignored

CI compiles all contracts with solc 0.8.28. The broad pragma allows compatibility; the pinned compiler version in CI is what matters for deployment.

Production contracts
lowIgnored
L-09
Slither
Reentrancy-benign on StrategyManager
Slither reentrancy-benign on manager push/pull/harvest paths.
Why ignored

Paths are access-controlled (keeper or owner only). push was hardened with checks-effects-interactions (principal booked before external calls). No user-facing reentrancy surface.

StrategyManager.sol
lowIgnored
L-10
Slither
Reentrancy-events on harvest paths
Slither reentrancy-events on harvestLp / harvestStaking after HarvestCompleted event emission.
Why accepted

Events are emitted after external harvest calls for observability. No state corruption — harvest functions are access-controlled and the events document manager return values intentionally.

LynxKeeperRouter.sol
lowAccepted
L-11
Slither
block.timestamp on deployLpIdle deadline
Slither timestamp on LynxLpAllocator.deployLpIdle deadline calculation.
Why accepted

Deadline uses block.timestamp + buffer — standard swap deadline pattern. Hedera consensus timestamp skew is negligible for LP deploy timeouts.

LynxLpAllocator.deployLpIdle()
lowAccepted
L-12
Slither + Aderyn
setSplitRouter(address(0)) allowed
Slither missing-zero-check / Aderyn L-12 on StrategyManager.setSplitRouter.
Why accepted

Accepted by design. address(0) disables split-router alignment checks — same intentional pattern as setGuardian(0) for clearing optional wiring.

StrategyManager.setSplitRouter()
lowAccepted
L-13
Aderyn
setPositionPool without event (Aderyn L-11)
SaucerSwapLiquidityStrategy.setPositionPool updates state without emitting an event.
Why accepted

Accepted. Retroactive ops wiring for existing LP positions; optional event can be added in a future version. No depositor-facing exploit path.

SaucerSwapLiquidityStrategy.setPositionPool()
lowAccepted
I-01
Slither
Solc version warning on HtsOps
Broad ^0.8.0 pragma triggered Slither’s historical solc bug list.
Why resolved

Pragma updated to ^0.8.28 aligned with the rest of the stack. CI compiles with solc 0.8.28 — the warning is resolved in the current codebase.

libraries/HtsOps.sol
infoResolved

Notable disposition rows from Slither and Aderyn triage (July 2026 exposure-cap snapshot). Slither: 141 findings (4 High, 68 Medium, 64 Low, 5 Informational) — all triaged. Aderyn: 5 High themes, 16 Low themes (2,382 nSLOC). Harvest unused-return, LpAllocatorSet, H-4 cast, Halmos harness drift, and allocator loop fixes resolved in code. Wake not used. No open exploits from tooling.