去中心化借貸協議
去中心化借貸是 DeFi 最核心的應用場景之一。Aave 和 Compound 這兩個協議鎖定了數百億美元的資產,讓使用者可以:
- 存款:將資產存入協議賺取利息
- 借款:抵押資產後借出其他資產
- 清算:當抵押率不足時,清算人可以償還借款並獲得折扣的抵押品
借貸協議的核心概念
抵押率 (Collateral Ratio)
$$\text{抵押率} = \frac{\text{抵押品價值}}{\text{借款金額}}$$
- 你的抵押率必須高於最低抵押率(如 150%)
- 如果抵押率低於清算門檻(如 130%),就會被清算
範例
- 你存入 1 ETH(價值 $3000)
- 最大可借款 = $3000 / 150% = $2000
- 你借出 1000 USDC
- 如果 ETH 跌到 $1300:抵押率 = $1300 / $1000 = 130% → 觸發清算
實作借貸合約
// contracts/SimpleLending.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract SimpleLending is Ownable, ReentrancyGuard {
IERC20 public collateralToken; // 抵押品代幣(如 ETH)
IERC20 public loanToken; // 借款代幣(如 USDC)
// 利率參數
uint256 public baseRate = 5; // 基礎年利率 5%
uint256 public utilizationRate = 0; // 資金利用率
// 抵押與清算參數
uint256 public constant COLLATERAL_RATIO = 150; // 最低抵押率 150%
uint256 public constant LIQUIDATION_THRESHOLD = 130; // 清算門檻 130%
uint256 public constant LIQUIDATION_BONUS = 10; // 清算獎勵 10%
// 使用者資料
struct UserInfo {
uint256 depositedAmount; // 存入的抵押品數量
uint256 borrowedAmount; // 借款數量
uint256 lastInterestTime; // 最後更新利息的時間
}
mapping(address => UserInfo) public users;
uint256 public totalDeposits; // 總存款
uint256 public totalLoans; // 總借款
// 事件
event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event Borrowed(address indexed user, uint256 amount);
event Repaid(address indexed user, uint256 amount);
event Liquidated(address indexed user, address indexed liquidator, uint256 amount);
constructor(address _collateralToken, address _loanToken) Ownable(msg.sender) {
collateralToken = IERC20(_collateralToken);
loanToken = IERC20(_loanToken);
}
// === 存款(抵押品) ===
function deposit(uint256 _amount) external nonReentrant {
require(_amount > 0, "金額必須大於 0");
// 更新使用者的利息
updateInterest(msg.sender);
// 轉入抵押品
collateralToken.transferFrom(msg.sender, address(this), _amount);
users[msg.sender].depositedAmount += _amount;
totalDeposits += _amount;
emit Deposited(msg.sender, _amount);
}
// === 提領抵押品 ===
function withdraw(uint256 _amount) external nonReentrant {
require(_amount > 0, "金額必須大於 0");
updateInterest(msg.sender);
UserInfo storage user = users[msg.sender];
require(user.depositedAmount >= _amount, "餘額不足");
// 檢查提領後抵押率是否足夠
uint256 remainingDeposit = user.depositedAmount - _amount;
if (user.borrowedAmount > 0) {
uint256 currentRatio = (remainingDeposit * 100) / user.borrowedAmount;
require(currentRatio >= COLLATERAL_RATIO, "提領後抵押率不足");
}
user.depositedAmount = remainingDeposit;
totalDeposits -= _amount;
collateralToken.transfer(msg.sender, _amount);
emit Withdrawn(msg.sender, _amount);
}
// === 借款 ===
function borrow(uint256 _amount) external nonReentrant {
require(_amount > 0, "金額必須大於 0");
require(loanToken.balanceOf(address(this)) >= _amount, "協議流動性不足");
updateInterest(msg.sender);
UserInfo storage user = users[msg.sender];
require(user.depositedAmount > 0, "請先存入抵押品");
// 檢查抵押率
uint256 maxBorrow = (user.depositedAmount * 100) / COLLATERAL_RATIO;
require(user.borrowedAmount + _amount <= maxBorrow, "超過最大可借款金額");
user.borrowedAmount += _amount;
totalLoans += _amount;
loanToken.transfer(msg.sender, _amount);
emit Borrowed(msg.sender, _amount);
}
// === 還款 ===
function repay(uint256 _amount) external nonReentrant {
require(_amount > 0, "金額必須大於 0");
updateInterest(msg.sender);
UserInfo storage user = users[msg.sender];
require(user.borrowedAmount >= _amount, "借款餘額不足");
uint256 interest = calculateInterest(msg.sender);
uint256 totalPayment = _amount + interest;
user.borrowedAmount -= _amount;
totalLoans -= _amount;
loanToken.transferFrom(msg.sender, address(this), totalPayment);
emit Repaid(msg.sender, _amount);
}
// === 清算 ===
function liquidate(address _user) external nonReentrant {
UserInfo storage user = users[_user];
require(user.borrowedAmount > 0, "沒有借款");
// 計算抵押率
uint256 currentRatio = (user.depositedAmount * 100) / user.borrowedAmount;
require(currentRatio < LIQUIDATION_THRESHOLD, "尚未達到清算門檻");
updateInterest(_user);
// 清算人可以償還借款,獲得抵押品(含獎勵)
uint256 repayAmount = user.borrowedAmount;
uint256 collateralToLiquidate = (repayAmount * (100 + LIQUIDATION_BONUS)) / 100;
if (collateralToLiquidate > user.depositedAmount) {
collateralToLiquidate = user.depositedAmount;
}
// 清算人償還借款
loanToken.transferFrom(msg.sender, address(this), repayAmount);
// 清算人獲得抵押品
user.depositedAmount -= collateralToLiquidate;
user.borrowedAmount = 0;
totalLoans -= repayAmount;
collateralToken.transfer(msg.sender, collateralToLiquidate);
emit Liquidated(_user, msg.sender, collateralToLiquidate);
}
// === 利息計算 ===
function calculateInterest(address _user) public view returns (uint256) {
UserInfo storage user = users[_user];
if (user.borrowedAmount == 0) return 0;
uint256 timeElapsed = block.timestamp - user.lastInterestTime;
// 簡單年利率計算
return (user.borrowedAmount * baseRate * timeElapsed) / (365 days * 100);
}
function updateInterest(address _user) internal {
UserInfo storage user = users[_user];
if (user.borrowedAmount > 0 && user.lastInterestTime > 0) {
uint256 interest = calculateInterest(_user);
if (interest > 0) {
user.borrowedAmount += interest;
totalLoans += interest;
}
}
user.lastInterestTime = block.timestamp;
}
// === 查看使用者狀態 ===
function getUserHealth(address _user) public view returns (
uint256 depositValue,
uint256 borrowValue,
uint256 ratio,
bool isHealthy
) {
UserInfo storage user = users[_user];
depositValue = user.depositedAmount;
borrowValue = user.borrowedAmount;
if (borrowValue == 0) {
return (depositValue, 0, 0, true);
}
ratio = (depositValue * 100) / borrowValue;
isHealthy = ratio >= LIQUIDATION_THRESHOLD;
}
}
測試借貸功能
// scripts/test_lending.js
async function main() {
const [deployer, user1, liquidator] = await hre.ethers.getSigners();
// 部署代幣
const VibeToken = await hre.ethers.getContractFactory("VibeToken");
const collateral = await VibeToken.deploy(deployer.address);
await collateral.waitForDeployment();
const loanToken = await VibeToken.deploy(deployer.address);
await loanToken.waitForDeployment();
// 部署借貸合約
const SimpleLending = await hre.ethers.getContractFactory("SimpleLending");
const lending = await SimpleLending.deploy(
await collateral.getAddress(),
await loanToken.getAddress()
);
await lending.waitForDeployment();
console.log("借貸合約地址:", await lending.getAddress());
// 給 user1 一些抵押品
const depositAmount = hre.ethers.parseEther("1000");
await collateral.transfer(user1.address, depositAmount);
// 給借貸合約一些借款代幣
const loanAmount = hre.ethers.parseEther("10000");
await loanToken.transfer(await lending.getAddress(), loanAmount);
// user1 存入抵押品
await collateral.connect(user1).approve(await lending.getAddress(), depositAmount);
await lending.connect(user1).deposit(depositAmount);
console.log("\nuser1 存入 1000 抵押品");
// user1 借款
const borrowAmount = hre.ethers.parseEther("500");
await lending.connect(user1).borrow(borrowAmount);
console.log("user1 借款 500");
// 查詢健康狀態
const health = await lending.getUserHealth(user1.address);
console.log(`\n健康狀態: 存款=${hre.ethers.formatEther(health[0])}, ` +
`借款=${hre.ethers.formatEther(health[1])}, ` +
`比率=${health[2]}, 健康=${health[3]}`);
}
main().catch(console.error);
使用 Vibe Coding 建借貸協議
🔥 【借貸協議詠唱範例】
「請幫我實作一個去中心化借貸合約:1. 支援存入 ETH 作為抵押品,借出 ERC-20 代幣。2. 最低抵押率 150%,清算門檻 120%。3. 利息根據資金利用率動態調整(利用率越高,利率越高)。4. 清算獎勵 5%(清算人可以低價買到抵押品)。5. 需要防止重入攻擊 (ReentrancyGuard)。6. 寫一個完整的測試流程。」
本日總結
在本章中,你學到了:
- ✅ 借貸協議核心機制:抵押存款、借款、還款、清算
- ✅ 抵押率計算:如何確保協議的安全性
- ✅ 清算機制:當抵押率不足時的處理流程
- ✅ 利息計算:基於時間的簡單利率模型
- ✅ 使用者健康狀態:監控使用者的抵押率
下一章,我們將建立一個 DApp 前端來與這些合約互動!