07-DeFi核心协议-借贷协议
第一部分:借贷协议基础
1.1 借贷协议原理
去中心化借贷协议允许用户无需中介即可借入和借出加密资产。
1.1.1 核心概念
超额抵押:
- 借款金额 < 抵押品价值
- 抵押率(Collateral Factor):可借金额/抵押品价值
- 例如:抵押率75%,存入100 ETH,最多借75 ETH等值资产
利率模型:
- 利率根据资金利用率动态调整
- 利用率 = 借款总额 / 存款总额
- 利用率越高,利率越高
清算:
- 当抵押品价值下跌,健康系数 < 1时触发
- 清算人可以部分偿还债务,获得折扣抵押品
- 激励机制确保协议偿付能力
1.2 关键指标
// 健康系数 = (抵押品价值 * 清算阈值) / 借款价值
// 健康系数 > 1: 安全
// 健康系数 < 1: 可被清算
// 示例:
// 抵押: 10 ETH (价格 $2000/ETH) = $20,000
// 借款: $12,000 USDT
// 清算阈值: 80%
// 健康系数 = ($20,000 * 0.8) / $12,000 = 1.33
利用率:
// 利用率 = 总借款 / (总存款)
// 例如: 存款 1000 ETH, 借款 600 ETH
// 利用率 = 600 / 1000 = 60%
第二部分:Compound协议
2.1 Compound架构
Compound的核心是cToken(如cETH、cUSDC),代表用户在池中的份额。
2.1.1 cToken原理
// 用户存入 100 DAI
// 获得 cDAI(数量根据汇率计算)
// 汇率随时间增加(利息累积)
// 提取时:cDAI数量 * 当前汇率 = 可提取DAI数量
2.2 核心合约实现
2.2.1 利率模型
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title InterestRateModel
* @dev 利率模型合约
*/
contract InterestRateModel {
// 基础利率(年化)
uint256 public constant BASE_RATE = 2e16; // 2%
// 斜率1(利用率在最优点之前)
uint256 public constant MULTIPLIER_PER_BLOCK_1 = 1e16; // 1%
// 斜率2(利用率超过最优点)
uint256 public constant MULTIPLIER_PER_BLOCK_2 = 3e17; // 30%
// 最优利用率
uint256 public constant OPTIMAL_UTILIZATION = 8e17; // 80%
// 每年区块数(以太坊约13秒一个块)
uint256 public constant BLOCKS_PER_YEAR = 2425000;
/**
* @dev 计算利用率
*/
function utilizationRate(
uint256 cash,
uint256 borrows,
uint256 reserves
) public pure returns (uint256) {
// 无借款时利用率为0
if (borrows == 0) {
return 0;
}
// 利用率 = 借款 / (现金 + 借款 - 储备金)
return (borrows * 1e18) / (cash + borrows - reserves);
}
/**
* @dev 计算借款利率(每区块)
*/
function getBorrowRate(
uint256 cash,
uint256 borrows,
uint256 reserves
) public pure returns (uint256) {
uint256 util = utilizationRate(cash, borrows, reserves);
if (util <= OPTIMAL_UTILIZATION) {
// 利用率 <= 最优点
// 利率 = 基础利率 + 利用率 * 斜率1
return BASE_RATE + (util * MULTIPLIER_PER_BLOCK_1) / 1e18;
} else {
// 利用率 > 最优点
// 正常利率 + 超额部分 * 斜率2
uint256 normalRate = BASE_RATE + (OPTIMAL_UTILIZATION * MULTIPLIER_PER_BLOCK_1) / 1e18;
uint256 excessUtil = util - OPTIMAL_UTILIZATION;
return normalRate + (excessUtil * MULTIPLIER_PER_BLOCK_2) / 1e18;
}
}
/**
* @dev 计算存款利率(每区块)
*/
function getSupplyRate(
uint256 cash,
uint256 borrows,
uint256 reserves,
uint256 reserveFactor
) public pure returns (uint256) {
uint256 oneMinusReserveFactor = 1e18 - reserveFactor;
uint256 borrowRate = getBorrowRate(cash, borrows, reserves);
uint256 util = utilizationRate(cash, borrows, reserves);
// 存款利率 = 借款利率 * 利用率 * (1 - 储备金系数)
return (borrowRate * util * oneMinusReserveFactor) / 1e36;
}
}
2.2.2 价格预言机
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title SimplePriceOracle
* @dev 简化的价格预言机
*/
contract SimplePriceOracle {
// 资产地址 => 价格(USD,18位小数)
mapping(address => uint256) public prices;
address public admin;
event PriceUpdated(address indexed asset, uint256 price);
constructor() {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
/**
* @dev 设置价格
*/
function setPrice(address asset, uint256 price) external onlyAdmin {
prices[asset] = price;
emit PriceUpdated(asset, price);
}
/**
* @dev 批量设置价格
*/
function setPrices(address[] calldata assets, uint256[] calldata _prices) external onlyAdmin {
require(assets.length == _prices.length, "Length mismatch");
for (uint256 i = 0; i < assets.length; i++) {
prices[assets[i]] = _prices[i];
emit PriceUpdated(assets[i], _prices[i]);
}
}
/**
* @dev 获取价格
*/
function getPrice(address asset) external view returns (uint256) {
uint256 price = prices[asset];
require(price > 0, "Price not set");
return price;
}
/**
* @dev 获取底层资产价格(用于cToken)
*/
function getUnderlyingPrice(address cToken) external view returns (uint256) {
address underlying = CToken(cToken).underlying();
return prices[underlying];
}
}
interface CToken {
function underlying() external view returns (address);
}
2.2.3 Comptroller(控制器)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Comptroller
* @dev 借贷协议的核心控制器
*/
contract Comptroller {
// 管理员
address public admin;
// 价格预言机
SimplePriceOracle public oracle;
// 清算激励(例如:108% = 1.08e18)
uint256 public liquidationIncentive = 108e16;
// 平仓系数(单次最多清算50%)
uint256 public closeFactorMantissa = 5e17;
// 市场信息
struct Market {
bool isListed; // 是否上线
uint256 collateralFactorMantissa; // 抵押率(例如:75% = 0.75e18)
uint256 liquidationThresholdMantissa; // 清算阈值(例如:80% = 0.8e18)
mapping(address => bool) accountMembership; // 用户是否进入该市场
}
// cToken => Market
mapping(address => Market) public markets;
// 用户加入的市场列表
mapping(address => address[]) public accountAssets;
event MarketListed(address cToken);
event MarketEntered(address cToken, address account);
event MarketExited(address cToken, address account);
constructor(address _oracle) {
admin = msg.sender;
oracle = SimplePriceOracle(_oracle);
}
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
/**
* @dev 添加市场
*/
function supportMarket(
address cToken,
uint256 collateralFactor,
uint256 liquidationThreshold
) external onlyAdmin {
require(!markets[cToken].isListed, "Market already listed");
require(collateralFactor <= liquidationThreshold, "Invalid factors");
require(liquidationThreshold < 1e18, "Invalid threshold");
markets[cToken].isListed = true;
markets[cToken].collateralFactorMantissa = collateralFactor;
markets[cToken].liquidationThresholdMantissa = liquidationThreshold;
emit MarketListed(cToken);
}
/**
* @dev 进入市场(启用资产作为抵押)
*/
function enterMarkets(address[] calldata cTokens) external {
for (uint256 i = 0; i < cTokens.length; i++) {
addToMarket(cTokens[i], msg.sender);
}
}
/**
* @dev 退出市场
*/
function exitMarket(address cToken) external {
require(markets[cToken].accountMembership[msg.sender], "Not in market");
// 检查退出后是否仍然满足抵押要求
(uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidity(
msg.sender,
cToken,
0,
0
);
require(shortfall == 0, "Insufficient liquidity");
// 移除市场成员资格
markets[cToken].accountMembership[msg.sender] = false;
// 从accountAssets中移除
address[] storage assets = accountAssets[msg.sender];
for (uint256 i = 0; i < assets.length; i++) {
if (assets[i] == cToken) {
assets[i] = assets[assets.length - 1];
assets.pop();
break;
}
}
emit MarketExited(cToken, msg.sender);
}
/**
* @dev 检查借款是否允许
*/
function borrowAllowed(
address cToken,
address borrower,
uint256 borrowAmount
) external view returns (bool) {
require(markets[cToken].isListed, "Market not listed");
if (!markets[cToken].accountMembership[borrower]) {
return false;
}
(uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidity(
borrower,
cToken,
0,
borrowAmount
);
return shortfall == 0;
}
/**
* @dev 检查赎回是否允许
*/
function redeemAllowed(
address cToken,
address redeemer,
uint256 redeemTokens
) external view returns (bool) {
if (!markets[cToken].accountMembership[redeemer]) {
return true;
}
(uint256 liquidity, uint256 shortfall) = getHypotheticalAccountLiquidity(
redeemer,
cToken,
redeemTokens,
0
);
return shortfall == 0;
}
/**
* @dev 计算账户流动性
* @return liquidity 可用流动性
* @return shortfall 缺口(如果<0表示健康,>0表示可清算)
*/
function getAccountLiquidity(address account)
external
view
returns (uint256 liquidity, uint256 shortfall)
{
return getHypotheticalAccountLiquidity(account, address(0), 0, 0);
}
/**
* @dev 计算假设的账户流动性
*/
function getHypotheticalAccountLiquidity(
address account,
address cTokenModify,
uint256 redeemTokens,
uint256 borrowAmount
) internal view returns (uint256, uint256) {
uint256 sumCollateral = 0;
uint256 sumBorrowPlusEffects = 0;
address[] memory assets = accountAssets[account];
for (uint256 i = 0; i < assets.length; i++) {
address asset = assets[i];
CTokenInterface cToken = CTokenInterface(asset);
// 获取账户余额
uint256 cTokenBalance = cToken.balanceOf(account);
uint256 borrowBalance = cToken.borrowBalanceStored(account);
uint256 exchangeRate = cToken.exchangeRateStored();
// 获取价格
uint256 oraclePrice = oracle.getUnderlyingPrice(asset);
// 计算抵押品价值
uint256 collateralFactor = markets[asset].collateralFactorMantissa;
uint256 collateralValue = (cTokenBalance * exchangeRate * oraclePrice * collateralFactor) / 1e36;
sumCollateral += collateralValue;
// 计算借款价值
uint256 borrowValue = (borrowBalance * oraclePrice) / 1e18;
sumBorrowPlusEffects += borrowValue;
// 应用假设的变化
if (asset == cTokenModify) {
if (redeemTokens > 0) {
uint256 redeemValue = (redeemTokens * exchangeRate * oraclePrice * collateralFactor) / 1e36;
sumCollateral = sumCollateral > redeemValue ? sumCollateral - redeemValue : 0;
}
if (borrowAmount > 0) {
uint256 borrowValue = (borrowAmount * oraclePrice) / 1e18;
sumBorrowPlusEffects += borrowValue;
}
}
}
// 计算流动性或缺口
if (sumCollateral > sumBorrowPlusEffects) {
return (sumCollateral - sumBorrowPlusEffects, 0);
} else {
return (0, sumBorrowPlusEffects - sumCollateral);
}
}
/**
* @dev 计算可清算金额
*/
function liquidateCalculateSeizeTokens(
address cTokenBorrowed,
address cTokenCollateral,
uint256 repayAmount
) external view returns (uint256) {
uint256 priceBorrowed = oracle.getUnderlyingPrice(cTokenBorrowed);
uint256 priceCollateral = oracle.getUnderlyingPrice(cTokenCollateral);
uint256 exchangeRate = CTokenInterface(cTokenCollateral).exchangeRateStored();
// seizeTokens = repayAmount * liquidationIncentive * priceBorrowed / priceCollateral / exchangeRate
uint256 seizeTokens = (repayAmount * liquidationIncentive * priceBorrowed) /
(priceCollateral * exchangeRate / 1e18);
return seizeTokens;
}
/**
* @dev 添加到市场
*/
function addToMarket(address cToken, address account) internal {
require(markets[cToken].isListed, "Market not listed");
if (markets[cToken].accountMembership[account]) {
return; // 已经在市场中
}
markets[cToken].accountMembership[account] = true;
accountAssets[account].push(cToken);
emit MarketEntered(cToken, account);
}
/**
* @dev 获取用户加入的市场
*/
function getAssetsIn(address account) external view returns (address[] memory) {
return accountAssets[account];
}
/**
* @dev 检查是否在市场中
*/
function checkMembership(address account, address cToken) external view returns (bool) {
return markets[cToken].accountMembership[account];
}
}
interface CTokenInterface {
function balanceOf(address account) external view returns (uint256);
function borrowBalanceStored(address account) external view returns (uint256);
function exchangeRateStored() external view returns (uint256);
function underlying() external view returns (address);
}
2.2.4 CToken合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title CToken
* @dev Compound的cToken实现
*/
contract CToken {
// 基础代币
IERC20 public underlying;
// cToken信息
string public name;
string public symbol;
uint8 public constant decimals = 8;
// 总供应量
uint256 public totalSupply;
// 余额
mapping(address => uint256) public balanceOf;
// 借款信息
struct BorrowSnapshot {
uint256 principal; // 借款本金
uint256 interestIndex; // 借款时的利息指数
}
mapping(address => BorrowSnapshot) public accountBorrows;
// 总借款
uint256 public totalBorrows;
// 总储备金
uint256 public totalReserves;
// 储备金系数(例如:10% = 0.1e18)
uint256 public reserveFactorMantissa = 1e17;
// 利息指数
uint256 public borrowIndex = 1e18;
// 上次计息区块
uint256 public accrualBlockNumber;
// 汇率
uint256 public exchangeRateStored;
// 控制器
Comptroller public comptroller;
// 利率模型
InterestRateModel public interestRateModel;
// 初始汇率
uint256 internal constant INITIAL_EXCHANGE_RATE = 2e26; // 0.02 (scaled by 1e18)
event Mint(address minter, uint256 mintAmount, uint256 mintTokens);
event Redeem(address redeemer, uint256 redeemAmount, uint256 redeemTokens);
event Borrow(address borrower, uint256 borrowAmount, uint256 accountBorrows, uint256 totalBorrows);
event RepayBorrow(address payer, address borrower, uint256 repayAmount, uint256 accountBorrows, uint256 totalBorrows);
event LiquidateBorrow(address liquidator, address borrower, uint256 repayAmount, address cTokenCollateral, uint256 seizeTokens);
constructor(
address _underlying,
address _comptroller,
address _interestRateModel,
string memory _name,
string memory _symbol
) {
underlying = IERC20(_underlying);
comptroller = Comptroller(_comptroller);
interestRateModel = InterestRateModel(_interestRateModel);
name = _name;
symbol = _symbol;
accrualBlockNumber = block.number;
exchangeRateStored = INITIAL_EXCHANGE_RATE;
}
/**
* @dev 存款(铸造cToken)
*/
function mint(uint256 mintAmount) external returns (uint256) {
accrueInterest();
uint256 exchangeRate = exchangeRateStoredInternal();
uint256 mintTokens = (mintAmount * 1e18) / exchangeRate;
totalSupply += mintTokens;
balanceOf[msg.sender] += mintTokens;
underlying.transferFrom(msg.sender, address(this), mintAmount);
emit Mint(msg.sender, mintAmount, mintTokens);
return mintTokens;
}
/**
* @dev 赎回(销毁cToken)
*/
function redeem(uint256 redeemTokens) external returns (uint256) {
accrueInterest();
require(comptroller.redeemAllowed(address(this), msg.sender, redeemTokens), "Redeem not allowed");
uint256 exchangeRate = exchangeRateStoredInternal();
uint256 redeemAmount = (redeemTokens * exchangeRate) / 1e18;
require(balanceOf[msg.sender] >= redeemTokens, "Insufficient balance");
totalSupply -= redeemTokens;
balanceOf[msg.sender] -= redeemTokens;
underlying.transfer(msg.sender, redeemAmount);
emit Redeem(msg.sender, redeemAmount, redeemTokens);
return redeemAmount;
}
/**
* @dev 借款
*/
function borrow(uint256 borrowAmount) external {
accrueInterest();
require(comptroller.borrowAllowed(address(this), msg.sender, borrowAmount), "Borrow not allowed");
BorrowSnapshot storage borrowSnapshot = accountBorrows[msg.sender];
// 更新借款信息
uint256 accountBorrowsPrev = (borrowSnapshot.principal * borrowIndex) / borrowSnapshot.interestIndex;
uint256 accountBorrowsNew = accountBorrowsPrev + borrowAmount;
borrowSnapshot.principal = accountBorrowsNew;
borrowSnapshot.interestIndex = borrowIndex;
totalBorrows += borrowAmount;
underlying.transfer(msg.sender, borrowAmount);
emit Borrow(msg.sender, borrowAmount, accountBorrowsNew, totalBorrows);
}
/**
* @dev 还款
*/
function repayBorrow(uint256 repayAmount) external returns (uint256) {
accrueInterest();
BorrowSnapshot storage borrowSnapshot = accountBorrows[msg.sender];
uint256 accountBorrows = (borrowSnapshot.principal * borrowIndex) / borrowSnapshot.interestIndex;
uint256 actualRepayAmount = repayAmount;
if (repayAmount > accountBorrows) {
actualRepayAmount = accountBorrows;
}
underlying.transferFrom(msg.sender, address(this), actualRepayAmount);
uint256 accountBorrowsNew = accountBorrows - actualRepayAmount;
borrowSnapshot.principal = accountBorrowsNew;
borrowSnapshot.interestIndex = borrowIndex;
totalBorrows -= actualRepayAmount;
emit RepayBorrow(msg.sender, msg.sender, actualRepayAmount, accountBorrowsNew, totalBorrows);
return actualRepayAmount;
}
/**
* @dev 清算
*/
function liquidateBorrow(
address borrower,
uint256 repayAmount,
address cTokenCollateral
) external {
accrueInterest();
CToken(cTokenCollateral).accrueInterest();
// 检查借款人是否可被清算
(, uint256 shortfall) = comptroller.getAccountLiquidity(borrower);
require(shortfall > 0, "Insufficient shortfall");
// 计算可清算的cToken数量
uint256 seizeTokens = comptroller.liquidateCalculateSeizeTokens(
address(this),
cTokenCollateral,
repayAmount
);
// 还款
underlying.transferFrom(msg.sender, address(this), repayAmount);
// 更新借款信息
BorrowSnapshot storage borrowSnapshot = accountBorrows[borrower];
uint256 accountBorrows = (borrowSnapshot.principal * borrowIndex) / borrowSnapshot.interestIndex;
uint256 accountBorrowsNew = accountBorrows - repayAmount;
borrowSnapshot.principal = accountBorrowsNew;
borrowSnapshot.interestIndex = borrowIndex;
totalBorrows -= repayAmount;
// 转移抵押品
CToken(cTokenCollateral).seize(msg.sender, borrower, seizeTokens);
emit LiquidateBorrow(msg.sender, borrower, repayAmount, cTokenCollateral, seizeTokens);
}
/**
* @dev 转移cToken(清算时使用)
*/
function seize(
address liquidator,
address borrower,
uint256 seizeTokens
) external {
require(msg.sender != address(this), "Cannot seize self");
require(balanceOf[borrower] >= seizeTokens, "Insufficient balance");
balanceOf[borrower] -= seizeTokens;
balanceOf[liquidator] += seizeTokens;
}
/**
* @dev 计息
*/
function accrueInterest() public {
uint256 currentBlockNumber = block.number;
uint256 accrualBlockNumberPrior = accrualBlockNumber;
if (accrualBlockNumberPrior == currentBlockNumber) {
return;
}
uint256 cashPrior = getCash();
uint256 borrowsPrior = totalBorrows;
uint256 reservesPrior = totalReserves;
uint256 borrowIndexPrior = borrowIndex;
// 计算借款利率
uint256 borrowRate = interestRateModel.getBorrowRate(cashPrior, borrowsPrior, reservesPrior);
// 计算区块差
uint256 blockDelta = currentBlockNumber - accrualBlockNumberPrior;
// 计算利息
uint256 interestAccumulated = (borrowRate * borrowsPrior * blockDelta) / 1e18;
uint256 totalBorrowsNew = borrowsPrior + interestAccumulated;
uint256 totalReservesNew = reservesPrior + (interestAccumulated * reserveFactorMantissa) / 1e18;
uint256 borrowIndexNew = borrowIndexPrior + (borrowRate * borrowIndexPrior * blockDelta) / 1e18;
// 更新状态
accrualBlockNumber = currentBlockNumber;
borrowIndex = borrowIndexNew;
totalBorrows = totalBorrowsNew;
totalReserves = totalReservesNew;
// 更新汇率
exchangeRateStored = exchangeRateStoredInternal();
}
/**
* @dev 计算当前汇率
*/
function exchangeRateStoredInternal() internal view returns (uint256) {
if (totalSupply == 0) {
return INITIAL_EXCHANGE_RATE;
}
uint256 cash = getCash();
uint256 cashPlusBorrowsMinusReserves = cash + totalBorrows - totalReserves;
return (cashPlusBorrowsMinusReserves * 1e18) / totalSupply;
}
/**
* @dev 获取现金(池中的底层代币数量)
*/
function getCash() public view returns (uint256) {
return underlying.balanceOf(address(this));
}
/**
* @dev 获取账户借款余额
*/
function borrowBalanceStored(address account) external view returns (uint256) {
BorrowSnapshot storage borrowSnapshot = accountBorrows[account];
if (borrowSnapshot.principal == 0) {
return 0;
}
return (borrowSnapshot.principal * borrowIndex) / borrowSnapshot.interestIndex;
}
/**
* @dev 获取当前汇率
*/
function exchangeRateCurrent() external returns (uint256) {
accrueInterest();
return exchangeRateStoredInternal();
}
/**
* @dev 获取存款APY
*/
function getSupplyRate() external view returns (uint256) {
uint256 cash = getCash();
return interestRateModel.getSupplyRate(cash, totalBorrows, totalReserves, reserveFactorMantissa);
}
/**
* @dev 获取借款APY
*/
function getBorrowRate() external view returns (uint256) {
uint256 cash = getCash();
return interestRateModel.getBorrowRate(cash, totalBorrows, totalReserves);
}
}
第三部分:Aave协议
3.1 Aave的创新特性
- 闪电贷:单笔交易内无抵押借款
- 稳定利率:固定利率借款
- aToken:自动计息的代币
- 信用授权:允许他人使用你的信用额度
3.2 aToken实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title AToken
* @dev Aave的计息代币
*/
contract AToken {
string public name;
string public symbol;
uint8 public constant decimals = 18;
IERC20 public immutable UNDERLYING_ASSET;
address public immutable POOL;
// 总供应量
uint256 private _totalSupply;
// 用户余额
mapping(address => uint256) private _balances;
// 流动性指数(用于计算利息)
uint256 private _liquidityIndex = 1e27;
// 上次更新时间
uint256 private _lastUpdateTimestamp;
event Mint(address indexed user, uint256 amount, uint256 index);
event Burn(address indexed user, address indexed target, uint256 amount, uint256 index);
event BalanceTransfer(address indexed from, address indexed to, uint256 amount, uint256 index);
constructor(
address pool,
address underlyingAsset,
string memory tokenName,
string memory tokenSymbol
) {
POOL = pool;
UNDERLYING_ASSET = IERC20(underlyingAsset);
name = tokenName;
symbol = tokenSymbol;
_lastUpdateTimestamp = block.timestamp;
}
modifier onlyPool() {
require(msg.sender == POOL, "Caller must be pool");
_;
}
/**
* @dev 铸造aToken
*/
function mint(
address user,
uint256 amount,
uint256 index
) external onlyPool returns (bool) {
uint256 previousBalance = balanceOf(user);
uint256 amountScaled = (amount * 1e27) / index;
_totalSupply += amountScaled;
_balances[user] += amountScaled;
emit Mint(user, amount, index);
emit Transfer(address(0), user, amount);
return previousBalance == 0;
}
/**
* @dev 销毁aToken
*/
function burn(
address user,
address receiverOfUnderlying,
uint256 amount,
uint256 index
) external onlyPool {
uint256 amountScaled = (amount * 1e27) / index;
_balances[user] -= amountScaled;
_totalSupply -= amountScaled;
emit Burn(user, receiverOfUnderlying, amount, index);
emit Transfer(user, address(0), amount);
}
/**
* @dev 获取余额(包含利息)
*/
function balanceOf(address user) public view returns (uint256) {
return (_balances[user] * _liquidityIndex) / 1e27;
}
/**
* @dev 获取缩放后的余额
*/
function scaledBalanceOf(address user) external view returns (uint256) {
return _balances[user];
}
/**
* @dev 总供应量(包含利息)
*/
function totalSupply() public view returns (uint256) {
return (_totalSupply * _liquidityIndex) / 1e27;
}
/**
* @dev 更新流动性指数
*/
function updateIndex(uint256 liquidityRate) external onlyPool {
uint256 timeDelta = block.timestamp - _lastUpdateTimestamp;
if (timeDelta > 0) {
uint256 cumulatedLiquidityInterest = (liquidityRate * timeDelta) / 365 days;
_liquidityIndex += (_liquidityIndex * cumulatedLiquidityInterest) / 1e27;
_lastUpdateTimestamp = block.timestamp;
}
}
/**
* @dev 转账
*/
function transfer(address recipient, uint256 amount) external returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
/**
* @dev 内部转账
*/
function _transfer(
address sender,
address recipient,
uint256 amount
) internal {
uint256 index = _liquidityIndex;
uint256 amountScaled = (amount * 1e27) / index;
_balances[sender] -= amountScaled;
_balances[recipient] += amountScaled;
emit Transfer(sender, recipient, amount);
emit BalanceTransfer(sender, recipient, amount, index);
}
}
3.3 Aave Pool(简化版)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title AavePool
* @dev Aave借贷池(简化版)
*/
contract AavePool {
// 资产配置
struct ReserveData {
AToken aToken;
IERC20 underlyingAsset;
uint256 liquidityRate;
uint256 variableBorrowRate;
uint256 liquidityIndex;
uint256 variableBorrowIndex;
uint256 lastUpdateTimestamp;
bool isActive;
}
// 用户数据
struct UserReserveData {
uint256 scaledVariableDebt;
}
// 储备资产映射
mapping(address => ReserveData) public reserves;
// 用户借款数据
mapping(address => mapping(address => UserReserveData)) public userReserves;
event Supply(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount
);
event Withdraw(
address indexed reserve,
address indexed user,
address indexed to,
uint256 amount
);
event Borrow(
address indexed reserve,
address user,
address indexed onBehalfOf,
uint256 amount,
uint256 borrowRate
);
event Repay(
address indexed reserve,
address indexed user,
address indexed repayer,
uint256 amount
);
/**
* @dev 存款
*/
function supply(
address asset,
uint256 amount,
address onBehalfOf
) external {
ReserveData storage reserve = reserves[asset];
require(reserve.isActive, "Reserve not active");
updateState(asset);
// 转入底层资产
reserve.underlyingAsset.transferFrom(msg.sender, address(reserve.aToken), amount);
// 铸造aToken
reserve.aToken.mint(onBehalfOf, amount, reserve.liquidityIndex);
emit Supply(asset, msg.sender, onBehalfOf, amount);
}
/**
* @dev 提款
*/
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256) {
ReserveData storage reserve = reserves[asset];
require(reserve.isActive, "Reserve not active");
updateState(asset);
uint256 userBalance = reserve.aToken.balanceOf(msg.sender);
uint256 amountToWithdraw = amount;
if (amount == type(uint256).max) {
amountToWithdraw = userBalance;
}
require(amountToWithdraw <= userBalance, "Insufficient balance");
// 销毁aToken
reserve.aToken.burn(msg.sender, to, amountToWithdraw, reserve.liquidityIndex);
// 转出底层资产
reserve.underlyingAsset.transfer(to, amountToWithdraw);
emit Withdraw(asset, msg.sender, to, amountToWithdraw);
return amountToWithdraw;
}
/**
* @dev 借款
*/
function borrow(
address asset,
uint256 amount,
address onBehalfOf
) external {
ReserveData storage reserve = reserves[asset];
require(reserve.isActive, "Reserve not active");
updateState(asset);
UserReserveData storage userReserve = userReserves[asset][onBehalfOf];
// 计算新的债务
uint256 amountScaled = (amount * 1e27) / reserve.variableBorrowIndex;
userReserve.scaledVariableDebt += amountScaled;
// 转出底层资产
reserve.underlyingAsset.transfer(onBehalfOf, amount);
emit Borrow(asset, msg.sender, onBehalfOf, amount, reserve.variableBorrowRate);
}
/**
* @dev 还款
*/
function repay(
address asset,
uint256 amount,
address onBehalfOf
) external returns (uint256) {
ReserveData storage reserve = reserves[asset];
require(reserve.isActive, "Reserve not active");
updateState(asset);
UserReserveData storage userReserve = userReserves[asset][onBehalfOf];
uint256 variableDebt = (userReserve.scaledVariableDebt * reserve.variableBorrowIndex) / 1e27;
uint256 paybackAmount = amount;
if (amount > variableDebt) {
paybackAmount = variableDebt;
}
// 转入底层资产
reserve.underlyingAsset.transferFrom(msg.sender, address(reserve.aToken), paybackAmount);
// 减少债务
uint256 amountScaled = (paybackAmount * 1e27) / reserve.variableBorrowIndex;
userReserve.scaledVariableDebt -= amountScaled;
emit Repay(asset, onBehalfOf, msg.sender, paybackAmount);
return paybackAmount;
}
/**
* @dev 更新储备状态
*/
function updateState(address asset) internal {
ReserveData storage reserve = reserves[asset];
uint256 timeDelta = block.timestamp - reserve.lastUpdateTimestamp;
if (timeDelta == 0) {
return;
}
// 简化:假设利率不变
uint256 cumulatedLiquidityInterest = (reserve.liquidityRate * timeDelta) / 365 days;
reserve.liquidityIndex += (reserve.liquidityIndex * cumulatedLiquidityInterest) / 1e27;
uint256 cumulatedVariableBorrowInterest = (reserve.variableBorrowRate * timeDelta) / 365 days;
reserve.variableBorrowIndex += (reserve.variableBorrowIndex * cumulatedVariableBorrowInterest) / 1e27;
reserve.lastUpdateTimestamp = block.timestamp;
// 更新aToken索引
reserve.aToken.updateIndex(reserve.liquidityRate);
}
/**
* @dev 初始化储备
*/
function initReserve(
address asset,
address aTokenAddress,
uint256 liquidityRate,
uint256 variableBorrowRate
) external {
ReserveData storage reserve = reserves[asset];
require(!reserve.isActive, "Reserve already initialized");
reserve.aToken = AToken(aTokenAddress);
reserve.underlyingAsset = IERC20(asset);
reserve.liquidityRate = liquidityRate;
reserve.variableBorrowRate = variableBorrowRate;
reserve.liquidityIndex = 1e27;
reserve.variableBorrowIndex = 1e27;
reserve.lastUpdateTimestamp = block.timestamp;
reserve.isActive = true;
}
/**
* @dev 获取用户借款余额
*/
function getUserVariableDebt(address asset, address user) external view returns (uint256) {
ReserveData storage reserve = reserves[asset];
UserReserveData storage userReserve = userReserves[asset][user];
return (userReserve.scaledVariableDebt * reserve.variableBorrowIndex) / 1e27;
}
}
3.4 闪电贷实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IFlashLoanReceiver {
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,
uint256[] calldata premiums,
address initiator,
bytes calldata params
) external returns (bool);
}
/**
* @title FlashLoanPool
* @dev 支持闪电贷的借贷池
*/
contract FlashLoanPool {
uint256 public constant FLASHLOAN_PREMIUM_TOTAL = 9; // 0.09%
event FlashLoan(
address indexed target,
address indexed initiator,
address indexed asset,
uint256 amount,
uint256 premium
);
/**
* @dev 闪电贷
*/
function flashLoan(
address receiverAddress,
address[] calldata assets,
uint256[] calldata amounts,
bytes calldata params
) external {
require(assets.length == amounts.length, "Inconsistent params");
uint256[] memory premiums = new uint256[](assets.length);
uint256[] memory amountsWithPremiums = new uint256[](assets.length);
// 计算费用
for (uint256 i = 0; i < assets.length; i++) {
premiums[i] = (amounts[i] * FLASHLOAN_PREMIUM_TOTAL) / 10000;
amountsWithPremiums[i] = amounts[i] + premiums[i];
}
// 转出资产
for (uint256 i = 0; i < assets.length; i++) {
IERC20(assets[i]).transfer(receiverAddress, amounts[i]);
}
// 调用接收者
require(
IFlashLoanReceiver(receiverAddress).executeOperation(
assets,
amounts,
premiums,
msg.sender,
params
),
"Invalid flashloan return value"
);
// 检查归还
for (uint256 i = 0; i < assets.length; i++) {
uint256 currentBalance = IERC20(assets[i]).balanceOf(address(this));
require(
currentBalance >= amountsWithPremiums[i],
"Flashloan not properly returned"
);
emit FlashLoan(
receiverAddress,
msg.sender,
assets[i],
amounts[i],
premiums[i]
);
}
}
}
第四部分:完整的借贷协议示例
4.1 SimpleLending(教学用简化版本)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title SimpleLending
* @dev 简化的借贷协议,包含核心功能
*/
contract SimpleLending {
// 资产信息
struct Asset {
uint256 totalDeposits; // 总存款
uint256 totalBorrows; // 总借款
uint256 collateralFactor; // 抵押率 (75% = 75)
uint256 liquidationThreshold; // 清算阈值 (80% = 80)
uint256 baseRate; // 基础利率
uint256 slope1; // 斜率1
uint256 slope2; // 斜率2
uint256 optimalUtilization; // 最优利用率
bool isSupported; // 是否支持
}
// 用户资产数据
struct UserAsset {
uint256 deposited; // 存款
uint256 borrowed; // 借款
uint256 borrowIndex; // 借款索引
uint256 lastUpdate; // 上次更新时间
}
// 价格预言机
mapping(address => uint256) public assetPrices;
// 资产配置
mapping(address => Asset) public assets;
// 用户数据: user => asset => UserAsset
mapping(address => mapping(address => UserAsset)) public userAssets;
// 用户抵押资产列表
mapping(address => address[]) public userCollateralAssets;
// 借款索引: asset => index
mapping(address => uint256) public borrowIndex;
// 上次更新时间
mapping(address => uint256) public lastUpdateTime;
address public admin;
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 assetBorrowed,
address assetCollateral,
uint256 repayAmount,
uint256 seizeAmount
);
constructor() {
admin = msg.sender;
}
modifier onlyAdmin() {
require(msg.sender == admin, "Not admin");
_;
}
/**
* @dev 添加支持的资产
*/
function addAsset(
address asset,
uint256 collateralFactor,
uint256 liquidationThreshold,
uint256 baseRate,
uint256 slope1,
uint256 slope2,
uint256 optimalUtilization
) external onlyAdmin {
require(!assets[asset].isSupported, "Asset already supported");
require(collateralFactor <= liquidationThreshold, "Invalid factors");
require(liquidationThreshold < 100, "Invalid threshold");
assets[asset] = Asset({
totalDeposits: 0,
totalBorrows: 0,
collateralFactor: collateralFactor,
liquidationThreshold: liquidationThreshold,
baseRate: baseRate,
slope1: slope1,
slope2: slope2,
optimalUtilization: optimalUtilization,
isSupported: true
});
borrowIndex[asset] = 1e18;
lastUpdateTime[asset] = block.timestamp;
}
/**
* @dev 设置价格
*/
function setPrice(address asset, uint256 price) external onlyAdmin {
assetPrices[asset] = price;
}
/**
* @dev 存款
*/
function deposit(address asset, uint256 amount) external {
require(assets[asset].isSupported, "Asset not supported");
require(amount > 0, "Amount must be positive");
accrueInterest(asset);
IERC20(asset).transferFrom(msg.sender, address(this), amount);
UserAsset storage userAsset = userAssets[msg.sender][asset];
if (userAsset.deposited == 0) {
userCollateralAssets[msg.sender].push(asset);
}
userAsset.deposited += amount;
userAsset.lastUpdate = block.timestamp;
assets[asset].totalDeposits += amount;
emit Deposit(msg.sender, asset, amount);
}
/**
* @dev 提款
*/
function withdraw(address asset, uint256 amount) external {
require(assets[asset].isSupported, "Asset not supported");
accrueInterest(asset);
UserAsset storage userAsset = userAssets[msg.sender][asset];
require(userAsset.deposited >= amount, "Insufficient balance");
// 检查健康系数
userAsset.deposited -= amount;
require(getHealthFactor(msg.sender) >= 1e18, "Insufficient collateral");
assets[asset].totalDeposits -= amount;
IERC20(asset).transfer(msg.sender, amount);
emit Withdraw(msg.sender, asset, amount);
}
/**
* @dev 借款
*/
function borrow(address asset, uint256 amount) external {
require(assets[asset].isSupported, "Asset not supported");
require(amount > 0, "Amount must be positive");
accrueInterest(asset);
// 更新借款信息
UserAsset storage userAsset = userAssets[msg.sender][asset];
uint256 currentBorrow = getCurrentBorrow(msg.sender, asset);
userAsset.borrowed = currentBorrow + amount;
userAsset.borrowIndex = borrowIndex[asset];
userAsset.lastUpdate = block.timestamp;
// 检查健康系数
require(getHealthFactor(msg.sender) >= 1e18, "Insufficient collateral");
assets[asset].totalBorrows += amount;
IERC20(asset).transfer(msg.sender, amount);
emit Borrow(msg.sender, asset, amount);
}
/**
* @dev 还款
*/
function repay(address asset, uint256 amount) external {
require(assets[asset].isSupported, "Asset not supported");
accrueInterest(asset);
UserAsset storage userAsset = userAssets[msg.sender][asset];
uint256 currentBorrow = getCurrentBorrow(msg.sender, asset);
uint256 repayAmount = amount > currentBorrow ? currentBorrow : amount;
IERC20(asset).transferFrom(msg.sender, address(this), repayAmount);
userAsset.borrowed = currentBorrow - repayAmount;
userAsset.borrowIndex = borrowIndex[asset];
userAsset.lastUpdate = block.timestamp;
assets[asset].totalBorrows -= repayAmount;
emit Repay(msg.sender, asset, repayAmount);
}
/**
* @dev 清算
*/
function liquidate(
address borrower,
address assetBorrowed,
address assetCollateral,
uint256 repayAmount
) external {
require(getHealthFactor(borrower) < 1e18, "Cannot liquidate");
accrueInterest(assetBorrowed);
accrueInterest(assetCollateral);
// 计算可清算金额(最多50%)
uint256 maxRepay = getCurrentBorrow(borrower, assetBorrowed) / 2;
uint256 actualRepay = repayAmount > maxRepay ? maxRepay : repayAmount;
// 计算获得的抵押品(含清算奖励8%)
uint256 collateralValue = (actualRepay * assetPrices[assetBorrowed] * 108) / 100;
uint256 seizeAmount = (collateralValue * 1e18) / assetPrices[assetCollateral];
// 还款
IERC20(assetBorrowed).transferFrom(msg.sender, address(this), actualRepay);
// 更新借款人借款
UserAsset storage borrowerBorrow = userAssets[borrower][assetBorrowed];
uint256 currentBorrow = getCurrentBorrow(borrower, assetBorrowed);
borrowerBorrow.borrowed = currentBorrow - actualRepay;
borrowerBorrow.borrowIndex = borrowIndex[assetBorrowed];
// 更新借款人抵押品
UserAsset storage borrowerCollateral = userAssets[borrower][assetCollateral];
borrowerCollateral.deposited -= seizeAmount;
// 转移抵押品给清算人
userAssets[msg.sender][assetCollateral].deposited += seizeAmount;
assets[assetBorrowed].totalBorrows -= actualRepay;
emit Liquidate(
msg.sender,
borrower,
assetBorrowed,
assetCollateral,
actualRepay,
seizeAmount
);
}
/**
* @dev 计息
*/
function accrueInterest(address asset) public {
Asset storage assetData = assets[asset];
uint256 timeDelta = block.timestamp - lastUpdateTime[asset];
if (timeDelta == 0) {
return;
}
uint256 borrowRate = getBorrowRate(asset);
uint256 interestFactor = (borrowRate * timeDelta) / 365 days;
uint256 interestAccumulated = (assetData.totalBorrows * interestFactor) / 1e18;
assetData.totalBorrows += interestAccumulated;
borrowIndex[asset] += (borrowIndex[asset] * interestFactor) / 1e18;
lastUpdateTime[asset] = block.timestamp;
}
/**
* @dev 计算借款利率
*/
function getBorrowRate(address asset) public view returns (uint256) {
Asset storage assetData = assets[asset];
if (assetData.totalDeposits == 0) {
return assetData.baseRate;
}
uint256 utilization = (assetData.totalBorrows * 100) / assetData.totalDeposits;
if (utilization <= assetData.optimalUtilization) {
return assetData.baseRate + (utilization * assetData.slope1) / 100;
} else {
uint256 excessUtilization = utilization - assetData.optimalUtilization;
return
assetData.baseRate +
(assetData.optimalUtilization * assetData.slope1) /
100 +
(excessUtilization * assetData.slope2) /
100;
}
}
/**
* @dev 获取当前借款金额
*/
function getCurrentBorrow(address user, address asset) public view returns (uint256) {
UserAsset storage userAsset = userAssets[user][asset];
if (userAsset.borrowed == 0) {
return 0;
}
return (userAsset.borrowed * borrowIndex[asset]) / userAsset.borrowIndex;
}
/**
* @dev 计算健康系数
*/
function getHealthFactor(address user) public view returns (uint256) {
uint256 totalCollateralValue = 0;
uint256 totalBorrowValue = 0;
address[] memory collateralAssets = userCollateralAssets[user];
for (uint256 i = 0; i < collateralAssets.length; i++) {
address asset = collateralAssets[i];
UserAsset storage userAsset = userAssets[user][asset];
// 计算抵押品价值
if (userAsset.deposited > 0) {
uint256 collateralValue = (userAsset.deposited *
assetPrices[asset] *
assets[asset].liquidationThreshold) / 100;
totalCollateralValue += collateralValue;
}
// 计算借款价值
if (userAsset.borrowed > 0) {
uint256 borrowValue = getCurrentBorrow(user, asset) * assetPrices[asset];
totalBorrowValue += borrowValue;
}
}
if (totalBorrowValue == 0) {
return type(uint256).max;
}
return (totalCollateralValue * 1e18) / totalBorrowValue;
}
/**
* @dev 获取账户信息
*/
function getAccountInfo(address user)
external
view
returns (
uint256 totalCollateral,
uint256 totalBorrow,
uint256 healthFactor
)
{
address[] memory collateralAssets = userCollateralAssets[user];
for (uint256 i = 0; i < collateralAssets.length; i++) {
address asset = collateralAssets[i];
UserAsset storage userAsset = userAssets[user][asset];
totalCollateral += userAsset.deposited * assetPrices[asset];
totalBorrow += getCurrentBorrow(user, asset) * assetPrices[asset];
}
healthFactor = getHealthFactor(user);
}
}