aToken
aToken 是一种产生收益的代币,会在deposit
时铸造,withdraw
时销毁。aToken 的价值与对应存入的资产以 1:1 的比例挂钩,能够安全地存储、转移或交易。通过 aToken 储备金收集的所有利息会直接分配给 aToken 持有者,具体表现为其钱包余额的持续增长。
有关所有 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.
这本质上是“记录”用户存入储备池的时间,并可用于计算用户当前的复利 aToken 余额。
什么是“复利”?
以下解释来源于 ChatGPT-4o:
复利(Compound Interest)是指在计算利息时,不仅对初始本金计算利息,还对之前累积的利息部分继续计算利息。换句话说,利息会周期性地加入到本金中,然后下一次利息计算时,基于新的总金额(本金 + 累积的利息)来计算利息。这种增长方式会使得资产随着时间呈现加速增长的特性。复利的公式:
复利的计算公式为:
- A:最终金额(本金 + 利息)
- P:初始本金
- r:年利率
- n:每年计息次数
- t:年数
举例:
假设你存入了 1000 元,年利率为 5%,并且每年复利计算一次。如果保持 10 年,那么最终你将得到的金额是:
这意味着 10 年后,你的 1000 元将变为 1628.89 元。
在 DeFi 中的复利:
在 DeFi 协议中,如 Aave,复利的概念被广泛应用。例如,aToken 持有者在存入资产后,他们的收益以复利的形式不断增长,即存款利息会持续累积到本金中,并生成更多利息。这种复利机制确保用户的资产随着时间的推移不断增加。
复利的力量在于长期持有,时间越长,资产增长得越快,因此复利被称为“世界第八大奇迹”。
举例:
- User A 以 1.1 的流动性指数存入 1000 DAI
- User B 将另一笔金额存入同一资金池中
- 流动性指数现在是 1.2
- 因此,要计算用户 A 当前的复利 aToken 余额,应进行反向操作:
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);
}