You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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: contractstype: refactorpriority: 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.
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).
In withdraw, read total_liquidity, locked_liquidity and total_shares once; derive available_liquidity and the payout locally; write once (:317-337).
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.
Compute the get_pool_stats share price from the totals already read in the same function (:752-760).
Gate any structural change behind POOL_SINGLE_PASS (default true) for bisecting.
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.
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.
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.
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), computescalculate_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) recomputeslp_amount/protocol_amount/merchant_amountfrom a freshinterest_amountwhilecreate_loan's fund path andget_pool_stats(:751-769) each recomputecalculate_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 overtotal_shares(:825-850); recomputing it after values have changed, or readingtotal_shares/total_liquiditytwice, adds host cost with no benefit.Labels
area: contractstype: refactorpriority: mediumWorkstream 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
depositperformsget_total_shares(contracts/liquidity-pool-contract/src/lib.rs:227-228), thencalculate_share_price_internal(:247, which itself readstotal_sharesandtotal_liquidityat:826-827), then readsget_total_sharesa second time (:267-268) andget_total_liquidity(:273-274) — four reads where two suffice.withdrawcomputes the share price (:309, reading both totals internally) and then re-readsget_total_shares(:331-332) andget_total_liquidity(:317-318) plusget_locked_liquidity(:319-320).get_pool_stats(:751-769) recomputes the share price (:759-760) even when the caller only needsavailable_liquidity.distribute_interest_internalre-readsget_token(:697) andget_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
deposit, readtotal_sharesandtotal_liquidityonce, compute the share price from those locals, and write the three updated totals once each — removing the secondget_total_shares/get_total_liquidity(:267-275).withdraw, readtotal_liquidity,locked_liquidityandtotal_sharesonce; deriveavailable_liquidityand the payout locally; write once (:317-337).calculate_share_price_internalaccept optional cached totals (or add a_withvariant) so callers that already hold the values do not force a second read (:825-850); keep the existing signature for external callers.get_pool_statsshare price from the totals already read in the same function (:752-760).POOL_SINGLE_PASS(defaulttrue) for bisecting.pool_share_price_readscounter (via (Medium) CON-E3.1 gas harness: resource-fee benchmark scaffold + reproducible measurement script #118); default off.Acceptance Criteria
get_share_price()is unchanged for every state.(total_liquidity + DEAD_SHARES_AMOUNT) * PRECISION / total_shares,:839-848) still holds, and the first-deposit 1:1 price (:245-252) is preserved.CostRecordand a counting-storage test) with no behaviour change.Testing
LP_FEE_BPS/PROTOCOL_FEE_BPS/MERCHANT_FEE_BPS,:674-695) unchanged.PoolStats.deposit/withdraw/distribute_interestshows strictly lower cost than baseline.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 --lockedall exit 0; test count does not drop; coverage on touched code not reduced.contracts/liquidity-pool-contract/src/*.Security & compatibility considerations
.depositat:225-243,calculate_share_price_internalat:834-848) is a deliberate anti-dust defense; the optimization must reproduce it exactly, not "simplify" it away.:249-252shares issued floored,:310-315return floored to favour the pool) must be preserved — rounding must never favour the provider.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.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.