feat(Contract): Cross-Chain Asset Data Structures and Enums #732#767
Merged
Conversation
…cverse#632) The risk engine valued collateral and debt with raw Decimal `*` and `/`, which (1) ignored each token's native on-chain precision, letting sub-unit dust retained by the flat NUMERIC(_,8) storage scale distort valuations; (2) could overflow rust_decimal's 28-significant-digit budget for high-value positions; and (3) compared the health factor at full precision while persisting it as DECIMAL(10,4), so the risk flag and the stored value could disagree at the liquidation-threshold boundary. Introduce pure, unit-tested helpers: - token_decimals(): native precision per asset (USDC 6, XLM 7, BTC 8, ETH 18) - normalize_amount(): round amounts to a token's native precision - value_in_usd(): normalize then price via SafeMath at a canonical USD scale - compute_health_factor(): SafeMath division rounded to the storage scale check_all_loans() now uses these and skips a plan (with a warning) instead of risking a panic when valuation arithmetic fails. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r improved maintainability and testing
Contributor
|
@martinvibes Pls kindly implement your issue description |
added 2 commits
June 18, 2026 11:59
…l handling in borrowing contract
…hots and add cross-chain inheritance module
ONEONUORA
approved these changes
Jun 18, 2026
ONEONUORA
left a comment
Contributor
There was a problem hiding this comment.
Great job @martinvibes
10 tasks
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.
fix(Contract): account for token decimals in collateral valuation (#732)
Summary
The risk engine's collateral valuation performed monetary arithmetic with raw
Decimaloperators that ignored each token's native on-chain precision, couldoverflow for high-value positions, and persisted the health factor at a
different precision than it used for the risk comparison. This PR replaces that
logic with a set of small, pure, fully unit-tested helpers that normalize token
amounts to their real precision, value both sides of a position safely, and make
a single consistent risk decision.
No schema changes, no API changes, no behavioral change for correctly-formed
data — this is a correctness and robustness fix for the edges.
Background
RiskEngine::check_all_loansperiodically recomputes the health factor of everyactive borrowing position and flags positions at risk of liquidation. For each
loan it:
loan_lifecycle.collateral_valueanddebt_valuein USD.health_factor = collateral_value / debt_value.persists
is_risky+health_factorback onto the plan.Token amounts are stored as
NUMERIC(30, 8)and thehealth_factorcolumn isDECIMAL(10, 4).Problem
The original valuation used raw
Decimal*and/:This produced three concrete defects:
1. Token decimals were ignored
Amounts are stored at a flat scale of 8, but each token has its own native
on-chain precision:
Any digits a stored amount carries beyond a token's real precision are dust that
should never have influenced a valuation. For example, a USDC balance with 8
stored decimals carries two decimals of dust beyond USDC's true 6-decimal
precision, which leaks directly into the USD value and, in turn, the health
factor and the liquidation decision.
2. Arithmetic could overflow
rust_decimalcarries a 28–29 significant-digit budget. Raw multiplication growsscale (8 + 8 = 16), so a sufficiently large position (large amount × large price)
can exhaust that budget and silently round, or panic, rather than failing
gracefully. A panic inside the risk-engine loop takes down the whole periodic
scan.
3. Health factor was compared and stored at different precisions
The health factor was compared against the liquidation threshold at full
precision, but persisted to a
DECIMAL(10, 4)column. At the threshold boundarythe in-memory risk flag and the value written to the database could disagree,
producing a position that reads as "not risky" in the DB while having been
flagged (or vice versa).
Solution
The valuation and the risk decision are extracted into pure, DB-free helpers,
each independently unit-tested. All arithmetic now goes through the existing
SafeMathmodule already used elsewhere in the codebase.token_decimals(asset)NUMERIC(_, 8)storage scale.normalize_amount(amount, asset)value_in_usd(amount, price, asset)SafeMath::mul, and returns the result at a canonical USD valuation scale.compute_health_factor(collat, debt)SafeMath::div(errors on zero debt), rounded to theDECIMAL(10, 4)storage scale used for both the comparison and persistence.liquidation_threshold_for_asset(asset, fallback)assess_position(...) -> Option<PositionAssessment><comparison.The consolidated decision:
assess_positionassess_positionreturns:Ok(None)when there is no positive debt to assess (nothing to flag),Ok(Some(assessment))with the full breakdown otherwise,Err(..)when any valuation overflows or debt-side arithmetic is invalid.check_all_loansnow delegates to it:Design decisions
<comparison. A position sitting exactly at its liquidationthreshold is not flagged — only positions below it are. This is encoded
and locked in by a dedicated boundary test.
the storage scale once, then used for both the risk decision and the DB write,
so the two can never diverge.
single offending plan; the periodic scan continues for everyone else.
SafeMath. No new arithmetic primitives — the fix builds on theoverflow-checked helpers already used across the codebase.
for decimals and the engine-wide threshold for liquidation, so the change is
conservative for assets not in the table.
Behavior changes
factors).
skip with a warning instead of panicking.
health_factorand theis_riskyflag are always consistent atthe threshold boundary.
outcome, clearer control flow).
Testing
20 unit tests, all exercising real behavior (no DB, no mocks), written test-first:
assess_position: healthy vs. undercollateralized, exact-threshold boundary, risk override, zero-debt →None, per-asset threshold selection, decimals normalization, overflow →ErrVerification
Files changed
backend/src/risk_engine.rsBackwards compatibility
check_all_loansareunaffected.
Checklist
SafeMathcargo fmt/cargo clippyclean