DeFi 借貸協議實作開發完整指南:從頭建構 AMM、借貸協議與可升級合約的完整程式碼範例

本文從工程師視角出發,提供從頭建構 DeFi 借貸協議的完整開發指南。涵蓋自動做市商(AMM)基礎、流動性池合約、借貸市場合約、利率模型、清算機制,以及可升級合約的實作策略。所有程式碼範例都基於 Solidity 語言,並遵循以太坊智能合約安全最佳實踐。我們提供完整的 AMM 數學推導、借貸市場合約程式碼、UUPS 代理模式實作,以及 Foundry 測試框架的詳細範例。

DeFi 借貸協議實作開發完整指南:從頭建構 AMM、借貸協議與可升級合約的完整程式碼範例

概述

去中心化金融(DeFi)借貸協議是以太坊生態系統中最具創新性和價值的應用之一。與傳統借貸系統不同,DeFi 借貸協議通過智能合約自動執行借貸邏輯,無需中介機構即可實現資產的借出和借入。這種模式的核心優勢包括:無需信任的資產托管、透明可驗證的合約邏輯、全球可訪問的流動性,以及可編程的利率模型。

本文將從工程師視角出發,提供從頭建構 DeFi 借貸協議的完整開發指南。我們將涵蓋自動做市商(AMM)基礎、流動性池合約、借貸市場合約、利率模型、清算機制,以及可升級合約的實作策略。所有程式碼範例都將基於 Solidity 語言,並遵循以太坊智能合約安全最佳實踐。我們將使用 Foundry 作為開發框架,因為它提供了業界領先的測試和部署工具鏈。

第一章:自動做市商(AMM)核心機制實作

1.1 AMM 基礎理論與數學模型

自動做市商是 DeFi 生態系統的基礎設施,其核心思想是用數學公式替代傳統的訂單簿來決定交易價格。最基本的 AMM 模型是常數乘積公式(Constant Product Market Maker),由 Uniswap 首創並廣泛採用。

常數乘積公式:

x * y = k

其中 x 和 y 分別是池子中兩種資產的數量,k 是常數。當用戶進行交易時,池子的資產數量會發生變化,但始終保持 x * y = k 的約束關係。這個公式的特點是:

交易價格計算:

當用戶想用 Δx 數量的資產 X 換取資產 Y 時,根據常數乘積公式:

(x + Δx) * (y - Δy) = k = x * y
y - Δy = (x * y) / (x + Δx)
Δy = y - (x * y) / (x + Δx)
Δy = y * (1 - x / (x + Δx))
Δy = y * (Δx / (x + Δx))

這就是計算輸出資產數量的基本公式。實際實現中,還需要考慮交易費用的扣除。

1.2 流動性池合約完整實作

合約架構設計:

LiquidityPool.sol
├── 狀態變量定義
├── 事件定義
├── 初始化函數
├── 流動性添加/移除
├── 交易功能
├── 價格查詢
└── 管理員函數

完整合約程式碼:

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

/**
 * @title ConstantProductAMM
 * @dev 實現基本常數乘積 AMM 的流動性池合約
 * 
 * 核心公式:x * y = k
 * 
 * 安全考量:
 * - 防止整數溢位攻擊
 * - 防止 k 值為零的邊界情況
 * - 防止過小的流動性導致定價錯誤
 * - 驗證滑點範圍以保護交易者
 */
contract ConstantProductAMM {
    // ============================================
    // 狀態變量定義
    // ============================================
    
    /// @dev 第一個代幣的合約地址
    address public tokenX;
    
    /// @dev 第二個代幣的合約地址
    address public tokenY;
    
    /// @dev 流動性池中 tokenX 的儲備量
    uint256 public reserveX;
    
    /// @dev 流動性池中 tokenY 的儲備量
    uint256 public reserveY;
    
    /// @dev 工廠合約地址(用於權限控制)
    address public factory;
    
    /// @dev 流動性池代幣(LP Token)的地址
    address public lpToken;
    
    /// @dev 協議手續費比例(以 basis points 為單位,1 = 0.01%)
    uint256 public feePercentage = 30; // 默認 0.3%
    
    /// @dev 總流動性份額
    uint256 public totalLiquidity;
    
    /// @dev 用戶流動性份額映射
    mapping(address => uint256) public liquidityOf;
    
    // ============================================
    // 事件定義
    // ============================================
    
    event Mint(
        address indexed sender,
        uint256 amountX,
        uint256 amountY,
        uint256 liquidity
    );
    
    event Burn(
        address indexed sender,
        uint256 amountX,
        uint256 amountY,
        uint256 liquidity
    );
    
    event Swap(
        address indexed sender,
        uint256 amountXIn,
        uint256 amountYIn,
        uint256 amountXOut,
        uint256 amountYOut
    );
    
    event Sync(
        uint256 reserveX,
        uint256 reserveY
    );
    
    // ============================================
    // 修飾符定義
    // ============================================
    
    modifier ensure(uint256 deadline) {
        require(deadline >= block.timestamp, "Expired deadline");
        _;
    }
    
    // ============================================
    // 建構函數
    // ============================================
    
    constructor(address _tokenX, address _tokenY) {
        require(_tokenX < _tokenY, "Invalid token order");
        tokenX = _tokenX;
        tokenY = _tokenY;
        factory = msg.sender;
    }
    
    // ============================================
    // 核心功能函數
    // ============================================
    
    /**
     * @dev 初始化流動性池
     * @param _lpToken 流動性代幣合約地址
     * 
     * 只能在部署時調用一次,用於設置 LP Token 地址
     */
    function initialize(address _lpToken) external {
        require(msg.sender == factory, "Unauthorized");
        lpToken = _lpToken;
    }
    
    /**
     * @dev 更新儲備量並同步狀態
     * 
     * 應該在任何改變儲備量的操作後調用
     */
    function _update() internal {
        reserveX = IERC20(tokenX).balanceOf(address(this));
        reserveY = IERC20(tokenY).balanceOf(address(this));
        emit Sync(reserveX, reserveY);
    }
    
    /**
     * @dev 鑄造流動性代幣(添加流動性)
     * @param to 接收流動性代幣的地址
     * @return liquidity 鑄造的流動性代幣數量
     */
    function mint(address to) external returns (uint256 liquidity) {
        (uint256 balanceX, uint256 balanceY) = _getBalances();
        uint256 amountX = balanceX - reserveX;
        uint256 amountY = balanceY - reserveY;
        
        if (totalLiquidity == 0) {
            // 首次添加流動性
            liquidity = _sqrt(amountX * amountY);
            require(liquidity > 0, "Insufficient liquidity");
        } else {
            // 計算應得的流動性份額
            uint256 liquidityX = (totalLiquidity * amountX) / reserveX;
            uint256 liquidityY = (totalLiquidity * amountY) / reserveY;
            liquidity = liquidityX < liquidityY ? liquidityX : liquidityY;
        }
        
        require(liquidity > 0, "Insufficient liquidity minted");
        
        liquidityOf[to] += liquidity;
        totalLiquidity += liquidity;
        
        _update();
        emit Mint(msg.sender, amountX, amountY, liquidity);
    }
    
    /**
     * @dev 燃燒流動性代幣(移除流動性)
     * @param to 接收代幣的地址
     * @return amountX 收到的 tokenX 數量
     * @return amountY 收到的 tokenY 數量
     */
    function burn(address to) external returns (uint256 amountX, uint256 amountY) {
        uint256 liquidity = liquidityOf[msg.sender];
        require(liquidity > 0, "Insufficient liquidity");
        
        liquidityOf[msg.sender] = 0;
        totalLiquidity -= liquidity;
        
        amountX = (liquidity * reserveX) / totalLiquidity;
        amountY = (liquidity * reserveY) / totalLiquidity;
        
        require(amountX > 0 && amountY > 0, "Insufficient liquidity burned");
        
        _safeTransfer(tokenX, to, amountX);
        _safeTransfer(tokenY, to, amountY);
        
        _update();
        emit Burn(msg.sender, amountX, amountY, liquidity);
    }
    
    /**
     * @dev 執行代幣交換
     * @param amountXIn 輸入的 tokenX 數量(若為 0,則計算為輸入)
     * @param amountYIn 輸入的 tokenY 數量(若為 0,則計算為輸入)
     * @param amountXMinOut 期望的最小 tokenX 輸出(防止過大滑點)
     * @param amountYMinOut 期望的最小 tokenY 輸出(防止過大滑點)
     * @param to 接收輸出代幣的地址
     * @param deadline 交易截止時間
     */
    function swap(
        uint256 amountXIn,
        uint256 amountYIn,
        uint256 amountXMinOut,
        uint256 amountYMinOut,
        address to,
        uint256 deadline
    ) external ensure(deadline) returns (uint256 amountXOut, uint256 amountYOut) {
        require(amountXIn > 0 || amountYIn > 0, "Insufficient input amount");
        require(amountXIn == 0 || amountYIn == 0, "Only one token allowed");
        
        (uint256 balanceX, uint256 balanceY) = _getBalances();
        
        // 計算輸出金額
        if (amountXIn > 0) {
            amountXOut = balanceX - ((balanceX * reserveX) / (reserveX + amountXIn));
            // 扣除手續費後的輸出
            amountXOut = (amountXOut * (10000 - feePercentage)) / 10000;
            require(amountXOut >= amountXMinOut, "Insufficient output amount");
            
            _safeTransfer(tokenX, to, amountXOut);
        } else {
            amountYOut = balanceY - ((balanceY * reserveY) / (reserveY + amountYIn));
            amountYOut = (amountYOut * (10000 - feePercentage)) / 10000;
            require(amountYOut >= amountYMinOut, "Insufficient output amount");
            
            _safeTransfer(tokenY, to, amountYOut);
        }
        
        _update();
        emit Swap(msg.sender, amountXIn, amountYIn, amountXOut, amountYOut);
    }
    
    /**
     * @dev 獲取當前代幣餘額(排除本合約持有的數量)
     */
    function _getBalances() internal view returns (uint256 balanceX, uint256 balanceY) {
        balanceX = IERC20(tokenX).balanceOf(address(this));
        balanceY = IERC20(tokenY).balanceOf(address(this));
    }
    
    /**
     * @dev 安全轉賬(防止代幣異常返回值)
     */
    function _safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(IERC20.transfer.selector, to, value)
        );
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            "Transfer failed"
        );
    }
    
    /**
     * @dev 安全的平方根計算(使用牛頓法)
     */
    function _sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y > 3) {
            z = y;
            uint256 x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }
    
    /**
     * @dev 獲取代幣對的流動性信息
     */
    function getReserves() external view returns (uint256, uint256) {
        return (reserveX, reserveY);
    }
    
    /**
     * @dev 計算給定輸入金額的輸出金額
     * @param amountIn 輸入金額
     * @param tokenIn 輸入代幣地址
     */
    function getAmountOut(
        uint256 amountIn,
        address tokenIn
    ) external view returns (uint256 amountOut) {
        require(amountIn > 0, "Invalid amount");
        require(tokenIn == tokenX || tokenIn == tokenY, "Invalid token");
        
        uint256 reserveIn = tokenIn == tokenX ? reserveX : reserveY;
        uint256 reserveOut = tokenIn == tokenX ? reserveY : reserveX;
        
        uint256 amountInWithFee = amountIn * (10000 - feePercentage);
        amountOut = (amountInWithFee * reserveOut) / (reserveIn * 10000 + amountInWithFee);
    }
}

/**
 * @dev ERC20 代幣介面定義
 */
interface IERC20 {
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
}

1.3 流動性提供者的收益與無常損失分析

流動性提供者(LP)在向 AMM 池子添加流動性時,會面臨「無常損失」(Impermanent Loss)的風險。這是因為池子中的資產比例會隨著交易而改變,導致 LP 的資產價值與簡單持有相比出現差異。

無常損失的數學推導:

假設初始狀態:

初始 LP 資產價值(以 tokenY 計價):

V₀ = k * (x₀ + y₀ * ratio) = k * 2 * x₀

交易後狀態:

LP 的資產價值(以 tokenY 計價):

V₁ = k * (x₁ + y₁ * ratio) = k * (x₁ + (x₀ * y₀ / x₁) * ratio)

無常損失 = (V₁ - V₀) / V₀

經過計算,無常損失與價格變化有確定的關係:

價格變化無常損失
+25%0.6%
+50%2.0%
+100%5.7%
+200%13.4%
+400%25.5%
+800%42.7%

無常損失計算合約:

/**
 * @title ImpermanentLossCalculator
 * @dev 計算無常損失的輔助庫
 */
library ImpermanentLossCalculator {
    /**
     * @dev 計算無常損失比例
     * @param priceRatio 最終價格與初始價格的比值
     * @return loss 無常損失比例(以 wei 為單位)
     * 
     * 公式:IL = 2 * sqrt(priceRatio) / (1 + priceRatio) - 1
     */
    function calculateLoss(uint256 priceRatio) internal pure returns (int256 loss) {
        // 處理邊界情況
        if (priceRatio == 1e18) {
            return 0;
        }
        
        // 使用整數算法計算 sqrt(priceRatio)
        uint256 sqrtPriceRatio = _sqrt(priceRatio * 1e18);
        
        // 計算 2 * sqrt(priceRatio) / (1 + priceRatio)
        uint256 numerator = 2 * sqrtPriceRatio * 1e18;
        uint256 denominator = 1e18 + priceRatio;
        
        uint256 ratio = numerator / denominator;
        
        // 計算最終的無常損失
        loss = int256(ratio) - int256(1e18);
    }
    
    /**
     * @dev 計算 LP 的實際資產價值
     * @param initialX 初始 tokenX 數量
     * @param initialY 初始 tokenY 數量
     * @param currentPriceRatio 當前與初始的價格比
     * @param liquidity LP 的流動性份額
     */
    function calculateCurrentValue(
        uint256 initialX,
        uint256 initialY,
        uint256 currentPriceRatio,
        uint256 liquidity,
        uint256 totalLiquidity
    ) internal pure returns (uint256 valueInTokenY) {
        // 計算 LP 在池子中的份額
        uint256 share = (liquidity * 1e18) / totalLiquidity;
        
        // 根據常數乘積公式計算當前池子狀態
        uint256 initialProduct = initialX * initialY;
        uint256 currentX = _sqrt(initialProduct * 1e18 / currentPriceRatio);
        uint256 currentY = _sqrt(initialProduct * currentPriceRatio / 1e18);
        
        // 計算 LP 的資產價值
        valueInTokenY = share * (currentX * currentPriceRatio + currentY) / 1e18;
    }
    
    function _sqrt(uint256 y) internal pure returns (uint256 z) {
        if (y > 3) {
            z = y;
            uint256 x = y / 2 + 1;
            while (x < z) {
                z = x;
                x = (y / x + x) / 2;
            }
        } else if (y != 0) {
            z = 1;
        }
    }
}

第二章:借貸市場合約架構

2.1 借貸協議核心機制

借貸協議允許用戶存入抵押品並借入其他資產。其核心機制包括:

抵押品系統:

利率模型:

清算機制:

2.2 借貸市場合約完整實作

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

/**
 * @title SimpleLendingMarket
 * @dev 基礎借貸市場合約實現
 * 
 * 核心功能:
 * - 存款:將代幣存入市場,獲得計息代幣(cToken)
 * - 借款:抵押品價值足夠時借入資產
 * - 還款:償還借款並收回部分抵押品
 * - 清算:當健康因子低於閾值時清算借款人的抵押品
 */
contract SimpleLendingMarket {
    // ============================================
    // 常量定義
    // ============================================
    
    /// @dev 健康因子閾值(低於此值可被清算)
    uint256 public constant LIQUIDATION_THRESHOLD = 1.1e18; // 110%
    
    /// @dev 健康因子精確度
    uint256 public constant HEALTH_FACTOR_PRECISION = 1e18;
    
    /// @dev 清算紅利(借款人支付的額外抵押品比例)
    uint256 public constant LIQUIDATION_BONUS = 1.1e18; // 10%
    
    // ============================================
    // 錯誤定義
    // ============================================
    
    error TransferFailed();
    error InsufficientBalance();
    error InsufficientCollateral();
    error HealthFactorTooLow();
    error LiquidationBlocked();

    // ============================================
    // 狀態變量
    // ============================================
    
    /// @dev 市場中支援的抵押品類型
    mapping(address => bool) public collateralMarkets;
    
    /// @dev 市場中支援的借款資產類型
    mapping(address => bool) public borrowMarkets;
    
    /// @dev 市場的流動性(可用於借款的金額)
    mapping(address => uint256) public marketLiquidity;
    
    /// @dev 存款利率模型參數
    uint256 public baseRate = 0.02e18;      // 基礎利率 2%
    uint256 public multiplier = 0.1e18;     // 利用率乘數
    uint256 public kink = 0.8e18;           // 拐點(80% 利用率)
    
    /// @dev 用戶的借款數量(資產地址 => 數量)
    mapping(address => mapping(address => uint256)) public borrowBalance;
    
    /// @dev 用戶的抵押品數量(資產地址 => 數量)
    mapping(address => mapping(address => uint256)) public collateralBalance;
    
    /// @dev 借款資產的價格(Oracle)
    mapping(address => uint256) public prices;
    
    /// @dev 市場崩潰事件
    event Accrue(uint256 interestAccumulated);
    event Deposit(address indexed user, address indexed asset, uint256 amount);
    event Withdraw(address indexed user, address indexed asset, uint256 amount);
    event Borrow(address indexed user, address indexed asset, uint256 amount);
    event Repay(address indexed user, address indexed asset, uint256 amount);
    event Liquidate(
        address indexed liquidator,
        address indexed borrower,
        address indexed collateralAsset,
        uint256 repayAmount,
        uint256 collateralAmount
    );

    // ============================================
    // 核心功能函數
    // ============================================
    
    /**
     * @dev 存款功能
     * @param asset 存款資產地址
     * @param amount 存款數量
     */
    function deposit(address asset, uint256 amount) external {
        require(amount > 0, "Amount must be positive");
        require(collateralMarkets[asset], "Not a collateral market");
        
        // 從用戶轉移代幣到合約
        _safeTransferFrom(asset, msg.sender, address(this), amount);
        
        // 更新抵押品餘額
        collateralBalance[msg.sender][asset] += amount;
        
        // 更新市場流動性
        marketLiquidity[asset] += amount;
        
        emit Deposit(msg.sender, asset, amount);
    }
    
    /**
     * @dev 提款功能
     * @param asset 提款資產地址
     * @param amount 提款數量
     */
    function withdraw(address asset, uint256 amount) external {
        require(amount > 0, "Amount must be positive");
        require(collateralMarkets[asset], "Not a collateral market");
        
        uint256 userCollateral = collateralBalance[msg.sender][asset];
        require(userCollateral >= amount, "Insufficient balance");
        
        // 計算提款後的健康因子
        uint256 newHealthFactor = _calculateHealthFactor(
            msg.sender,
            asset,
            userCollateral - amount
        );
        
        // 檢查是否有未償還的借款
        bool hasBorrows = _hasAnyBorrows(msg.sender);
        if (hasBorrows) {
            require(
                newHealthFactor >= HEALTH_FACTOR_PRECISION,
                "Health factor would be too low"
            );
        }
        
        // 更新狀態
        collateralBalance[msg.sender][asset] -= amount;
        marketLiquidity[asset] -= amount;
        
        // 轉移代幣給用戶
        _safeTransfer(asset, msg.sender, amount);
        
        emit Withdraw(msg.sender, asset, amount);
    }
    
    /**
     * @dev 借款功能
     * @param asset 借款資產地址
     * @param amount 借款數量
     */
    function borrow(address asset, uint256 amount) external {
        require(amount > 0, "Amount must be positive");
        require(borrowMarkets[asset], "Not a borrow market");
        require(marketLiquidity[asset] >= amount, "Insufficient liquidity");
        
        // 計算借款後的健康因子
        uint256 currentHealthFactor = _calculateHealthFactor(msg.sender, asset, 0);
        uint256 newBorrowBalance = borrowBalance[msg.sender][asset] + amount;
        
        // 檢查借款後的健康因子
        // 這裡需要重新計算,因為借款金額增加了
        uint256 collateralValue = _getCollateralValue(msg.sender);
        uint256 borrowValue = _getBorrowValue(msg.sender, asset, newBorrowBalance);
        
        uint256 newHealthFactor = (collateralValue * HEALTH_FACTOR_PRECISION) / borrowValue;
        require(
            newHealthFactor >= HEALTH_FACTOR_PRECISION,
            "Health factor too low"
        );
        
        // 更新借款餘額
        borrowBalance[msg.sender][asset] = newBorrowBalance;
        
        // 更新市場流動性
        marketLiquidity[asset] -= amount;
        
        // 轉移借款資產給用戶
        _safeTransfer(asset, msg.sender, amount);
        
        emit Borrow(msg.sender, asset, amount);
    }
    
    /**
     * @dev 還款功能
     * @param asset 還款資產地址
     * @param amount 還款數量
     */
    function repay(address asset, uint256 amount) external {
        require(amount > 0, "Amount must be positive");
        require(borrowMarkets[asset], "Not a borrow market");
        
        uint256 borrowBal = borrowBalance[msg.sender][asset];
        require(borrowBal >= amount, "Repay amount exceeds borrow balance");
        
        // 從用戶轉移代幣
        _safeTransferFrom(asset, msg.sender, address(this), amount);
        
        // 更新借款餘額
        borrowBalance[msg.sender][asset] = borrowBal - amount;
        
        // 更新市場流動性
        marketLiquidity[asset] += amount;
        
        emit Repay(msg.sender, asset, amount);
    }
    
    /**
     * @dev 清算功能
     * @param borrower 借款人被清算的地址
     * @param repayAsset 被清算的借款資產
     * @param collateralAsset 清算後獲得的抵押品資產
     * @param repayAmount 清算人願意償還的金額
     */
    function liquidate(
        address borrower,
        address repayAsset,
        address collateralAsset,
        uint256 repayAmount
    ) external {
        require(borrowBalance[borrower][repayAsset] >= repayAmount, "Invalid repay amount");
        require(borrowMarkets[repayAsset] && collateralMarkets[collateralAsset], "Invalid markets");
        
        // 計算清算後借款人的健康因子
        uint256 remainingBorrow = borrowBalance[borrower][repayAsset] - repayAmount;
        uint256 healthFactor = _calculateHealthFactorAfterLiquidation(
            borrower,
            repayAsset,
            remainingBorrow,
            collateralAsset,
            repayAmount
        );
        
        require(healthFactor < HEALTH_FACTOR_PRECISION, "Health factor OK");
        
        // 計算清算人可獲得的抵押品
        uint256 collateralValue = _getAssetValue(collateralAsset, repayAsset, repayAmount);
        uint256 seizedCollateral = (collateralValue * LIQUIDATION_BONUS) / 1e18;
        
        // 執行清算
        borrowBalance[borrower][repayAsset] = remainingBorrow;
        collateralBalance[borrower][collateralAsset] -= seizedCollateral;
        
        // 轉移抵押品給清算人
        _safeTransfer(collateralAsset, msg.sender, seizedCollateral);
        
        emit Liquidate(msg.sender, borrower, collateralAsset, repayAmount, seizedCollateral);
    }
    
    // ============================================
    // 健康因子計算
    // ============================================
    
    /**
     * @dev 計算用戶的健康因子
     * @param user 用戶地址
     * @param borrowAsset 考慮中的借款資產
     * @param newBorrowAmount 新的借款金額
     */
    function _calculateHealthFactor(
        address user,
        address borrowAsset,
        uint256 newBorrowAmount
    ) internal view returns (uint256) {
        uint256 collateralValue = _getCollateralValue(user);
        
        if (collateralValue == 0) {
            return 0;
        }
        
        uint256 borrowValue = _getTotalBorrowValue(user);
        
        // 如果我們正在計算借款後的健康因子
        if (borrowAsset != address(0)) {
            uint256 assetValue = _getAssetValue(borrowAsset, borrowAsset, newBorrowAmount);
            borrowValue += assetValue;
        }
        
        if (borrowValue == 0) {
            return type(uint256).max;
        }
        
        return (collateralValue * HEALTH_FACTOR_PRECISION) / borrowValue;
    }
    
    /**
     * @dev 計算清算後的健康因子
     */
    function _calculateHealthFactorAfterLiquidation(
        address borrower,
        address repayAsset,
        uint256 remainingBorrow,
        address collateralAsset,
        uint256 repayAmount
    ) internal view returns (uint256) {
        // 抵押品價值(扣除被清算的部分)
        uint256 collateralAmount = collateralBalance[borrower][collateralAsset];
        uint256 collateralAssetValue = _getAssetValue(
            collateralAsset,
            repayAsset,
            collateralAmount
        );
        
        // 剩餘借款價值
        uint256 borrowAssetValue = _getAssetValue(
            repayAsset,
            repayAsset,
            remainingBorrow
        );
        
        if (borrowAssetValue == 0) {
            return type(uint256).max;
        }
        
        return (collateralAssetValue * HEALTH_FACTOR_PRECISION) / borrowAssetValue;
    }
    
    /**
     * @dev 獲取用戶的總抵押品價值(以 ETH 或基準資產計價)
     */
    function _getCollateralValue(address user) internal view returns (uint256) {
        uint256 totalValue;
        
        // 這裡應該遍歷所有抵押品市場並計算總價值
        // 為了簡化,假設只有一個抵押品市場
        
        return totalValue;
    }
    
    /**
     * @dev 獲取用戶的總借款價值
     */
    function _getTotalBorrowValue(address user) internal view returns (uint256) {
        uint256 totalValue;
        
        // 遍歷所有借款市場並計算總價值
        
        return totalValue;
    }
    
    /**
     * @dev 檢查用戶是否有任何借款
     */
    function _hasAnyBorrows(address user) internal view returns (bool) {
        // 遍歷借款市場檢查
        return false;
    }
    
    /**
     * @dev 計算資產間的價值轉換
     */
    function _getAssetValue(
        address assetFrom,
        address assetTo,
        uint256 amount
    ) internal view returns (uint256) {
        if (amount == 0) return 0;
        
        // 使用 Oracle 價格
        uint256 priceFrom = prices[assetFrom];
        uint256 priceTo = prices[assetTo];
        
        // 簡化的價值計算:amount * priceFrom / priceTo
        return (amount * priceFrom) / priceTo;
    }
    
    /**
     * @dev 計算借款價值
     */
    function _getBorrowValue(
        address user,
        address asset,
        uint256 amount
    ) internal view returns (uint256) {
        return _getAssetValue(asset, asset, amount);
    }
    
    // ============================================
    // 工具函數
    // ============================================
    
    function _safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(IERC20.transfer.selector, to, value)
        );
        if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
            revert TransferFailed();
        }
    }
    
    function _safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(
            abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value)
        );
        if (!success || (data.length != 0 && !abi.decode(data, (bool)))) {
            revert TransferFailed();
        }
    }
    
    /**
     * @dev 獲取市場利用率
     */
    function getUtilizationRate(address asset) external view returns (uint256) {
        uint256 totalBorrows = IERC20(asset).balanceOf(address(this)) - marketLiquidity[asset];
        if (marketLiquidity[asset] == 0) return 0;
        
        return (totalBorrows * 1e18) / marketLiquidity[asset];
    }
    
    /**
     * @dev 計算借款利率
     */
    function getBorrowRate(address asset) external view returns (uint256) {
        uint256 utilization = getUtilizationRate(asset);
        
        if (utilization <= kink) {
            return baseRate + (utilization * multiplier) / 1e18;
        } else {
            uint256 excess = utilization - kink;
            uint256 kinkRate = baseRate + (kink * multiplier) / 1e18;
            uint256 slope = 0.5e18; // 拐點後的斜率
            return kinkRate + (excess * slope) / 1e18;
        }
    }
}

第三章:可升級合約實作策略

3.1 可升級合約模式

智能合約的不可變性是區塊鏈安全的基礎,但在實際開發中,合約往往需要修復漏洞或添加新功能。可升級合約通過代理模式解決了這個問題,將合約的業務邏輯(Implementation)與存儲(Storage)分離。

代理模式架構:

                    ┌─────────────────┐
                    │   Proxy Contract │
                    │                 │
                    │ - storage slot  │
                    │ - delegatecall  │
                    └────────┬────────┘
                             │ delegatecall
                             ▼
                    ┌─────────────────┐
                    │ Implementation  │
                    │                 │
                    │ - logic code    │
                    │ - no own storage│
                    └─────────────────┘

3.2 UUPS 代理模式實作

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

/**
 * @title UUPSProxy
 * @dev UUPS 代理合約
 * 
 * UUPS (Universal Upgradeable Proxy Standard) 是一種代理模式,
 * 升級邏輯在 implementation 合約中實現,而非 proxy 合約
 */
contract UUPSProxy {
    /**
     * @dev 存儲 slot:將 implementation 地址存在特定的 slot 中
     * 這個 slot 是 keccak256("PROXY_IMPLEMENTATION_SLOT") - 1
     */
    bytes32 internal constant _IMPLEMENTATION_SLOT = 
        0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    
    /**
     * @dev admin slot:用於存儲管理員地址
     */
    bytes32 internal constant _ADMIN_SLOT = 
        0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;

    constructor(address implementation, bytes memory data) {
        _setImplementation(implementation);
        _setAdmin(msg.sender);
        
        // 執行初始化(如果提供了初始化數據)
        if (data.length > 0) {
            (bool success, ) = implementation.delegatecall(data);
            require(success, "Initialization failed");
        }
    }

    /**
     * @dev 代理調用的 fallback 函數
     */
    fallback() external payable {
        address impl = _getImplementation();
        assembly {
            // 複製 msg.data 到內存
            let ptr := mload(0x40)
            calldatacopy(ptr, 0, calldatasize())
            
            // 調用 implementation 合約
            let result := delegatecall(
                sub(gas(), 5000),
                impl,
                ptr,
                calldatasize(),
                0,
                0
            )
            
            // 複製返回值到內存
            let size := returndatasize()
            returndatacopy(ptr, 0, size)
            
            // 根據結果 revert 或返回
            if iszero(result) {
                revert(ptr, size)
            }
            return(ptr, size)
        }
    }
    
    receive() external payable {}

    /**
     * @dev 獲取 implementation 地址
     */
    function _getImplementation() internal view returns (address impl) {
        assembly {
            impl := sload(_IMPLEMENTATION_SLOT)
        }
    }
    
    /**
     * @dev 設置 implementation 地址
     */
    function _setImplementation(address newImplementation) internal {
        require(newImplementation.code.length > 0, "Invalid implementation");
        assembly {
            sstore(_IMPLEMENTATION_SLOT, newImplementation)
        }
    }
    
    /**
     * @dev 獲取 admin 地址
     */
    function _getAdmin() internal view returns (address admin) {
        assembly {
            admin := sload(_ADMIN_SLOT)
        }
    }
    
    /**
     * @dev 設置 admin 地址
     */
    function _setAdmin(address newAdmin) internal {
        assembly {
            sstore(_ADMIN_SLOT, newAdmin)
        }
    }
}

/**
 * @title LendingProtocolV1
 * @dev 借貸協議 v1 版本 - 實現 UUPS 升級介面
 */
contract LendingProtocolV1 is IERC165 {
    // ============================================
    // 狀態變量(需要小心安排以避免存儲衝突)
    // ============================================
    
    address public admin;
    uint256 public totalDeposits;
    uint256 public totalBorrows;
    
    // 映射
    mapping(address => uint256) public deposits;
    mapping(address => uint256) public borrows;

    // ============================================
    // 事件
    // ============================================
    
    event Upgraded(address indexed newImplementation);
    event DepositMade(address indexed user, uint256 amount);
    event BorrowMade(address indexed user, uint256 amount);

    // ============================================
    // 修飾符
    // ============================================
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }

    // ============================================
    // 初始化函數(只能用一次)
    // ============================================
    
    function initialize() external {
        require(admin == address(0), "Already initialized");
        admin = msg.sender;
    }

    // ============================================
    // 業務邏輯
    // ============================================
    
    function deposit() external payable {
        require(msg.value > 0, "Amount must be positive");
        deposits[msg.sender] += msg.value;
        totalDeposits += msg.value;
        emit DepositMade(msg.sender, msg.value);
    }
    
    function borrow(uint256 amount) external {
        require(amount <= address(this).balance, "Insufficient liquidity");
        require(deposits[msg.sender] >= amount * 2, "Insufficient collateral");
        
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
        
        borrows[msg.sender] += amount;
        totalBorrows += amount;
        emit BorrowMade(msg.sender, amount);
    }

    // ============================================
    // UUPS 升級介面
    // ============================================
    
    /**
     * @dev 升級到新的 implementation
     * 這個函數定義了升級的權限控制邏輯
     */
    function upgradeTo(address newImplementation) external onlyAdmin {
        _upgradeTo(newImplementation);
    }
    
    function _upgradeTo(address newImplementation) internal virtual {
        address oldImplementation = _getImplementation();
        require(newImplementation != oldImplementation, "Same implementation");
        require(newImplementation.code.length > 0, "Invalid implementation");
        
        _setImplementation(newImplementation);
        emit Upgraded(newImplementation);
    }
    
    function _getImplementation() internal view virtual returns (address) {
        assembly {
            impl := sload(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc)
        }
    }
    
    function _setImplementation(address newImplementation) internal virtual {
        assembly {
            sstore(0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc, newImplementation)
        }
    }

    // ============================================
    // ERC165 介面
    // ============================================
    
    function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}

interface IERC165 {
    function supportsInterface(bytes4 interfaceId) external pure returns (bool);
}

第四章:測試案例與質量保證

4.1 Foundry 測試框架

// test/LendingProtocol.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import "../src/LendingProtocol.sol";

contract LendingProtocolTest is Test {
    ConstantProductAMM public amm;
    SimpleLendingMarket public lending;
    
    MockERC20 public tokenX;
    MockERC20 public tokenY;
    
    address public alice = address(0x1);
    address public bob = address(0x2);
    
    function setUp() public {
        // 部署測試代幣
        tokenX = new MockERC20("Token X", "TKX", 18);
        tokenY = new MockERC20("Token Y", "TKY", 18);
        
        // 部署 AMM
        amm = new ConstantProductAMM(
            address(tokenX) < address(tokenY) ? address(tokenX) : address(tokenY),
            address(tokenX) < address(tokenY) ? address(tokenY) : address(tokenX)
        );
        
        // 部署借貸市場
        lending = new SimpleLendingMarket();
        
        // 設置測試代幣
        tokenX.mint(alice, 1000 ether);
        tokenY.mint(alice, 1000 ether);
        tokenX.mint(bob, 1000 ether);
        tokenY.mint(bob, 1000 ether);
        
        // 用戶授權合約使用代幣
        vm.prank(alice);
        tokenX.approve(address(amm), type(uint256).max);
        tokenY.approve(address(amm), type(uint256).max);
        
        vm.prank(bob);
        tokenX.approve(address(amm), type(uint256).max);
        tokenY.approve(address(amm), type(uint256).max);
    }
    
    function testAddLiquidity() public {
        // Alice 添加流動性
        uint256 amountX = 100 ether;
        uint256 amountY = 100 ether;
        
        vm.prank(alice);
        tokenX.transfer(address(amm), amountX);
        tokenY.transfer(address(amm), amountY);
        
        // 驗證流動性
        (uint256 reserveX, uint256 reserveY) = amm.getReserves();
        assertEq(reserveX, amountX, "Reserve X incorrect");
        assertEq(reserveY, amountY, "Reserve Y incorrect");
    }
    
    function testSwapWithFee() public {
        // 初始流動性
        uint256 liquidityX = 1000 ether;
        uint256 liquidityY = 1000 ether;
        
        vm.prank(alice);
        tokenX.transfer(address(amm), liquidityX);
        tokenY.transfer(address(amm), liquidityY);
        amm.mint(alice);
        
        // 計算 swap 輸出
        uint256 swapAmount = 10 ether;
        uint256 expectedOutput = amm.getAmountOut(swapAmount, address(tokenX));
        
        // 執行 swap
        vm.prank(bob);
        tokenX.transfer(address(amm), swapAmount);
        amm.swap(0, swapAmount, 0, expectedOutput - 1, bob, block.timestamp + 100);
        
        // 驗證輸出
        assertGt(tokenY.balanceOf(bob), 0, "No tokenY received");
    }
    
    function testHealthFactorCalculation() public {
        // 測試健康因子計算
        uint256 collateral = 100 ether;
        uint256 borrow = 50 ether;
        uint256 collateralFactor = 150; // 150%
        
        uint256 expectedHealthFactor = (collateral * 1e18) / borrow;
        assertEq(expectedHealthFactor, 2e18, "Health factor incorrect");
    }
    
    function testLiquidation() public {
        // 設置借款人情況
        
        // 執行清算
        // 驗證狀態變化
    }
}

/**
 * @dev 測試用的 ERC20 代幣合約
 */
contract MockERC20 is ERC20 {
    uint8 private _decimals;
    
    constructor(
        string memory name,
        string memory symbol,
        uint8 decimals_
    ) ERC20(name, symbol) {
        _decimals = decimals_;
    }
    
    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
    
    function decimals() public view override returns (uint8) {
        return _decimals;
    }
}

abstract contract ERC20 {
    string public name;
    string public symbol;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    
    constructor(string memory name_, string memory symbol_) {
        name = name_;
        symbol = symbol_;
    }
    
    function transfer(address to, uint256 amount) external returns (bool) {
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
        return true;
    }
    
    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        return true;
    }
    
    function transferFrom(address from, address to, uint256 amount) external returns (bool) {
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        return true;
    }
    
    function _mint(address to, uint256 amount) internal {
        totalSupply += amount;
        balanceOf[to] += amount;
    }
}

結論與最佳實踐

本文提供了從頭建構 DeFi 借貸協議的完整技術指南,涵蓋了自動做市商、流動性池、借貸市場和可升級合約的核心實作。實際開發中需要注意以下關鍵點:

  1. 安全性優先:所有涉及資產轉移的函數都應進行嚴格的輸入驗證,並使用 SafeMath 或 Solidity 0.8+ 的內建溢位檢查。
  1. 利率模型設計:合理的利率模型應激勵流動性供應,同時防止流動性枯竭。建議使用分段線性模型,設置合理的拐點和斜率。
  1. 清算機制:清算機制是維護系統償付能力的關鍵。清算紅利應足夠吸引清算人參與,但又不應過度惩罚借款人。
  1. 可升級性:使用 UUPS 或透明代理模式時,必須確保升級邏輯的安全性和權限控制。
  1. 測試覆蓋:對於金融合約,應達到 100% 的測試覆蓋率,並進行形式化驗證以確保數學邏輯的正確性。

參考資料

  1. Uniswap Labs (2023). Uniswap V3 Core Technical Whitepaper.
  2. Aave (2023). Aave V3 Technical Paper.
  3. Compound Finance (2020). Compound Finance Whitepaper.
  4. OpenZeppelin (2024). Smart Contract Security Best Practices.
  5. Solidity Documentation. Proxy Upgrade Pattern.
  6. Foundry Book. Testing Smart Contracts with Foundry.

延伸閱讀與來源

這篇文章對您有幫助嗎?

評論

發表評論

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

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