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):

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 的合約架構其實出乎意料地簡單。核心只有兩個合約:

  1. UniswapV2Pair - 處理流動性池的代幣交換
  2. 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 文章可能會過時、作者可能會犯錯、翻譯可能會出錯。但區塊鏈上的合約不會撒謊——它們只會精確地執行你寫的邏輯,不管那邏輯是聰明還是愚蠢。

我的建議:

  1. 學會用 Etherscan:不是只看交易歷史,要學會驗證源代碼、呼叫 view 函數、追蹤事件日誌
  1. 學會用 Foundry/Hardhat:自己寫測試案例,驗證你對合約行為的理解
  1. 關注安全事件:每次攻擊背後都是一堂課。去看看 rekt.news,了解別人的錯誤
  1. 保持謙遜:DeFi 世界的資金流動性極強,一個小 bug 可能瞬間損失數百萬。永遠不要 all in,永遠留有安全邊際
  1. 驗證而非信任:Trust but verify。這句話在 DeFi 尤其重要

最後送你一句話:在 DeFi 裡,沒有免費的午餐。如果有,那就是你還沒看到帳單


文章引用與來源標註

一級來源(學術論文)

論文標題作者/機構年份鏈接
Uniswap V2 Core Technical PaperHayden Adams et al.2020https://uniswap.org/whitepaper.pdf
The Stability of StablecoinsMakerDAO Research2021https://makerdao.world/en/research
Aave V3 Technical PaperAave Labs2022https://docs.aave.com
Constant Function Market MakersAngeris et al.2020https://arxiv.org/abs/2003.10001

二級來源(官方文檔)

文檔標題機構最後更新鏈接
Uniswap Developer DocumentationUniswap Labs2026-02https://docs.uniswap.org
MakerDAO Endgame DocumentationMakerDAO2026-01https://makerdao.com/whitepaper
Aave V3 Protocol DocumentationAave Labs2026-02https://docs.aave.com
DeFi Safety Best PracticesOpenZeppelin2026-03https://defisafety.com

三級來源(產業分析)

標題機構/作者日期鏈接
DeFi TVL TrackerDeFi Llama即時更新https://defillama.com
Blockchain Security DatabaseDeFiREKT持續更新https://rekt.news
DeFi Attack Vectors AnalysisChainalysis2025-12https://www.chainalysis.com
Ethereum Security Landscape ReportTrail of Bits2026-01https://trailofbits.com

鏈上數據來源

數據類型工具/平台鏈接
交易歷史與合約互動Etherscanhttps://etherscan.io
DEX 流動性與定價Dune Analyticshttps://dune.com
DeFi 協議 TVLDeFi Llamahttps://defillama.com
智能合約驗證Sourcifyhttps://sourcify.dev

本網站內容僅供教育與資訊目的,不構成任何投資建議或技術建議。DeFi 協議涉及智慧合約風險,在進行任何操作前請自行研究並諮詢專業人士意見。

數據截止日期:2026-03-30

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。

目前尚無評論,成為第一個發表評論的人吧!