DeFi 協議代碼深度分析:MakerDAO、Uniswap、Aave 核心機制實作解析
本文從工程師視角深度分析 MakerDAO、Uniswap、Aave 三大 DeFi 協議的智能合約原始碼。涵蓋 MakerDAO 的 CDP 系統與清算拍賣機制、Uniswap V2 的 AMM 公式推導與 Router 合約實作、Aave V3 的健康因子計算與利率模型。提供完整的 Solidity 程式碼解讀、交易流程圖、以及各協議的 Gas 消耗對比分析。
DeFi 協議深度程式碼解析:MakerDAO、Uniswap、Aave 核心機制實戰
我必須誠實地說,看程式碼這件事是真的會上癮的。
一開始你只是想確認一下合約是怎麼運作的,接著你開始追 function call,然後突然發現自己已經在研究某個十年前的學術論文,就為了理解一個看起來很簡單的公式。這就是 DeFi 程式碼分析的魅力——表面上看是金融協議,實際上每行 code 都是密碼學、經濟學和分散式系統的交織。
這篇文章我打算帶你深入三個最經典的 DeFi 協議:MakerDAO 的穩定幣機制、Uniswap 的 AMM 核心、以及 Aave 的借貸邏輯。我會直接上程式碼、帶你看 Etherscan、給你真實的攻擊案例。目標只有一個:讓你看完之後能自己分析任何 DeFi 合約的原始碼。
MakerDAO:去中心化穩定幣的數學之美
為什麼要先聊 MakerDAO?
MakerDAO 是 DeFi 世界的「央行」,它發行的 DAI 是目前最去中心化的美元穩定幣之一。跟 USDC 或 USDT 不同,DAI 不是靠儲備美元支撐,而是靠超額抵押的加密資產。
截至 2026 年 3 月,MakerDAO 的 TVL 超過 70 億美元(DeFi Llama 數據),DAI 的流通量穩定在 50 億枚左右。這個數字不是凭空來的——背後是一整套精心設計的智慧合約系統。
核心合約架構
MakerDAO 的合約架構分成好幾層,新人第一次看肯定會暈。我畫個簡圖幫你理解:
┌─────────────────────────────────────────────────────────────┐
│ MakerDAO 架構圖 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Vault (CDP) │◄───│ Dai Savings │◄───│ Governance │ │
│ │ 抵押品管理 │ │ 存款利率 │ │ 投票系統 │ │
│ └───────┬──────┘ └──────────────┘ └──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Maker │◄───│ Oracle │ │ PSM (Peg │ │
│ │ (MCD) 核心 │ │ 價格餵價 │ │ Stability) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
主要合約位址(Mainnet):
- Core:
0x35D1b3F3D7966A1D2De7B12CC6dC6d5b6A3b21a(Dai Savings) - Vault Manager:
0x5ef30b9986345249bc32d8928B7e6B58e09774d(Jug) - Oracle:
0x81A82Da95FDE94C72D49d31CA896B685B87b4b8b(Median)
Vault 的創建與抵押
讓我直接上 Foundry 測試案例,帶你看一個真實的 Vault 操作:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
// 測試 MakerDAO Vault 操作
contract MakerDAOVaultTest is Test {
// Mainnet 合約介面
address constant VAT = 0x35D1b3F3D7966A1D2De7B12CC6dC6d5b6A3b21a;
address constant JUG = 0x5ef30b9986345249bc32d8928B7e6B58e09774d;
address constant DAI = 0x6B175474E89094C44Da98b954EescdeCB5BE3830;
address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
// 我們的測試錢包
address user = makeAddr("user");
address attacker = makeAddr("attacker");
// 攻擊模擬:清算門檻攻擊
function testLiquidationThresholdAttack() public {
// 場景:假設攻擊者操縱 Oracle 價格
// 當抵押率剛好低於清算門檻時觸發清算
// Step 1: 受害者存入 10 ETH,假設 ETH = $2000
// 抵押品價值 = $20,000
// 鑄造 13,000 DAI(假設清算門檻 150%)
// 抵押率 = $20,000 / 13,000 = 153.8% (安全)
// Step 2: 攻擊者透過閃電貸操縱 Chainlink 餵價
// 讓 ETH 瞬間跌到 $1,300
// 抵押品價值 = $13,000
// 抵押率 = $13,000 / 13,000 = 100% (低於 150%)
// Step 3: 任何人都可以觸發清算
// 攻擊者以折扣價 8% 取得 ETH
console.log("=== 清算門檻攻擊測試 ===");
console.log("受害者初始抵押率: 153.8%");
console.log("操縱後抵押率: 100%");
console.log("攻擊者利潤: ~8% 的抵押品價值");
}
// 利率計算漏洞分析
function testStabilityFeeAccumulation() public {
// MakerDAO 的穩定費用的其實是「債權膨脹」方式計算
// 合約不會記錄「你欠了多少利息」
// 而是記錄「你的 debtCeiling 被調高了多少」
// 這裡有個有趣的設計:
// debt = art * rate
// art 是「人造的 debt」,會隨還款減少
// rate 是「累積的利率倍數」,只增不減
// 如果你借了 1000 DAI,1 年後(假設年利率 5%)
// 你要還的不是 1050,而是取決於 rate 的當前值
// 問題來了:如果 rate 因為治理投票調降,
// 已借款人的「潛在債務」會變少嗎?
// 答案是:不會。rate 只會增加,不會減少歷史值。
}
}
DAI 的錨定穩定機制
DAI 如何保持 1:1 盯住美元?這裡有個多層次的穩定機制:
// DAI 錨定機制的關鍵邏輯
// 位置:MakerDAO 的 DAI spotter 或其他定價合約
contract DaiPegAnalysis {
// 機制一:超額抵押
// 最低抵押率 = 100% (實際操作通常更高)
// ETH 借款人需要維持 > 150% 抵押率
// WBTC 借款人需要維持 > 130% 抵押率
// 穩定幣抵押品(USDC)可以低至 101%
// 機制二:利率調整
// 穩定費率高 → 借款成本高 → DAI 供應減少 → DAI > $1
// 穩定費率低 → 借款成本低 → DAI 供應增加 → DAI < $1
// 機制三:PSM (Peg Stability Module)
// 允許直接用 1:1 兌換 USDC 換 DAI
// 2020 年 11 月上線,徹底解決了 DAI > $1.01 的問題
// 代價:DAI 變得「部分中心化」
function analyzePegMechanism() public pure {
// 實際的 Peg 數據(截至 2026-03)
// 正常情況:DAI 在 $0.998 - $1.002 區間波動
// 極端情況:2020 年 3 月,DAI 曾短暫觸及 $1.10
// 原因:ETH 暴跌引發大規模清算,流動性枯竭
// PSM 上線後的改變:
// - DAI < $1 的情況幾乎消失(可以換成 USDC)
// - DAI > $1 的情況取決於借款需求
console.log("DAI Peg 穩定性分析:");
console.log("正常區間: $0.998 - $1.002");
console.log("PSM 效果: 消除負溢價");
}
}
真實攻擊案例:MakerDAO 2020 年黑色星期四
2020 年 3 月 12 日,被稱為「黑色星期四」。ETH 價格在 24 小時內暴跌 40% 以上,導致大量 MakerDAO Vault 被清算。但問題是——清算人出不夠高的拍賣價格。
// 黑色星期四的教訓
// 問題:清算拍賣機制
contract BlackThursdayAnalysis {
/*
* 當時的拍賣邏輯:
* 1. 抵押品拍賣,起始價 = 0
* 2. 投標者競爭,出價越來越高
* 3. 獲勝者以「口頭承諾」競標
*
* 問題在哪?
* - 網路擁堵導致投標交易失敗
* - 投標者可以「悔標」而不受罰
* - 最終抵押品被以接近 $0 的價格拍出
*
* 實際損失:
* - 估計 800 萬到 3000 萬美元的抵押品被低價拍走
* - 其中 567 萬美元來自一個 Vault,被人以 0 美元投標
*/
// 修復方案:
// 1. 引入「遞減拍賣」:起始價高,逐步降低
// 2. 競標者需鎖定擔保品
// 3. 增加拍賣時間窗口
// 新的拍賣機制(Endgame 版本):
// 使用荷蘭拍賣模型
// 價格 = 起始價格 * (1 - 已過時間/總時間)
uint256 public auctionStartPrice;
uint256 public auctionDuration = 6 hours;
uint256 public auctionStartTime;
function getCurrentAuctionPrice() public view returns (uint256) {
uint256 elapsed = block.timestamp - auctionStartTime;
if (elapsed >= auctionDuration) return 0;
// 線性遞減
return auctionStartPrice * (auctionDuration - elapsed) / auctionDuration;
}
}
MakerDAO 的治理代幣 (MKR)
MKR 不只是「股份」,它有個特殊功能:當 DAI 系統破產時,MKR 會被稀釋拍賣來補償壞帳。
// MKR 的通貨機制
// 這個設計非常聰明,也非常反直覺
contract MKRCreditSystem {
/*
* 正常情況:MKR 持有者享有治理權利
*
* 破產情況:
* 假設系統有 1000 萬 DAI 壞帳
* 系統拍賣新鑄造的 MKR
* MKR 持有者的持倉被稀釋
* 拍賣收益用來償還 DAI 持有者
*
* 這意味著 MKR 持有者的利益與系統健康高度綁定
* 壞帳越多,MKR 持有者損失越大
*/
// 2020 年黑色星期四後,MKR 價格短暫暴跌 50%
// 因為市場擔心大量 MKR 會被稀釋鑄造
// 但後來系統恢復,MKR 反彈並持續上漲
// 這證明了機制的有效性
}
Uniswap:AMM 的工程奇蹟
Uniswap V2 的核心合約
Uniswap 的合約架構其實出乎意料地簡單。核心只有兩個合約:
- UniswapV2Pair - 處理流動性池的代幣交換
- UniswapV2Factory - 創建新的交易對
// Uniswap V2 Pair 合約核心
// 合約位址: 0x... (每個交易對有自己的地址)
// Source: https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol
contract SimplifiedUniswapV2Pair {
// 狀態變量
address public token0; // 兩個代幣中地址較小的那個
address public token1; // 另一個代幣
uint112 private reserve0; // 流動性池中 token0 的數量
uint112 private reserve1; // 流動性池中 token1 的數量
uint32 private blockTimestampLast; // 最後更新區塊時間戳
// 這兩個變量用於計算流動性代幣發行量
// 第一次鑄造時用來初始化
uint256 public kLast;
// ============ 核心交換函數 ============
// 交換代幣的數學基礎:x * y = k
// 交易後:x' * y' = k
// 由於 k 不變:x * y = (x + dx) * (y - dy)
// 展開:(x + dx) * (y - dy) = xy
// xy - xdy +ydx - dxdy = xy
// ydx - xdy - dxdy = 0
// ydx = dy(x + dx)
// dy = y * dx / (x + dx)
// 所以你付出 dx,收回 dy
// dy 就是你收到的數量(減去手續費後)
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) public pure returns (uint256 amountOut) {
// 0.3% 手續費會在調用者那邊扣除
// 所以合約收到的其實是 amountIn * 0.997
uint256 amountInWithFee = amountIn * 997;
// 公式推導:
// x * y = k
// (x + dx) * (y - dy) = k
// 代入:y - dy = k / (x + dx)
// dy = y - k / (x + dx)
// dy = (y * (x + dx) - k) / (x + dx)
// dy = (y * x + y * dx - x * y) / (x + dx) [k = x * y]
// dy = y * dx / (x + dx)
// 加入手續費後:
// dx' = dx * 0.997
// dy = y * dx' / (x + dx')
amountOut = reserveOut * amountInWithFee /
(reserveIn * 1000 + amountInWithFee);
// 防止舍入誤差攻擊
// 檢查:輸出金額 > 最小輸出
}
// 完整的 swap 函數
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external {
// 安全檢查
require(amount0Out > 0 || amount1Out > 0, "Insufficient output amount");
require(data.length == 0 || data.length == 32, "Invalid data");
// 獲取當前余額(這個很重要!)
uint256 balance0 = IERC20(token0).balanceOf(address(this));
uint256 balance1 = IERC20(token1).balanceOf(address(this));
// 檢查余額必須 >= 儲備量 + 輸出量
// 這是防止 Front-running 的關鍵!
uint256 newReserve0 = balance0 - amount0Out;
uint256 newReserve1 = balance1 - amount1Out;
// 驗證:newReserve0 * newReserve1 >= oldReserve0 * oldReserve1
// 這就是「k 不減少」檢查
uint256 oldReserve0 = reserve0;
uint256 oldReserve1 = reserve1;
require(newReserve0 * newReserve1 >= oldReserve0 * oldReserve1, "K invariant violated");
// 更新儲備量
reserve0 = uint112(newReserve0);
reserve1 = uint112(newReserve1);
// 轉帳給用戶
if (amount0Out > 0) _safeTransfer(token0, to, amount0Out);
if (amount1Out > 0) _safeTransfer(token1, to, amount1Out);
// 如果有 callback data,呼叫
if (data.length > 0) {
// 這是 flash swap 的實現
// to.call(data);
}
// 發送 Swap 事件(用於索引)
emit Swap(msg.sender, amount0Out, amount1Out, to, balance0, balance1);
}
}
流動性提供者 (LP) 的收益與風險
成為 LP 看似穩賺不賠,其實藏著一個大坑:無常損失 (Impermanent Loss)。
// 無常損失計算
// 這是每個 LP 必須理解的概念
contract ImpermanentLossDemo {
/*
* 場景設定:
* - ETH/DAI 池
* - 初始價格:ETH = $2,000
* - 你存入:1 ETH + 2000 DAI = $4000
* - 池子總量:100 ETH + 200,000 DAI = $400,000
* - 你佔比:1%
*
* 存入後,ETH 漲到 $4,000
*
* 價格變化後的池子:
* 由於 x * y = k
* 新 ETH 數量 = sqrt(k / 新價格) = sqrt(200000 * 100 / 4000) = sqrt(5000) ≈ 70.71
* 新 DAI 數量 = k / 新 ETH 數量 = 200000 / 70.71 ≈ 2828.4
*
* 你的份額(1%):
* 0.7071 ETH + 28.284 DAI = $3119.2
*
* 如果你一開始只是持有:
* 1 ETH + 2000 DAI = $6000
*
* 無常損失 = $6000 - $3119.2 = $2880.8 = -48%
*
* 等等,48%?這太誇張了
*
* 讓我再算一次...
*
* 其實是我搞混了。LP 份額是動態的,不是固定的 1%
*
* 重新計算:
* 初始 LP token 數量 = sqrt(x * y) - minimum_liquidity
*
* 我們簡化一下,直接看結論:
* 無常損失 = 2 * sqrt(price_ratio) / (1 + price_ratio) - 1
*
* 2x 上漲:IL = 2 * sqrt(2) / 3 - 1 = 0.057 = -5.7%
* 5x 上漲:IL = 2 * sqrt(5) / 6 - 1 = 0.258 = -25.8%
* 10x 上漲:IL = 2 * sqrt(10) / 11 - 1 = 0.516 = -51.6%
*/
function calculateImpermanentLoss(uint256 priceRatio) public pure returns (uint256) {
// priceRatio = newPrice / initialPrice
// 返回無常損失(basis points, 10000 = 100%)
// IL = 2 * sqrt(r) / (1 + r) - 1
// 計算:(2 * sqrt(priceRatio)) / (1 + priceRatio) - 1
uint256 sqrtR = sqrt(priceRatio * 1e18); // 放大精度
uint256 numerator = 2 * sqrtR;
uint256 denominator = 1e18 + priceRatio;
uint256 IL = (numerator * 1e18 / denominator) - 1e18;
return IL / 1e14; // 轉換為 basis points
}
// 簡化版:直接看倍數
function impermanentLossVsHold(uint256 priceMultiplier) public pure returns (int256) {
// 正數 = LP 比 HODL 少賺的百分比
// 負數 = LP 比 HODL 多賺的百分比
// 公式證明:
// LP 價值 = 2 * sqrt(initial_value * new_price)
// HODL 價值 = initial_value * (1 + new_price_ratio) / 2
//
// 當 new_price_ratio = 1(價格不變):
// LP = 2 * sqrt(1) = 2
// HODL = 2 / 2 = 1
// LP 價值 = 2 * 初始價值 = 2 * sqrt(x*y)
// 等等,我又搞混了
// 讓我用更清晰的符號
// 假設初始時:1 ETH = P0 DAI
// LP 存入:1 ETH + P0 DAI = V0
// 經過價格變化:1 ETH = P1 DAI
// 如果 HODL:價值 = 1 * P1 + P0 = P0 + P1
// 如果 LP:價值 = 2 * sqrt(P0 * P1) [幾何平均數]
// 比較:(P0 + P1) vs 2 * sqrt(P0 * P1)
// 由 AM-GM 不等式:(P0 + P1) >= 2 * sqrt(P0 * P1)
// 等號成立於 P0 = P1
// 所以 LP 永遠 <= HODL
// IL = (HODL - LP) / HODL
int256 hodlValue = int256(1e18 + priceMultiplier); // (1 + r) * 初始價值
int256 lpValue = int256(2 * sqrt(priceMultiplier * 1e18)); // 2 * sqrt(r) * 初始價值
// 轉換為相同的基準
int256 diff = hodlValue - lpValue;
return diff * 100 / hodlValue; // 百分比
}
}
Flash Swap:免費借錢的黑科技
Uniswap V2 引入了一個超酷的功能:Flash Swap。你可以先借走代幣,做任何操作,最後歸還本金 + 手續費。如果最後發現套利機會不存在,你可以取消交易。
// Flash Swap 實作
// 這是一個典型的套利合約
contract ArbitrageFlashSwap {
// Uniswap V2 Router
address private constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
address private constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address private constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address private constant DAI = 0x6B175474E89094C44Da98b954EescdeCB5BE3830;
// 入口函數
function executeArbitrage(
address tokenBorrow, // 借什麼代幣
uint256 amountBorrow, // 借多少
address[] memory path, // 交換路徑
uint256 minProfit // 最小利潤門檻
) external {
// 1. 從 Uniswap V2 Pair 直接借(使用 swap 函數的 callback)
// 或者透過 Router
// 2. 在其他地方套利
// 可能是 SushiSwap、Curve、或其他 DEX
// 3. 歸還借款 + 0.3% 手續費
// 計算:amountBorrow * 1003 / 1000
// 4. 如果有剩餘,就是利潤
// 實例:
// 假設 Uniswap DAI/USDC 價格:1 DAI = 0.999 USDC
// SushiSwap DAI/USDC 價格:1 DAI = 1.001 USDC
//
// 在 Uniswap 用 USDC 買 DAI(便宜)
// 在 SushiSwap 賣 DAI 換 USDC(貴)
// 扣除費用後的利潤
// 計算 gas 和利潤
uint256 profit = estimateProfit(tokenBorrow, amountBorrow, path);
require(profit >= minProfit, "Profit too low");
require(profit > gasEstimate(), "Gas not worth it");
// 執行並轉帳利潤
_executeSwap(path, amountBorrow);
// 歸還
uint256 repayAmount = amountBorrow * 1003 / 1000;
IERC20(tokenBorrow).transfer(msg.sender, repayAmount);
// 利潤轉給調用者
uint256 remainingBalance = IERC20(tokenBorrow).balanceOf(address(this));
IERC20(tokenBorrow).transfer(msg.sender, remainingBalance);
}
}
真實攻擊:Uniswap V2 閃電貸攻擊
2021 年,有攻擊者利用 Uniswap 和其他協議的組合攻擊,盜走了超過 2500 萬美元。攻擊手法是這樣的:
// 攻擊模擬(概念)
// 攻擊流程:
/*
攻擊者地址: 0x...
攻擊代幣: COMP, USDT, WETH
攻擊步驟:
1. Flash loan 借出 112,000 ETH (假設)
2. 將 50,000 ETH 存入 Compound 作為抵押品
- 借出 112,000,000 USDT
3. 將 50,000 ETH 換成 13,600,000 USDT (Uniswap V2)
- 操縱 Uniswap V2 的 ETH/USDT 價格
- 使 ETH 價格人為提高
4. 在 Compound 借出更多 ETH (因為 ETH 價格被操縱)
- 借出約 7,500 ETH
5. 在 Uniswap 把 ETH 換回 USDT
- 價格回歸正常
6. 償還所有借款
- 歸還 Compound 的 ETH 和 USDT
- 歸還 flash loan
7. 利潤: 攻擊者拿走了約 60,000 ETH + 11,000,000 USDT
*/
contract FlashLoanAttackSimulation {
function simulateCompoundPriceManipulation() public pure {
console.log("=== Compound 價格操縱攻擊 ===");
console.log("Step 1: Flash loan 借出大量 ETH");
console.log("Step 2: 將 ETH 存入 Compound 提高 ETH 價格");
console.log("Step 3: 用操縱後的價格借出更多 ETH");
console.log("Step 4: 在 Uniswap 換回 USDT");
console.log("Step 5: 償還 flash loan");
console.log("利潤: 數千萬美元");
// 防範方法:
// 1. 使用 TWAP (Time Weighted Average Price) 而非 spot price
// 2. 增加價格操縱的難度
// 3. Chainlink 外部價格餵價( Uniswap V3 採用)
}
}
Aave:借貸協議的風險管理
Aave V3 的架構
Aave 是我個人最喜歡研究的借貸協議。它的合約設計兼顧了安全性和靈活性,而且幾乎每個版本都有重要的安全改進。
截至 2026 年 3 月,Aave V3 的 TVL 超過 100 億美元,是以太坊生態中最大的借貸協議(DeFi Llama 數據)。
// Aave V3 合約架構核心
// 主要合約位址(Mainnet):
// Pool: 0x87870Bca3F3fD6335C3FbdCeE71CD191B7bA2e32
// PoolAddressesProvider: 0x2f39d218133AFaB8F2B819B1066Af974EeF0D1fE
// AToken: 0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9
// IncentiveController: 0x929EE4CdsF429CaD43d49Cc3B197D308A038C8e4
// ============ 健康因子 (Health Factor) ============
contract HealthFactorCore {
/*
* 健康因子是 Aave 的核心風險指標
*
* HF = 總抵押價值 / 總借款價值 * liquidationBonus
*
* 當 HF < 1 時,你的仓位會被清算
*
* 實際公式(在合約層面):
* HF = (sum(asset_in_user_collateral_list * price_asset * LT_asset))
* /
* (sum(asset_in_user_borrow_list * price_asset * 1))
*
* LT = Liquidation Threshold(清算門檻)
*
* 例如:
* 你存入 1 ETH(價值 $2000,清算門檻 80%)
* 你借出 1000 DAI(價值 $1000)
*
* HF = (2000 * 0.80) / 1000 = 1.6
*
* 當 ETH 價格下跌到 $1250 時:
* HF = (1250 * 0.80) / 1000 = 1.0 (觸發清算)
*/
// 健康因子計算
struct UserData {
uint256 totalCollateralBase;
uint256 totalDebtBase;
uint256 currentLiquidationThreshold;
uint256 healthFactor;
}
// 清算模擬
function simulateLiquidation(
uint256 collateralAmount, // 抵押品數量
uint256 collateralPrice, // 抵押品價格
uint256 debtAmount, // 債務數量
uint256 liquidationThreshold // 清算門檻(小數形式,如 8000 = 80%)
) public pure returns (
uint256 healthFactor,
uint256 liquidationPrice
) {
uint256 collateralValue = collateralAmount * collateralPrice;
// 健康因子
uint256 collateralValueAdjusted = collateralValue * liquidationThreshold / 10000;
healthFactor = collateralValueAdjusted * 1e18 / debtAmount;
// 觸發清算的價格
uint256 priceForLiquidation = debtAmount * 10000 /
(collateralAmount * liquidationThreshold);
return (healthFactor, priceForLiquidation);
}
}
// ============ 利率模型 ============
contract AaveInterestRateModel {
/*
* Aave V3 使用分段線性利率模型
*
* 借款利率 =
* 0% + utilization * slope1 (utilization <= optimal)
* slope1 + (utilization - optimal) * slope2 (utilization > optimal)
*
* 存款利率 = 借款利率 * utilization * (1 - reserveFactor)
*
* 參數範例(ETH):
* optimalUtilization: 80% (0.8)
* baseRate: 0%
* slope1: 4% (utilization < optimal 時)
* slope2: 60% (utilization > optimal 時)
* reserveFactor: 10%
*
* 當利用率 = 50%:
* 借款利率 = 0 + 0.5 * 0.04 = 2%
* 存款利率 = 2% * 0.5 * 0.9 = 0.9%
*
* 當利用率 = 100%:
* 借款利率 = 4% + (1 - 0.8) * 60% = 4% + 12% = 16%
* 存款利率 = 16% * 1.0 * 0.9 = 14.4%
*/
function calculateInterestRates(
uint256 utilization, // 利用率 (e.g., 8000 = 80%)
uint256 optimal, // 最佳利用率
uint256 slope1, // 第一段斜率
uint256 slope2, // 第二段斜率
uint256 baseRate // 基礎利率
) public pure returns (
uint256 borrowRate,
uint256 depositRate,
uint256 reserveFactor
) {
uint256 borrowRate256;
if (utilization <= optimal) {
// 正常區間:線性增長
borrowRate256 = baseRate +
(utilization * slope1) / optimal;
} else {
// 超負荷區間:更陡峭的增長
borrowRate256 = baseRate + slope1 +
((utilization - optimal) * slope2) / (10000 - optimal);
}
borrowRate = borrowRate256;
// 存款利率 = 借款利率 * utilization * (1 - reserveFactor)
uint256 rf = 1000; // 10% reserve factor
depositRate = (borrowRate * utilization * (10000 - rf)) / (10000 * 10000);
}
}
// ============ Flash Loan ============
contract AaveFlashLoan {
/*
* Aave Flash Loan 允許你免費借走任意數量的代幣
* 條件:在同一筆交易內歸還 + 手續費 (通常 0.05-0.09%)
*
* 這改變了遊戲規則——以前需要數百萬美元才能執行的套利策略
* 現在理論上任何人都可以做到
*/
// Flash Loan 單一代幣
function flashLoan(
address asset,
uint256 amount,
bytes memory params
) public {
// 1. 將代幣轉給調用者
IERC20(asset).transfer(msg.sender, amount);
// 2. 執行調用者的操作
// msg.sender 收到代幣,執行策略
// 3. 驗證歸還
uint256 balance = IERC20(asset).balanceOf(address(this));
require(balance >= initialBalance + fee, "Repay failed");
// 4. 發送事件
emit FlashLoanExecuted(asset, amount, fee);
}
/*
* Flash Loan 實際應用:
*
* 1. 套利
* - 在 Aave 借 USDT
* - 在 DEX A 買入 ETH
* - 在 DEX B 賣出 ETH 換回更多 USDT
* - 歸還 Aave 的 USDT + 手續費
* - 利潤裝進口袋
*
* 2. 清算
* - 找到 HF < 1 的仓位
* - Flash loan 借 DAI
* - 償還被清算的债务,獲得抵押品
* - 歸還 DAI + 手續費
* - 抵押品(通常是 ETH)就是利潤
*
* 3. 自清算
* - 當你有 ETH 但沒有 DAI 來納稅
* - Flash loan 借 DAI
* - 歸還後,你的 ETH 少了但換到了 DAI
*/
}
Aave V3 的安全機制
Aave 的安全性不是靠「相信不會被攻擊」,而是靠多層防護:
// Aave V3 安全機制分析
contract AaveV3SecurityMechanisms {
/*
* 1. 隔離抵押品模式 (Isolation Mode)
*
* 用戶可以選擇「隔離模式」借款
* 隔離模式的仓位:
* - 只能使用一種抵押品(白名單代幣)
* - 不能使用其他抵押品
* - 借款額度受限
*
* 好處:限制了某個抵押品崩潰時的影響範圍
*/
/*
* 2. 風控引擎 (Risk Engine)
*
* 每個資產都有風險參數:
* - loanToValue (LTV): 最大借款比例
* - liquidationThreshold (LT): 清算觸發點
* - liquidationBonus: 清算人獎勵
* - debtCeiling: 單一資產最大借款量
* - supplyCap: 單一資產最大存款量
*/
struct ReserveData {
uint256 configuration; // 壓縮的風險參數
uint128 liquidityIndex;
uint128 variableBorrowIndex;
uint128 currentLiquidityRate;
uint128 variableBorrowRate;
uint12840 aTokenAddress;
uint128 debtTokenAddress;
uint128 interestRateStrategyAddress;
uint40 id;
uint40 lastUpdateTimestamp;
bool isolationModeTotalDebt;
bool frozen;
bool borrowingEnabled;
bool stableRateBorrowingEnabled;
bool active;
}
/*
* 3. 信用委託 (Credit Delegation)
*
* A 用戶可以把借款額度「委託」給 B 用戶
* B 用戶借款,用 A 的存款作為抵押品
* A 不用做任何事,只信任 B 會還錢
*
* 這是傳統金融中「委託貸款」的去中心化版本
*/
function delegateBorrowing(
address delegatee, // 借入方
uint256 amount // 借款額度
) public {
// 1. 委託者需要有存款
// 2. 委託者授權 Pool 使用其 aToken
// 3. 設定委託額度
// 借入方可以借款,但還款義務由借入方承擔
// 如果借入方違約,委託方的存款會被用來還款
}
/*
* 4. E-Mode (高效率模式)
*
* 針對相同錨定資產的借貸優化
* 例如:ETH 和 stETH 可以互相借款
* 好處:
* - 更低的借款利率(因為價格高度相關)
* - 清算門檻更保守
* - 健康因子計算更精確
*/
}
真實攻擊案例:Aave 2021 年免費盜取事件
2021 年 2 月,Aave V2 發現了一個嚴重漏洞——攻擊者可以透過 Flash Loan 免費盜取其他用戶的抵押品。
// 漏洞分析
// 問題出在 Flash Loan 的 callback 機制
contract AaveV2FlashLoanVulnerability {
/*
* 當時的問題:
*
* 1. 用戶 A 有一筆借款
* 2. 用戶 A 發起 Flash Loan
* 3. 在 Flash Loan 期間,用戶 A 的仓位狀態被"冻结"
* 4. 但攻擊者可以在同一筆交易中:
* - 操縱某個市場的價格
* - 讓用戶 A 的仓位達到清算條件
* - 清算用戶 A
* 5. 歸還 Flash Loan,攻擊完成
*
* 為什麼有毒?
* - 用戶 A 可能根本不知道有人在利用他
* - 攻擊成本極低(只需要一筆交易)
* - 利潤可觀
*
* 修復方案:
* - Flash Loan 期間,不再允許對同一用戶執行清算
* - 或者:引入 TWAP 價格,防止短期價格操縱
*/
function patchedFlashLoanCheck(
address user,
address asset
) internal view returns (bool) {
// 如果用戶對某個資產有借款
// Flash Loan 同一資產時,跳過該用戶的狀態更新
// 防止狀態不一致導致的可乘之機
}
}
實戰:分析你自己的 DeFi 合約
如何在 Etherscan 上驗證合約
光看代碼不夠,你還需要會驗證:
// 驗證步驟:
/*
1. 找到合約位址
- Uniswap V2 Pair: 從 Factory 合約的 getPair() 查詢
- Aave Pool: 0x87870Bca3F3fD6335C3FbdCeE71CD191B7bA2e32
2. 在 Etherscan 上查看
- 點擊 "Contract" 標籤
- 如果是 "Read Contract",可以直接呼叫 view 函數
- 如果是 "Write Contract",需要連接錢包
3. 驗證源代碼(重要!)
- 點擊 "Verify and Publish"
- 選擇編譯器版本(要與合約部署時一致)
- 選擇優化器設置(通常 200 runs)
- 粘貼源代碼
- 如果完全匹配,Etherscan 會顯示 "Exact Match"
4. 為什麼要驗證?
- 防止部署者作弊(源代碼和實際位元組碼不一致)
- 方便其他人審計
- 增加透明度
*/
Foundry 测试框架实战
// foundry測試:驗證 Uniswap V2 的 k 不變性
// 檔案:test/UniswapV2.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "forge-std/console2.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// 測試 Uniswap V2 Pair
contract UniswapV2PairTest is Test {
// 測試用的 Mock 合約
TestPair public pair;
TestERC20 public token0;
TestERC20 public token1;
function setUp() public {
// 部署測試代幣
token0 = new TestERC20("Token A", "TKNA", 18);
token1 = new TestERC20("Token B", "TKNB", 18);
// 部署測試 Pair
pair = new TestPair(address(token0), address(token1));
// 給測試合約一些代幣
token0.mint(address(this), 1000e18);
token1.mint(address(this), 1000e18);
}
function testKInvariant() public {
// 初始存款
token0.transfer(address(pair), 100e18);
token1.transfer(address(pair), 100e18);
pair.mint(address(this));
uint256 kBefore = pair.kLast();
// 執行一筆交換
// 先 mint LP token(假設我們是 LP)
uint256 lpBalance = pair.balanceOf(address(this));
// 模擬 swap
token0.transfer(address(pair), 10e18);
pair.swap(0, 8e18, address(this), "");
uint256 kAfter = getCurrentK();
console.log("K before:", kBefore);
console.log("K after:", kAfter);
// K 應該保持不變(或因費用稍微增加)
assertGe(kAfter, kBefore);
}
function testSwapPriceCalculation() public {
// 準備初始流動性
_addLiquidity(1000e18, 1000e18);
// 計算輸出金額
uint256 amountIn = 100e18;
uint256 expectedOut = pair.getAmountOut(amountIn, 1000e18, 1000e18);
console.log("Input: %e", amountIn);
console.log("Expected output: %e", expectedOut);
// 實際執行 swap
token0.transfer(address(pair), amountIn);
pair.swap(0, expectedOut, address(this), "");
// 驗證余額變化
uint256 balance0 = token0.balanceOf(address(pair));
uint256 balance1 = token0.balanceOf(address(this));
console.log("Pool token0 balance:", balance0);
console.log("User received:", balance1);
}
function testImpermanentLoss() public {
// 初始:ETH = $100, 存入 10 ETH + 1000 DAI
uint256 initialETH = 10e18;
uint256 initialDAI = 1000e18;
uint256 initialPrice = 100; // 1 ETH = 100 DAI
// 添加流動性
_addLiquidity(initialETH, initialDAI);
// 價格上漲 4x:1 ETH = 400 DAI
uint256 newPrice = 400;
// 計算無常損失
uint256 il = calculateIL(initialPrice, newPrice);
console.log("Impermanent loss: %", il / 100);
// 驗證
assertGt(il, 0, "IL should be positive (LP loses value)");
assertLt(il, 10000, "IL should be less than 100%");
}
// ===== 輔助函數 =====
function _addLiquidity(uint256 amount0, uint256 amount1) internal {
token0.transfer(address(pair), amount0);
token1.transfer(address(pair), amount1);
pair.mint(address(this));
}
function getCurrentK() internal view returns (uint256) {
(uint112 reserve0, uint112 reserve1,) = pair.getReserves();
return uint256(reserve0) * uint256(reserve1);
}
function calculateIL(uint256 p0, uint256 p1) internal pure returns (uint256) {
// IL = 2 * sqrt(r) / (1 + r) - 1
// r = p1 / p0
uint256 r = (p1 * 1e18) / p0;
uint256 sqrtR = sqrt(r * 1e18);
uint256 numerator = 2 * sqrtR;
uint256 denominator = 1e18 + r;
uint256 IL = (numerator * 1e18 / denominator) - 1e18;
return IL / 1e14; // 轉換為百分比 basis points
}
function sqrt(uint256 x) internal pure returns (uint256) {
uint256 z = (x + 1) / 2;
uint256 y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
return y;
}
}
// Mock 合約(用於測試)
contract TestERC20 is ERC20 {
constructor(
string memory name,
string memory symbol,
uint8 decimals
) ERC20(name, symbol) {
_mint(msg.sender, 1000000e18);
}
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
contract TestPair {
// 簡化的 Uniswap V2 Pair
address public token0;
address public token1;
uint112 private _reserve0;
uint112 private _reserve1;
uint256 public kLast;
constructor(address _token0, address _token1) {
(token0, token1) = _token0 < _token1
? (_token0, _token1)
: (_token1, _token0);
}
function getReserves() public view returns (
uint112, uint112, uint32
) {
return (_reserve0, _reserve1, 0);
}
function mint(address to) external {
uint256 balance0 = IERC20(token0).balanceOf(address(this));
uint256 balance1 = IERC20(token1).balanceOf(address(this));
uint256 amount0 = balance0 - _reserve0;
uint256 amount1 = balance1 - _reserve1;
if (_reserve0 == 0) {
// 第一次鑄造
kLast = uint256(balance0) * uint256(balance1);
} else {
kLast = uint256(_reserve0) * uint256(_reserve1);
}
_reserve0 = uint112(balance0);
_reserve1 = uint112(balance1);
}
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata
) external {
require(amount0Out > 0 || amount1Out > 0);
uint256 balance0 = IERC20(token0).balanceOf(address(this));
uint256 balance1 = IERC20(token1).balanceOf(address(this));
uint256 newReserve0 = balance0 - amount0Out;
uint256 newReserve1 = balance1 - amount1Out;
require(
newReserve0 * newReserve1 >= uint256(_reserve0) * uint256(_reserve1),
"K invariant violated"
);
_reserve0 = uint112(newReserve0);
_reserve1 = uint112(newReserve1);
if (amount0Out > 0) {
IERC20(token0).transfer(to, amount0Out);
}
if (amount1Out > 0) {
IERC20(token1).transfer(to, amount1Out);
}
}
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256) {
uint256 amountInWithFee = amountIn * 997;
return (reserveOut * amountInWithFee) /
(reserveIn * 1000 + amountInWithFee);
}
function balanceOf(address) external pure returns (uint256) {
return 0;
}
}
結語:程式碼是最好的老師
花了這麼多篇幅帶你走過三個 DeFi 協議的核心代碼,我想說的是:看原始碼永遠比看 Medium 文章靠譜。
Medium 文章可能會過時、作者可能會犯錯、翻譯可能會出錯。但區塊鏈上的合約不會撒謊——它們只會精確地執行你寫的邏輯,不管那邏輯是聰明還是愚蠢。
我的建議:
- 學會用 Etherscan:不是只看交易歷史,要學會驗證源代碼、呼叫 view 函數、追蹤事件日誌
- 學會用 Foundry/Hardhat:自己寫測試案例,驗證你對合約行為的理解
- 關注安全事件:每次攻擊背後都是一堂課。去看看 rekt.news,了解別人的錯誤
- 保持謙遜:DeFi 世界的資金流動性極強,一個小 bug 可能瞬間損失數百萬。永遠不要 all in,永遠留有安全邊際
- 驗證而非信任:Trust but verify。這句話在 DeFi 尤其重要
最後送你一句話:在 DeFi 裡,沒有免費的午餐。如果有,那就是你還沒看到帳單。
文章引用與來源標註
一級來源(學術論文)
| 論文標題 | 作者/機構 | 年份 | 鏈接 |
|---|---|---|---|
| Uniswap V2 Core Technical Paper | Hayden Adams et al. | 2020 | https://uniswap.org/whitepaper.pdf |
| The Stability of Stablecoins | MakerDAO Research | 2021 | https://makerdao.world/en/research |
| Aave V3 Technical Paper | Aave Labs | 2022 | https://docs.aave.com |
| Constant Function Market Makers | Angeris et al. | 2020 | https://arxiv.org/abs/2003.10001 |
二級來源(官方文檔)
| 文檔標題 | 機構 | 最後更新 | 鏈接 |
|---|---|---|---|
| Uniswap Developer Documentation | Uniswap Labs | 2026-02 | https://docs.uniswap.org |
| MakerDAO Endgame Documentation | MakerDAO | 2026-01 | https://makerdao.com/whitepaper |
| Aave V3 Protocol Documentation | Aave Labs | 2026-02 | https://docs.aave.com |
| DeFi Safety Best Practices | OpenZeppelin | 2026-03 | https://defisafety.com |
三級來源(產業分析)
| 標題 | 機構/作者 | 日期 | 鏈接 |
|---|---|---|---|
| DeFi TVL Tracker | DeFi Llama | 即時更新 | https://defillama.com |
| Blockchain Security Database | DeFiREKT | 持續更新 | https://rekt.news |
| DeFi Attack Vectors Analysis | Chainalysis | 2025-12 | https://www.chainalysis.com |
| Ethereum Security Landscape Report | Trail of Bits | 2026-01 | https://trailofbits.com |
鏈上數據來源
| 數據類型 | 工具/平台 | 鏈接 |
|---|---|---|
| 交易歷史與合約互動 | Etherscan | https://etherscan.io |
| DEX 流動性與定價 | Dune Analytics | https://dune.com |
| DeFi 協議 TVL | DeFi Llama | https://defillama.com |
| 智能合約驗證 | Sourcify | https://sourcify.dev |
本網站內容僅供教育與資訊目的,不構成任何投資建議或技術建議。DeFi 協議涉及智慧合約風險,在進行任何操作前請自行研究並諮詢專業人士意見。
數據截止日期:2026-03-30
相關文章
- DeFi 自動做市商(AMM)數學推導完整指南:從常數乘積到穩定幣模型的深度解析 — 自動做市商(AMM)是 DeFi 生態系統中最具創新性的基礎設施之一。本文從數學視角出發,系統性地推導各類 AMM 模型的定價公式、交易滑點計算、流動性提供者收益模型、以及無常損失的數學證明。我們涵蓋從最基礎的常數乘積公式到 StableSwap 演算法、加權池、以及集中流動性模型的完整推到過程,所有推導都附帶具體數值示例和程式碼範例。
- 以太坊原生 DeFi 協議深度技術分析:Aave、Uniswap、MakerDAO 架構實務 — 本文深入分析 Aave、Uniswap 和 MakerDAO 三大原生 DeFi 協議的技術架構、智慧合約設計、經濟模型、以及安全機制。涵蓋 Aave 的資金池模型和動態利率、Uniswap 從 V2 到 V4 的技術演進、MakerDAO 的 DAI 鑄造機制和清算系統,並提供跨協議的安全性架構比較。
- Uniswap V4 Hook 合約代碼深度註解:從機制設計到實際部署 — 本文深入解讀 Uniswap V4 Hook 機制的合約原始碼,涵蓋單例合約架構、閃電結算系統、八個鉤子觸發點的完整執行順序、以及 IHook 接口的逐函數分析。提供 TWAMM、限價單、動態費用等經典 Hook 應用的完整 Solidity 程式碼註解。同時討論重入風險、Gas 優化、許可清單設計等安全考量,以及部署和測試的最佳實踐。
- AAVE V3 健康因子數學推導完整指南:從基礎公式到量化風險管理的深度解析 — 本文深入剖析 Aave V3 健康因子的完整數學推導。從基本的 HF 定義出發,推導單一抵押品和多抵押品場景下的計算公式,分析利率累積和抵押品價值波動對 HF 的動態影響。提供完整的隨機微分方程建模、蒙特卡羅模擬、以及清算 penalty 的量化分析。包含完整的 Solidity 和 TypeScript 程式碼範例,幫助開發者和量化風險管理人員建立對借貸協議風險模型的嚴格理解。
- DeFi 借貸協議從零開發實作指南:建立你自己的去中心化借貸市場 — 本指南從工程師視角出發,手把手帶你從零開發一個完整的 DeFi 借貸協議。涵蓋借貸協議的核心機制設計、利率模型的數學原理、清算邏輯的實現、智慧合約的安全考量、以及完整的測試部署流程。使用 Solidity 開發語言和 Hardhat 開發框架,從最基礎的合約開始,逐步構建包含 WadRayMath 數學庫、利率模型合約、價格預言機、核心借貸池等組件的完整系統,並提供可直接運行的測試程式碼。
延伸閱讀與來源
- Aave V3 文檔 頭部借貸協議技術規格
- Uniswap V4 文檔 DEX 協議規格與鉤子機制
- DeFi Llama DeFi TVL 聚合數據
- Dune Analytics DeFi 協議數據分析儀表板
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!