A simplified, modular lending pool. Supply to earn, post collateral to borrow — and swap the interest, liquidation and pricing policies on a live market without migrating a balance.
STRATA
Solidity License Build Coverage
A simplified, modular lending pool. Supply an asset to earn yield, post collateral to borrow it — and swap the interest, liquidation and pricing policies on a live market without migrating a single balance.
Compound and Aave proved the money-market shape by 2020. The part worth re-studying isn't the accounting — it's that the policy (how rates are set, when a position is unsafe, how collateral is priced) lives in swappable strategy contracts, not welded into the pool. STRATA keeps the core tiny and immutable and pushes all three policies behind interfaces:
| Policy | Interface | Swappable? | Ships with |
|---|---|---|---|
| Interest rate | IInterestRateModel | FixedRateModel, UtilizationRateModel | |
| Risk / liquidation | ILiquidationModule | LiquidationModule (LTV, threshold, bonus…) | |
| Pricing | IPriceOracle | SimplePriceOracle (swap in Chainlink later) |
Governance can move a market from a fixed rate to a utilization-driven curve, tighten the LTV before a volatile event, or upgrade the oracle — each is a one-transaction module swap on the running pool.
How it works
SUPPLIERS POOL CORE BORROWERS ───────── ┌──────────────────────┐ ───────── supply(debt) ───────────→ │ cash + borrows │ ←─ borrow(debt) redeem(shares)←─────────── │ - reserves │ ── repay(debt) ─→ │ │ │ borrowIndex (compounds) │ supply shares (exchange rate floats up) └──────────┬────────────┘ │ reads policy from… ┌──────────────────────┼───────────────────────┐ ↓ ↓ ↓ IInterestRateModel ILiquidationModule IPriceOracle rate = f(utilization) LTV / threshold / price(collateral) closeFactor / bonus price(debt)Interest accrues Compound-style. A global borrowIndex compounds the debt side at every interaction:
interestFactor = ratePerSecond × secondsElapsedtotalBorrows += interestFactor × totalBorrowsborrowIndex += interestFactor × borrowIndex // borrowers' debt grows with thistotalReserves += interestFactor × totalBorrows × reserveFactorA borrower's live debt is principal × borrowIndex / indexAtLastTouch. Suppliers hold shares; the exchange rate (cash + borrows − reserves) / shares drifts upward as interest lands, so redeeming returns more than was supplied.
Health. A position is solvent while
healthFactor = collateralValue × liquidationThreshold / debt ≥ 1.0Borrowing is gated by the (lower) ltv; liquidation triggers at the (higher) liquidationThreshold. The band between them is the borrower's margin of safety.
Liquidation. Once healthFactor < 1, anyone may repay up to closeFactor × debt and seize collateral worth the repayment plus the liquidationBonus:
collateralSeized = (repayAmount / collateralPriceInDebt) × (1 + liquidationBonus)The two interest models
UtilizationRateModel is the kinked "JumpRate" curve — cheap while the pool has liquidity, punitive once it's nearly drained, so the market self-corrects toward a healthy utilization:
rate │ ╱ slope2 (scarcity premium) │ ╱ │ slope1 ____╱ ← kink (e.g. 80%) │ ____╱ │ base ____╱ └──────────────────┴──────── utilization → 100%FixedRateModel ignores utilization entirely — one flat APR. Useful to bootstrap a market, then setInterestModel(variable) once there's real supply and demand.
Layout
contracts/├─ LendingPool.sol core market — accrual, supply/borrow/liquidate├─ access/│ ├─ Ownable.sol governance owner│ └─ ReentrancyGuard.sol single-slot lock on every entrypoint├─ interfaces/│ ├─ IERC20.sol│ ├─ IInterestRateModel.sol│ ├─ ILiquidationModule.sol│ └─ IPriceOracle.sol├─ lib/│ ├─ SafeMath.sol overflow-checked maths (pre-0.8.0)│ ├─ WadMath.sol 1e18 fixed-point (wmul / wdiv)│ └─ SafeERC20.sol tolerates non-standard tokens (USDT, …)├─ models/│ ├─ FixedRateModel.sol flat APR│ └─ UtilizationRateModel.sol kinked two-slope curve└─ modules/ ├─ LiquidationModule.sol LTV / threshold / closeFactor / bonus + seize math └─ SimplePriceOracle.sol owner-posted pricesUsage
// 1. Policy modules (per-second rates derived from APR wads inside the models)UtilizationRateModel rates = new UtilizationRateModel( 0.02e18, // 2% base APR 0.08e18, // +8% APR by the kink 1.00e18, // +100% APR from kink to 100% utilization 0.80e18 // kink at 80%);LiquidationModule risk = new LiquidationModule( 0.75e18, // LTV 75% 0.80e18, // liquidation threshold 80% 0.50e18, // close factor 50% 0.08e18 // 8% liquidation bonus);SimplePriceOracle oracle = new SimplePriceOracle();oracle.setPrice(address(WETH), 1800e18); // unit of account is arbitrary…oracle.setPrice(address(USDC), 1e18); // …only the ratio matters // 2. The market: collateral = WETH, debt = USDC, 10% reserve factorLendingPool pool = new LendingPool(WETH, USDC, rates, risk, oracle, 0.10e18); // 3. Flowpool.supply(50_000e18); // a lender funds the USDC sidepool.depositCollateral(10e18); // a borrower posts 10 WETHpool.borrow(12_000e18); // draws USDC, must stay under 75% LTVpool.repay(borrower, 12_000e18); // ...later, with interest // 4. Governance swaps policy on the live pool — no migrationpool.setInterestModel(rates); // fixed -> variablerisk.setParameters(0.70e18, 0.78e18, 0.5e18, 0.10e18); // tighten before volatilityBuild & test
npm installnpx hardhat compilenpx hardhat testSecurity
- Not audited. Study build, published for review — no real value on mainnet without an audit.
- Simplifications, stated plainly: single market; both assets assumed 18-decimal; no bad-debt socialisation (a liquidation reverts if it would seize more than the borrower's collateral); rates are linear-per-second, compounded only at accrual points.
- Every state-changing entrypoint is
nonReentrantand follows checks-effects-interactions; collateral is moved only after debt is written down. - The oracle fails closed — an unset/zero price reverts rather than valuing collateral at zero.
SafeERC20tolerates tokens that don't return a bool. Fee-on-transfer / rebasing tokens are not supported as the debt asset (the accounting assumes received amount == stated amount).- Trust assumptions: the owner (governance) can swap modules and the oracle — i.e. it can reprice risk for everyone. Hand it to a timelock/multisig.
License
MIT © 2021 c0llback