Repository navigation
feat(lending): multi-collateral positions, per-asset liquidation incentive, configurable rate model (#1193, #1194, #1195) - #1457
Merged
github-actions[bot] merged 4 commits intoOct 17, 2026
Conversation
… contract compiles Smartdevs17#1355 renamed `BorrowDataKey::StableRateRecalcIntervalSecs` to `StableRateRecalcInterval` and removed `RateSwitchFeeBps`, while `get_stable_rate_recalc_interval_secs` and `get_rate_switch_fee_bps` still read them, so the lending contract no longer compiled. Restore both under their original names: Soroban encodes enum keys by variant name, so this also keeps values stored by existing deployments readable. Annotate the cached oracle price read, whose type could not be inferred. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…entive, configurable rate model (Smartdevs17#1193, Smartdevs17#1194, Smartdevs17#1195) Smartdevs17#1195: a borrow position now holds up to MAX_COLLATERAL_ASSETS (8) collateral assets in a per-user map. Legacy single-asset records are read transparently and migrated on the next write. Collateral value and the health factor sum every asset at its own oracle price. `liquidate` was a stub returning Ok(()); it now requires a health factor below 1.0, caps repayment by the close factor, seizes the liquidator's chosen collateral asset at repay * (1 + incentive), and caps the seizure at that asset's balance. It fails closed when any collateral asset has no usable price, so an oracle gap cannot make a healthy position liquidatable. Settlement mirrors `repay` (ledger only), since the borrow module holds no tokens in custody. Smartdevs17#1193: each collateral asset can override the global liquidation incentive. Both are admin-only and bounded to 1%..25% (previously 0..100%, and the global setter was not exposed). Liquidation applies the seized asset's incentive. Smartdevs17#1194: expose the kinked rate model through admin-gated `set_interest_rate_config` / `get_interest_rate_config`, plus borrow, supply and utilization views. Updates are validated as a whole and emit RateConfigUpdatedEvent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e model (Smartdevs17#1193, Smartdevs17#1194, Smartdevs17#1195) Integration suites against the public contract surface: per-asset health factor aggregation, the collateral asset cap, liquidation sizing (close factor, balance cap, price-dependent seizure), fail-closed pricing, incentive bounds and admin gating, and rate model outputs across every utilization region plus accrual. Pin the new events in event_topics. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…able rate model interest_rate_models.md described a model-switching API that was never merged; it now documents the kinked model and its configuration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@Abidoyesimze is attempting to deploy a commit to the smartdevs17's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@Abidoyesimze 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! 🚀 |
github-actions Bot
pushed a commit
that referenced
this pull request
Oct 17, 2026
Refs #1192 **This PR contains no code changes.** It adds one short note, `docs/ISSUE_1192_SETTLEMENT_NOTE.md`. #1192 was assigned to me through the Stellar Wave. It's a request from @OrderStream for manual bounty settlement for 11 already-merged security PRs. There's nothing in the codebase for a contributor to implement, so I can't resolve it with code. Settling and closing it is your call. The note records that the issue was reviewed and found to have no implementation work. It also mentions that `SECURITY.md` currently has no bounty or settlement section, which the issue refers to. The PR links the issue with **Refs** rather than **Closes**, so merging it won't close #1192; please close it yourself if you agree. If you'd prefer I not add a note file at all, close this PR and unassign me from #1192 instead. If there's a concrete deliverable you'd like, for example documenting a settlement process in `SECURITY.md`, let me know and I'll pick it up. The other issues assigned to me in this batch (#1193, #1194, #1195) were resolved in #1457. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1192
Closes #1193
Closes #1194
Closes #1195
All three changes are in
contracts/lending, where each issue's description matches the code:BorrowCollateral { amount, asset };liquidatewas a stub that returnedOk(());Prerequisite:
lendingdidn't compile onmain#1355 renamed
BorrowDataKey::StableRateRecalcIntervalSecstoStableRateRecalcIntervaland removedRateSwitchFeeBps, butborrow.rsstill reads both. The first commit restores the original names. Soroban encodes enum keys by variant name, so this also keeps values that existing deployments stored readable. It also annotates one type the compiler couldn't infer. This commit is standalone and compiles on its own.#1195 Multi-collateral positions
MAX_COLLATERAL_ASSETS(8) assets in a per-userMap<asset, amount>. Legacy single-asset records are read transparently and moved into the map on their next write, so no migration step is needed. The cap bounds the oracle calls made per health check.get_collateral_value,get_health_factor,get_max_liquidatable_amountandget_user_positionsum every asset at its own oracle price.get_user_collaterals(user)lists the balances.get_user_collateralkeeps working and returns the largest holding.liquidate(liquidator, borrower, debt_asset, collateral_asset, amount) -> LiquidationResult:min(amount, debt × close_factor).repay × debt_price × (1 + incentive) / collateral_price.LiquidationEvent.PriceUnavailableinstead of counting that asset as worthless. An oracle gap therefore can't make a healthy position liquidatable.TooManyCollateralAssets,SelfLiquidation,PriceUnavailable.#1193 Per-asset liquidation incentive
set_asset_liq_incentive_bps(admin, asset, Some(bps) | None)sets or clears an override.get_asset_liq_incentive_bps(asset)returns the incentive in effect. The globalset_liquidation_incentive_bps/get_liquidation_incentive_bpsare now exposed too.IncentiveOutOfBoundsotherwise). The internal setter previously accepted 0–100%.threshold × (1 + incentive) = 100%, so a liquidation never seizes more value than backs the debt it repays.LiqIncentiveUpdatedEvent.#1194 Configurable interest rate model
set_interest_rate_config(admin, InterestRateConfigUpdate)exposes every parameter of the existing kinked model: base, kink, slope, jump slope, floor, ceiling and spread.RateConfigUpdatedEvent.get_interest_rate_config,get_borrow_rate_bps,get_supply_rate_bps,get_utilization_bps.Design notes for review
repay. The borrow module holds no tokens in custody:deposit_collateral,borrowandrepaydon't transfer any. Paying out "seized" collateral as real tokens would draw on funds the module never received. Real custody would need deposit, borrow, repay and liquidation changed together, so it's a separate change.set_variable_borrow_rate_bpsalready behaves this way; fixing it needs a global borrow index.liq_incentive_updated_event,rate_config_updated_event). The entry points areset_asset_liq_incentive_bps/get_asset_liq_incentive_bpsfor the same reason.Testing
The in-crate unit tests don't compile on
main: about 50 errors in five files that test features which were never merged. Theadmin_privilegesintegration target is broken for the same reason (set_guardian/guardian_pause). I didn't touch either. The new tests are integration suites against the public contract surface:multi_collateral(new)liquidation_incentive(new)interest_rate_model(new)event_topics(+1 test pinning the new events)admin_config_auth,config_auth,fuzz_invariants,protocol_formal_specs,scenarios,user_journeysinterest_rate_modelchecks the model's output against hand-computed values at 0%, below the kink, at the kink, above it and at 100%, under both the default and a custom config. It also checks that the configured rate actually drives accrual over a year.As a check that the tests catch real regressions, I temporarily broke each feature and confirmed the matching tests fail:
Not verified locally: the
wasm32v1-nonerelease build. A soroban-sdk 28 build script in the dependency graph requires stellar-cli ≥ 25.2, and the local install is 25.1.Docs
liquidation.md.interest_rate_models.mdrewritten. It described a model-switching API (update_interest_rate_model) that was never merged.views.md,borrow.mdandREADME.mdupdated.🤖 Generated with Claude Code