fix(lp): validate fund_loan recipients and cap outflows & merchant exposure - #111
Conversation
…n-recipient-validation-caps # Conflicts: # context/progress-tracker.md
EmeditWeb
left a comment
There was a problem hiding this comment.
✅ Automated Audit: solves
@Emeka-12 Excellent work, thank you! 🎉
The PR GENUINELY solves Issue #106. The root cause was fund_loan() honoring any merchant address with zero independent verification and no caps. The PR layers defense-in-depth: (1) optional vendor registry cross-check (fail-closed when set, skipped when unset for backward compatibility), (2) per-ledger outflow cap with rolling window reset keyed to ledger sequence, (3) cumulative merchant exposure cap. All three address the root causes: zero independent verification, unbounded drain rate, and unbounded concentration. Backward compatibility is maintained (default caps = 0 = disabled), all admin setters have proper auth guards, and 16 new tests plus 401 total passing tests cover the new behavior. PR title/description are substantive and of good quality. CI is intact and all 401 tests pass.
CI checks: ✅ PASSED: Build and Test Contracts
Merge conflicts: ✅ none — but the PR is blocked (failing/missing required checks or reviews).
Audited by stepfi-audit-bot 🤖
Closes #106
Summary
liquidity_pool::fund_loanpreviously paid anymerchantaddress the registered creditline supplied, with no independent recipient verification and no bound on how fast a misbehaving or compromised creditline could drain pool liquidity. This PR layers defense-in-depth guards on top of the existingrequire_creditlinecaller restriction (kept):set_outflow_cap_bps(1..=10_000 bps of available liquidity;0= disabled). Cumulativefund_loanoutflows within a ledger are bounded, with a rolling window keyed to the ledger sequence that resets automatically on ledger change.set_merchant_exposure_cap(token units;0= disabled) bounds cumulative funding to any one merchant. Cumulative exposure is tracked regardless (persistent with TTL) so admins can monitorget_merchant_fundedbefore enabling a ceiling.initialize()accepts an optionalvendor_registryaddress (also settable post-init via admin-onlyset_vendor_registry). When set,fund_loanrequires the recipient to be an active (approved) vendor via the registry'sis_active(), fail-closed (invocation errors block funding). Unset = backward-compatible legacy behavior.Changes
contracts/liquidity-pool-contract/src/lib.rs— guards, admin setters, getters, internalvendor_is_activecross-checkcontracts/liquidity-pool-contract/src/storage.rs— outflow window, exposure, vendor-registry storage + TTLcontracts/liquidity-pool-contract/src/types.rs— default cap constants (targeted, zero, default-disabled)contracts/liquidity-pool-contract/src/errors.rs—OutflowCapExceeded,MerchantExposureCapExceeded,VendorNotActive,InvalidCapcontracts/liquidity-pool-contract/src/events.rs—LOAN_FUNDEDnow carries(merchant, amount, outflow_remaining, merchant_remaining); newCAPSUPD/VREGUPDeventscontracts/liquidity-pool-contract/src/tests.rs— 16 new tests: cap enforcement under repeated calls, rolling window reset, guarded/validated admin setters, per-recipient independence, enabled/disabled vendor modes, legacy-restore, event payloadcontracts/creditline-contract/src/tests.rs— updated for newinitializesignaturescripts/deploy-testnet.sh— passes--vendor_registry, opt-inOUTFLOW_CAP_BPS/MERCHANT_EXPOSURE_CAPenv-gated capscontext/progress-tracker.md— documented entryAcceptance criteria
cargo test --locked: 401 passing (125 liquidity-pool, 143 creditline, 20 parameters, 60 reputation, 26 vendor-registry, 27 vouching)