Skip to content

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

Closed
Emmazlee wants to merge 1 commit into
OracleDesk:mainfrom
Emmazlee:security/issue-5-add-complete-set-split-and-merge-entrypoints-to
Closed

Emmazlee wants to merge 1 commit into
OracleDesk:mainfrom
Emmazlee:security/issue-5-add-complete-set-split-and-merge-entrypoints-to

Conversation

@Emmazlee

Copy link
Copy Markdown

Overview

This PR adds split and merge entrypoints to market-core, giving holders a fee-free way to mint and burn complete sets of YES + NO shares against collateral without touching the FPMM pool. It also adds the SetsSplit / SetsMerged events, a merge_max helper (Rust, Python, TS), a quote_merge_max view, tightens sell's sets_minted accounting to checked_sub, and documents the new lifecycle rules, invariants, and MAX_SETS griefing trade-off.

Related Issue

Changes

🔀 Split / Merge Primitives

  • [MODIFY] oracledesk-stellar/contracts/market-core/src/lib.rs

    • split(holder, market_id, amount) — requires assert_not_paused + assert_tradable (Open, before close), pulls amount collateral, credits amount YES and amount NO, bumps sets_minted with checked_add and enforces MAX_SETS. Effects-before-interaction, matching buy.
    • merge(holder, market_id, amount) — requires MarketStatus::Open but is allowed after close_time and while paused; checks both legs with InsufficientShares (Make positions and LP ownership transferable to enable treasury migration #13), decrements sets_minted via checked_sub, pays amount collateral back at par.
    • Neither entrypoint touches reserve_yes, reserve_no, or fees_accrued.
    • SetsSplit / SetsMerged #[contractevent]s with #[topic] market_id and #[topic] holder.
    • quote_merge_max(market_id, holder) -> i128 view wrapping math::merge_max over get_position.
    • sell now uses checked_sub for m.sets_minted instead of the previous unchecked -=.
  • [MODIFY] oracledesk-stellar/contracts/market-core/src/math.rs

    • merge_max(yes, no) -> i128 returning min(yes, no) so frontends can offer "merge all".
  • [MODIFY] oracledesk-stellar/agents/core/fpmm.ts

    • Mirrors merge_max for the TS agent stack.
  • [MODIFY] oracledesk-stellar/packages/bindings/market-core/src/index.ts

    • Regenerated bindings including split, merge, quote_merge_max, and the new event spec.

📚 Docs & Security

  • [MODIFY] oracledesk-stellar/docs/architecture.md

    • Lifecycle rules for split (Open + before close + unpaused) vs merge (Open, allowed after close and while paused) with the asymmetry rationale.
    • Restated invariants: reserve_yes + Σpos.yes == sets_minted (and NO), balance == sets_minted + fees_accrued, and the resolved/void payout bounds including the flooring argument.
    • MAX_SETS griefing note: an attacker can split up to MAX_SETS and block further buy calls at the cost of locked capital, then merge out risk-free; decision recorded (no separate split cap in this PR).
  • [MODIFY] oracledesk-stellar/docs/frontend-integration.md

    • Describes split / merge / quote_merge_max for integrators.
  • [MODIFY] oracledesk-stellar/SECURITY.md

    • Records the pause-semantics decision: merge remains callable while paused because it is a pure exit that cannot harm the pool.

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 ✅ Implemented with require_auth, assert_not_paused / assert_tradable per the asymmetry above
Neither entrypoint changes reserve_yes, reserve_no or fees_accrued ✅ Asserted in tests
SetsSplit / SetsMerged events published and in regenerated bindings spec ✅ Added and reflected in packages/bindings/market-core
Invariant test across mixed operation sequence + void payout-sum test ✅ invariant_holds_after_mixed_split_buy_sell_merge, void_total_payout_never_exceeds_sets_with_split
sell uses checked_sub for sets_minted ✅ Switched
wrong_caller_cannot_* tests with targeted mock_auths for split and merge ✅ Added
MAX_SETS griefing decision documented in PR and docs/architecture.md ✅ Documented (no separate split cap in this PR)
Vendored wasm copies and packages/bindings/market-core regenerated ✅ Via make build + make bindings
cargo test --workspace, make lint, Python model, agents tests pass ✅
docs/architecture.md lifecycle/invariant sections and docs/frontend-integration.md updated ✅

Non-goals

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

Closes #5

@drips-wave

drips-wave Bot commented Oct 16, 2026

Copy link
Copy Markdown

@Emmazlee 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

@github-actions

Copy link
Copy Markdown

👋 Thanks for the contribution! This repository only accepts contributor pull requests into the develop branch. PRs raised against main cannot be merged, so this one has been closed automatically.

Please re-open your PR with base: develop (rebase your branch onto develop first if needed). See the note at the bottom of the issue you're working on.

@github-actions github-actions Bot closed this Oct 16, 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