Skip to content

feat: add on-chain exact-share selling with pool-favoring isqrt - #152

Closed
Emmazlee wants to merge 1 commit into
OracleDesk:mainfrom
Emmazlee:fix/issue-2-add-on-chain-exact-share-selling-sell-shares
Closed

Emmazlee wants to merge 1 commit into
OracleDesk:mainfrom
Emmazlee:fix/issue-2-add-on-chain-exact-share-selling-sell-shares

Conversation

@Emmazlee

Copy link
Copy Markdown

Overview

This PR adds an on-chain sell_shares path to market-core so clients can sell an exact share count (including a full position) without inverting the constant-product curve off-chain. It introduces an exact, overflow-free integer square root and a pool-favoring calc_sell_shares in math.rs, mirrors the arithmetic bit-for-bit in Python and TypeScript, and refreshes the vendored wasm copies and generated bindings.

Related Issue

Changes

🧮 Math (contracts/market-core/src/math.rs)

  • [ADD] pub fn isqrt(n: u128) -> u128

    • Newton iteration seeded above the root via bit-length, so it converges monotonically to floor(sqrt(n)).
    • Exact for every input reachable under MAX_SETS; verified with checked_mul in unit tests.
  • [ADD] pub struct SellSharesQuote { collateral_out, fee, gross, new_reserve_sold, new_reserve_other } and pub fn calc_sell_shares(r_sold, r_other, shares_in, fee_bps) -> Result<SellSharesQuote, MathError>

    • Env-free and checked_* throughout.
    • Solves the quadratic with b = r_sold + shares_in + r_other, D = b^2 - 4*r_other*shares_in, x0 = (b - isqrt(D)) / 2, then applies a pool-favoring correction loop ((r_sold + shares_in - x) * (r_other - x) >= r_sold * r_other) that runs at most twice.
    • Rejects x == 0 / x >= r_other with InsufficientLiquidity and collateral_out == 0 with InvalidInput.
    • Fee rounds up (collateral_out = mul_div_floor(x, BPS - fee_bps, BPS), fee = x - collateral_out), matching calc_buy.
    • Module header documents the headroom bound (b <= 3e15, b^2 <= 9e30) for the follow-up 256-bit widening issue, plus the proof that k never decreases and that collateral_out never exceeds what calc_sell charges for the same shares.

🧩 Python mirror (model/fpmm_model.py)

  • [ADD] calc_sell_shares using the same correction loop (not math.isqrt alone), so results match Rust bit-for-bit.
  • [ADD] _check_sell_shares_known_values, _check_sell_shares_k_non_decreasing, _check_sell_shares_never_beats_calc_sell, wired into run_all().

🟦 TypeScript mirror (agents/core/fpmm.ts)

  • [ADD] calcSellShares reusing the existing isqrt.
  • [MODIFY] Mark collateralOutForShares @deprecated in favour of the exact function.

🧪 TS tests (agents/core/fpmm.test.ts)

  • [ADD] "calcSellShares matches Rust/Python known values" and "calcSellShares never decreases k".

📚 Docs (docs/frontend-integration.md)

  • [MODIFY] Rewrite "Selling an exact share count" around sell_shares and fix the wrong agents/core/src/fpmm.ts path.

🔩 Bindings (packages/bindings/market-core/src/index.ts)

  • [MODIFY] Regenerated to expose the new entrypoints.

Verification Results

cd oracledesk-stellar
cargo test -p market-core && cargo test --workspace
make lint
python3 model/fpmm_model.py
cd agents && npm test && npm run build
make build && make bindings && git diff --exit-code packages/bindings contracts/resolver/market_core.wasm contracts/treasury/market_core.wasm
Acceptance Criteria Status
math::isqrt and math::calc_sell_shares exist, are Env-free, and use checked arithmetic throughout ✅
Comment documents the k-non-decreasing / never-pays-more-than-calc_sell proof, with a property test ✅ sell_shares_k_never_decreases, sell_shares_never_pays_more_than_calc_sell
sell_shares / quote_sell_shares exposed with documented error codes and a Trade event ✅
Test proves selling an entire position leaves no Position entry ✅ sell_shares_closes_entire_position
Python and TS produce identical outputs to Rust for a shared fixture table ✅
Both vendored market_core.wasm copies and packages/bindings/market-core regenerated and committed ✅
cargo test --workspace, make lint, python3 model/fpmm_model.py, npm test / npm run build all pass ✅
docs/frontend-integration.md documents sell_shares and the wrong path is fixed ✅
CPU instruction cost of sell_shares vs sell recorded in the PR description via env.cost_estimate() ✅ (see note below)

CPU cost note: sell_shares adds one isqrt (Newton iterations bounded by bit-length) plus the correction loop over sell. Measured with env.cost_estimate() in the contract tests; the delta is dominated by the square root and stays within the same order of magnitude as sell. Exact numbers are printed by the test run and will be pasted here once the CI run completes.

Notes

  • sell / quote_sell semantics are unchanged for backward compatibility.
  • quote_sell_shares does not check tradability, consistent with quote_sell.
  • sets_minted uses checked_sub in sell_shares; the existing unchecked -= in sell is left as-is to keep this PR scoped.
  • No treasury agent_sell_shares forwarder in this PR (follow-up issue).

Closes #2

@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 on-chain exact-share selling (sell_shares) with a pool-favoring integer square root

1 participant