Skip to content

feat: add split and merge entrypoints to market-core - #166

Open
Emmazlee wants to merge 9 commits into
OracleDesk:developfrom
Emmazlee:security/issue-5-add-complete-set-split-and-merge-entrypoints-to-r2
Open

Emmazlee wants to merge 9 commits into
OracleDesk:developfrom
Emmazlee:security/issue-5-add-complete-set-split-and-merge-entrypoints-to-r2

Conversation

@Emmazlee

@Emmazlee Emmazlee commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Overview

This PR adds the two missing complete-set primitives to market-core: split (mint one YES + one NO per unit of collateral) and merge (burn one YES + one NO for one unit of collateral). Neither touches the FPMM pool, charges a fee, or moves the price. Alongside the entrypoints, this PR restates the core accounting invariants, adds a merge_max helper across the Rust/Python/TS models, exposes a quote_merge_max view, publishes SetsSplit / SetsMerged events, and hardens sell's sets_minted accounting with checked_sub.

Related Issue

Changes

🔀 Split / merge entrypoints (contracts/market-core/src/lib.rs)

  • [ADD] split(holder, market_id, amount)

    • Requires assert_not_paused and assert_tradable (Open + now < close_time).
    • holder.require_auth(); rejects amount <= 0 with InvalidAmount.
    • Bumps sets_minted with checked_add, enforces MAX_SETS (MarketTooLarge), credits both legs with checked_add, saves position/market, then transfers collateral holder -> contract (effects before interaction, matching buy).
    • Publishes SetsSplit { market_id, holder, amount }.
  • [ADD] merge(holder, market_id, amount)

    • Requires MarketStatus::Open only — allowed after close_time and while paused (documented asymmetry: merge is a pure exit that cannot harm the pool).
    • Rejects unbalanced positions with InsufficientShares (Make positions and LP ownership transferable to enable treasury migration #13); sets_minted decremented via checked_sub.
    • Saves position (deletes when both legs hit zero), saves market, transfers contract -> holder, publishes SetsMerged { market_id, holder, amount }.
  • [MODIFY] sell now uses checked_sub for m.sets_minted -= q.gross.

  • [ADD] quote_merge_max(market_id, holder) -> i128 view wrapping merge_max over get_position.

  • [ADD] #[contractevent] SetsSplit / SetsMerged with market_id and holder as topics.

🧮 Shared helper (math.rs, model/fpmm_model.py, agents/core/fpmm.ts)

  • [ADD] merge_max(yes, no) = min(yes, no) in Rust, mirrored in the Python model and TS agent core so frontends and agents can offer "merge all" consistently.

📚 Docs

  • [MODIFY] docs/architecture.md

    • Restates reserve_yes + Σ position.yes == sets_minted (and NO) and token.balance == sets_minted + fees_accrued.
    • Split/merge invariant proof: split adds amount to sets_minted and both leg sums; Resolved(Yes) pays exactly sets_minted; Void pays Σ floor((y_i+n_i)/2) + floor((r_y+r_n)/2) <= sets_minted.
    • Lifecycle rules for split (Open + pre-close) vs merge (Open, incl. post-close / paused).
    • MAX_SETS griefing note: a splitter can lock capital to push buy over MarketTooLarge, but must lock real collateral and can merge out; documented as accepted risk pending a future per-market split cap.
    • Void dust note: more holders means more 0.5-unit flooring loss, bounded by holder count.
  • [MODIFY] docs/frontend-integration.md — describes split / merge / quote_merge_max and the new events for the indexer.

🔧 Bindings

  • [MODIFY] packages/bindings/market-core/src/index.ts — regenerated to include split, merge, quote_merge_max, and the SetsSplit / SetsMerged event spec.

Verification Results

cd oracledesk-stellar
cargo test -p market-core
cargo test --workspace
make lint
python3 model/fpmm_model.py
cd agents && npm test
make bindings
Acceptance Criteria Status
split and merge exist with documented auth, pause and lifecycle rules ✅
Neither entrypoint changes reserve_yes, reserve_no or fees_accrued ✅ asserted in tests
SetsSplit / SetsMerged events published and in regenerated bindings spec ✅
Invariant test across mixed split/buy/sell/merge sequence + void payout-sum test ✅
sell uses checked_sub for sets_minted ✅
wrong_caller_cannot_* tests with targeted mock_auths for split and merge ✅
MAX_SETS griefing decision documented in PR and docs/architecture.md ✅
Vendored wasm and packages/bindings/market-core regenerated ✅
cargo test --workspace, make lint, Python model, agents tests pass ✅
docs/architecture.md lifecycle/invariants and docs/frontend-integration.md updated ✅

Notes / Non-goals

  • Treasury agent_split / agent_merge forwarders are intentionally deferred to a follow-up.
  • Merging after finality is out of scope — redeem already covers that path.
  • Multi-LP liquidity provision (docs/wave-issues/02) is untouched; split/merge never touch the pool.
  • No changes to fee policy or the Market struct layout.

Closes #5

@Emmazlee Emmazlee changed the title feat: add split/merge entrypoints to market-core feat: add split and merge entrypoints to market-core Oct 20, 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

Development

Successfully merging this pull request may close these issues.

Add complete-set split and merge entrypoints to market-core

1 participant