feat(scanner): 3-bucket DashMap health-factor scanner#34
Merged
Conversation
Classifies `Position` records into healthy / near-liquidation / liquidatable buckets based on `health_factor`, backed by a lock-free DashMap keyed by borrower address. Unblocks the profit calculator and flash-loan router (Day 3 work) with a proper "what's currently liquidatable" query. - `HealthScanner::new(liquidatable, near_liq)` validates and scales float thresholds once to 1e18-fixed U256; per-tick comparisons stay in integer arithmetic - `upsert`, `classify`, `bucket_counts`, `liquidatable`, `near_liquidation` — the surface downstream stages consume - New `liquidatable_threshold` / `near_liq_threshold` fields on `BotConfig` with sane defaults (1.0 / 1.05) via serde `default`, so the existing config keeps loading cleanly - `dashmap 6` added as a workspace dep - CLI `listen` now pipes Venus scans through the scanner and emits per-block bucket counts alongside the existing fields - Five unit tests cover boundary classification, bucket transitions on upsert, and threshold validation (NaN / inverted)
This was referenced Apr 22, 2026
[PR #34] NearLiquidation bucket dead code while adapter returns binary HF sentinel (blocks #98)
#104
Closed
…metrics
- BotConfig drops the f64 liquidatable_threshold / near_liq_threshold in
favour of integer basis-point fields (10_000 = 1.0e18). bps_to_1e18()
uses integer arithmetic only; 10_500 bps -> exactly 1.05e18 rather
than the ULP-short 1_049_999_999_999_999_872 the old f64_to_1e18()
produced.
- Add hot_scan_blocks / warm_scan_blocks / cold_scan_blocks to BotConfig
(defaults 1 / 10 / 100). ScanScheduler in charon-scanner consumes them
and answers should_scan(bucket, block) so COLD positions stop burning
RPC every block. main.rs now scans only the union of buckets whose
cadence fires on the current block.
- HealthScanner tracks per-borrower bucket transitions inside upsert()
and emits charon_scanner_transitions_total{from,to}. bucket_counts()
feeds gauge charon_scanner_borrowers_in_bucket{bucket}. The CLI's
scan loop records charon_scanner_scan_duration_seconds histogram per
block. Grafana dashboards now have signals instead of log scraping.
- HealthScanner::remove(addr) and HealthScanner::prune(current) so
borrowers that fully repay stop lingering as stale Liquidatable
entries after adapter.fetch_positions stops returning them. prune() is
invoked by main.rs every block after upsert.
- warn_if_binary_sentinel() emits a loud warning if every observed HF
is 0 or 2e18, making the (ties to #98) binary-HF dependency visible
at runtime instead of silently dead-coding the NearLiquidation bucket.
- HealthScanner::borrowers_in_bucket(b) exposed so the scheduler knows
which addresses to refetch for each cadence.
Closes #103 #104 #105 #106 #107
# Conflicts: # Cargo.lock # Cargo.toml # crates/charon-cli/Cargo.toml # crates/charon-cli/src/main.rs # crates/charon-scanner/Cargo.toml # crates/charon-scanner/src/lib.rs
4 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.
Closes #10
Classifies
Positionrecords into healthy / near-liquidation / liquidatable buckets based onhealth_factor, backed by a lock-freeDashMapkeyed by borrower. Unblocks the profit calculator and flash-loan router with a proper "what's currently liquidatable" query.HealthScanner::new(liquidatable, near_liq)— validates and scales float thresholds once to 1e18-fixed U256; per-tick comparisons stay in integer arithmeticupsert,classify,bucket_counts,liquidatable,near_liquidation— surface consumed by downstream stagesliquidatable_threshold/near_liq_thresholdonBotConfigwith sane defaults (1.0 / 1.05) via serdedefaultdashmap 6added as workspace deplistenpipes Venus scans through the scanner and emits per-block bucket counts alongside existing fieldsFive unit tests cover boundary classification, bucket transitions, threshold validation (NaN / inverted).
Depends on #9 (
feat/08-venus-adapter).