HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • Web3 完整技术体系

    • Web3 完整技术体系
    • 区块链基础与密码学
    • 第一章:比特币原理与实现
    • 第二章:以太坊架构与核心概念
    • Solidity智能合约开发基础
    • 04-Solidity进阶与安全
    • 05-ERC标准详解
    • 06-DeFi核心协议-去中心化交易所
    • 07-DeFi核心协议-借贷协议
    • 08-DeFi核心协议-稳定币
    • NFT与元宇宙
    • Layer2扩容方案
    • 跨链技术
    • Web3前端开发
    • 链下数据与预言机
    • 智能合约测试与部署
    • MEV与交易优化
    • DAO治理
    • 项目实战:完整DeFi借贷协议

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的创新特性

  1. 闪电贷:单笔交易内无抵押借款
  2. 稳定利率:固定利率借款
  3. aToken:自动计息的代币
  4. 信用授权:允许他人使用你的信用额度

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);
    }
}
Prev
06-DeFi核心协议-去中心化交易所
Next
08-DeFi核心协议-稳定币