Skip to content

Repository files navigation

EVM Lending / Borrowing Protocol

CI License: MIT Solidity Foundry

A single-market money market in the style of Compound III (Comet): one borrowable base asset (USDC), isolated supply-only collateral (WETH, wBTC), index-based interest accrual, and protocol-absorbed liquidations. Built as a proof of concept of DeFi lending primitives, around provable solvency.

Proof of concept. Not audited and not deployed. All code is written from scratch; Compound III is the architectural reference, and no code is copied or forked. Do not use in production.


How it works

  • Suppliers deposit USDC and hold a rebasing balance (lmUSDC) that grows with interest.
  • Borrowers post WETH or wBTC collateral and borrow USDC against it.
  • Liquidator bots absorb underwater accounts and buy the seized collateral at a discount.

One borrowable asset. Inert collateral. Every rounding direction favors the protocol, and solvency is designed to be provable, not assumed.

┌──────────────────────────────────────────────────────────────────┐
│                            THE MODEL                             │
│                                                                  │
│   SUPPLIERS ──── USDC ────►┌──────────────────┐                  │
│   (earn interest,          │                  │                  │
│    hold lmUSDC)            │  LENDING MARKET  │◄── WETH / wBTC   │
│                            │   (singleton)    │    BORROWERS     │
│   LIQUIDATORS ◄─ discount ─┤                  │    (post inert   │
│   (absorb, then            │  one base asset  │──── USDC ──►     │
│    buyCollateral)          │  derived reserves│    (borrow)      │
│                            └──────────────────┘                  │
│                                                                  │
│         interest split: suppliers + reserves, by construction    │
└──────────────────────────────────────────────────────────────────┘

Key features

Feature Description
Single-base market (Comet) One borrowable asset; collateral is deposit-only, bounding risk per asset.
Signed-principal accounting One int104 per account; supply and borrow are mutually exclusive states.
Rebasing ERC20 (lmUSDC) The market itself is the token; balances grow in place with accrual.
Jump-rate interest model Kinked borrow curve; supply rate derived so reserves never accrue negative.
Absorb liquidations Protocol wipes debt, seizes collateral, resells via buyCollateral.
Explicit bad debt Shortfalls recognized at absorb time; reserves are derived and can go negative visibly.
Pyth + Chainlink oracle Pull-based primary with confidence intervals; independent deviation anchor.
Immutable deployment No proxy, no parameter setters; owner limited to reserves and pause flags.

Architecture

                      ┌─────────────────────────────────┐
                      │        LENDING MARKET           │
                      │  (accounting, custody, ERC20)   │
                      │                                 │
                      │  supply / withdraw / transfer   │
                      │  borrow / repay (signed paths)  │
                      │  accrue / absorb / buyCollateral│
                      │  getReserves / withdrawReserves │
                      └────────┬───────────────┬────────┘
                               │               │
                    rates      ▼               ▼      validated prices
              ┌──────────────────────┐  ┌──────────────────────────┐
              │  INTEREST RATE MODEL │  │  PYTH + CHAINLINK ORACLE │
              │  (stateless, kinked  │  │  (staleness, confidence, │
              │   curve + derived    │  │   deviation anchor)      │
              │   supply rate)       │  │                          │
              └──────────────────────┘  └──────────────────────────┘
Contract Role
LendingMarket.sol Singleton market: accounting, custody and the rebasing ERC20
InterestRateModel.sol Stateless kinked curve with a derived supply rate
PythChainlinkOracle.sol Price validation pipeline: staleness, confidence band, Chainlink deviation anchor

Every non-obvious decision is recorded as an ADR in Guide 3.


Security and testing

The design specifies 14 system invariants (Guide 6), checked by 17 stateful invariant tests. The load-bearing ones:

// INV-1: exact integer accounting (load-bearing)
sum(positive principals) == totalSupplyBase;
sum(negative principals) == totalBorrowBase;

// INV-2: indexes only grow
baseSupplyIndex' >= baseSupplyIndex;  baseBorrowIndex' >= baseBorrowIndex;

// INV-3/4: every rounding favors the protocol; the residual accrues to reserves
getReserves() non-decreasing except by absorb and withdrawReserves;

// INV-9: no action leaves an account undercollateralized
isBorrowCollateralized(account) after every health-reducing call;
Area Result
Tests 331, all green: 236 unit, 43 in test/fuzz, 32 integration, 17 stateful invariant, 3 fork
Fuzzing 41 of the 43 tests in test/fuzz take fuzzed inputs and run 1,000 times each (41,000 runs); the other 2 are deterministic. Each of the 17 invariant tests runs 1,000 sequences of 100 calls (1,700,000 calls)
Coverage Above 95% on lines, statements, branches and functions on every contract
Fork tests The oracle against the real Pyth pull contract and Chainlink feeds, and supply, borrow, accrual and repay against real USDC and WETH at a pinned mainnet block
Static analysis Slither: 0 findings. Aderyn: 12 detector categories, all triaged (4 false positives, 8 intended design or accepted gas choices, none a vulnerability)
Bugs caught The INV-1 invariant caught a self-transfer minting bug

Every figure comes from the commands in Testing Documentation, which also catalogues each test and maps each invariant to what asserts it. Static analysis triage: Static Analysis.


Getting started

Requires Foundry and Git.

git clone https://github.com/GushALKDev/evm-lending-borrowing-protocol.git
cd evm-lending-borrowing-protocol
forge install
forge build
forge test
forge test --fuzz-runs 10000                        # fuzz tests with more runs
forge test --match-contract InvariantsTest          # invariant suite
forge test --match-path "test/integration/*"        # local deployment, real oracle over MockPyth
FORK_RPC_URL=<eth-mainnet-rpc> forge test --match-path "test/fork/*"   # no-op without FORK_RPC_URL
forge coverage --report lcov

Fork scope (what runs against mainnet and why absorb, buyCollateral and wBTC do not): Fork Tests.

Local deployment

Every external address must point at a deployed contract first: the placeholder defaults have no code, so the market constructor reverts without them.

anvil
export USDC=<addr> WETH=<addr> WBTC=<addr> PYTH=<addr>
export USDC_CL_FEED=<addr> WETH_CL_FEED=<addr> WBTC_CL_FEED=<addr>
export USDC_PYTH_ID=<id> WETH_PYTH_ID=<id> WBTC_PYTH_ID=<id>   # optional: OWNER, GUARDIAN
forge script script/Deploy.s.sol --rpc-url http://localhost:8545 --broadcast

test/integration/DeployScript.t.sol rehearses this exact flow in-test, against mock dependencies exported into the same variables.


Roadmap

70 of the 72 PoC items are done. Every item with its scope and deliverables: docs/ROADMAP.md.

  • Phase 0: Setup & Infrastructure (6/6)
  • Phase 1: Core: Index Accounting & Storage (8/8)
  • Phase 2: Interest Rate Model (6/6)
  • Phase 3: Supply & Withdraw (8/8)
  • Phase 4: Borrow & Repay (7/7)
  • Phase 5: Oracle (Pyth + Chainlink) (10/10)
  • Phase 6: Absorb Liquidation (8/8)
  • Phase 7: Reserves & Protocol Management (6/6)
  • Phase 8: Invariant & Fuzz Testing + Audit Prep (11/13)
    • 8.1 Invariant: cash conservation via ghost tracking
    • 8.2 Invariant: signed principals sum exactly to the supply and borrow totals
    • 8.3 Invariant: monotone indexes, supplyRate <= borrowRate, reserves only decrease by absorb or withdrawReserves
    • 8.4 Invariant: no action leaves an account below the borrow threshold; collateral totals match per-user sums
    • 8.5 Fuzz: conversion, rate and quote math with directed-rounding assertions
    • 8.6 End-to-end integration on a local deployment, plus a deploy script rehearsal
    • 8.7 Fork tests against real USDC, WETH, Pyth and Chainlink on an Ethereum mainnet fork
    • 8.8 Static analysis (Slither, Aderyn) with no criticals
    • 8.9 Coverage above 95% on all contracts
    • 8.10 Liquidation (absorb, buyCollateral) through the real oracle's fee and refund path
    • 8.11 Invariant suite completed against the Guide 6 testing plan
    • 8.12 Audit checklist from Guide 6 completed, plus an internal line-by-line review
    • 8.13 Findings remediation and a re-run of the full suite
  • Phase 9: Future Work, post-PoC and outside its scope (0/6)

Documentation

Document Covers
Documentation index Master index and reading orders
Roadmap Implementation phases and progress (72 PoC items + 6 future work)
Guide 1: Fundamentals Money markets and the single-base model
Guide 2: Mathematics Indexes, rates, liquidation, rounding policy
Guide 3: Architecture Contracts, state, flows, ADRs
Guide 4: Trade-offs Risks, mitigations, risk matrix
Guide 5: Implementation Interfaces, errors, access control
Guide 6: Security Threat model, invariants, testing plan
Testing Documentation Per-test inventory, invariant coverage map, fork and static analysis

Project structure

src/
  LendingMarket.sol          Singleton market (accounting + custody + ERC20)
  InterestRateModel.sol      Kinked curve, derived supply rate
  PythChainlinkOracle.sol    Price validation pipeline
  interfaces/                ILendingMarket, IInterestRateModel, IPriceOracle
test/
  unit/  fuzz/  invariant/  integration/  fork/  mocks/
script/
  Deploy.s.sol               Deployment script
docs/                        Guides, roadmap and testing documentation

Tech stack

Solidity 0.8.26 · Foundry (unit, fuzz, invariant, integration and fork tests) · OpenZeppelin v5 · Solady · Pyth Network + Chainlink

License

MIT, see LICENSE.

Author

@GushALKDev, Gustavo Martín (LinkedIn).

Acknowledgments

About

Isolated, single-base money market on EVM. Compound III (Comet)-inspired architecture, fully original code. Index-based accounting, absorb liquidation, Pyth + Chainlink oracle, built around a provable-solvency thesis (unit, integration, fuzz, invariant and fork tested).

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages