feat(protocols): Venus adapter (charon-protocols crate, closes #9)#33
Merged
feat(protocols): Venus adapter (charon-protocols crate, closes #9)#33
Conversation
…ub (#8) First commit in the #8 series. Lays out the crate layout so subsequent commits can land ABIs, RPC wiring, and the LendingProtocol impl without simultaneously introducing new module boundaries. - New `charon-protocols` workspace member with a single `venus` module - `VenusAdapter` is a minimal struct holding the Comptroller address; provider, vToken discovery, and trait impl land in follow-ups - No scanner/executor consumption yet — zero behaviour change for existing commands (`listen`, `test-connection`)
#8) Second commit in the #8 series. Defines typed RPC bindings for every Venus method the scanner and executor need, using alloy's `sol!` macro with `#[sol(rpc)]` so each interface gets a `new(address, provider)` constructor and per-method decoding for free. - `IVenusComptroller`: getAccountLiquidity, getAssetsIn, getAllMarkets, closeFactorMantissa, liquidationIncentiveMantissa, oracle - `IVToken`: underlying, balanceOf, borrowBalanceStored/Current, balanceOfUnderlying, exchangeRateStored, decimals, symbol, liquidateBorrow - `IVenusOracle`: getUnderlyingPrice (Compound-style scaling) Still no runtime logic — bindings only. VenusAdapter continues to be a scaffold struct; the LendingProtocol impl lands next commit.
Third commit in the #8 series. Wires the adapter to a shared pub-sub provider and caches Venus's rarely-changing market config at connect time, so per-block calls don't re-hit the Comptroller for invariants. - `VenusAdapter::connect(provider, comptroller)` fetches the oracle address, vToken market list, and close factor via three read-only RPCs; stores them on the struct - Shared provider held as `Arc<RootProvider<PubSubFrontend>>` so the scanner can hand the same provider to multiple adapters cheaply - Live integration test (`tests/venus_connect.rs`) hits BSC mainnet when `BNB_WS_URL` is set, skipped otherwise — proves the Comptroller roundtrip works end to end Venus's Diamond Comptroller does not expose `liquidationIncentiveMantissa()` globally (per-market in Diamond facets); that lookup is deferred to the liquidation builder in a later commit rather than bundled into `connect`.
Fourth commit in the #8 series. Implements on-chain position discovery for Venus — the core translation that turns Compound V2's `(errorCode, liquidity, shortfall)` + per-vToken balances into the shared `Position` shape the scanner consumes. - `VenusAdapter::connect` now also resolves every vToken's `underlying()` address and caches both directions of the map; vBNB-style native-wrapping markets (no `underlying()`) are skipped with a debug log and left unmapped - Adapter tracks its own `chain_id` via `eth_chainId` for position metadata - `fetch_position_inner` walks `getAssetsIn`, reads per-vToken `borrowBalanceStored` + `balanceOfUnderlying` + oracle price, and picks the single biggest-value debt vToken and biggest-value collateral vToken as the Position to report - Per-asset failures (oracle revert, missing market) are logged and skipped so one broken market can't blank an entire borrower - Health factor synthesised as a binary 0 / 2e18 signal from Venus's `shortfall`: enough for the scanner's `< 1e18` predicate, precise HF arithmetic deferred to the 3-bucket scanner (#9) - `LendingProtocol::get_liquidation_params` and `build_liquidation_calldata` are stubbed with explicit errors — they land in the next commit alongside per-market liquidation incentive lookup Live integration test (`tests/venus_fetch.rs`) calls `fetch_positions` for a clean address on BSC mainnet, verifying the full pipeline returns well-formed (or empty) results without panicking.
) Fifth commit in the #8 series. Completes the `LendingProtocol` impl on the Rust side — the trait no longer returns `bail!("unimplemented")` and an opportunity can now be turned into ready-to-sign bytes. - `get_liquidation_params` maps underlying ERC-20 debt / collateral addresses on the `Position` back to Venus vToken addresses via the adapter's lookup map, and caps `repay_amount` at `debt_amount × close_factor / 1e18` (50% on BSC Venus). Refuses to emit a zero-repay params struct. - `build_liquidation_calldata` delegates to a free `encode_liquidate_borrow_calldata` helper that encodes `VToken.liquidateBorrow(borrower, repayAmount, vTokenCollateral)` via alloy's generated call struct + `SolCall::abi_encode`. Split as a free function so it can be unit-tested without constructing a full adapter. - Unit test asserts the 4-byte selector matches alloy's `liquidateBorrowCall::SELECTOR` constant and that calldata length is selector + 3 × 32-byte slots — catches any accidental ABI drift (wrong arg order, extra params). The outer wrapping into `CharonLiquidator.executeLiquidation(...)` is intentionally *not* done here; that calldata shape depends on the Solidity contract, which is a separate milestone (M2 Execution). Shipping the inner Venus-specific calldata now unblocks scanner + profit-calc work without waiting on the contract.
Final commit in the #8 series. The adapter built across Parts A–E is now driven by real block events: `charon listen` constructs a `VenusAdapter` at startup and, for every new BSC block from the existing `BlockListener`, runs one `fetch_positions` scan against a caller-supplied borrower list. - `listen --borrower 0x…` flag (repeatable, empty by default) seeds the scan list. Full borrower discovery from indexed `Borrow` events is its own task, tracked under the health-scanner milestone (#9) - Per-block structured log: `venus scan chain=… block=… tracked=… returned=… scan_ms=…` - Adapter and listener use separate WebSocket connections for now; sharing a single pub-sub provider is a cheap optimisation once the scanner owns the full runtime (#9) - `charon-cli` now depends on `charon-protocols` and the alloy primitives crate for address parsing via clap Live soak on BSC: 25 blocks / 25 scans in 30 s, zero warnings, Venus adapter snapshot reports 48 markets with 47 mapped (vBNB skipped, as intended).
This was referenced Apr 22, 2026
…ntive, concurrent borrowers - Replace balanceOfUnderlying (non-view, accrues interest) with balanceOf * exchangeRateStored / 1e18 on the scan path. Works against rate-limited and view-only RPC proxies that reject state-mutating eth_calls. balanceOfUnderlying + borrowBalanceCurrent removed from the ABI entirely to prevent future accidental use. - Real health factor derived from the Comptroller's own liquidity and shortfall values plus the per-block sum of borrow * price: HF = (total_borrow_val +/- liquidity_or_shortfall) / total_borrow_val, 1e18-scaled. Replaces the 0/2e18 binary placeholder so bucket classifiers can rank positions by urgency. - vBNB special case: vBNB (0xA07c…) has no underlying() and used to be silently skipped, invisibling BSC's largest Venus market. connect() now maps vBNB -> Wrapped BNB (0xbb4C…) in both directions so BNB- collateral borrowers are scanned and can be liquidated. - Add liquidationIncentiveMantissa() to the Comptroller ABI, fetch it at connect alongside closeFactorMantissa, and derive liquidation_bonus_bps from the live value at scan time: bps = (mantissa - 1e18) / 1e14. Governance can change the incentive without the bot running on a stale hardcoded 1000 bps. - VenusAdapter fields are now pub(crate) / private, guarded by a tokio RwLock so they can be refreshed atomically. A new refresh() async method re-queries Comptroller state and swaps the snapshot; operators (or a timer/event watcher) call it to pick up governance changes and newly listed markets without restart. markets(), oracle(), close_factor_mantissa(), liquidation_incentive_mantissa() expose read-only accessors. - fetch_positions now processes borrowers concurrently through FuturesUnordered so the scan walltime drops from sequential 50+ borrowers to the slowest single borrower. Follow-up issue should introduce Multicall3 aggregate for per-borrower per-vToken reads. Closes #97 #98 #99 #100 #101 #102
# Conflicts: # crates/charon-cli/src/main.rs
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 #9
New
charon-protocolscrate with the Venus adapter — the firstLendingProtocolimplementation. Six-commit series consolidated on squash:VenusAdapterstructunderlying ↔ vTokenmap (vBNB skipped — nounderlying())fetch_positions— walksgetAssetsIn, reads per-vToken borrow + supply + oracle price, picks biggest-value debt + collateral pair, synthesises health factor fromshortfalldebt × close_factor / 1e18(50% on BSC Venus), ABI-encodes innerVToken.liquidateBorrowcalllistenbuilds the adapter at startup; every new block triggers onefetch_positionsscan with--borrower 0x…flag for test seedingLive-verified on BSC: 48 markets, 47 mapped, oracle
0x6592…b8A, close factor 0.5e18. 25 scans / 25 blocks in 30 s with zero warnings.Depends on #8 (
feat/07-block-listener).