Skip to content

fix(farming-pool): add derived value ceilings to multiplier and credit-rate setters - #98

Merged
prodbycorne merged 2 commits into
SmartDropLabs:mainfrom
Cyber-Mitch:fix/89-farming-pool-value-ceilings
Jul 26, 2026
Merged

fix(farming-pool): add derived value ceilings to multiplier and credit-rate setters#98
prodbycorne merged 2 commits into
SmartDropLabs:mainfrom
Cyber-Mitch:fix/89-farming-pool-value-ceilings

Conversation

@Cyber-Mitch

Copy link
Copy Markdown
Contributor

Closes #89

Adds sanity ceilings to set_global_multiplier, set_credit_rate, and initialize, with the constants derived from compute_credits' actual overflow boundary rather than guessed — per the issue's explicit requirement ("this should be computed, not guessed").

Derivation

Worst-case overflow chain: amount_max × multiplier_max × credit_rate_max × elapsed_max ≤ i128::MAX / 16 (16x headroom).

compute_total_stake(amount, allocation_pct, multiplier) reduces to exactly amount × multiplier at allocation_pct = 100 — confirming the /100 division does not loosen the bound at the boundary; the naive product is tight, not conservative (stated explicitly per the issue's Additional Notes).

Inputs:

  • amount_max = 10^18 — 100 billion whole tokens at Stellar's 7-decimal stroop convention; far above any realistic pool TVL
  • elapsed_max = 63_072_000 ledgers — ~10 years at 5s/ledger
  • Headroom factor: 16x

Solving: (i128::MAX / 16) / (amount_max × elapsed_max) ≈ 1.686 × 10^11 budget for multiplier_max × credit_rate_max.

Chosen constants: MAX_GLOBAL_MULTIPLIER = 1_000, MAX_CREDIT_RATE = 100_000_000 (product = 10^11, under budget).

Verification: 10^18 × 1_000 × 10^8 × 63_072_000 ≈ 6.3 × 10^36 vs i128::MAX ≈ 1.7 × 10^38~27x headroom, exceeding the required 16x. Confirmed at runtime by test_compute_credits_no_overflow_at_ceilings at these exact ceilings.

Note: the issue sketch's pair (1_000 / 1_000_000_000) does not survive the derivation — its worst case fits under raw i128::MAX but with only ~2.7x headroom, failing the 16x margin. This is why MAX_CREDIT_RATE here is 10x smaller than the sketch. The full derivation is also documented as a doc comment on the constants in lib.rs.

Acceptance criteria

  • MAX_GLOBAL_MULTIPLIER/MAX_CREDIT_RATE constants added, chosen and documented with the derivation reasoning — lib.rs:56-57, full worked derivation as doc comment immediately above (formula, amount_max, elapsed_max, headroom factor, and the note that the issue sketch's 10^9 fails the 16x margin)
  • set_global_multiplier, set_credit_rate, and initialize all reject values above the ceilings with a typed PoolErrorlib.rs:558 (InvalidGlobalMultiplier), lib.rs:578 (InvalidCreditRate), lib.rs:240,243 (initialize, both)
  • test_set_global_multiplier_rejects_above_ceiling and test_set_credit_rate_rejects_above_ceiling added — test.rs, both passing
  • Test proving compute_credits never overflows within the accepted ranges — test_compute_credits_no_overflow_at_ceilings: both ceilings, allocation_pct = 100, amount_max = 10^18, elapsed_max = 63,072,000 ledgers; checkpoints without panic, credit value matches the derivation exactly. Constants intended for reuse by farming-pool: no fuzz/boundary test exercises i128 overflow boundaries in compute_credits/compute_total_stake #76's boundary/fuzz tests, tying this fix to the companion issues per the criterion's wording

Tests (7 new, 110 total passing, 0 failures)

  • test_set_global_multiplier_rejects_above_ceiling / test_set_credit_rate_rejects_above_ceiling (named in acceptance criteria)
  • test_initialize_rejects_global_multiplier_above_ceiling / test_initialize_rejects_credit_rate_above_ceiling (initialize-path gap from Additional Notes)
  • test_set_global_multiplier_accepts_exactly_the_ceiling / test_set_credit_rate_accepts_exactly_the_ceiling (ceilings are inclusive as documented)
  • test_compute_credits_no_overflow_at_ceilings — pool at both ceilings, allocation_pct = 100, amount_max, ledgers advanced to elapsed_max; checkpoints without panic, credit value matches the derivation

Existing test updated

test_admin_multiplier_rejects_zero previously matched the literal panic string of the assert! this PR converts to a typed error. Updated minimally to assert rejection via try_set_global_multiplier — identical rejection behavior (multiplier = 0 still rejected), now via the typed-error mechanism. No other existing test used values above the new ceilings.

Cross-references


Task completed
@prodbycorne Please Review

@netlify

netlify Bot commented Jul 17, 2026

Copy link
Copy Markdown

Deploy Preview for sdcontracts ready!

Name Link
🔨 Latest commit 34bc466
🔍 Latest deploy log https://app.netlify.com/projects/sdcontracts/deploys/6a623df49559cf0007ba9616
😎 Deploy Preview https://deploy-preview-98--sdcontracts.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@Cyber-Mitch

Copy link
Copy Markdown
Contributor Author

@prodbycorne Please Review

@prodbycorne
prodbycorne merged commit 4113e95 into SmartDropLabs:main Jul 26, 2026
4 checks passed
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.

farming-pool: set_global_multiplier and set_credit_rate accept unbounded values with no sanity ceiling, guaranteeing compute_credits overflow at scale

2 participants