Developing a lending protocol
Typical situation: a team wants to launch a lending protocol, forks Compound v2, changes collateral factor parameters, deploys — and three weeks later discovers that the oracle works via TWAP with a 30-minute window, and liquidations don't catch up during sharp price movements. Positions go negative, protocol suffers losses. This is not a fork bug — it's an architectural decision that in the original was compensated by other risk parameters.
Developing a lending protocol from scratch or adapting an existing one is primarily work with risk mathematics and liquidation mechanics, not just Solidity writing.
Where lending protocols break
Oracle manipulation via flash loan
The most destructive attack vector in DeFi lending is price oracle manipulation. If the protocol reads price directly from Uniswap v2 pool spot price, the attacker takes a flash loan, moves the price in the pool, gets an under-collateralized loan, returns the flash loan. Protocol loses collateral.
The 2022 Mango Markets attack — $117 million — worked exactly like this. The attacker used their own token as collateral, artificially raised its price through spot purchases, took loans against the inflated collateral.
Protection is built on multiple levels:
-
Chainlink price feeds with
updatedAtcheck — if data is older than N seconds, transaction reverts - TWAP from Uniswap v3 as secondary source with window of at least 30 minutes for illiquid assets
- Deviation check — if Chainlink and TWAP diverge more than X%, accept the lower value
- Circuit breaker — temporary pause on new borrowing during abnormal price movement
function getPrice(address asset) internal view returns (uint256) {
(, int256 answer, , uint256 updatedAt, ) = chainlinkFeed.latestRoundData();
require(block.timestamp - updatedAt <= STALENESS_THRESHOLD, "Stale price");
require(answer > 0, "Invalid price");
uint256 twapPrice = getTWAP(asset, TWAP_PERIOD);
uint256 chainlinkPrice = uint256(answer);
// Accept minimum of two — conservative position
return twapPrice < chainlinkPrice ? twapPrice : chainlinkPrice;
}
Liquidation mechanics and bad debt
The second critical moment is liquidation threshold and health factor. Aave uses healthFactor = (collateralETH * liquidationThreshold) / totalDebtETH. As soon as health factor drops below 1.0, the position is open to liquidators.
The problem occurs with gap risk: an asset falls 30% in one candle (liquidity crisis, exchange collapse), liquidators don't catch up to close positions, protocol accumulates bad debt. Compound faced this during LUNA collapse — some positions went negative.
Architectural solutions:
| Mechanism | Essence | Application |
|---|---|---|
| Liquidation bonus | Liquidator gets collateral at 5-10% discount | Incentive for quick liquidation |
| Partial liquidation | Only part of position is closed | Reduces gas cost for liquidators |
| Dutch auction liquidation | Bonus price rises over time | Automatic attractiveness during volatility |
| Insurance fund | Reserve from part of interest income | Bad debt coverage on gap risk |
We implement dutch auction following MakerDAO: if a position is not liquidated within N blocks, liquidation bonus starts to rise. This guarantees that even with low liquidator interest, the position will eventually close.
Interest rate model: kink and utilization
Interest rate in Compound v2 and Aave v3 is calculated via utilization rate: U = totalBorrow / totalSupply. At low utilization rate is low, at high it rises sharply (kink model).
The kink parameter is critical. If utilization reaches 100%, depositors cannot withdraw funds — no liquidity. Classic kink model puts inflection point at 80-90% and sharply raises the rate above to incentivize new deposits or debt repayment.
function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves)
external view returns (uint256)
{
uint256 util = utilizationRate(cash, borrows, reserves);
if (util <= kink) {
return util * multiplierPerBlock / BASE + baseRatePerBlock;
} else {
uint256 normalRate = kink * multiplierPerBlock / BASE + baseRatePerBlock;
uint256 excessUtil = util - kink;
return excessUtil * jumpMultiplierPerBlock / BASE + normalRate;
}
}
How we build lending protocols
Architecture and stack
Basic architecture divides logic into several contracts:
- LendingPool — entry point, routes calls
- ReserveLogic — index calculation, interest accrual
- ValidationLogic — checks before operations
- aToken / debtToken — ERC-20 tokens of positions (Aave v3 style)
- PriceOracle — price aggregator with fallback logic
- InterestRateStrategy — pluggable rate strategy
We use Foundry for development and testing. Fork-tests against Ethereum mainnet are mandatory: need to verify integration with real Chainlink feeds, real asset prices.
For upgradability — UUPS proxy (EIP-1822). Transparent proxy at this scale of logic creates storage collision risks during upgrades. ERC-7201 namespaced storage isolates variables for each logical module.
Special attention to reentrancy in cross-contract calls: aToken.mint() on deposit, collateral transfer on liquidation — all external calls after state update, nonReentrant on all entry points.
Testing
Property-based tests via Echidna with invariants:
- Sum of all debts never exceeds sum of all deposits
- Health factor of position after liquidation always above 1.0
- Interest index monotonically increases
Fuzz-tests in Foundry on deposit, borrow, repay functions — parameters: random amounts, random operation sequences, random oracle values.
Working process
Analytics (3-5 days). Determine list of assets, collateral factor for each, liquidation parameters, oracle sources. Model stress scenarios: what happens when the main collateral asset falls 50% in 1 block.
Design (5-7 days). Storage layout, contract interfaces, mathematical rate models. Formal verification for core invariants via Certora Prover or Halmos — for protocols with TVL goal >10M USD this is a mandatory step.
Development (4-8 weeks). Core contracts + tests. Fork-tests on mainnet. Coverage >95%.
Internal security review (1 week). Slither, Mythril, manual review per SWC checklist + DeFi-specific vectors: flash loan attacks, oracle manipulation, liquidation griefing.
External audit. For lending protocol — mandatory. Recommend Trail of Bits, Spearbit, or Code4rena contest depending on budget.
Deployment. First testnet (Sepolia) simulating market conditions, then mainnet via Gnosis Safe multisig. Risk parameters set conservatively with ability to adjust via governance.
Timeline estimates
Minimal protocol version (one asset, basic operations) — 4-6 weeks. Full multi-asset lending with governance and insurance fund — 3-4 months. Audit timeline not included and depends on chosen company (usually 2-6 weeks in queue).
Cost is calculated after detailed discussion of architecture and security requirements.







