DeFi 借貸協議從零開發實作指南:建立你自己的去中心化借貸市場

本指南從工程師視角出發,手把手帶你從零開發一個完整的 DeFi 借貸協議。涵蓋借貸協議的核心機制設計、利率模型的數學原理、清算邏輯的實現、智慧合約的安全考量、以及完整的測試部署流程。使用 Solidity 開發語言和 Hardhat 開發框架,從最基礎的合約開始,逐步構建包含 WadRayMath 數學庫、利率模型合約、價格預言機、核心借貸池等組件的完整系統,並提供可直接運行的測試程式碼。

DeFi 借貸協議從零開發實作指南:建立你自己的去中心化借貸市場

概述

去中心化金融(DeFi)借貸協議是區塊鏈領域最成功的應用場景之一。Aave、Compound、MakerDAO 等協議管理著數百億美元的資產,每日處理數萬筆借貸交易。本指南將從工程師視角出發,手把手帶你從零開發一個完整的 DeFi 借貸協議。

本文涵蓋的核心內容包括:借貸協議的核心機制設計、利率模型的數學原理、清算邏輯的實現、智慧合約的安全考量、以及完整的測試部署流程。我們將使用 Solidity 開發語言和 Hardhat 開發框架,從最基礎的合約開始,逐步構建一個功能完整的借貸市場。

閱讀本文的前提是讀者已經熟悉 Solidity 基礎語法、了解以太坊智能合約的基本概念。如果你需要先補充這些知識,建議參閱我們的「以太坊智能合約開發實踐完整指南」。

一、借貸協議核心機制解析

1.1 借貸協議的基本原理

DeFi 借貸協議的核心創新在於消除了傳統金融中的中介機構。在銀行借貸模型中,銀行作為中介,連接存款人和借款人,並從利差中獲利。在 DeFi 模型中,智能合約取代了銀行的角色,自動執行以下功能:

流動性池的運作機制

借貸協議將所有存款人的資金匯集到一個「流動性池」中。這個池子就像一個共享的資金庫,借款人從中提取資金,存款人則根據其在池中的份額獲得利息收入。

存款人的收益來自借款人的利息支付。借款利率由協議的利率模型自動計算,確保流動性池的供需平衡。當借款需求高時,利率上升,吸引更多存款;當借款需求低時,利率下降,刺激借貸需求。

抵押與清算機制

為了防止借款人違約不還,DeFi 借貸協議要求借款人提供超額抵押品。超額抵押意味著借款人存入的抵押品價值必須超過其借款金額的某個比例(通常為 125-150%)。

當抵押品的價值下跌至不足以覆蓋借款金額時,清算機制會自動啟動。任何人都可以作為清算人,拍賣借款人的抵押品來償還協議。清算人通常能夠以折扣價格獲得抵押品,這提供了套利機會,激勵清算活動的及時進行。

1.2 核心合約架構設計

一個完整的借貸協議需要多個智能合約协同工作。以下是我們的系統架構:

合約層級結構

LendingProtocol/
├── core/
│   ├── LendingPool.sol          # 核心借貸池
│   ├── InterestRateModel.sol    # 利率模型
│   ├── LiquidationManager.sol   # 清算管理
│   └── PriceOracle.sol          # 價格預言機
├── tokens/
│   ├── ERC20.sol                # 基礎代幣介面
│   └── WETH.sol                 # WETH 包裝
├── libraries/
│   ├── SafeMath.sol             # 安全數學
│   └── WadRayMath.sol           # 定點數學
└── tests/
    └── LendingPool.test.js      # 測試用例

合約間的依賴關係

LendingPool 是整個系統的核心,負責處理存款和借款邏輯。它依賴於 InterestRateModel 計算利率,依賴於 LiquidationManager 處理清算,依賴於 PriceOracle 獲取資產價格。

二、項目初始化與環境配置

2.1 項目結構建立

首先,我們需要建立一個完整的 Hardhat 項目結構。

# 創建項目目錄
mkdir defi-lending-protocol
cd defi-lending-protocol

# 初始化 npm 項目
npm init -y

# 安裝 Hardhat 和相關依賴
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox chai ethers

# 安裝 OpenZeppelin 安全庫
npm install @openzeppelin/contracts

# 安裝開發依賴
npm install --save-dev @nomicfoundation/hardhat-network-helpers

# 創建合約目錄結構
mkdir contracts/core
mkdir contracts/tokens
mkdir contracts/libraries
mkdir contracts/interfaces
mkdir scripts
mkdir test

2.2 Hardhat 配置檔案

創建 hardhat.config.js 配置文件:

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
  solidity: {
    version: "0.8.24",
    settings: {
      optimizer: {
        enabled: true,
        runs: 200,
      },
      viaIR: true,
      evmVersion: "paris",
    },
  },
  networks: {
    hardhat: {
      chainId: 31337,
      forking: process.env.MAINNET_RPC_URL ? {
        url: process.env.MAINNET_RPC_URL,
        blockNumber: 19500000,
      } : undefined,
    },
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL || "",
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
      chainId: 11155111,
    },
  },
  gasReporter: {
    enabled: process.env.REPORT_GAS === "true",
    currency: "USD",
  },
  paths: {
    sources: "./contracts",
    tests: "./test",
    cache: "./cache",
    artifacts: "./artifacts",
  },
};

2.3 環境變數配置

創建 .env 檔案:

# Infura 或其他 RPC 提供者
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY

# 部署私鑰
PRIVATE_KEY=0x_your_private_key_here

# Etherscan API Key(用於合約驗證)
ETHERSCAN_API_KEY=your_etherscan_api_key

# Gas 報告
REPORT_GAS=true

三、核心程式碼實現

3.1 數學庫:定點運算

在金融合約中,精確的定點運算至關重要。我們需要處理小數和利率計算。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/**
 * @title WadRayMath
 * @notice 處理 WAD(18 位小數)和 RAY(27 位小數)運算的數學庫
 * @dev 用於處理借貸協議中的精確利率計算
 */
library WadRayMath {
    uint256 internal constant WAD = 1e18;
    uint256 internal constant RAY = 1e27;
    uint256 internal constant HALF_WAD = WAD / 2;
    uint256 internal constant HALF_RAY = RAY / 2;

    // 錯誤定義
    error DivisionByZero();
    error Overflow();
    error InvalidPower(uint256 base, uint256 exponent);

    /**
     * @dev 安全的加法
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        if (c < a) revert Overflow();
        return c;
    }

    /**
     * @dev 安全的減法
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b > a) revert Overflow();
        return a - b;
    }

    /**
     * @dev 安全的乘法(WAD 精度)
     */
    function mulWad(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0 || b == 0) return 0;
        if (a > type(uint256).max / b) revert Overflow();
        return (a * b + HALF_WAD) / WAD;
    }

    /**
     * @dev 安全的除法(WAD 精度)
     */
    function divWad(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) revert DivisionByZero();
        if (a == 0) return 0;
        uint256 first = a * WAD;
        if (first / a != WAD) revert Overflow();
        return (first + HALF_WAD) / b;
    }

    /**
     * @dev 安全的乘法(RAY 精度)
     */
    function mulRay(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0 || b == 0) return 0;
        if (a > type(uint256).max / b) revert Overflow();
        return (a * b + HALF_RAY) / RAY;
    }

    /**
     * @dev 安全的除法(RAY 精度)
     */
    function divRay(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) revert DivisionByZero();
        if (a == 0) return 0;
        uint256 first = a * RAY;
        if (first / a != RAY) revert Overflow();
        return (first + HALF_RAY) / b;
    }

    /**
     * @dev RAY 轉 WAD
     */
    function rayToWad(uint256 ray) internal pure returns (uint256 wad) {
        wad = ray / RAY;
        uint256 remainder = ray % RAY;
        if (remainder > HALF_RAY) wad++;
    }

    /**
     * @dev WAD 轉 RAY
     */
    function wadToRay(uint256 wad) internal pure returns (uint256 ray) {
        ray = wad * RAY / WAD;
    }

    /**
     * @dev 指數運算(基數以 RAY 為單位)
     */
    function rayPow(uint256 x, uint256 n) internal pure returns (uint256 z) {
        if (n == 0) return RAY;
        if (x == 0) return 0;
        
        z = RAY;
        uint256 tmp = x;
        
        // 二分指數法
        while (n > 1) {
            if (n % 2 == 1) {
                z = mulRay(z, tmp);
            }
            tmp = mulRay(tmp, tmp);
            n /= 2;
        }
        
        // 如果 n 是奇數,需要再乘一次
        if (n % 2 == 1) {
            z = mulRay(z, tmp);
        }
    }
}

3.2 利率模型實現

利率模型是借貸協議的核心組件,決定了借款利率和存款利率的計算方式。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "./libraries/WadRayMath.sol";

/**
 * @title InterestRateModel
 * @notice 線性利率模型實現
 * @dev 借款利率 = 基礎利率 + 利用率 * 斜率
 */
contract InterestRateModel {
    using WadRayMath for uint256;

    // 利率參數
    uint256 public immutable baseRate;      // 基礎年利率(以 RAY 為單位)
    uint256 public immutable slope;          // 斜率(利用率每增加 1 對應的利率增加)
    uint256 public constant OPTIMAL = 0.8e27; // 最佳利用率(80%)

    // 年利率到每秒利率的轉換因子
    uint256 public constant SECONDS_PER_YEAR = 365 days;
    uint256 public constant RAY = 1e27;

    /**
     * @param _baseRate 基礎年利率(以 wad 為單位,例如 0.05 = 5%)
     * @param _slope 斜率參數(以 wad 為單位)
     */
    constructor(uint256 _baseRate, uint256 _slope) {
        baseRate = _baseRate.wadToRay();
        slope = _slope.wadToRay();
    }

    /**
     * @notice 計算借款年利率
     * @param utilisation 當前利用率(以 RAY 為單位)
     * @return 借款年利率(以 RAY 為單位)
     */
    function getBorrowRate(uint256 utilisation) public view returns (uint256) {
        // 當利用率超過 80% 時,利率急劇上升
        if (utilisation <= OPTIMAL) {
            // 線性區間
            return baseRate.add(slope.mulRay(utilisation) / OPTIMAL);
        } else {
            // 超過最佳利用率,利率急劇上升
            uint256 excess = utilisation - OPTIMAL;
            uint256 excessRatio = excess / (RAY - OPTIMAL);
            
            // 最大斜率 = slope * 5(急劇上升區間的斜率)
            uint256 highSlope = slope * 5;
            
            uint256 rateAtOptimal = baseRate.add(slope);
            return rateAtOptimal.add(highSlope.mulRay(excessRatio));
        }
    }

    /**
     * @notice 計算借款年利率(合約內部使用,精度處理)
     * @param utilisation 當前利用率(百分比,例如 80 表示 80%)
     * @return 借款年利率(以 RAY 為單位)
     */
    function getBorrowRateWad(uint256 utilisation) public view returns (uint256) {
        // 將利用率從百分比轉換為 RAY
        uint256 utilisationRay = (utilisation * RAY) / 100;
        return getBorrowRate(utilisationRay);
    }

    /**
     * @notice 計算存款年利率
     * @param utilisation 當前利用率
     * @param borrowRate 借款年利率
     * @return 存款年利率(以 RAY 為單位)
     */
    function getSupplyRate(
        uint256 utilisation,
        uint256 borrowRate
    ) public view returns (uint256) {
        // 存款利率 = 借款利率 * 利用率 * (1 - 協議費用)
        uint256 protocolFee = 0.1e18; // 10% 協議費用
        uint256 poolFactor = RAY - protocolFee;
        
        // 利用率需要從 RAY 格式轉換
        uint256 utilisationRay = utilisation; // 已經是 RAY 格式
        
        return borrowRate.mulRay(utilisationRay).mulRay(poolFactor) / RAY;
    }

    /**
     * @notice 計算協議費用(以 wad 為單位)
     */
    function getProtocolFee() external pure returns (uint256) {
        return 0.1e18; // 10%
    }
}

/**
 * @title JumpRateModelV2
 * @notice 帶跳躍的利率模型(類 Aave)
 */
contract JumpRateModelV2 is InterestRateModel {
    uint256 public immutable jump;
    uint256 public immutable kink;

    /**
     * @param _baseRate 基礎年利率
     * @param _slope 斜率參數
     * @param _jump 跳躍係數
     * @param _kink 跳躍點
     */
    constructor(
        uint256 _baseRate,
        uint256 _slope,
        uint256 _jump,
        uint256 _kink
    ) InterestRateModel(_baseRate, _slope) {
        jump = _jump.wadToRay();
        kink = _kink.wadToRay();
    }

    /**
     * @notice 重寫借款利率計算,支援跳躍
     */
    function getBorrowRate(uint256 utilisation) public view override returns (uint256) {
        // 低利用率區間
        if (utilisation <= kink) {
            return super.getBorrowRate(utilisation);
        }
        
        // 高利用率區間,跳躍上升
        uint256 rateAtKink = super.getBorrowRate(kink);
        uint256 excess = utilisation - kink;
        uint256 excessRatio = excess / (RAY - kink);
        
        return rateAtKink.add(jump.mulRay(excessRatio));
    }
}

3.3 價格預言機

預言機為借貸協議提供資產價格數據。我們將實現一個簡化的 Chainlink 風格預言機介面。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/**
 * @title PriceOracle
 * @notice 聚合價格預言機
 * @dev 支持 Chainlink 和 Uniswap TWAP 的聚合
 */
contract PriceOracle {
    // 錯誤定義
    error PriceExpired(uint256 timestamp);
    error PriceNotPositive(uint256 price);

    // 價格數據結構
    struct PriceData {
        uint256 price;        // 價格(以 USD 為單位,8 位小數)
        uint256 timestamp;    // 最後更新時間
    }

    // 資產地址到價格數據的映射
    mapping(address => PriceData) public prices;
    
    // 可接受的價格過期時間(秒)
    uint256 public constant HEART_BEAT = 1 hours;

    /**
     * @notice 更新資產價格
     * @param asset 資產地址
     * @param price 價格(8 位小數)
     */
    function updatePrice(address asset, uint256 price) external {
        if (price == 0) revert PriceNotPositive(price);
        prices[asset] = PriceData({
            price: price,
            timestamp: block.timestamp
        });
    }

    /**
     * @notice 批量更新價格
     * @param assets 資產地址陣列
     * @param _prices 價格陣列
     */
    function batchUpdatePrice(address[] calldata assets, uint256[] calldata _prices) external {
        require(assets.length == _prices.length, "Length mismatch");
        for (uint256 i = 0; i < assets.length; i++) {
            updatePrice(assets[i], _prices[i]);
        }
    }

    /**
     * @notice 獲取資產價格
     * @param asset 資產地址
     * @return 價格(標準化到 18 位小數)
     */
    function getPrice(address asset) public view returns (uint256) {
        PriceData memory data = prices[asset];
        
        if (data.timestamp == 0) {
            revert("Price not set");
        }
        
        if (block.timestamp - data.timestamp > HEART_BEAT) {
            revert PriceExpired(data.timestamp);
        }
        
        // 將 8 位小數標準化為 18 位小數
        return data.price * 1e10;
    }

    /**
     * @notice 獲取多個資產價格
     */
    function getPrices(address[] calldata assets) external view returns (uint256[] memory) {
        uint256[] memory result = new uint256[](assets.length);
        for (uint256 i = 0; i < assets.length; i++) {
            result[i] = getPrice(assets[i]);
        }
        return result;
    }

    /**
     * @notice 計算兩個資產之間的兌換價格
     * @param assetA 資產 A
     * @param assetB 資產 B
     * @return 1 單位資產 A 兌換多少單位資產 B
     */
    function getAssetPrice(address assetA, address assetB) external view returns (uint256) {
        uint256 priceA = getPrice(assetA);
        uint256 priceB = getPrice(assetB);
        
        // 兩個價格都已經標準化為 USD 報價
        // 例如:ETH/USD = 2000e18, BTC/USD = 50000e18
        // ETH/BTC = ETH/USD / BTC/USD = 2000/50000 = 0.04
        return priceA / priceB;
    }
}

/**
 * @title SimpleChainlinkOracle
 * @notice 模擬 Chainlink 預言機介面
 */
contract SimpleChainlinkOracle {
    struct ChainlinkData {
        int256 answer;         // 最新價格
        uint256 updatedAt;     // 最後更新時間
    }
    
    mapping(address => ChainlinkData) public chainlinkPrices;

    /**
     * @notice 模擬 Chainlink 的 latestAnswer
     */
    function latestAnswer(address asset) external view returns (int256) {
        return chainlinkPrices[asset].answer;
    }

    /**
     * @notice 模擬 Chainlink 的 latestTimestamp
     */
    function latestTimestamp(address asset) external view returns (uint256) {
        return chainlinkPrices[asset].updatedAt;
    }

    /**
     * @notice 模擬 Chainlink 數據源的更新
     */
    function updateChainlinkPrice(address asset, int256 price) external {
        chainlinkPrices[asset] = ChainlinkData({
            answer: price,
            updatedAt: block.timestamp
        });
    }
}

3.4 核心借貸池合約

這是整個借貸協議的核心合約,負責處理存款、借款、清算等核心邏輯。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./libraries/WadRayMath.sol";
import "./InterestRateModel.sol";
import "./PriceOracle.sol";

/**
 * @title LendingPool
 * @notice 核心借貸池合約
 * @dev 處理存款、借款、清算和利率計算
 */
contract LendingPool is Ownable, ReentrancyGuard {
    using WadRayMath for uint256;
    using SafeERC20 for IERC20;

    // ============ 錯誤定義 ============
    error InvalidAsset();
    error AssetNotSupported();
    error AmountMustBePositive();
    error InsufficientBalance();
    error InsufficientLiquidity();
    error HealthFactorTooLow();
    error HealthFactorNotBelowThreshold();
    error LiquidationBlocked();
    error TransferFailed();
    error InvalidCloseFactor();
    error InvalidLiquidationBonus();

    // ============ 事件 ============
    event Deposit(address indexed user, address indexed asset, uint256 amount, uint256 shares);
    event Withdraw(address indexed user, address indexed asset, uint256 amount, uint256 shares);
    event Borrow(address indexed user, address indexed asset, uint256 amount, uint256 debt);
    event Repay(address indexed user, address indexed asset, uint256 amount, uint256 debtBurned);
    event Liquidate(
        address indexed liquidator,
        address indexed borrower,
        address indexed collateralAsset,
        address borrowedAsset,
        uint256 amount,
        uint256 collateralLiquidated
    );
    event ReserveUpdated(address indexed asset, uint256 newSize);

    // ============ 結構定義 ============
    
    /**
     * @notice 市場狀態結構
     */
    struct Market {
        uint256 totalDeposits;           // 總存款額
        uint256 totalBorrows;             // 總借款額
        uint256 liquidity;                // 可用流動性
        uint256 lastUpdateTimestamp;      // 最後更新時間
        InterestRateModel rateModel;      // 利率模型
        bool isActive;                    // 是否啟用
        uint256 collateralFactor;         // 抵押因子(百分比)
        uint256 liquidationThreshold;    // 清算閾值
        uint256 liquidationBonus;         // 清算獎勵
    }

    /**
     * @notice 用戶存款結構
     */
    struct UserDeposit {
        uint256 shares;                   // 份額
        uint256 depositTimestamp;         // 存款時間
    }

    /**
     * @notice 用戶借款結構
     */
    struct UserBorrow {
        uint256 principal;               // 本金
        uint256 borrowIndex;              // 借款時的市場指數
        uint256 borrowTimestamp;          // 借款時間
    }

    /**
     * @notice 用戶狀態結構
     */
    struct UserState {
        mapping(address => UserDeposit) deposits;    // 各資產存款
        mapping(address => UserBorrow) borrows;      // 各資產借款
        uint256 totalCollateralValue;                // 總抵押品價值(USD)
        uint256 totalBorrowValue;                    // 總借款價值(USD)
    }

    // ============ 狀態變量 ============
    
    // 市場映射
    mapping(address => Market) public markets;
    
    // 用戶狀態映射
    mapping(address => UserState) public userStates;
    
    // 支持的資產列表
    address[] public supportedAssets;
    
    // 預言機
    PriceOracle public priceOracle;
    
    // 利率指數(每個市場一個)
    mapping(address => uint256) public borrowIndex;
    
    // 全局狀態
    uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18; // 健康因子低於此值可被清算
    uint256 public constant CLOSE_FACTOR = 0.5e18; // 每次清算最多償還 50% 的借款
    uint256 public constant SECONDS_PER_YEAR = 365 days;

    // ============ 修飾符 ============
    
    modifier marketExists(address asset) {
        if (!markets[asset].isActive) revert AssetNotSupported();
        _;
    }

    // ============ 初始化 ============
    
    constructor(address _priceOracle) Ownable(msg.sender) {
        priceOracle = PriceOracle(_priceOracle);
    }

    // ============ 管理員函數 ============

    /**
     * @notice 添加支持的資產市場
     */
    function addMarket(
        address asset,
        uint256 collateralFactor,
        uint256 liquidationThreshold,
        uint256 liquidationBonus,
        InterestRateModel rateModel
    ) external onlyOwner {
        require(!markets[asset].isActive, "Market already exists");
        
        markets[asset] = Market({
            totalDeposits: 0,
            totalBorrows: 0,
            liquidity: 0,
            lastUpdateTimestamp: block.timestamp,
            rateModel: rateModel,
            isActive: true,
            collateralFactor: collateralFactor,
            liquidationThreshold: liquidationThreshold,
            liquidationBonus: liquidationBonus
        });
        
        supportedAssets.push(asset);
        borrowIndex[asset] = WadRayMath.RAY; // 初始指數為 1 RAY
        
        emit ReserveUpdated(asset, 0);
    }

    /**
     * @notice 更新市場參數
     */
    function updateMarket(
        address asset,
        uint256 collateralFactor,
        uint256 liquidationThreshold,
        uint256 liquidationBonus
    ) external onlyOwner marketExists(asset) {
        markets[asset].collateralFactor = collateralFactor;
        markets[asset].liquidationThreshold = liquidationThreshold;
        markets[asset].liquidationBonus = liquidationBonus;
    }

    /**
     * @notice 更新預言機
     */
    function setPriceOracle(address _priceOracle) external onlyOwner {
        priceOracle = PriceOracle(_priceOracle);
    }

    // ============ 存款功能 ============

    /**
     * @notice 存款資產到池中
     * @param asset 資產地址
     * @param amount 存款金額(0 表示全部)
     */
    function deposit(address asset, uint256 amount) 
        external 
        nonReentrant 
        marketExists(asset) 
    {
        if (amount == 0) {
            amount = IERC20(asset).balanceOf(address(this));
        }
        if (amount == 0) revert AmountMustBePositive();

        // 轉移代幣
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);

        // 計算份額
        Market storage market = markets[asset];
        uint256 shares = amount;
        
        if (market.totalDeposits > 0) {
            shares = amount.mulWad(market.totalDeposits) / market.totalDeposits;
        }
        
        // 更新市場狀態
        market.totalDeposits += amount;
        market.liquidity += amount;
        
        // 更新用戶狀態
        UserState storage userState = userStates[msg.sender];
        userState.deposits[asset].shares += shares;
        userState.deposits[asset].depositTimestamp = block.timestamp;

        emit Deposit(msg.sender, asset, amount, shares);
    }

    /**
     * @notice 從池中提取存款
     * @param asset 資產地址
     * @param shares 要提取的份額(0 表示全部)
     */
    function withdraw(address asset, uint256 shares) 
        external 
        nonReentrant 
        marketExists(asset) 
    {
        UserState storage userState = userStates[msg.sender];
        UserDeposit storage deposit_ = userState.deposits[asset];
        
        if (shares == 0) {
            shares = deposit_.shares;
        }
        if (shares == 0) revert AmountMustBePositive();
        
        Market storage market = markets[asset];
        
        // 計算可提取金額
        uint256 amount = shares.mulWad(market.totalDeposits) / deposit_.shares;
        
        // 檢查流動性
        if (amount > market.liquidity) revert InsufficientLiquidity();
        
        // 更新狀態
        deposit_.shares -= shares;
        market.totalDeposits -= amount;
        market.liquidity -= amount;

        // 轉移代幣
        IERC20(asset).safeTransfer(msg.sender, amount);

        // 檢查用戶健康因子
        _revertIfHealthFactorBelowThreshold(msg.sender);

        emit Withdraw(msg.sender, asset, amount, shares);
    }

    // ============ 借款功能 ============

    /**
     * @notice 借款資產
     * @param asset 資產地址
     * @param amount 借款金額
     */
    function borrow(address asset, uint256 amount) 
        external 
        nonReentrant 
        marketExists(asset) 
    {
        if (amount == 0) revert AmountMustBePositive();
        
        Market storage market = markets[asset];
        uint256 price = priceOracle.getPrice(asset);
        
        // 更新借款指數
        _updateBorrowIndex(asset);

        // 計算新借款後的總借款價值
        uint256 newBorrowValue = _getUserTotalBorrowValue(msg.sender) + (amount.mulWad(price));
        
        // 計算最大可借款額(基於抵押品)
        uint256 maxBorrowValue = _getUserMaxBorrowValue(msg.sender);
        if (newBorrowValue > maxBorrowValue) revert HealthFactorTooLow();

        // 檢查流動性
        if (amount > market.liquidity) revert InsufficientLiquidity();

        // 更新市場狀態
        market.totalBorrows += amount;
        market.liquidity -= amount;

        // 更新用戶借款狀態
        UserState storage userState = userStates[msg.sender];
        UserBorrow storage borrow_ = userState.borrows[asset];
        
        uint256 borrowIndex_ = borrowIndex[asset];
        if (borrow_.principal > 0) {
            // 累積利息後疊加新借款
            uint256 accruedDebt = _calculateAccruedDebt(borrow_, borrowIndex_);
            borrow_.principal += accruedDebt;
        }
        
        borrow_.principal += amount;
        borrow_.borrowIndex = borrowIndex_;
        borrow_.borrowTimestamp = block.timestamp;

        // 轉移代幣
        IERC20(asset).safeTransfer(msg.sender, amount);

        emit Borrow(msg.sender, asset, amount, borrow_.principal);
    }

    /**
     * @notice 償還借款
     * @param asset 資產地址
     * @param amount 償還金額(0 表示全部)
     */
    function repay(address asset, uint256 amount) 
        external 
        nonReentrant 
        marketExists(asset) 
    {
        UserState storage userState = userStates[msg.sender];
        UserBorrow storage borrow_ = userState.borrows[asset];
        
        // 計算應計利息
        _updateBorrowIndex(asset);
        uint256 totalDebt = _calculateAccruedDebt(borrow_, borrowIndex[asset]);
        
        if (amount == 0 || amount > totalDebt) {
            amount = totalDebt;
        }
        if (amount == 0) revert AmountMustBePositive();
        if (borrow_.principal == 0) revert InsufficientBalance();

        // 轉移代幣
        IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);

        // 更新市場狀態
        Market storage market = markets[asset];
        market.totalBorrows -= amount;
        market.liquidity += amount;

        // 更新用戶借款狀態
        borrow_.principal = totalDebt - amount;
        borrow_.borrowIndex = borrowIndex[asset];
        borrow_.borrowTimestamp = block.timestamp;

        emit Repay(msg.sender, asset, amount, totalDebt - borrow_.principal);
    }

    // ============ 清算功能 ============

    /**
     * @notice 清算健康因子過低的用戶
     * @param borrower 借款人被清算的用戶
     * @param borrowedAsset 借款資產
     * @param collateralAsset 抵押品資產
     * @param amount 要清算的借款金額
     */
    function liquidate(
        address borrower,
        address borrowedAsset,
        address collateralAsset,
        uint256 amount
    ) external nonReentrant marketExists(borrowedAsset) marketExists(collateralAsset) {
        if (amount == 0) revert AmountMustBePositive();
        
        UserState storage borrowerState = userStates[borrower];
        UserBorrow storage borrow_ = borrowerState.borrows[borrowedAsset];
        
        // 計算總債務
        _updateBorrowIndex(borrowedAsset);
        uint256 totalDebt = _calculateAccruedDebt(borrow_, borrowIndex[borrowedAsset]);
        
        // 限制清算金額
        uint256 maxLiquidatable = totalDebt.mulWad(CLOSE_FACTOR);
        if (amount > maxLiquidatable) {
            amount = maxLiquidatable;
        }
        
        // 計算抵押品金額(包含清算獎勵)
        Market storage collateralMarket = markets[collateralAsset];
        uint256 collateralPrice = priceOracle.getPrice(collateralAsset);
        uint256 borrowedPrice = priceOracle.getPrice(borrowedAsset);
        
        uint256 collateralAmount = amount
            .mulWad(borrowedPrice)
            .mulWad(WadRayMath.WAD + collateralMarket.liquidationBonus)
            .divWad(collateralPrice);

        // 執行清算
        Market storage borrowedMarket = markets[borrowedAsset];
        borrowedMarket.totalBorrows -= amount;
        borrowedMarket.liquidity += amount;

        collateralMarket.totalDeposits -= collateralAmount;
        collateralMarket.liquidity -= collateralAmount;

        // 更新借款人狀態
        borrow_.principal = totalDebt - amount;
        borrow_.borrowIndex = borrowIndex[borrowedAsset];
        
        UserDeposit storage deposit_ = borrowerState.deposits[collateralAsset];
        deposit_.shares = deposit_.shares.mulWad(
            (collateralMarket.totalDeposits + collateralAmount) / collateralMarket.totalDeposits
        );

        // 轉移資產
        IERC20(borrowedAsset).safeTransferFrom(msg.sender, address(this), amount);
        IERC20(collateralAsset).safeTransfer(msg.sender, collateralAmount);

        emit Liquidate(msg.sender, borrower, collateralAsset, borrowedAsset, amount, collateralAmount);
    }

    // ============ 視圖函數 ============

    /**
     * @notice 獲取用戶的健康因子
     */
    function getHealthFactor(address user) public view returns (uint256) {
        uint256 totalCollateralValue = _getUserTotalCollateralValue(user);
        uint256 totalBorrowValue = _getUserTotalBorrowValue(user);
        
        if (totalBorrowValue == 0) return type(uint256).max;
        
        return totalCollateralValue.divWad(totalBorrowValue);
    }

    /**
     * @notice 獲取用戶的存款餘額
     */
    function getDepositBalance(address user, address asset) 
        public 
        view 
        returns (uint256) 
    {
        UserState storage userState = userStates[user];
        UserDeposit storage deposit_ = userState.deposits[asset];
        Market storage market = markets[asset];
        
        if (market.totalDeposits == 0 || deposit_.shares == 0) return 0;
        
        return deposit_.shares.mulWad(market.totalDeposits);
    }

    /**
     * @notice 獲取用戶的借款餘額
     */
    function getBorrowBalance(address user, address asset) 
        public 
        view 
        returns (uint256) 
    {
        UserState storage userState = userStates[user];
        UserBorrow storage borrow_ = userState.borrows[asset];
        
        if (borrow_.principal == 0) return 0;
        
        uint256 currentIndex = borrowIndex[asset];
        uint256 accruedDebt = borrow_.principal.mulWad(currentIndex) / borrow_.borrowIndex;
        
        return accruedDebt;
    }

    /**
     * @notice 獲取市場利用率
     */
    function getUtilization(address asset) public view returns (uint256) {
        Market storage market = markets[asset];
        if (market.totalDeposits == 0) return 0;
        return market.totalBorrows.mulWad(WadRayMath.WAD) / market.totalDeposits;
    }

    /**
     * @notice 獲取當前借款年利率
     */
    function getCurrentBorrowRate(address asset) public view returns (uint256) {
        Market storage market = markets[asset];
        uint256 utilization = getUtilization(asset);
        return market.rateModel.getBorrowRate(utilization);
    }

    /**
     * @notice 獲取當前存款年利率
     */
    function getCurrentSupplyRate(address asset) public view returns (uint256) {
        Market storage market = markets[asset];
        uint256 utilization = getUtilization(asset);
        uint256 borrowRate = market.rateModel.getBorrowRate(utilization);
        return market.rateModel.getSupplyRate(utilization, borrowRate);
    }

    // ============ 內部函數 ============

    /**
     * @notice 更新借款指數(累計利息)
     */
    function _updateBorrowIndex(address asset) internal {
        Market storage market = markets[asset];
        uint256 currentTime = block.timestamp;
        uint256 timeDelta = currentTime - market.lastUpdateTimestamp;
        
        if (timeDelta == 0) return;
        
        // 計算期間利率
        uint256 borrowRate = market.rateModel.getBorrowRate(
            getUtilization(asset)
        );
        
        // 累積利息
        uint256 interestAccrued = market.totalBorrows.mulWad(
            borrowRate.mulWad(timeDelta) / SECONDS_PER_YEAR
        );
        
        // 更新市場狀態
        market.totalBorrows += interestAccrued;
        market.lastUpdateTimestamp = currentTime;
        
        // 更新借款指數
        borrowIndex[asset] = borrowIndex[asset].mulWad(
            WadRayMath.RAY + borrowRate.mulWad(timeDelta) / SECONDS_PER_YEAR
        );
    }

    /**
     * @notice 計算應計利息
     */
    function _calculateAccruedDebt(UserBorrow storage borrow_, uint256 currentIndex) 
        internal 
        view 
        returns (uint256) 
    {
        if (borrow_.principal == 0) return 0;
        return borrow_.principal.mulWad(currentIndex) / borrow_.borrowIndex;
    }

    /**
     * @notice 獲取用戶總抵押品價值
     */
    function _getUserTotalCollateralValue(address user) 
        internal 
        view 
        returns (uint256) 
    {
        UserState storage userState = userStates[user];
        uint256 totalValue;
        
        for (uint256 i = 0; i < supportedAssets.length; i++) {
            address asset = supportedAssets[i];
            Market storage market = markets[asset];
            
            if (!market.isActive) continue;
            
            uint256 depositBalance = getDepositBalance(user, asset);
            if (depositBalance == 0) continue;
            
            uint256 price = priceOracle.getPrice(asset);
            totalValue += depositBalance.mulWad(price).mulWad(market.collateralFactor);
        }
        
        return totalValue;
    }

    /**
     * @notice 獲取用戶總借款價值
     */
    function _getUserTotalBorrowValue(address user) internal view returns (uint256) {
        UserState storage userState = userStates[user];
        uint256 totalValue;
        
        for (uint256 i = 0; i < supportedAssets.length; i++) {
            address asset = supportedAssets[i];
            UserBorrow storage borrow_ = userState.borrows[asset];
            
            if (borrow_.principal == 0) continue;
            
            uint256 borrowBalance = getBorrowBalance(user, asset);
            uint256 price = priceOracle.getPrice(asset);
            totalValue += borrowBalance.mulWad(price);
        }
        
        return totalValue;
    }

    /**
     * @notice 獲取用戶最大可借款價值
     */
    function _getUserMaxBorrowValue(address user) internal view returns (uint256) {
        return _getUserTotalCollateralValue(user);
    }

    /**
     * @notice 檢查健康因子
     */
    function _revertIfHealthFactorBelowThreshold(address user) internal view {
        uint256 healthFactor = getHealthFactor(user);
        if (healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD) {
            revert HealthFactorTooLow();
        }
    }
}

四、測試框架與案例

4.1 基本存款借款測試

// test/LendingPool.test.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-network-helpers");

describe("LendingPool", function () {
  let lendingPool;
  let mockToken;
  let priceOracle;
  let interestRateModel;
  let owner;
  let user1;
  let user2;

  const INITIAL_MINT = ethers.parseEther("1000000");
  const DEPOSIT_AMOUNT = ethers.parseEther("1000");
  const BORROW_AMOUNT = ethers.parseEther("500");

  beforeEach(async function () {
    [owner, user1, user2] = await ethers.getSigners();

    // 部署 Mock 代幣
    const MockToken = await ethers.getContractFactory("MockERC20");
    mockToken = await MockToken.deploy("Mock USDT", "mUSDT", 6);
    
    // 部署 Mock 預言機
    const MockOracle = await ethers.getContractFactory("MockOracle");
    priceOracle = await MockOracle.deploy();
    
    // 部署利率模型
    const InterestRateModel = await ethers.getContractFactory("InterestRateModel");
    interestRateModel = await InterestRateModel.deploy(
      ethers.parseEther("0.05"), // 5% 基礎利率
      ethers.parseEther("0.4")   // 40% 斜率
    );

    // 部署借貸池
    const LendingPool = await ethers.getContractFactory("LendingPool");
    lendingPool = await LendingPool.deploy(priceOracle.target);

    // 初始化市場
    await lendingPool.addMarket(
      mockToken.target,
      ethers.parseEther("0.8"),  // 80% 抵押因子
      ethers.parseEther("0.85"), // 85% 清算閾值
      ethers.parseEther("0.1"),  // 10% 清算獎勵
      interestRateModel.target
    );

    // 設置初始價格
    await priceOracle.setPrice(mockToken.target, ethers.parseUnits("1", 8));

    // 鑄造代幣給用戶
    await mockToken.mint(user1.address, INITIAL_MINT);
    await mockToken.mint(user2.address, INITIAL_MINT);
    
    // 用戶批准 LendingPool
    await mockToken.connect(user1).approve(lendingPool.target, ethers.MaxUint256);
    await mockToken.connect(user2).approve(lendingPool.target, ethers.MaxUint256);
  });

  describe("存款功能", function () {
    it("應該成功存款", async function () {
      const tx = await lendingPool.connect(user1).deposit(mockToken.target, DEPOSIT_AMOUNT);
      
      await expect(tx).to.emit(lendingPool, "Deposit")
        .withArgs(user1.address, mockToken.target, DEPOSIT_AMOUNT, DEPOSIT_AMOUNT);
      
      const balance = await lendingPool.getDepositBalance(user1.address, mockToken.target);
      expect(balance).to.equal(DEPOSIT_AMOUNT);
    });

    it("應該正確計算存款份額", async function () {
      // 用戶 1 存款
      await lendingPool.connect(user1).deposit(mockToken.target, DEPOSIT_AMOUNT);
      
      // 用戶 2 存款(假設此時存款價值相同)
      await lendingPool.connect(user2).deposit(mockToken.target, DEPOSIT_AMOUNT);
      
      const balance1 = await lendingPool.getDepositBalance(user1.address, mockToken.target);
      const balance2 = await lendingPool.getDepositBalance(user2.address, mockToken.target);
      
      expect(balance1).to.equal(DEPOSIT_AMOUNT);
      expect(balance2).to.equal(DEPOSIT_AMOUNT);
    });
  });

  describe("借款功能", function () {
    beforeEach(async function () {
      // 先存款作為抵押品
      await lendingPool.connect(user1).deposit(mockToken.target, DEPOSIT_AMOUNT);
    });

    it("應該成功借款", async function () {
      const tx = await lendingPool.connect(user1).borrow(mockToken.target, BORROW_AMOUNT);
      
      await expect(tx).to.emit(lendingPool, "Borrow");
      
      const borrowBalance = await lendingPool.getBorrowBalance(user1.address, mockToken.target);
      expect(borrowBalance).to.be.gt(0);
    });

    it("借款後健康因子應該降低", async function () {
      const healthFactorBefore = await lendingPool.getHealthFactor(user1.address);
      
      await lendingPool.connect(user1).borrow(mockToken.target, BORROW_AMOUNT);
      
      const healthFactorAfter = await lendingPool.getHealthFactor(user1.address);
      expect(healthFactorAfter).to.lt(healthFactorBefore);
    });

    it("借款金額不應超過抵押品價值", async function () {
      // 嘗試借款超過 80% 抵押因子
      const excessiveBorrow = DEPOSIT_AMOUNT; // 100% 抵押品
      
      await expect(
        lendingPool.connect(user1).borrow(mockToken.target, excessiveBorrow)
      ).to.be.reverted;
    });
  });

  describe("償還功能", function () {
    beforeEach(async function () {
      await lendingPool.connect(user1).deposit(mockToken.target, DEPOSIT_AMOUNT);
      await lendingPool.connect(user1).borrow(mockToken.target, BORROW_AMOUNT);
    });

    it("應該成功償還借款", async function () {
      const initialBalance = await lendingPool.getBorrowBalance(user1.address, mockToken.target);
      
      await lendingPool.connect(user1).repay(mockToken.target, BORROW_AMOUNT);
      
      const finalBalance = await lendingPool.getBorrowBalance(user1.address, mockToken.target);
      expect(finalBalance).to.lt(initialBalance);
    });

    it("償還後健康因子應該恢復", async function () {
      await lendingPool.connect(user1).repay(mockToken.target, BORROW_AMOUNT);
      
      const healthFactor = await lendingPool.getHealthFactor(user1.address);
      expect(healthFactor).to.equal(ethers.parseEther("100")); // 100% = 無借款
    });
  });

  describe("清算功能", function () {
    beforeEach(async function () {
      // 用戶 1 存款作為抵押品
      await lendingPool.connect(user1).deposit(mockToken.target, DEPOSIT_AMOUNT);
      
      // 用戶 1 借款
      await lendingPool.connect(user1).borrow(mockToken.target, BORROW_AMOUNT);
      
      // 用戶 2 存款以便有流動性
      await lendingPool.connect(user2).deposit(mockToken.target, DEPOSIT_AMOUNT);
    });

    it("健康因子低於閾值時應可被清算", async function () {
      // 模擬抵押品價格下跌 50%
      await priceOracle.setPrice(mockToken.target, ethers.parseUnits("0.5", 8));
      
      const healthFactor = await lendingPool.getHealthFactor(user1.address);
      expect(healthFactor).to.lt(ethers.parseEther("1")); // 低於清算閾值
      
      // 用戶 2 作為清算人
      const tx = await lendingPool.connect(user2)
        .liquidate(user1.address, mockToken.target, mockToken.target, BORROW_AMOUNT / 2n);
      
      await expect(tx).to.emit(lendingPool, "Liquidate");
    });
  });

  describe("利率計算", function () {
    it("利用率為 0 時借款利率應等於基礎利率", async function () {
      const borrowRate = await lendingPool.getCurrentBorrowRate(mockToken.target);
      // 基礎利率 5% = 0.05 * 1e27
      expect(borrowRate).to.equal(ethers.parseEther("0.05"));
    });

    it("隨著存款增加,利用率應保持為 0", async function () {
      await lendingPool.connect(user1).deposit(mockToken.target, DEPOSIT_AMOUNT);
      
      const utilization = await lendingPool.getUtilization(mockToken.target);
      expect(utilization).to.equal(0);
    });

    it("借款後利用率應上升", async function () {
      await lendingPool.connect(user1).deposit(mockToken.target, DEPOSIT_AMOUNT);
      await lendingPool.connect(user1).borrow(mockToken.target, BORROW_AMOUNT);
      
      const utilization = await lendingPool.getUtilization(mockToken.target);
      expect(utilization).to.equal(ethers.parseEther("0.5")); // 50% 利用率
    });
  });
});

4.2 Mock 合約

// contracts/mocks/MockERC20.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    uint8 private _decimals;

    constructor(
        string memory name,
        string memory symbol,
        uint8 decimals_
    ) ERC20(name, symbol) {
        _decimals = decimals_;
    }

    function decimals() public view override returns (uint8) {
        return _decimals;
    }

    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }

    function burn(address from, uint256 amount) external {
        _burn(from, amount);
    }
}

// contracts/mocks/MockOracle.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract MockOracle {
    mapping(address => uint256) private prices;
    uint256 private constant PRICE_DECIMALS = 8;

    error PriceNotSet();
    error InvalidPrice();

    function setPrice(address asset, uint256 price) external {
        if (price == 0) revert InvalidPrice();
        prices[asset] = price;
    }

    function getPrice(address asset) external view returns (uint256) {
        uint256 price = prices[asset];
        if (price == 0) revert PriceNotSet();
        // 標準化為 18 位小數
        return price * 1e10;
    }
}

五、部署腳本

5.1 主網部署腳本

// scripts/deploy.js
const { ethers } = require("hardhat");
const hre = require("hardhat");

async function main() {
  const [deployer] = await ethers.getSigners();
  
  console.log("部署錢包:", deployer.address);
  console.log("帳戶餘額:", ethers.formatEther(await ethers.provider.getBalance(deployer.address)));

  // 部署 Mock 代幣(如果需要)
  const MockToken = await ethers.getContractFactory("MockERC20");
  const usdt = await MockToken.deploy("Mock USDT", "mUSDT", 6);
  await usdt.waitForDeployment();
  console.log("Mock USDT 地址:", usdt.target);

  // 部署預言機
  const PriceOracle = await ethers.getContractFactory("PriceOracle");
  const priceOracle = await PriceOracle.deploy();
  await priceOracle.waitForDeployment();
  console.log("PriceOracle 地址:", priceOracle.target);

  // 設置初始價格
  await priceOracle.updatePrice(usdt.target, 1e8);
  console.log("預言機價格已設置");

  // 部署利率模型
  const InterestRateModel = await ethers.getContractFactory("InterestRateModel");
  const interestModel = await InterestRateModel.deploy(
    ethers.parseEther("0.05"), // 5% 基礎利率
    ethers.parseEther("0.4")    // 40% 斜率
  );
  await interestModel.waitForDeployment();
  console.log("利率模型地址:", interestModel.target);

  // 部署借貸池
  const LendingPool = await ethers.getContractFactory("LendingPool");
  const lendingPool = await LendingPool.deploy(priceOracle.target);
  await lendingPool.waitForDeployment();
  console.log("LendingPool 地址:", lendingPool.target);

  // 添加市場
  await lendingPool.addMarket(
    usdt.target,
    ethers.parseEther("0.8"),  // 80% 抵押因子
    ethers.parseEther("0.85"),  // 85% 清算閾值
    ethers.parseEther("0.1"),   // 10% 清算獎勵
    interestModel.target
  );
  console.log("USDT 市場已添加");

  // 驗證合約(僅非本地網絡)
  if (hre.network.name !== "hardhat" && hre.network.name !== "localhost") {
    console.log("等待區塊確認...");
    await lendingPool.deploymentTransaction()?.wait(6);

    try {
      await hre.run("verify:verify", {
        address: lendingPool.target,
        constructorArguments: [priceOracle.target],
      });
      console.log("LendingPool 合約已驗證");
    } catch (e) {
      console.log("驗證失敗:", e.message);
    }
  }

  console.log("\n部署完成!");
  console.log("================");
  console.log("Mock USDT:", usdt.target);
  console.log("PriceOracle:", priceOracle.target);
  console.log("InterestRateModel:", interestModel.target);
  console.log("LendingPool:", lendingPool.target);

  // 保存部署信息
  const deploymentInfo = {
    network: hre.network.name,
    deployer: deployer.address,
    timestamp: new Date().toISOString(),
    contracts: {
      MockUSDTToken: usdt.target,
      PriceOracle: priceOracle.target,
      InterestRateModel: interestModel.target,
      LendingPool: lendingPool.target,
    },
  };

  const fs = require("fs");
  fs.writeFileSync(
    "deployment-info.json",
    JSON.stringify(deploymentInfo, null, 2)
  );
  console.log("部署信息已保存到 deployment-info.json");
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

5.2 運行測試網絡測試

# 編譯合約
npx hardhat compile

# 運行測試
npx hardhat test

# 部署到 Sepolia 測試網
npx hardhat run scripts/deploy.js --network sepolia

# 運行 Gas 報告
REPORT_GAS=true npx hardhat test

六、安全考量與最佳實踐

6.1 常見漏洞防護

在我們的借貸合約中,需要特別注意以下安全問題:

重入攻擊防護

我們使用 ReentrancyGuard 修飾符防止重入攻擊。在 deposit、withdraw、borrow、repay、liquidate 等關鍵函數上都應用此修飾符。

function withdraw(address asset, uint256 shares) 
    external 
    nonReentrant  // 防止重入
    marketExists(asset) 
{
    // ... 業務邏輯
}

價格操控防護

簡單的鏈上價格預言機容易遭受閃電貸攻擊。實際部署時應使用:

精度處理

金融計算中的精度問題可能導致資金損失。使用 WadRayMath 庫處理 18 位和 27 位精度運算。

6.2 額外安全建議

完整的安全審計

在生產部署前,必須進行:

升級機制

考慮實現代理模式,允許未來修復漏洞和升級:

緊急暫停機制

實現 Circuit Breaker 模式,在異常情況下暫停協議:

bool public paused;
modifier whenNotPaused() {
    require(!paused, "Protocol paused");
    _;
}

七、擴展功能建議

完成基礎版本後,可以考慮添加以下功能:

隔離市場

限制某些高風險資產只能作為抵押品或借款資產,不能兩者兼得。這降低了系統性風險。

利率敏感性

根據借款期限動態調整利率。長期借款可以獲得更優惠的利率,激勵穩定的借貸行為。

信用委托

允許存款人將其信用額度委托給其他人使用。例如,A 存款 100 ETH,可以委托給 B 借款 80 ETH。

收益分層

實現类似 Yearn 的 Vault 分層策略,將存款人的資金用於自動化收益策略。

八、結論

本指南從零開始,完整實現了一個 DeFi 借貸協議的核心功能:

這個實現雖然簡化了真實 Aave/Compound 的一些複雜功能,但涵蓋了所有核心概念和機制。在生產環境中,還需要考慮更多的安全措施、風險參數和治理機制。

DeFi 借貸協議的開發是一個持續學習的過程。建議讀者在完成本指南後,進一步研究:

這個領域的創新仍在快速發展,保持學習和實驗的熱情將是你最重要的資產。


本網站內容僅供教育與資訊目的,不構成任何投資建議或推薦。在進行任何加密貨幣相關操作前,請自行研究並諮詢專業人士意見。所有投資均有風險,請謹慎評估您的風險承受能力。

資料截止日期:2026 年 3 月

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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