同理,为了理解Compound的整体架构,我们从他的borrow/repay入手,出于0市场攻击等达成的条件太苛刻。我们在此不再介绍。

存款流程

用户 -> 存入DAI -> CErc20合约 
     <- 获得cDAI <- 

借款流程

用户 -> 存入ETH -> CEther合约
     <- 获得cETH <-
     -> 借出DAI -> Comptroller检查 -> CErc20合约放款

借款的直接入口:

// CToken.sol
function borrow(uint borrowAmount) external returns (uint) {
    // 1. 累计利息
    accrueInterest();
    
    // 2. 调用内部借款函数
    return borrowInternal(borrowAmount);
}

核心的借款逻辑

function borrowInternal(uint borrowAmount) internal nonReentrant returns (uint) {
    // 检查市场是否激活
    require(accrualBlockTimestamp == getBlockTimestamp(), "Accrue interest failed");
    
    // 调用 Comptroller 检查借款条件
    uint allowed = comptroller.borrowAllowed(address(this), msg.sender, borrowAmount);
    require(allowed == 0, "Borrow not allowed");
    
    // 检查市场是否有足够的现金
    require(getCashPrior() >= borrowAmount, "Insufficient cash");
    
    // 更新借款人状态
    accountBorrows[msg.sender].principal = borrowAmount;
    accountBorrows[msg.sender].interestIndex = borrowIndex;
    totalBorrows = totalBorrows + borrowAmount;
    
    // 转移资产给借款人
    doTransferOut(msg.sender, borrowAmount);
    
    emit Borrow(msg.sender, borrowAmount, accountBorrows[msg.sender].principal, accountBorrows[msg.sender].interestIndex);
    return 0;
}

Comptroller的借款允许检查

function borrowAllowed(address cToken, address borrower, uint borrowAmount) external returns (uint) {
    // 验证市场
    require(markets[cToken].isListed, "Market not listed");
    require(!borrowGuardianPaused[cToken], "Borrow is paused");
    
    // 检查用户是否在市场中
    require(markets[cToken].accountMembership[borrower], "Not in market");
    
    // 检查用户的流动性
    (Error err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(
        borrower,
        CToken(cToken),
        0,
        borrowAmount
    );
    require(err == Error.NO_ERROR, "Error calculating liquidity");
    require(shortfall == 0, "Insufficient liquidity");
    
    // 其他检查...
    
    return uint(Error.NO_ERROR);
}

整体的借款流程

用户
  │
  ▼
CToken.borrow()
  │
  ├─► accrueInterest()  // 更新利息
  │
  ├─► borrowInternal()
  │     │
  │     ├─► Comptroller.borrowAllowed()  // 检查借款条件
  │     │     │
  │     │     ├─► 检查市场状态
  │     │     ├─► 检查用户资格
  │     │     └─► 检查用户抵押品充足性
  │     │
  │     ├─► 更新借款状态
  │     └─► 转移资产
  │
  └─► 发出事件

要进行借款的话:

要进行借款,用户需要:
先存入抵押品(调用相应 CToken 的 mint 函数)
确保已进入相关市场(通过 Comptroller.enterMarkets)
然后才能调用目标 CToken 的 borrow 函数进行借款