有了前面的 AAVE 收益计算,我们采用相同的思路来查看如何计算 Compound 的收益1,首先来查看 balanceOf2

/**
 * @notice Query the current positive base balance of an account or zero
 * @dev Note: uses updated interest indices to calculate
 * @param account The account whose balance to query
 * @return The present day base balance magnitude of the account, if positive
 */
function balanceOf(address account) override public view returns (uint256) {
    (uint64 baseSupplyIndex_, ) = accruedInterestIndices(getNowInternal() - lastAccrualTime);
    int104 principal = userBasic[account].principal;
    return principal > 0 ? presentValueSupply(baseSupplyIndex_, unsigned104(principal)) : 0;
}

这里用到两个变量:principalbaseSupplyIndexprincipal 可以通过 userBasic(address) 读取3,但是 baseSupplyIndex 只能通过合约读取,无法在链下获取, 仔细研究presentValueSupply4

/**
 * @dev The principal amount projected forward by the supply index
 */
function presentValueSupply(uint64 baseSupplyIndex_, uint104 principalValue_) internal pure returns (uint256) {
    return uint256(principalValue_) * baseSupplyIndex_ / BASE_INDEX_SCALE;
}

baseSupplyIndex 可以通过 balance / principal 获取,下面给出 Go 代码获取 principal

package main

import (
  "context"
  "math/big"

  "github.com/ethereum/go-ethereum/common"
  "github.com/ethereum/go-ethereum/common/hexutil"
  "github.com/ethereum/go-ethereum/crypto"
  "github.com/ethereum/go-ethereum/rpc"
  "github.com/shopspring/decimal"
)

func getCompoundUserPrincipal(address string) (decimal.Decimal, error){
  userAddress := common.HexToAddress(OperatorEVMAddress)
  fun := []byte("userBasic(address)")
  hash := crypto.NewKeccakState()
  hash.Write(fun)
  methodID := hash.Sum(nil)[:4]

  var data []byte
  data = append(data, methodID...)
  data = append(data, common.LeftPadBytes(userAddress.Bytes(), 32)...)
  callMsg := map[string]interface{}{
    "from": userAddress,
    "to":   common.HexToAddress(sp.goal.FoundsToken),
    "data": hexutil.Bytes(data),
  }
  var result string

  rpcClient, err := rpc.DialHTTP(client.url)
  if err != nil {
    return decimal.Zero, err
  }
  err := rpcClient.CallContext(context.TODO(),  &result, "eth_call", callMsg,nil)
  if err != nil {
    return decimal.Zero, err
  }
 // XXX: 这里不知为什么 int104 占了 uint256 的大小
  principal := new(big.Int).SetBytes(common.FromHex(result[:66]))
  return principal, nil
}

获取 balance 的代码就省略了,其实可以简单的 balance - principal 即可算出收益。