aToken 是一种产生收益的代币,会在deposit时铸造,withdraw时销毁。aToken 的价值与对应存入的资产以 1:1 的比例挂钩,能够安全地存储、转移或交易。通过 aToken 储备金收集的所有利息会直接分配给 aToken 持有者,具体表现为其钱包余额的持续增长。

源码:https://github.com/aave/protocol-v2/blob/ice/mainnet-deployment-03-12-2020/contracts/protocol/tokenization/AToken.sol

有关所有 mint 和 burn 的操作,参阅LendingPool合约中的Deposit()Withdraw()方法

EIP20 Methods

实现了所有标准的 EIP20 方法,例如 balanceOf()transfer()transferFrom()approve()totalSupply()

balanceOf()将始终返回用户的最新余额,其中包括他们的本金余额 + 本金余额产生的利息。

EIP2612 Methods

permit()

function permit(address owner, address spender, uint256 value, uint256 deadline,uint8 v,  bytes32 r, bytes32 s)

允许用户允许其他账户(或合约)使用已签名的消息使用其资金。这实现了无 gas 交易和单一 approve/transfer 交易。

参数名称 类型 描述
owner address 资金的所有者
spender address 资金的花费着
value uint256 允许spender使用的金额
deadline uint256 许可有效的截止时间戳。使用 type(uint).max表示无截止日期。
v uint8 签名参数
r bytes32 签名参数
s bytes32 签名参数
import { signTypedData_v4 } from 'eth-sig-util'
import { fromRpcSig } from 'ethereumjs-util'

// ... other imports

import aTokenAbi from "./aTokenAbi.json"

// ... setup your web3 provider

const aTokenAddress = "ATOKEN_ADDRESS"
const aTokenContract = new web3.eth.Contract(aTokenAbi, aTokenAddress)

const privateKey = "YOUR_PRIVATE_KEY_WITHOUT_0x"
const chainId = 1
const owner = "OWNER_ADDRESS"
const spender = "SPENDER_ADDRESS"
const value = 100 // Amount the spender is permitted
const nonce = 1 // The next valid nonce, use `_nonces()`
const deadline = 1600093162

const permitParams = {
  types: {
    EIP712Domain: [
      { name: "name", type: "string" },
      { name: "version", type: "string" },
      { name: "chainId", type: "uint256" },
      { name: "verifyingContract", type: "address" },
    ],
    Permit: [
      { name: "owner", type: "address" },
      { name: "spender", type: "address" },
      { name: "value", type: "uint256" },
      { name: "nonce", type: "uint256" },
      { name: "deadline", type: "uint256" },
    ],
  },
  primaryType: "Permit",
  domain: {
    name: "aTOKEN_NAME",
    version: "1",
    chainId: chainId,
    verifyingContract: aTokenAddress,
  },
  message: {
    owner,
    spender,
    value,
    nonce,
    deadline,
  },
}

const signature = signTypedData_v4(
  Buffer.from(privateKey, "hex"),
  { data: permitParams }
)

// The signature can now be used to execute the transaction

const { v, r, s } = fromRpcSig(signature)

await aTokenContract.methods
    .permit({
      owner,
      spender,
      value,
      deadline,
      v,
      r,
      s
    })
    .send()
    .catch((e) => {
        throw Error(`Error permitting: ${e.message}`)
    })

_nonces()

function _nonce(address owner) public

返回调用permit()时要提交的下一个有效 nonce

Methods

UNDERLYING_ASSET_ADDRESS()

function UNDERLYING_ASSET_ADDRESS()

返回 aToken的底层资产。

RESERVE_TREASURY_ADDRESS()

function RESERVE_TREASURY_ADDRESS()

返回 aToken储备金库的地址。

POOL()

function POOL()

返回 aToken的关联 LendingPool的地址。

scaledBalanceOf()

function scaledBalanceOf(address user)

返回 user的缩放金额(uint256表示)

按比例调整的余额是用户存入的底层的资产余额,除以更新时的当前流动性指数。

i.e. img

这本质上是“记录”用户存入储备池的时间,并可用于计算用户当前的复利 aToken 余额。

什么是“复利”?
以下解释来源于 ChatGPT-4o:
复利(Compound Interest)是指在计算利息时,不仅对初始本金计算利息,还对之前累积的利息部分继续计算利息。换句话说,利息会周期性地加入到本金中,然后下一次利息计算时,基于新的总金额(本金 + 累积的利息)来计算利息。这种增长方式会使得资产随着时间呈现加速增长的特性。

复利的公式:

复利的计算公式为:

img

  • A:最终金额(本金 + 利息)
  • P:初始本金
  • r:年利率
  • n:每年计息次数
  • t:年数

举例:

假设你存入了 1000 元,年利率为 5%,并且每年复利计算一次。如果保持 10 年,那么最终你将得到的金额是:

img

这意味着 10 年后,你的 1000 元将变为 1628.89 元。

在 DeFi 中的复利:

在 DeFi 协议中,如 Aave,复利的概念被广泛应用。例如,aToken 持有者在存入资产后,他们的收益以复利的形式不断增长,即存款利息会持续累积到本金中,并生成更多利息。这种复利机制确保用户的资产随着时间的推移不断增加。

复利的力量在于长期持有,时间越长,资产增长得越快,因此复利被称为“世界第八大奇迹”。

举例:

  • User A 以 1.1 的流动性指数存入 1000 DAI
  • User B 将另一笔金额存入同一资金池中
  • 流动性指数现在是 1.2
  • 因此,要计算用户 A 当前的复利 aToken 余额,应进行反向操作:img

scaledTotalSupply()

function scaledTotalSupply()

返回 aToken 的缩放总供应量(uint256

缩放后的总供应量是所有更新的余额之和除以更新时的储备因子。

function isTransferAllowed(address user, uint256 amount)

如果允许转账,则返回 true

具体而言,如果转账后user的健康因子最终低于 1,则转账失败。

IAToken

// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;

import {IERC20} from './IERC20.sol';
import {IScaledBalanceToken} from './IScaledBalanceToken.sol';

interface IAToken is IERC20, IScaledBalanceToken {
  /**
   * @dev Emitted after the mint action
   * @param from The address performing the mint
   * @param value The amount being
   * @param index The new liquidity index of the reserve
   **/
  event Mint(address indexed from, uint256 value, uint256 index);

  /**
   * @dev Mints `amount` aTokens to `user`
   * @param user The address receiving the minted tokens
   * @param amount The amount of tokens getting minted
   * @param index The new liquidity index of the reserve
   * @return `true` if the the previous balance of the user was 0
   */
  function mint(
    address user,
    uint256 amount,
    uint256 index
  ) external returns (bool);

  /**
   * @dev Emitted after aTokens are burned
   * @param from The owner of the aTokens, getting them burned
   * @param target The address that will receive the underlying
   * @param value The amount being burned
   * @param index The new liquidity index of the reserve
   **/
  event Burn(address indexed from, address indexed target, uint256 value, uint256 index);

  /**
   * @dev Emitted during the transfer action
   * @param from The user whose tokens are being transferred
   * @param to The recipient
   * @param value The amount being transferred
   * @param index The new liquidity index of the reserve
   **/
  event BalanceTransfer(address indexed from, address indexed to, uint256 value, uint256 index);

  /**
   * @dev Burns aTokens from `user` and sends the equivalent amount of underlying to `receiverOfUnderlying`
   * @param user The owner of the aTokens, getting them burned
   * @param receiverOfUnderlying The address that will receive the underlying
   * @param amount The amount being burned
   * @param index The new liquidity index of the reserve
   **/
  function burn(
    address user,
    address receiverOfUnderlying,
    uint256 amount,
    uint256 index
  ) external;

  /**
   * @dev Mints aTokens to the reserve treasury
   * @param amount The amount of tokens getting minted
   * @param index The new liquidity index of the reserve
   */
  function mintToTreasury(uint256 amount, uint256 index) external;

  /**
   * @dev Transfers aTokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken
   * @param from The address getting liquidated, current owner of the aTokens
   * @param to The recipient
   * @param value The amount of tokens getting transferred
   **/
  function transferOnLiquidation(
    address from,
    address to,
    uint256 value
  ) external;

  /**
   * @dev Transfers the underlying asset to `target`. Used by the LendingPool to transfer
   * assets in borrow(), withdraw() and flashLoan()
   * @param user The recipient of the aTokens
   * @param amount The amount getting transferred
   * @return The amount transferred
   **/
  function transferUnderlyingTo(address user, uint256 amount) external returns (uint256);
}
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.12;

interface IScaledBalanceToken {
  /**
   * @dev Returns the scaled balance of the user. The scaled balance is the sum of all the
   * updated stored balance divided by the reserve's liquidity index at the moment of the update
   * @param user The user whose balance is calculated
   * @return The scaled balance of the user
   **/
  function scaledBalanceOf(address user) external view returns (uint256);

  /**
   * @dev Returns the scaled balance of the user and the scaled total supply.
   * @param user The address of the user
   * @return The scaled balance of the user
   * @return The scaled balance and the scaled total supply
   **/
  function getScaledUserBalanceAndSupply(address user) external view returns (uint256, uint256);

  /**
   * @dev Returns the scaled total supply of the variable debt token. Represents sum(debt/index)
   * @return The scaled total supply
   **/
  function scaledTotalSupply() external view returns (uint256);
}
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.6.8;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
  /**
   * @dev Returns the amount of tokens in existence.
   */
  function totalSupply() external view returns (uint256);

  /**
   * @dev Returns the amount of tokens owned by `account`.
   */
  function balanceOf(address account) external view returns (uint256);

  /**
   * @dev Moves `amount` tokens from the caller's account to `recipient`.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  function transfer(address recipient, uint256 amount) external returns (bool);

  /**
   * @dev Returns the remaining number of tokens that `spender` will be
   * allowed to spend on behalf of `owner` through {transferFrom}. This is
   * zero by default.
   *
   * This value changes when {approve} or {transferFrom} are called.
   */
  function allowance(address owner, address spender) external view returns (uint256);

  /**
   * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * IMPORTANT: Beware that changing an allowance with this method brings the risk
   * that someone may use both the old and the new allowance by unfortunate
   * transaction ordering. One possible solution to mitigate this race
   * condition is to first reduce the spender's allowance to 0 and set the
   * desired value afterwards:
   * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
   *
   * Emits an {Approval} event.
   */
  function approve(address spender, uint256 amount) external returns (bool);

  /**
   * @dev Moves `amount` tokens from `sender` to `recipient` using the
   * allowance mechanism. `amount` is then deducted from the caller's
   * allowance.
   *
   * Returns a boolean value indicating whether the operation succeeded.
   *
   * Emits a {Transfer} event.
   */
  function transferFrom(
    address sender,
    address recipient,
    uint256 amount
  ) external returns (bool);

  /**
   * @dev Emitted when `value` tokens are moved from one account (`from`) to
   * another (`to`).
   *
   * Note that `value` may be zero.
   */
  event Transfer(address indexed from, address indexed to, uint256 value);

  /**
   * @dev Emitted when the allowance of a `spender` for an `owner` is set by
   * a call to {approve}. `value` is the new allowance.
   */
  event Approval(address indexed owner, address indexed spender, uint256 value);
}