一 什么是Vault协议?
Vault协议是一种可以用于资产的管理和分红的协议。用户充值某一个资产,会获取某一个凭证;该凭证作为分红,退出的依据;通过Yield Farming(收益农场)、质押、借贷等等
二Tokenized Vaults (代币化金库)
2.1 什么是Tokenized Vaults代币化金库
代币化金库是一个智能合约,用户可以将他们的代币(如USDC、DAI等)存入其中,并获得相应的份额。这些份额代表了用户在金库中的所有权,按他们存入的代币数量比例分配。
根据金库的表现,用户可以提取他们的初始存款以及任何赚取的利润或承担的损失,这时他们的份额将被取消,表示其所有权的结束。
2.2 代币化金库的核心组成
2.2.1 智能合约
代币化金库是运行在区块链上的智能合约上,自动执行存款、取款、投资操作,确保系统透明、安全
2.2.2 基础资产
用户存入的加密货币或者其他数字资产,这些资产可以被智能合约代币化金库进行管理和投资
2.2.3 金库代币
代表用户在金库份额的代币,也就是金库代币。用户存入基础资产后,金库会按照一定的比例发行金库代币。用户可以随时用这些代币赎回相应比例的基础资产
2.2.4 投资策略
金库智能合约包含预先编写的投资策略,用于自动将存入的资产分配到各种收益生成活动中,例如借贷、流动性挖矿、收益聚合等
2.3 代币化金库的应用场景
2.3.1 收益聚合器
如Yearn Finance,用户将资产存入金库,金库自动将这些资产投资到不同的DeFi协议中,以获得最佳收益。
2.3.2 流动性挖矿
如SushiSwap的金库,用户将资产存入金库,金库将这些资产提供给流动性池,用户获得流动性挖矿奖励。
2.3.3 稳定币金库
如MakerDAO的DAI金库,用户可以将ETH等资产抵押生成DAI稳定币,同时获得一定的利息收益。
2.3.4 借贷金库
如Aave和Compound,用户将资产存入金库,金库将这些资产借出,用户获得借贷利息。
2.4 工作原理举例
第一: 用户Alice存入10 ETH到金库中,金库根据当前的兑换比例,发行10个金库代币给用户Alice(假设兑换比例为1:1)
第二: 用户Bob随后存入20 ETH到金库中,金库发行20个金库代币给用户B。此时,金库总资产为30 ETH,总代币数为30个。
第三: 金库自动将这30 ETH投资到一个借贷平台中,每年获得10%的利息收益。一年后,金库总资产增至33 ETH。
第四: 用户Alice想要赎回资产,将其持有的10个金库代币兑换回ETH。此时,每个代币的价值为33/30 = 1.1 ETH。因此,用户Alice可以赎回11 ETH。
三 ERC-4626标准提案内容
3.1 函数
3.1.1 asset
function asset() external view returns (address assetTokenAddress);
返回基础资产的token 地址
3.1.2 totalAssets
function totalAssets() external view returns (uint256 totalManagedAssets);
返回Vault金库目前持有多少基础资产
3.1.3 convertToAssets
function convertToShares(uint256 assets) external view returns (uint256 shares);
用户的基础资产转化为金库代币或者说份额
totalShares: 是金库中所有金库代币的总量
totalAssets: 总资产是金库当前持有的所有基础资产的总和,用于衡量金库的价值
计算公式:shares = assets * totalShares / totalAssets
3.1.4 convertToShares
function convertToAssets(uint256 shares) external view returns (uint256 assets);
根据份额转化为基础代币
totalShares: 是金库中所有金库代币的总量
totalAssets: 总资产是金库当前持有的所有基础资产的总和,用于衡量金库的价值
计算公式: assets = shares * totalAssets / totalSupply
3.1.5 maxDeposit
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
某地址最大可以的一次 deposit 调用中可以存入的最大标的资产数量
3.1.6 maxWithdraw
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
最大允许提现多少基础资产
3.1.7 maxMint
function maxMint(address receiver) external view returns (uint256 maxShares);
最大允许铸造多少份额
3.1.8 maxRedeem
function maxRedeem(address owner) external view returns (uint256 maxShares);
最大允许赎回多少份额
3.1.9 previewDeposit
function previewDeposit(uint256 assets) external view returns (uint256 shares);
在deposit之前,预先计算存入指定数量基础资产可以转化为多少份额(金库代币)
3.1.10 previewWithdraw
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
在提现之前,估算需要销毁多少份额以取出指定数量的底层资产。
3.1.11 previewMint
function previewMint(uint256 shares) external view returns (uint256 assets);
在不进行交易的情况下,显示铸造指定数量份额所需的底层资产的数量。或者说在mint之前预先计算铸造指定数量份额所需的底层资产的数量
3.1.12 previewRedeem
function previewRedeem(uint256 shares) external view returns (uint256 assets);
在赎回前,预估赎回特定数量份额将返还的底层资产的数量
3.1.13 deposit
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
允许用户存储基础资产到金库
3.1.14 withdraw
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
允许用户从金库进行基础资产提现,并销毁对应的金库代币或者份额,和redeem不一样,它虽然也是取出,但是是根据资产计算份额数据,然后销毁对应的数量的份额
3.1.15 mint
function mint(uint256 shares, address receiver) external returns (uint256 assets);
根据份额进行金库代币或者份额的铸造
3.1.16 redeem
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
从金库赎回指定份额的基础资产,和withdraw不同的是,它是指定份额,根据份额计算需要赎回的基础资产,以及最终会从金库销毁指定数量份额
3.2 事件
3.2.1 Deposit
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
3.2.2 Withdraw
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
四 手动实现ERC-4626
interface IERC4626 is IERC20, IERC20Metadata {
event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);
event Withdraw(
address indexed sender,
address indexed receiver,
address indexed owner,
uint256 assets,
uint256 shares
);
function asset() external view returns (address assetTokenAddress);
function totalAssets() external view returns (uint256 totalManagedAssets);
function convertToShares(uint256 assets) external view returns (uint256 shares);
function convertToAssets(uint256 shares) external view returns (uint256 assets);
function maxDeposit(address receiver) external view returns (uint256 maxAssets);
function previewDeposit(uint256 assets) external view returns (uint256 shares);
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
function maxMint(address receiver) external view returns (uint256 maxShares);
function previewMint(uint256 shares) external view returns (uint256 assets);
function mint(uint256 shares, address receiver) external returns (uint256 assets);
function maxWithdraw(address owner) external view returns (uint256 maxAssets);
function previewWithdraw(uint256 assets) external view returns (uint256 shares);
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
function maxRedeem(address owner) external view returns (uint256 maxShares);
function previewRedeem(uint256 shares) external view returns (uint256 assets);
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
interface ERC4626Errors {
/**
* @dev Attempted to deposit more assets than the max amount for `receiver`.
*/
error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max);
/**
* @dev Attempted to mint more shares than the max amount for `receiver`.
*/
error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max);
/**
* @dev Attempted to withdraw more assets than the max amount for `receiver`.
*/
error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);
/**
* @dev Attempted to redeem more shares than the max amount for `receiver`.
*/
error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
import "./IERC4626.sol";
import "./ERC4626Errors.sol";
abstract contract ERC4626 is ERC20, IERC4626 {
using Math for uint256;
IERC20 private immutable _asset;
uint8 private immutable _decimals;
constructor(IERC20 asset) {
(bool success, uint8 decimal) = _tryGetAssetDecimals(asset);
_decimals = success ? decimal : 18;
_asset = asset;
}
function _tryGetAssetDecimals(
IERC20 _token
) internal view returns (bool, uint8) {
(bool success, bytes memory data) = address(_token)
.staticcall(abi.encodeCall(IERC20Metadata.decimals, ()));
if (!success || data.length < 32) {
return (false, 0);
}
uint256 decimal = abi.decode(data, (uint256));
if (decimal <= type(uint8).max) {
return (true, uint8(decimal));
}
return (false, 0);
}
function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
return _decimals;
}
function asset() external view override returns (address) {
return address(_asset);
}
function totalAssets() public view override returns (uint256) {
return _asset.balanceOf(address(this));
}
/**
* 用户资产转化为份额
* totalShares: 是金库中所有金库代币的总量
* totalAssets: 总资产是金库当前持有的所有基础资产的总和,用于衡量金库的价值
* 计算公式:shares = assets * totalShares / totalAssets
* @param assets 用户基础资产
*/
function convertToShares(uint256 assets) external view override returns (uint256 shares) {
// assets * (totalSupply() + 1) / (totalAssets() + 1);
// totalAssets() + 1: The purpose of adding 1 to totalAssets is to prevent the divisor from being 0
// totalSupply() + 1: When totalSupply is close to 0, this adjustment of adding 1 ensures that the calculation at the initial state does not fail due to division by zero.
return assets.mulDiv(totalSupply() + 1, totalAssets() + 1, Math.Rounding.Floor);
}
/**
* 根据份额转化为基础代币
* 计算公式: assets = shares * totalAssets / totalSupply
* @param shares 持有的金库份额
*/
function convertToAssets(uint256 shares) external view override returns (uint256 assets) {
return shares.mulDiv(totalAssets() + 1, totalSupply() + 1, Math.Rounding.Floor);
}
/**
* 某地址最大可以的一次 deposit 调用中可以存入的最大标的资产数量
* 有的可能有限制,有的没有限制,看业务需求
*/
function maxDeposit(address receiver) public view override returns (uint256 maxAssets) {
return type(uint256).max;
}
/**
* 预存款,可以进行一些计算,看可以得到多少份额
* @param assets 存储的基础资产数量
*/
function previewDeposit(uint256 assets) public view override returns (uint256 shares) {
return assets.mulDiv(totalSupply() + 1, totalAssets() + 1, Math.Rounding.Floor);
}
/**
* 用户存储基础资产到某一个账户
* @param assets deposit amount
* @param receiver An address where assets deposited in
*/
function deposit(uint256 assets,address receiver) external override returns (uint256 shares) {
// 获取接收者允许的最大的存储的基础资产
uint256 maxAssets = maxDeposit(receiver);
if (assets > maxAssets) {
revert ERC4626Errors.ERC4626ExceededMaxDeposit(receiver, assets, maxAssets);
}
// 根据基础资产,计算份额
uint256 shares = previewDeposit(assets);
// 当前合约存储基础资产,并且mint出金库代币给指定的接收地址
_deposit(msg.sender, receiver, assets, shares);
return shares;
}
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
// msg.sender transfer some assets to current contract
SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
// mint vault token(shares) to receiver
_mint(receiver, shares);
emit Deposit(caller, receiver, assets, shares);
}
/**
* 此函数返回 receiver 在单次 mint 调用中可以铸造的最大份额
* 有的可能有限制,有的没有限制,看业务需求
*/
function maxMint(address receiver) public view override returns (uint256 maxShares) {
return type(uint256).max;
}
/**
* 计算出mint特定数量的shares(金库代币)所需的基础资产数量(assets)
* @param shares 金库代币份额
*/
function previewMint(uint256 shares) public view override returns (uint256 assets) {
return shares.mulDiv(totalAssets() + 1, totalSupply() + 1, Math.Rounding.Ceil);
}
function mint(uint256 shares, address receiver) external override returns (uint256 assets) {
// 计算出接收地址可以mint的最大份额数量
uint256 maxShares = maxMint(receiver);
if (shares > maxShares) {
revert ERC4626Errors.ERC4626ExceededMaxMint(receiver, shares, maxShares);
}
// 计算出mint特定数量的shares(金库代币)所需的基础资产数量(assets)
uint256 assets = previewMint(shares);
_deposit(msg.sender, receiver, assets, shares);
return assets;
}
/**
* 最多可以提现多少基础资产
*/
function maxWithdraw(address owner) public view override returns (uint256 maxAssets) {
// onwer有多少金库代币(份额)
uint256 shares = balanceOf(owner);
// convert shares to assets
return shares.mulDiv(totalAssets() + 1, totalSupply() + 1, Math.Rounding.Floor);
}
function previewWithdraw(uint256 assets) public view override returns (uint256 shares) {
return assets.mulDiv(totalSupply() + 1, totalAssets() + 1, Math.Rounding.Ceil);
}
function withdraw(uint256 assets, address receiver, address owner) external override returns (uint256 shares) {
uint256 maxAssets = maxWithdraw(owner);
if (assets > maxAssets) {
revert ERC4626Errors.ERC4626ExceededMaxWithdraw(owner, assets, maxAssets);
}
// 需要重新计算一下金库代币(份额),如果是挣钱了肯定份额就比以前多,如果是亏钱了可能会比以前少
uint256 shares = previewWithdraw(assets);
_withdraw(_msgSender(), receiver, owner, assets, shares);
return shares;
}
function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal virtual {
// 提现地址如果不是owner, 那么就需要owner授权,如果授权额度用完了则不能提现;否则更新授权额度
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
// 将对应的金库代币燃烧掉
_burn(owner, shares);
// 转移基础资产代币
SafeERC20.safeTransfer(_asset, receiver, assets);
emit Withdraw(caller, receiver, owner, assets, shares);
}
/**
* 用户最大可以赎回多少份额
* @param owner 份额所有者
*/
function maxRedeem(address owner) public view override returns (uint256 maxShares) {
return balanceOf(owner);
}
function previewRedeem(uint256 shares) public view override returns (uint256 assets) {
return shares.mulDiv(totalAssets() + 1, totalSupply() + 1, Math.Rounding.Floor);
}
function redeem(uint256 shares, address receiver, address owner) external override returns (uint256 assets) {
uint256 maxShares = maxRedeem(owner);
if (shares > maxShares) {
revert ERC4626Errors.ERC4626ExceededMaxRedeem(owner, shares, maxShares);
}
// 根据要赎回的份额计算资产数量
uint256 assets = previewRedeem(shares);
_withdraw(msg.sender, receiver, owner, assets, shares);
return assets;
}
}