DeFi Protocols
Decentralized finance - exchanges, lending, and financial primitives
DeFi Protocols
Decentralized Finance (DeFi) is a category of applications that recreate traditional financial services using smart contracts. DeFi protocols are permissionless, composable, and operate 24/7 without intermediaries.
This chapter covers the major DeFi primitives and how they work at the smart contract level.
What Makes DeFi Different
Traditional finance relies on trusted intermediaries (banks, brokers, exchanges). DeFi replaces these with smart contracts:
| Traditional | DeFi |
|---|---|
| Bank accounts | Self-custody wallets |
| Stock exchanges | AMM DEXes |
| Loans from banks | Lending protocols |
| Market makers | Liquidity providers |
| Clearing houses | Smart contracts |
| Business hours | 24/7/365 |
| KYC required | Permissionless |
Decentralized Exchanges (DEXes)
Order Book vs AMM
Order Book DEXes (like traditional exchanges):
- Buyers and sellers post orders
- Orders match when prices cross
- Examples: dYdX, Serum
Automated Market Makers (AMMs):
- Use mathematical formulas for pricing
- Liquidity providers deposit token pairs
- Examples: Uniswap, Sushiswap, Curve
How AMMs Work
The constant product formula (Uniswap V2):
x * y = k
Where:
x= reserve of token Ay= reserve of token Bk= constant (maintained after trades)
// Simplified swap calculation
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256) {
uint256 amountInWithFee = amountIn * 997; // 0.3% fee
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = (reserveIn * 1000) + amountInWithFee;
return numerator / denominator;
}Liquidity Provision
Liquidity providers (LPs) deposit equal value of two tokens:
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountA,
uint256 amountB
) external returns (uint256 liquidity) {
// Transfer tokens to pool
IERC20(tokenA).transferFrom(msg.sender, address(this), amountA);
IERC20(tokenB).transferFrom(msg.sender, address(this), amountB);
// Mint LP tokens
liquidity = sqrt(amountA * amountB);
_mint(msg.sender, liquidity);
}LPs earn trading fees but face impermanent loss when prices change.
Impermanent Loss
When you provide liquidity and prices change, you would have been better off just holding:
| Price Change | Impermanent Loss |
|---|---|
| 1.25x | 0.6% |
| 1.5x | 2.0% |
| 2x | 5.7% |
| 3x | 13.4% |
| 5x | 25.5% |
Concentrated Liquidity (Uniswap V3)
LPs can concentrate liquidity in price ranges for higher capital efficiency:
// V2: Liquidity spread across 0 to ∞
// V3: Liquidity only in [priceLower, priceUpper]
function mint(
address recipient,
int24 tickLower,
int24 tickUpper,
uint128 amount
) external returns (uint256 amount0, uint256 amount1);Lending Protocols
How Lending Works
Users deposit assets to earn interest. Borrowers provide collateral and pay interest:
┌─────────────┐ deposits ┌─────────────────┐
│ Lenders │ ─────────────────▶ │ Lending Pool │
│ │ ◀───────────────── │ (Smart Contract)│
└─────────────┘ interest │ │
│ Collateral: $150│
┌─────────────┐ collateral │ Borrowed: $100 │
│ Borrowers │ ─────────────────▶ │ │
│ │ ◀───────────────── │ │
└─────────────┘ borrowed └─────────────────┘
Interest Rate Models
Rates adjust based on utilization:
// Simplified interest rate model
function getBorrowRate(uint256 cash, uint256 borrows) public pure returns (uint256) {
uint256 utilization = borrows * 1e18 / (cash + borrows);
// Low utilization: low rates
// High utilization: high rates (incentivize deposits)
if (utilization <= 0.8e18) {
return utilization * 5 / 100; // 0-4% base rate
} else {
// Steep increase above 80% utilization
return 0.04e18 + (utilization - 0.8e18) * 75 / 100;
}
}Collateralization
Most DeFi lending is over-collateralized:
// Example: 150% collateral ratio required
function borrow(uint256 amount) external {
uint256 collateralValue = getCollateralValue(msg.sender);
uint256 borrowValue = getBorrowValue(msg.sender) + amount;
// Collateral must be > 150% of borrows
require(collateralValue >= borrowValue * 150 / 100, "Undercollateralized");
// Process borrow
borrows[msg.sender] += amount;
IERC20(borrowToken).transfer(msg.sender, amount);
}Liquidations
When collateral falls below threshold, liquidators can repay debt and claim collateral at a discount:
function liquidate(address borrower, uint256 repayAmount) external {
require(isLiquidatable(borrower), "Not liquidatable");
// Liquidator repays part of debt
IERC20(borrowToken).transferFrom(msg.sender, address(this), repayAmount);
// Liquidator receives collateral at discount (e.g., 5%)
uint256 collateralAmount = repayAmount * 105 / 100 / collateralPrice;
IERC20(collateralToken).transfer(msg.sender, collateralAmount);
borrows[borrower] -= repayAmount;
}Stablecoins
Types of Stablecoins
Fiat-backed (USDC, USDT):
- 1:1 backed by USD in bank accounts
- Centralized, can freeze accounts
Crypto-backed (DAI, LUSD):
- Over-collateralized by crypto
- Decentralized, censorship-resistant
Algorithmic (FRAX, RAI):
- Use algorithms to maintain peg
- Various stability mechanisms
MakerDAO and DAI
DAI is created by depositing collateral into "Vaults":
// Simplified Vault (CDP) logic
function openVault(uint256 collateralAmount, uint256 daiAmount) external {
// Deposit collateral
collateral[msg.sender] += collateralAmount;
IERC20(collateralToken).transferFrom(msg.sender, address(this), collateralAmount);
// Mint DAI (must maintain collateral ratio)
uint256 collateralValue = collateralAmount * getCollateralPrice();
require(collateralValue >= daiAmount * 150 / 100, "Below min ratio");
debt[msg.sender] += daiAmount;
daiToken.mint(msg.sender, daiAmount);
}Flash Loans
Flash loans allow borrowing without collateral — as long as you repay within the same transaction:
interface IFlashLoanReceiver {
function executeOperation(
address asset,
uint256 amount,
uint256 premium,
address initiator,
bytes calldata params
) external returns (bool);
}
function flashLoan(
address receiver,
address asset,
uint256 amount,
bytes calldata params
) external {
uint256 balanceBefore = IERC20(asset).balanceOf(address(this));
// Send funds to receiver
IERC20(asset).transfer(receiver, amount);
// Receiver executes arbitrary logic
IFlashLoanReceiver(receiver).executeOperation(
asset, amount, 0, msg.sender, params
);
// Must repay (with premium)
require(
IERC20(asset).balanceOf(address(this)) >= balanceBefore,
"Flash loan not repaid"
);
}Flash Loan Use Cases
- Arbitrage — Profit from price differences across DEXes
- Collateral swaps — Change vault collateral without repaying
- Liquidations — Fund large liquidations without capital
- Self-liquidation — Close underwater positions efficiently
Yield Aggregators
Yield aggregators automatically compound rewards:
function harvest() external {
// Claim rewards
uint256 rewards = farmingContract.pendingRewards(address(this));
farmingContract.harvest();
// Swap rewards to base token
uint256 baseAmount = dex.swap(rewardToken, baseToken, rewards);
// Reinvest
farmingContract.deposit(baseAmount);
}Examples: Yearn Finance, Beefy Finance, Convex
DeFi on Ethereum Classic
While most DeFi activity is on Ethereum mainnet, DeFi can exist on any EVM chain:
Security Considerations
Oracle Manipulation
Flash loans can manipulate on-chain price oracles:
// VULNERABLE - uses spot price
function getPrice() public view returns (uint256) {
(uint112 reserve0, uint112 reserve1, ) = pair.getReserves();
return reserve0 / reserve1; // Can be manipulated!
}
// SAFE - use Chainlink or TWAP
function getPrice() public view returns (uint256) {
(, int256 price, , , ) = chainlink.latestRoundData();
return uint256(price);
}Reentrancy in DeFi
DeFi protocols are prime targets for reentrancy:
// VULNERABLE
function withdraw(uint256 shares) external {
uint256 amount = sharesToAssets(shares);
(bool success, ) = msg.sender.call{value: amount}(""); // Reentrancy!
balances[msg.sender] -= shares;
}
// SAFE
function withdraw(uint256 shares) external nonReentrant {
uint256 amount = sharesToAssets(shares);
balances[msg.sender] -= shares; // Effects before interactions
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}Economic Attacks
DeFi introduces new attack vectors:
- Flash loan attacks
- Governance attacks
- MEV extraction
- Sandwich attacks
Composability
DeFi's "money legos" allow protocols to build on each other:
// Example: Leveraged yield farming
function leveragedFarm() external {
// 1. Deposit collateral to Aave
aave.deposit(collateral, amount);
// 2. Borrow stablecoins
aave.borrow(usdc, amount * 60 / 100);
// 3. Swap to LP token
uint256 lpAmount = curve.addLiquidity([usdc, 0, 0]);
// 4. Stake LP for rewards
convex.deposit(lpAmount);
// Result: Earning yield on borrowed funds
}Conclusions
DeFi recreates financial services without intermediaries:
- DEXes — Trade without order books using AMMs
- Lending — Borrow and lend without banks
- Stablecoins — Stable value without central issuers
- Flash loans — Uncollateralized loans in single transactions
- Yield — Compound rewards automatically
Key considerations:
- Smart contract risk is real — use audited protocols
- Oracle manipulation is a major attack vector
- Composability creates systemic risk
- The same protocols can run on ETH and ETC
DeFi demonstrates the power of permissionless innovation on the EVM.