Skip to content

(Medium) CON-E3.2 optimize liquidity-pool deposit/withdraw/distribute #152

Description

@EmeditWeb

Summary

The liquidity-pool deposit/withdraw/interest paths recompute the share price and re-read pool stats more often than a single transaction needs. deposit (contracts/liquidity-pool-contract/src/lib.rs:215-286) reads total shares (:227-228), computes calculate_share_price_internal (:247), then re-reads total shares again (:267-269) and total liquidity (:273-275) before writing each back. withdraw (:293-347) re-reads provider shares, total shares and total liquidity (:303,331-337) after already computing the share price (:309). distribute_interest_internal (:667-739) recomputes lp_amount/protocol_amount/merchant_amount from a fresh interest_amount while create_loan's fund path and get_pool_stats (:751-769) each recompute calculate_share_price_internal. This workstream minimizes the recomputations and repeated instance reads.

Why this matters: the property is each share-price computation and each pool-state read happens once per operation. The share price is derived from total_liquidity + dead shares over total_shares (:825-850); recomputing it after values have changed, or reading total_shares/total_liquidity twice, adds host cost with no benefit.

Labels

area: contracts type: refactor priority: medium


Workstream 1 — Single-pass share-price and pool-state handling in LP ops

Objective

Compute the share price and read pool state once per deposit/withdraw/distribute, reusing the values for the accounting writes, with identical shares/tokens issued.

Problem

deposit performs get_total_shares (contracts/liquidity-pool-contract/src/lib.rs:227-228), then calculate_share_price_internal (:247, which itself reads total_shares and total_liquidity at :826-827), then reads get_total_shares a second time (:267-268) and get_total_liquidity (:273-274) — four reads where two suffice. withdraw computes the share price (:309, reading both totals internally) and then re-reads get_total_shares (:331-332) and get_total_liquidity (:317-318) plus get_locked_liquidity (:319-320). get_pool_stats (:751-769) recomputes the share price (:759-760) even when the caller only needs available_liquidity. distribute_interest_internal re-reads get_token (:697) and get_total_liquidity (:726-727) after already having the amounts in hand.

Scope

  • contracts/liquidity-pool-contract/src/lib.rs (edit — deposit/withdraw/stats single-pass)
  • contracts/liquidity-pool-contract/src/storage.rs (edit — batch read/write helpers if needed)
  • contracts/liquidity-pool-contract/src/tests.rs (edit — parity tests)

Implementation

  1. In deposit, read total_shares and total_liquidity once, compute the share price from those locals, and write the three updated totals once each — removing the second get_total_shares/get_total_liquidity (:267-275).
  2. In withdraw, read total_liquidity, locked_liquidity and total_shares once; derive available_liquidity and the payout locally; write once (:317-337).
  3. Make calculate_share_price_internal accept optional cached totals (or add a _with variant) so callers that already hold the values do not force a second read (:825-850); keep the existing signature for external callers.
  4. Compute the get_pool_stats share price from the totals already read in the same function (:752-760).
  5. Gate any structural change behind POOL_SINGLE_PASS (default true) for bisecting.
  6. Emit harness-only pool_share_price_reads counter (via (Medium) CON-E3.1 gas harness: resource-fee benchmark scaffold + reproducible measurement script #118); default off.

Acceptance Criteria

  • Deposit/withdraw return identical shares/tokens for first-LP, subsequent-LP, partial and full-withdraw scenarios; get_share_price() is unchanged for every state.
  • The dead-share virtual-backing invariant ((total_liquidity + DEAD_SHARES_AMOUNT) * PRECISION / total_shares, :839-848) still holds, and the first-deposit 1:1 price (:245-252) is preserved.
  • Per-op share-price reads drop (observable via (Medium) CON-E3.1 gas harness: resource-fee benchmark scaffold + reproducible measurement script #118 CostRecord and a counting-storage test) with no behaviour change.

Testing

Shared acceptance criteria (whole epic)

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo build --locked --target wasm32-unknown-unknown --release, cargo test --locked all exit 0; test count does not drop; coverage on touched code not reduced.
  • No public entrypoint signature, error code, event or storage layout changes; share/token math identical.
  • No secrets; no changes outside contracts/liquidity-pool-contract/src/*.

Security & compatibility considerations

  • The dead-shares / virtual-backing mechanism (.deposit at :225-243, calculate_share_price_internal at :834-848) is a deliberate anti-dust defense; the optimization must reproduce it exactly, not "simplify" it away.
  • Floor-division directions (:249-252 shares issued floored, :310-315 return floored to favour the pool) must be preserved — rounding must never favour the provider.
  • TTL/instance-vs-persistent placement of the totals must not change (footprint affects rent): keep the same keys and durability.

References

  • contracts/liquidity-pool-contract/src/lib.rs:215-286 — deposit.
  • contracts/liquidity-pool-contract/src/lib.rs:293-347 — withdraw.
  • contracts/liquidity-pool-contract/src/lib.rs:667-739 — distribute_interest_internal.
  • contracts/liquidity-pool-contract/src/lib.rs:746-813 — get_share_price/get_pool_stats/calculate_withdrawal.
  • contracts/liquidity-pool-contract/src/lib.rs:825-850 — calculate_share_price_internal.
  • contracts/liquidity-pool-contract/src/storage.rs — total shares/liquidity accessors.
  • contracts/liquidity-pool-contract/src/tests.rs — deposit/withdraw/interest suites.
  • Depends on issue (Medium) CON-E3.1 gas harness: resource-fee benchmark scaffold + reproducible measurement script #118 (gas harness + baselines).

If you're solving this with AI

In scope: contracts/liquidity-pool-contract/src/lib.rs, contracts/liquidity-pool-contract/src/storage.rs, contracts/liquidity-pool-contract/src/tests.rs — only.
Out of scope: the other contracts; changing fee split, dead-shares or rounding rules; changing any error code or event; new dependencies; reformatting untouched modules.
Must: follow the repo PR template exactly; keep CI green for real (fmt + clippy + build + test); add the parity/regression tests above; reference this issue (Closes #<n>); no secrets.

Contribution requirements: Follow the repo PR template exactly — https://github.com/StepFi-app/StepFi-Contracts/blob/main/.github/pull_request_template.md. Reference this issue in your PR. CI must pass green (fmt + clippy + build + tests). No secrets; no out-of-scope changes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions