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 的資產價值與簡單持有相比出現差異。
無常損失的數學推導:
假設初始狀態:
- 池子中 tokenX 數量 = x₀,tokenY 數量 = y₀
- 資產價格 ratio = x₀ / y₀
- LP 的初始份額 = k
初始 LP 資產價值(以 tokenY 計價):
V₀ = k * (x₀ + y₀ * ratio) = k * 2 * x₀
交易後狀態:
- 池子中 tokenX 數量 = x₁,tokenY 數量 = y₁
- 根據常數乘積:x₁ y₁ = x₀ y₀
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 借貸協議核心機制
借貸協議允許用戶存入抵押品並借入其他資產。其核心機制包括:
抵押品系統:
- 用戶存入資產作為抵押
- 抵押品價值必須始终超过借款價值(超額抵押)
- 抵押品比率(Collateral Factor)決定了可借款金額上限
利率模型:
- 存款利率:根據存款利率模型計算
- 借款利率:根據借款利率模型計算
- 利率與資金利用率(Utilization Rate)掛鉤
清算機制:
- 當抵押品價值低於閾值時,任何人都可以執行清算
- 清算人獲得借款人的抵押品作為獎勵
- 清算紅利(Liquidation Bonus)激勵清算行為
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 借貸協議的完整技術指南,涵蓋了自動做市商、流動性池、借貸市場和可升級合約的核心實作。實際開發中需要注意以下關鍵點:
- 安全性優先:所有涉及資產轉移的函數都應進行嚴格的輸入驗證,並使用 SafeMath 或 Solidity 0.8+ 的內建溢位檢查。
- 利率模型設計:合理的利率模型應激勵流動性供應,同時防止流動性枯竭。建議使用分段線性模型,設置合理的拐點和斜率。
- 清算機制:清算機制是維護系統償付能力的關鍵。清算紅利應足夠吸引清算人參與,但又不應過度惩罚借款人。
- 可升級性:使用 UUPS 或透明代理模式時,必須確保升級邏輯的安全性和權限控制。
- 測試覆蓋:對於金融合約,應達到 100% 的測試覆蓋率,並進行形式化驗證以確保數學邏輯的正確性。
參考資料
- Uniswap Labs (2023). Uniswap V3 Core Technical Whitepaper.
- Aave (2023). Aave V3 Technical Paper.
- Compound Finance (2020). Compound Finance Whitepaper.
- OpenZeppelin (2024). Smart Contract Security Best Practices.
- Solidity Documentation. Proxy Upgrade Pattern.
- Foundry Book. Testing Smart Contracts with Foundry.
相關文章
- ERC-4626 Tokenized Vault 完整實現指南:從標準規範到生產級合約 — 本文深入探討 ERC-4626 標準的技術細節,提供完整的生產級合約實現。內容涵蓋標準接口定義、資產與份額轉換的數學模型、收益策略整合、費用機制設計,並提供可直接部署的 Solidity 代碼範例。通過本指南,開發者可以構建安全可靠的代幣化 vault 系統。
- 以太坊開發者完整學習路徑:從 Solidity 基礎到智能合約安全大師 — 本文專為軟體開發者設計系統化的以太坊學習路徑,涵蓋區塊鏈基礎理論、Solidity 智能合約開發、以太坊開發工具生態、Layer 2 開發、DeFi 協議實現、以及智能合約安全審計等核心主題。從工程師視角出發,提供可直接應用於實際項目的技術內容,包括完整的程式碼範例和開發環境配置。
- 以太坊零知識證明 DeFi 實戰程式碼指南:從電路設計到智慧合約整合 — 本文聚焦於零知識證明在以太坊 DeFi 應用中的實際程式碼實現,從電路編寫到合約部署,從隱私借貸到隱私交易,提供可運行的程式碼範例和詳細的實現說明。涵蓋 Circom、Noir 開發框架、抵押率驗證電路、隱私交易電路、Solidity 驗證合約與 Gas 優化策略。
- 以太坊 AI 代理與 DePIN 整合開發完整指南:從理論架構到實際部署 — 人工智慧與區塊鏈技術的融合正在重塑數位基礎設施的格局。本文深入探討 AI 代理與 DePIN 在以太坊上的整合開發,提供完整的智慧合約程式碼範例,涵蓋 AI 代理控制框架、DePIN 資源協調、自動化 DeFi 交易等實戰應用,幫助開發者快速掌握這項前沿技術。
- 以太坊智能合約開發實戰:從基礎到 DeFi 協議完整代碼範例指南 — 本文提供以太坊智能合約開發的完整實戰指南,透過可直接運行的 Solidity 代碼範例,幫助開發者從理論走向實踐。內容涵蓋基礎合約開發、借貸協議實作、AMM 機制實現、以及中文圈特有的應用場景(台灣交易所整合、香港監管合規、Singapore MAS 牌照申請)。本指南假設讀者具備基本的程式設計基礎,熟悉 JavaScript 或 Python 等語言,並對區塊鏈概念有基本理解。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案完整列表
- Solidity 文檔 智慧合約程式語言官方規格
- EVM 代碼庫 EVM 實作的核心參考
- Alethio EVM 分析 EVM 行為的正規驗證
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!