This document is the formal description of the on-chain risk-pricing function:
how Creditra computes a credit limit and an interest rate for each borrower,
how interest accrues, how delinquency is priced, and how recovery is settled
in a default auction. Every formula here is implemented in the source under
contracts/credit/src/{risk.rs,accrual.rs,math_utils.rs,lifecycle.rs} and is
covered by the test files enumerated in
contracts/credit/tests/{accrual_overflow_audit.rs,risk_formula_tests.rs,...}.
Companion docs: docs/risk-based-rate-formula.md (terse normative reference),
docs/interest-accrual.md (accrual normative reference),
WHITEPAPER.md (protocol-level model).
| Input | Type | Range | Source |
|---|---|---|---|
| Risk score |
u32 |
[0, MAX_RISK_SCORE=100] |
Off-chain scorer pushed via update_risk_parameters (risk.rs:207) |
| Credit limit |
i128 |
[MinCreditLimit, MaxCreditLimit] |
Off-chain scorer / admin policy |
| Rate config |
RateFormulaConfig |
each u32 in [0, 10_000], r_{\min} \leq r_{\max} \leq 10\,000
|
set_rate_formula_config (lib.rs:1159) |
| Per-borrower floor |
Option<u32> |
[0, 10_000] |
set_borrower_rate_floor (lib.rs:578) |
| Per-borrower ceiling |
Option<u32> |
[0, 10_000] and floor <= ceiling when both are set |
set_borrower_rate_ceiling (lib.rs:775) |
| Rate-change config |
RateChangeConfig |
bps, seconds |
set_rate_change_limits (lib.rs:569) |
| Penalty surcharge |
u32 |
[0, 10_000] |
set_penalty_surcharge_bps (lib.rs:587) |
| Grace period |
GracePeriodConfig |
|
set_grace_period_config (lib.rs:646) |
| Utilization |
i128 |
[0, \ell] |
mutated on draw/repay |
| Accrued interest |
i128 |
[0, ...) |
mutated on accrual fold |
| Last accrual timestamp |
u64 |
unix seconds | updated only when |
The on-chain function is therefore deterministic in
Let
The on-chain rate function compute_rate_from_score
(contracts/credit/src/risk.rs:77) is:
where risk.rs:24).
The implementation uses saturating arithmetic on the u32
multiplication, so a misconfigured b + 100·s > u32::MAX saturates rather
than overflowing. The clamp then brings it back into the configured range.
Equivalent pseudocode:
pub fn compute_rate_from_score(cfg: &RateFormulaConfig, k: u32) -> u32 {
let raw = cfg.base_rate_bps.saturating_add(k.saturating_mul(cfg.slope_bps_per_score));
let upper = cfg.max_rate_bps.min(MAX_INTEREST_RATE_BPS);
raw.clamp(cfg.min_rate_bps, upper)
}After the formula computes
The floor is stored under DataKey::RateFloorBps(Address) (Persistent,
contracts/credit/src/storage.rs:357). Use cases: a borrower in a higher-risk
jurisdiction, or one with a sticky penalty history, can be assigned a hard
minimum rate that overrides a favorable formula.
The ceiling is stored under DataKey::RateCeilingBps(Address) (Persistent).
It caps that borrower's manual or formula-derived rate before rate-change
guardrails run. The admin setters reject inconsistent bounds in either
direction: a ceiling below an existing floor, or a floor above an existing
ceiling, reverts with ContractError::RateTooHigh.
When either bound is unset, that side of the clamp is skipped.
update_risk_parameters (risk.rs:207) further constrains rate changes via
RateChangeConfig:
Violations revert:
- Magnitude breach →
ContractError::RateTooHigh = 8 - Cadence breach →
ContractError::TimestampRegression = 33
last_rate_update_ts is only advanced when a rate actually changes, so a
no-op update_risk_parameters does not reset the cadence clock.
Configure:
base_rate_bps = 200 // 2.00 % floor
slope_bps_per_score = 50 // 0.5 % per score point
min_rate_bps = 200 // 2.00 %
max_rate_bps = 5000 // 50.00 %
For
| clamp to |
|
||
|---|---|---|---|
| 0 | 200 | 200 | 2.00 % |
| 25 | 1450 | 1450 | 14.50 % |
| 50 | 2700 | 2700 | 27.00 % |
| 75 | 3950 | 3950 | 39.50 % |
| 100 | 5200 | 5000 | 50.00 % (clamped) |
A borrower with
A borrower with
This example is the canonical test fixture in tests/risk_formula_tests.rs.
update_risk_parameters enforces |r_new - r_old| ≤ Δr_max when
RateChangeConfig is active (risk.rs:340-355).
Configure max_rate_change_bps = 200 (2.00 % per update).
| Scenario | Old rate | New formula rate | Delta | Permitted? |
|---|---|---|---|---|
| Gradual increase | 500 bps (5.00 %) | 650 bps (6.50 %) | 150 bps | Yes (150 ≤ 200) |
| Sharp increase | 500 bps (5.00 %) | 1 000 bps (10.00 %) | 500 bps | No (500 > 200) → revert RateTooHigh |
| Gradual decrease | 3 000 bps (30.00 %) | 2 850 bps (28.50 %) | 150 bps | Yes (150 ≤ 200) |
| Sharp decrease | 3 000 bps (30.00 %) | 2 500 bps (25.00 %) | 500 bps | No (500 > 200) → revert RateTooHigh |
| No change | 1 500 bps | 1 500 bps | 0 bps | Skipped (no delta check) |
The delta check uses u32::abs_diff (risk.rs:343), so the cap is
symmetric — it applies equally to rate increases and decreases.
Tested in tests/state_transition_invariants.rs (magnitude) and
risk_formula_tests.rs:514-535 (formula + rate-change limits).
update_risk_parameters also enforces a minimum interval between rate
changes when rate_change_min_interval > 0 (risk.rs:348-355).
Configure max_rate_change_bps = 500, rate_change_min_interval = 86 400
(1 day).
| Event | Timestamp | Rate change | Permitted? | Reason |
|---|---|---|---|---|
| Open line | t=0 | 500 → 500 bps | N/A | No prior rate |
| First update | t=3 600 (1 hr) | 500 → 700 bps (Δ=200) | Yes | No prior rate change |
| Second update | t=3 600 + 43 200 (12 hr) | 700 → 900 bps (Δ=200) | No | 43 200 s < 86 400 s → revert TimestampRegression |
| Third update | t=3 600 + 86 400 (1 day) | 700 → 900 bps (Δ=200) | Yes | 86 400 s ≥ 86 400 s |
| Fourth update | t=3 600 + 86 400 + 1 | 900 → 800 bps (Δ=100) | Yes | ≥86 400 s elapsed |
The cadence check is skipped when rate_change_min_interval == 0
(no minimum) or when last_rate_update_ts == 0 (no prior rate change,
risk.rs:348). last_rate_update_ts is only advanced when the rate
actually changes, so a no-op update does not reset the cadence clock.
Tested in tests/monotonic_timestamps.rs.
After the formula computes the rate, two optional per-borrower overrides
apply in sequence (risk.rs:328-338):
r_mid = max(r_formula, r_floor) // floor applied first
r_eff = min(r_mid, r_ceiling) // ceiling applied second
Example with RateFormulaConfig(200, 50, 200, 5 000):
| Scenario | Floor (bps) | Ceiling (bps) | |||
|---|---|---|---|---|---|
| No overrides | 25 | 1 450 | — | — | 1 450 bps (14.50 %) |
| Floor only | 0 | 200 | 1 000 | — | 1 000 bps (10.00 %) |
| Ceiling only | 75 | 3 950 | — | 2 500 | 2 500 bps (25.00 %) |
| Floor + ceiling sandwich | 50 | 2 700 | 3 000 | 4 000 | 3 000 bps (30.00 %) |
| Ceiling below floor (rejected) | 50 | 2 700 | 3 000 | 2 500 | Rejected at config-set time (RateTooHigh) |
The contract rejects inconsistent borrower-specific bounds in either
direction: ceiling < floor when setting a ceiling and floor > ceiling
when setting a floor. That prevents a misconfigured admin from creating an
unresolvable ordering. Tested in tests/borrower_rate_floor.rs and
tests/borrower_rate_ceiling.rs.
In v1 the on-chain function accepts an admin-supplied credit_limit and
validates it. The off-chain composition is:
where:
-
$\ell_{\text{base}}$ is a per-protocol base limit (e.g. 100 XLM equivalent) set by policy. -
$f(k, h, a, \alpha)$ is the off-chain multiplier as a function of the score$k$ , history vector$h$ (repayments, recoveries), attestation bundle$a$ , and the historical default recovery probability$\alpha$ . -
$\ell_{\text{min}}, \ell_{\text{max}}$ are the on-chain bounds set byset_credit_limit_bounds(min, max)(lib.rs:862, validated inlifecycle.rs:78-145).
The on-chain validation enforces:
Violations revert LimitOutOfBounds = 34 or NegativeLimit = 7.
update_risk_parameters reduces the limit relative to current utilization.
If the new status = Restricted (risk.rs:207). The borrower:
- Cannot draw further (the limit check fails).
- Can still repay normally.
- On repayments that reduce
$u$ below the new$\ell$ , the line auto-cures back toActive.
The off-chain protocol design intent is that a scorer who detects increased risk (e.g. a counterparty default in the borrower's transaction graph) can unilaterally tighten the credit line without forcing an immediate default — the line is rate-limited rather than terminated.
MaxTotalExposure (lib.rs:827) is an absolute ceiling:
Violations revert ExposureCapExceeded = 31. This is the protocol-wide
circuit breaker that bounds total loss from a misbehaving scorer or
misconfigured limits.
UtilizationCapBps(borrower) (storage.rs:364) is the per-borrower draw
ratio cap, applied at draw time as:
cap_bps = 0 removes the cap. Documented in docs/utilization-cap.md.
apply_accrual (contracts/credit/src/accrual.rs:87) is invoked at the
head of every state-mutating entrypoint. The pure-math part is in
math_utils::prorate_interest
(contracts/credit/src/math_utils.rs:244):
where:
-
$u$ isutilized_amountat the start of the fold -
$r_{\text{eff}}$ is the effective rate in bps (see §4.2) -
$\Delta t = t_{\text{now}} - t_{\text{last}}$ in seconds -
$Y$ =SECONDS_PER_YEAR = 31_557_600(Julian year,math_utils.rs:60) - Floor rounding via
Rounding::Floor(math_utils.rs:76)
Capitalization:
If
apply_accrual chooses
where interest_rate_bps. Standard case.
If is_delinquent(borrower) == true
(query.rs:57, which checks
The first delinquent accrual emits PenaltyRateEnteredEvent (topic
("credit","pen_enter")); the first non-delinquent accrual after delinquency
emits PenaltyRateExitedEvent (("credit","pen_exit")). Source:
accrual.rs, events in events.rs:278,296.
If the line is Suspended and a GracePeriodConfig { T_g, m, r_g } is set,
Then:
FullWaiver is the default (GraceWaiverMode::FullWaiver = 0,
types.rs:267).
Two reasons:
-
Gas predictability. Per-call accrual cost is
$O(1)$ — one mul, one div, one storage write per affected key. There is no on-chain compounding loop. A borrower who never touches the line for 5 years incurs the same accrual cost as one who repays daily. -
Floor rounding favors the borrower. Every
$\Delta I$ rounds down. The aggregate bias is against protocol revenue, never against borrower balance. This is intentional — it makes the contract trivially safe to audit for rounding-direction attacks.
The model in docs/interest-accrual.md notes this as a "checkpoint-on-
mutation" design and contrasts it with the per-block accrual of
Aave/Compound (which requires a separate accrue keeper or per-call
state mutation on every read).
A borrower draws 1 000 XLM (with XLM as a 7-decimal asset, so the
on-chain i128 value is
After 30 days (Δt = 2 592 000 seconds):
Numerator:
This is the realized 30-day interest at 15 % APR on 1 000 XLM, which is the
expected result (15 % × 30/365.25 × 1 000 ≈ 12.32 XLM). The on-chain stored
value is exactly tests/accrual_overflow_audit.rs confirms.
If the borrower is now delinquent (past next_due_ts + grace) with a
PenaltyRateEnteredEvent is emitted on
the first such accrual.
Same 1 000 XLM at 15 % APR, but the line is Suspended and the borrower
calls repay_credit 45 days after suspension. GracePeriodConfig is
set with
Split:
-
$\Delta t_g = \min(45,\text{d}, 30,\text{d}) = 30,\text{d} = 2,592,000$ s -
$\Delta t_p = 45 - 30 = 15,\text{d} = 1,296,000$ s
Grace-period accrual at 5 % APR for 30 days on 1 000 XLM:
Post-grace accrual at 15 % APR for 15 days:
Total
Compare to no-grace accrual (45 days at 15 %):
The grace mode reduced the realized interest by ~8.20 XLM.
Creditra uses simple interest capitalized at mutation. There is no automatic compounding loop. A borrower who draws and never touches the line for 5 years accrues interest linearly, not exponentially.
Take the §4.4 scenario (1 000 XLM at 15.00 % APR). Compare simple vs. compound interest over multiple years:
| Year | Simple interest accrued (cumulative) | Compound interest (annual compounding, cumulative) |
|---|---|---|
| 0 | — | — |
| 1 | 12.32 XLM | 12.32 XLM |
| 2 | 24.65 XLM | 26.49 XLM |
| 3 | 36.97 XLM | 42.59 XLM |
| 4 | 49.30 XLM | 60.99 XLM |
| 5 | 61.62 XLM | 82.23 XLM |
After 5 years the simple-interest borrower owes approximately 1 061.62 XLM, while a compound-interest equivalent would owe approximately 1 082.23 XLM — a difference of ~20.61 XLM in the borrower's favor.
The simple-interest design:
- Benefits consistent repayers. A borrower who repays frequently minimizes the principal on which future interest accrues, without being penalised by a compounding treadmill.
- Eliminates the compounding oracle problem. No need for keeper bots to periodically compound — the contract charges interest only when a mutation occurs.
-
Is trivially auditable. Every
$\Delta I$ is a singlemul_div(utilized, rate * Δt, 10_000 * Y, Floor)call. There is no compounding loop to reason about.
The multi-period test in contracts/credit/src/accrual_tests.rs:100-127
demonstrates the additive property across two six-month windows.
Floor rounding (Rounding::Floor in prorate_interest,
math_utils.rs:244) means very short time windows produce last_accrual_ts even on a zero-interest
fold (provided accrual.rs design). This means fractional "dust" is not accumulated
indefinitely — the contract trades sub-tick precision for gas efficiency.
Example: 1 000 000 XLM at 500 bps (5.00 % APR).
| Δt |
|
|
Note |
|---|---|---|---|
| 1 s | 0 | Sub-tick: rounds to 0 | |
| 3 600 s (1 hr) | 57 035 | ~0.00057 % of principal | |
| 86 400 s (1 day) | 1 368 847 | ~0.0137 % of principal | |
| 604 800 s (1 wk) | 9 581 932 | ~0.096 % of principal | |
| 31 557 600 s (1 yr) | 500 000 000 | Exactly 5.00 % |
A 1-second accrual on any realistic principal produces last_accrual_ts unconditionally (when
...but only if the line is touched every second, which is impractical.
In practice, real-world usage patterns produce
Tested in tests/accrual_overflow_audit.rs and
contracts/credit/src/accrual_tests.rs:173-190.
repay_credit (lib.rs:437-556) allocates a repayment
where lib.rs:63). The fee is transfer_from(borrower, contract, fee)
(accumulates in TreasuryBalance); the reserve portion is
transfer_from(borrower, liquidity_source, a_reserve). The admin later
calls withdraw_treasury(admin) (lib.rs:770) to drain accumulated fees
to TreasuryAddress.
If repayments were applied principal-first, a borrower making the minimum payment that covers interest would never amortize. Interest-first is the amortization-honest split that gives the borrower deterministic principal reduction per repayment dollar past the interest portion.
Charging the fee on principal would make the protocol fee an additional
borrowing cost, distinct from the interest rate. Fee-on-interest is
algebraically equivalent to: "the protocol takes a fixed cut of the
interest revenue". The borrower sees only the headline rate
FeeAccruedEvent is emitted only when
events.rs:170).
Continuing the §4.4 scenario. After 30 days the borrower owes:
- Principal: 1 000 XLM
- Accrued interest: 12.32 XLM
- Total utilized (post-capitalization): 1 012.32 XLM
The borrower repays
State after repay:
- Treasury balance:
$+0.246$ XLM - Reserve receives:
$99.754$ XLM (out of borrower's allowance) - Accrued interest:
$0$ - Utilized:
$1,012.32 - 100 = 912.32$ XLM - Schedule's
next_due_tsadvanced by floor($100 / \text{amount_per_period}$ ) installments.
default_credit_line(borrower) (lifecycle.rs:450) transitions the line
to Defaulted. The accrual is applied before the transition so the
recorded utilized_amount is the realized debt at default. An event
("credit","liq_req") is emitted with the outstanding amount
(events.rs:236):
DefaultLiquidationRequestedEvent {
borrower: Address,
utilized_amount: i128, // the debt
}
Off-chain orchestrator listens for this topic and constructs an auction.
Minimum next bid:
where min_increment_bps (init_auction parameter, capped at
10 000). The +1 floor prevents zero-increment grief at very small bids.
Refund of previous bidder is atomic with new bid record, under the
reentrancy guard (storage.rs:316, lib.rs place_bid English branch).
Anti-snipe: see WHITEPAPER.md §6.3 — documented in PR #430 but not
active in the live place_bid path; tracked as a known gap.
AuctionMode::Dutch with init params dutch_start_price = p_0,
dutch_floor_price = p_f, plus optional dutch_decay and
dutch_step_count.
dutch_decay = Linear(or omitted) keeps the original linear decay:
dutch_decay = Steppedsplits the same total drop intodutch_step_countequal time buckets and reprices only at bucket boundaries.dutch_step_countis required and must be greater than zero.
A bid Closed and records the
winner.
Auction with
|
|
|
|---|---|
| 0 | 1 200 XLM |
| 600 | 1 100 XLM |
| 1 200 | 1 000 XLM |
| 1 800 | 900 XLM |
| 2 400 | 800 XLM |
| 3 000 | 700 XLM |
| 3 600 | 600 XLM |
| 4 200 | 600 XLM (clamped to floor) |
A bid of 1 050 XLM at BidTooLow = 7). A bid of 1 100 XLM at
After the auction closes, admin calls
settle_default_liquidation(borrower, recovered_amount, settlement_id, oracle_price)
on the credit contract (lib.rs:953). The cross-contract call to the
auction's settle_default_liquidation(settlement_id, credit_addr, borrower)
returns highest_bid: i128, and the credit contract asserts this equals
the admin-supplied recovered_amount (else InvalidAmount).
Accounting:
If Closed and the repayment schedule is cleared.
The settlement event DefaultLiquidationSettledEvent (topic
("credit","liq_setl")) records the full breakdown for the indexer.
The protocol's empirical recovery rate for a line is:
This is computable directly from on-chain events:
("credit","defaulted") event;
("credit","liq_setl") events for that borrower. The scorer should feed
this back into future
A borrower defaults with the following state (lifecycle.rs:450):
- Principal drawn: 4 500 XLM
- Accrued interest: 500 XLM
- Utilized at default:
$u = 5,000$ XLM -
$I_{\text{accrued}} = 500$ XLM
The Dutch auction resolves with a winning bid of 3 000 XLM. Admin calls
settle_default_liquidation (lib.rs:953). The settlement math
(§6.4) gives:
Post-settlement state:
| Field | Before | After |
|---|---|---|
utilized_amount |
5 000 XLM | 2 000 XLM |
accrued_interest |
500 XLM | 0 XLM |
| Status | Defaulted | Defaulted (still outstanding) |
Since Defaulted and may be re-auctioned for
the remaining 2 000 XLM. The DefaultLiquidationSettledEvent records:
recovered_amount = 3 000, interest_settled = 500,
principal_settled = 2 500, remaining = 2 000.
Partial recovery scenario: If the auction recovers 6 000 XLM (above the full debt):
The surplus 500 XLM is not refunded to the defaulting borrower — the
recovery auction is an enforced liquidation, not a voluntary sale. The
contract caps the recovered amount at utilized_amount during settlement
validation. Status transitions to Closed and the repayment schedule is
cleared.
Tested in tests/credit_auction_e2e.rs and
tests/default_liquidation_settled_event.rs.
The empirical recovery rate
Borrower Alice has:
| Default event |
|
Settlement events | ||
|---|---|---|---|---|
| 2026-01-15 | 5 000 XLM | 3 000 XLM (Jan), 1 500 XLM (Feb) | 4 500 XLM | |
| 2026-06-01 | 2 000 XLM | 800 XLM (Jun) | 800 XLM | |
| Lifetime | 7 000 XLM | 5 300 XLM | 5 300 XLM |
The protocol's aggregate
This ratio feeds back into the off-chain multiplier
Every settlement emits ("credit","liq_setl") with the full breakdown
(events.rs:236), making
Tested in tests/default_liquidation_settled_event.rs.
The intended anti-snipe behavior, per PR #430, is:
- An extension window
$W$ beforeend_time. - A bid extension
$E$ added toend_timeif a qualifying bid lands inside$W$ .
Pseudocode:
if state.config.end_time - now < ANTI_SNIPE_WINDOW_SECS {
state.config.end_time = state.config.end_time + ANTI_SNIPE_EXTEND_SECS;
}Live place_bid (gateway-contract/.../lib.rs) does not extend
end_time and instead hard-rejects bids when now >= end_time. The
extension is documented but not active in this release. See
docs/SECURITY.md known gaps (§6.1) and WHITEPAPER.md §6.3.
| Protocol | Rate determination | Limit determination | Penalty mechanism |
|---|---|---|---|
| Aave v3 | Utilization-curve (r = r_0 + u·slope1 below kink, kinked slope above) |
Fixed LTV × collateral, fixed liquidation threshold | Liquidation bonus to keeper |
| Compound v3 | Utilization-curve | Per-asset risk parameter × collateral | Same |
| MakerDAO Spark | Stability fee (governance vote) | Vault min-collat ratio | Stability fee + auction discount |
| Creditra | r = clamp(b + k·s, r_min, min(r_max, 10000)) clamped by RateChangeConfig |
Off-chain score × policy multiplier, clipped to admin bounds | Penalty surcharge ρ added to base rate; grace mode; cross-contract auction recovery |
Key differences:
-
The borrower's behavior (the score
$k$ ) is a first-class input to the rate, not just to eligibility. A behaving borrower in good standing pays less; the rate adjustment is bounded byRateChangeConfigto prevent shock. - No utilization curve. Creditra's per-borrower rate is set by score, not by aggregate pool utilization. The market-wide utilization signal can be folded into the score off-chain if desired.
- The recovery rate is empirical, not assumed. Each settlement contributes one data point to the protocol's recovery distribution.
-
The penalty surcharge has an exit (
PenaltyRateExitedEvent). A delinquent borrower who cures returns to the base rate — this is the on-chain equivalent of a "good standing" credit-card mechanic.
| Behavior | Test file |
|---|---|
compute_rate_from_score clamp |
contracts/credit/src/risk_formula_tests.rs (inline tests) |
| Saturating arithmetic on rate | risk_formula_tests.rs |
| Per-borrower rate floor override | contracts/credit/tests/borrower_rate_floor.rs |
| Per-borrower rate ceiling interaction | contracts/credit/tests/borrower_rate_ceiling.rs |
| Rate-change cap (magnitude) | tests/state_transition_invariants.rs, worked example §2.5 |
| Rate-change cap (cadence) | tests/monotonic_timestamps.rs, worked example §2.6 |
| Floor-rounded accrual | tests/accrual_overflow_audit.rs, inline accrual_tests.rs |
| Sub-tick dust rounding | inline accrual_tests.rs, worked example §4.7 |
| Multi-year simple-interest accumulation | inline accrual_tests.rs, worked example §4.6 |
| Overflow safety | tests/accrual_overflow_audit.rs |
| Penalty surcharge entry/exit | tests/penalty_surcharge.rs |
| Grace waiver (FullWaiver vs ReducedRate) | tests/grace_waiver.rs, worked example §4.5 |
| Grace + ReducedRate split-window | inline accrual_tests.rs, worked example §4.5 |
| Restricted-on-limit-decrease | tests/restricted_status.rs |
| Interest-first repay allocation | tests/protocol_fee.rs |
| Fee accounting (protocol fee) | tests/protocol_fee.rs |
| Default → auction → settle flow | tests/credit_auction_e2e.rs, worked example §6.6 |
| Settlement replay protection | tests/default_liquidation_settled_event.rs |
| Recovery rate accounting | worked example §6.7, computable from events |
| Dutch auction curve | gateway-contract/.../test.rs, worked example §6.3.1 |
| Anti-snipe (open gap) | not currently tested in live path |
contracts/credit/src/risk.rs—compute_rate_from_score,update_risk_parameterscontracts/credit/src/accrual.rs—apply_accrual, branchescontracts/credit/src/math_utils.rs—prorate_interest,mul_div,Roundingcontracts/credit/src/lib.rs—repay_credit,settle_default_liquidationcontracts/credit/src/lifecycle.rs—default_credit_line,settle_default_liquidationgateway-contract/contracts/auction_contract/src/lib.rs— Dutch & English auctionsdocs/risk-based-rate-formula.md— normative referencedocs/interest-accrual.md,docs/interest-accrual-design.md— accrual referencesWHITEPAPER.md— protocol-level model