以太坊应用层标准提案DeFi之ERC4626

一 什么是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;
    }
}

【A股温度计】www.agwdj.com 镜像版程序V1.0说明 •通过数据可视化技术,将复杂的A股市场数据转化为直观的图形界面,帮助投资者快速把握市场脉搏。 【核心功能】 •全景视角:突破信息碎片化局限,快速定位涨跌分布,一眼锁定今日热点板块 •板块排序:基于申万行业分类标准,对31个一级行业和131个二级行业实时动态排序 •硬件适配:智能适配不同分辨率屏幕,4K以上屏幕显示信息更多(视觉更佳) •智能缩放:A股全图让大A市场5000+个股同屏显示(支持鼠标滚轮及触摸设备5级缩放) 【三秒原则】 •三秒看懂:通过精心设计的视觉图形,让用户在三秒内看清市场整体状况 •三秒定位:智能算法让大成交额个股和热点板块自动靠前,快速定位机会 •三秒操作:极简的界面,让用户减少操作 【使用场景】 •盘前准备:快速了解隔夜市场变化,制定当日策略 •盘中监控:实时跟踪市场动向,及时把握当日机会 •盘后复盘:全面分析当日市场表现,总结经验教训 【适合人群】 •个人用户:快速了解市场整体趋势变化,辅助决策 •专业人员:获取每天市场的数据云图支持研究工作 •金融机构:作为投研系统的可视化补充组件 •财经媒体:制作专业市场分析图表和报道 【市场切换】 •默认加载"A股全图",可切换单独显示的类型如下: •上证A股/深证A股/北证A股/创业板/科创板/ST板块/可转债/ETF 【程序优势】 •运行环境:纯PHP运行(无需安装任何数据库) •数据更新:实时同步→A股温度计→www.agwdj.com •显示优化:自动适配8K/4K/2K/1080P等不同分辨率的屏幕 •设备兼容:对市面上主流的设备及浏览器做了适配(检测到手机/平板/电视等默认Chrome/Firefox/Edge内核过低的情况会自动提示) 【其他说明】 •A股温度计程序演示网址:https://www.agwdj.com
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值