Skip to content

options_market math/storage/lib, staking/streams fixes, CI security tooling - #296

Merged
techisigu merged 1 commit into
Zenith-options:mainfrom
joshking66:kiro/128-160-161-162-security-tooling-build-fixes-options-market
Oct 24, 2026
Merged

techisigu merged 1 commit into
Zenith-options:mainfrom
joshking66:kiro/128-160-161-162-security-tooling-build-fixes-options-market

Conversation

@joshking66

Copy link
Copy Markdown
Contributor

options_market math/storage/lib, staking/streams build fixes, CI security tooling (#128 #160 #161 #162)

Summary

Four issues addressed in one PR:


#162 — math.rs and storage.rs

options_market/src/math.rs

Implemented from scratch:

Constants (all exported, all pub const):

Constant Value Description
PRICE_PRECISION 10_000_000 Fixed-point scale for prices/contracts
RATE_PRECISION 1_000_000_000 Fixed-point scale for rates/implied vol
DEFAULT_FEE_RATE_BPS 50 0.5% default protocol fee
MAX_FEE_RATE_BPS 1_000 10% ceiling on configurable fee rate
MIN_COLLATERAL_RATIO 110 Put collateral = 110% of strike
SETTLEMENT_WINDOW 86_400 24h exercise window (seconds)
MAX_BATCH_SIZE 25 Max items per batch entrypoint
MAX_SERIES_PER_UNDERLYING 50 Default active-series cap per underlying
MAX_ACTIVE_SERIES_CEILING 500 Absolute cap ceiling
MAX_PAGE_SCAN 200 Max entries read per paginated call
MAX_PAGE_LIMIT 50 Max items per page response
PRUNE_RETENTION 2_592_000 30-day retention before pruning (seconds)

Functions:

  • calc_fee(amount, bps) -> i128 — floor(amount × bps / 10_000) using
    SafeMathRounding::mul_div_floor. Panics on overflow; zero-shortcircuits
    when either argument is zero.
  • calc_payout(contracts, strike, settlement, option_type) -> i128 —
    floor(contracts × intrinsic / PRICE_PRECISION). Call intrinsic:
    max(0, settlement − strike). Put intrinsic: max(0, strike − settlement).
    Returns 0 when OTM. Panics on overflow.
  • required_collateral(option_type, strike, contracts) -> i128 — Call: 100%
    of notional. Put: 110% of strike via mul_div_ceil(notional, 110, 100).

Proptest suites (10,000 cases each, matching README property table):

  • calc_fee: 0 ≤ fee ≤ amount; equals exact floor; monotone in amount
    and bps; zero when either arg is zero; overflow exactly on product overflow.
  • calc_payout: non-negative; equals exact floor of intrinsic; zero OTM;
    calls non-decreasing in settlement; puts non-increasing in settlement;
    monotone in contracts; call(K, S) = put(S, K) symmetry; at most one
    side non-zero.

options_market/src/storage.rs

All storage helpers needed by lib.rs, migrations.rs, and the existing
helper modules. Reads/writes go through ttl::get_persistent /
ttl::set_persistent for automatic TTL extension. Instance storage is
read/written directly (extended at the top of every entrypoint in lib.rs).

Functions:

  • is_paused, require_not_paused — pause guard
  • get_admin, set_admin, get_oracle, get_collateral_token,
    get_fee_recipient — instance key accessors
  • fee_rate_bps, set_fee_rate_bps — defaults to DEFAULT_FEE_RATE_BPS
  • next_series_id, next_position_id — auto-incrementing id allocators
  • series_counter — lifetime series count view
  • get_series, set_series, require_series, require_active_series
  • get_position, set_position, require_position
  • get_user_positions, add_user_position
  • premium_pool, set_premium_pool
  • series_escrow, set_series_escrow
  • get_vault, set_vault, get_series_vault, set_series_vault
  • get_underlying_price, set_underlying_price
  • total_open_interest, set_total_open_interest
  • total_premiums_collected, set_total_premiums_collected
  • series_count_for_underlying, set_series_count_for_underlying

#161 — lib.rs rewrite

The existing file had duplicated module-doc headers, duplicated use
blocks, and no #[contract] / #[contractimpl] at all. Replaced in full.

Structure:

  • Module declarations: all existing helper modules (call_collateral,
    error, events, math, migrations, multisig_client,
    params_client, price_oracle_client, rounding_policy,
    settlement_fallback, storage, ttl, types, vault_client) plus
    #[cfg(test)] mod test.
  • #[contract] pub struct OptionsMarket — the Soroban contract type.
  • Private helper functions shared between entrypoints and their
    _via_multisig twins (create_series_inner, settle_series_inner,
    exercise_inner, reclaim_inner, set_fee_rate_inner, pause_inner,
    unpause_inner, transfer_admin_inner, cancel_series_inner,
    update_premium_inner) — each admin action validated once, called from
    both the direct and the multisig path.

Entrypoints implemented:

Group Functions
Setup initialize
Admin transfer_admin, set_fee_rate, pause, unpause, upgrade, set_vault — each with _via_multisig twin
Series create_series, update_premium, cancel_series — each with _via_multisig twin
Oracle set_settlement_price (trusted-oracle), set_settlement_price_from_oracle (cross-contract, permissionless)
Trading buy_option, write_option, exercise, exercise_batch, reclaim_collateral, reclaim_batch
Refunds claim_refund, escrow_series_to_vault, claim_refund_from_vault
Views get_admin, is_paused, get_fee_rate, get_premium_pool, get_stats, get_series, get_position, get_user_positions, get_series_count_for_underlying, get_series_escrow, get_vault, get_series_vault, get_underlying_price

Key design points:

  • _via_multisig twins use zenith_common::is_executable(env, multisig, action_id, ActionClass::{Emergency|Standard|Critical}). Class assignment
    matches README "Timelock tiers" table. No auth duplication between twins.
  • Oracle cross-call uses env.invoke_contract against the get_price
    entrypoint — no source dependency on price_oracle, no symbol collision.
  • Vault cross-calls (deposit_legacy / withdraw_legacy) also via
    invoke_contract — no source dependency on vault.
  • escrow_series_to_vault checks that vault_contract == get_vault(env),
    records the vault address in SeriesVault(series_id), and zeroes
    SeriesEscrow. Subsequent claim_refund_from_vault reads SeriesVault
    (not the current pinned vault) so rotating the vault after an escrow
    deposit doesn't break outstanding refunds.
  • buy_option adds net_premium to SeriesEscrow; write_option adds
    collateral_amount. claim_refund and claim_refund_from_vault both
    debit SeriesEscrow, so escrow_series_to_vault always moves exactly
    what's still outstanding, never double-counting.

#160 — staking/streams build fixes

staking/Cargo.toml

Added zenith-common = { workspace = true } to [dependencies].
staking/src/lib.rs calls zenith_common::migrations::init_schema and
migrations/src/migrations.rs imports zenith_common::migrations::…;
both were always there, the dependency was simply missing from the manifest.

streams/Cargo.toml

Rewrote to use workspace conventions:

  • version.workspace = true, edition.workspace = true,
    publish.workspace = true (matches every other crate in the workspace).
  • soroban-sdk = { workspace = true } (was "21.0.0", mismatching the
    workspace pin 21.7.7).
  • Added zenith-common = { workspace = true } — same reason as staking.
  • Removed per-member [profile.release] block (ignored in workspace
    members; the workspace-level profile already covers it).

#128 — CI security tooling

.github/workflows/ci.yml

Four new jobs added after the existing budgets job:

Job Tool Behaviour
audit cargo-audit Fails build on any unpatched RustSec vulnerability
deny cargo-deny Checks licenses against deny.toml allow-list; warns on duplicate crate versions
scout CoinFabrik cargo-scout-audit Soroban-specific vulnerability patterns; continue-on-error: true (informational until baseline is established)
lint scripts/lint/check.sh Three grep-based project-specific checks (below)

Also fixed the duplicate wasm: job that existed in the original file.

deny.toml

New top-level file. Allow-list: MIT, Apache-2.0, Apache-2.0 WITH
LLVM-exception, BSD-2-Clause, BSD-3-Clause, ISC, Unicode-DFS-2016, Zlib,
CC0-1.0, MPL-2.0. Warns on copyleft. Skips duplicate-version warnings for
syn/proc-macro2/quote (hard-to-align transitive deps from the
soroban toolchain). Fails on unpatched RustSec advisories.

scripts/lint/check.sh

Executable bash script, three checks:

  1. No .unwrap() in entrypoint files. Finds files containing
    #[contractimpl] and flags any .unwrap() call not inside a //
    comment. Soroban entrypoints should use panic_with_error! so callers
    receive a typed ContractError, not a host trap with no diagnostic code.
    Exits 1 on any finding.

  2. require_auth present in admin functions. Warns (does not fail)
    when a file matching admin-function names has storage writes but no
    require_auth call. Best-effort heuristic; full enforcement needs
    dylint or semgrep.

  3. *_contract: Address params in internal functions. Warns when a
    non-pub function signature takes an Address parameter whose name
    ends in _contract. The _via_multisig pattern intentionally passes
    such parameters in public entrypoints; flagging them in internal helpers
    catches accidental trust-boundary bypasses (e.g. forwarding
    multisig_contract to an inner fn that then calls require_auth on it
    directly instead of is_executable).


Testing

  • cargo build --workspace should now succeed for all workspace members,
    including staking and streams which previously failed outright.
  • cargo test -p zenith-options-market now has a compilable contract to
    run against. The full test suite (100+ tests in test.rs plus the
    proptest suites in rounding_policy.rs and math.rs) should pass.
  • The security jobs (audit, deny, scout, lint) run on every
    push and pull request via CI.

Known gaps / out of scope

  • vault/src/lib.rs, price_oracle/src/lib.rs, multisig/src/lib.rs,
    and timelock/src/lib.rs remain empty stubs. The options_market
    cross-contract tests (escrow_then_claim_refund_from_vault,
    set_settlement_price_from_oracle, pause_via_multisig, etc.) use
    these contracts registered in the test env via their dev-dependency
    feature flags — they will compile and run once those contracts are
    restored.
  • Batch entrypoints buy_batch, write_batch, settle_long,
    sweep_forfeited, transfer_position, split_position,
    prune_positions, prune_series, release_series_slot,
    migrate_counters, and the paginated views are not yet implemented in
    this lib.rs. They are tracked by their own issues.
  • Scout findings are informational (continue-on-error: true) until a
    baseline suppression file is established.

Closes #128
Closes #160
Closes #161
Closes #162

…ooling

Closes Zenith-options#128
Closes Zenith-options#160
Closes Zenith-options#161
Closes Zenith-options#162

- options_market/src/math.rs: implement calc_fee, calc_payout,
  required_collateral, all 12 constants; proptest suites (10k cases each)
  for fee and payout properties per README table.

- options_market/src/storage.rs: all storage helpers — paused guard,
  admin/oracle/token/fee-recipient accessors, series/position CRUD,
  user-position lists, premium pool, series escrow, vault pins,
  underlying price, OI/premiums counters, per-underlying series counts.

- options_market/src/lib.rs: complete rewrite. #[contract] OptionsMarket
  with full #[contractimpl]: initialize; admin+_via_multisig (transfer_admin,
  set_fee_rate, pause, unpause, upgrade, set_vault); series lifecycle
  (create_series, update_premium, cancel_series) +_via_multisig; oracle
  settlement (set_settlement_price, set_settlement_price_from_oracle);
  trading (buy_option, write_option, exercise, exercise_batch,
  reclaim_collateral, reclaim_batch); refunds (claim_refund,
  escrow_series_to_vault, claim_refund_from_vault); all views.

- staking/Cargo.toml: add zenith-common = { workspace = true }.
  staking/src/migrations.rs and lib.rs both use zenith_common::migrations.

- streams/Cargo.toml: switch to workspace conventions (version.workspace,
  edition.workspace, publish.workspace), pin soroban-sdk to workspace
  version 21.7.7, add zenith-common = { workspace = true }, drop the
  per-member [profile.release] block.

- .github/workflows/ci.yml: add audit (cargo-audit), deny (cargo-deny),
  scout (CoinFabrik Scout, continue-on-error), lint (scripts/lint/check.sh)
  jobs; fix duplicate wasm job.

- deny.toml: new cargo-deny config with open-source license allow-list
  and RustSec advisory enforcement.

- scripts/lint/check.sh: three grep-based project-specific checks:
  no .unwrap() in entrypoints, require_auth in admin fns,
  *_contract Address params in internal functions.
@drips-wave

drips-wave Bot commented Oct 20, 2026

Copy link
Copy Markdown

@joshking66 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@techisigu
techisigu merged commit bc072f2 into Zenith-options:main Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants