ERC4626简介
ERC4626 协议是一种用于代币化保险库的标准。
我们经常说 DeFi 是货币乐高,可以通过组合多个协议来创造新的协议; ERC4626 扩展了 ERC20 代币标准,旨在推动收益金库的标准化,它是 DeFi 乐高中的基础,它允许你把底层资产质押到金库合约中,从而获取一定比例的金库代币;你存入的底层资产存储在金库中的这段时间,会产生一定的收益——例如被用于借贷平台、收益聚合、流动资金池等;你可以在任何时间拿着金库代币赎回本金以及一定收益。
ERC4626 主要逻辑
ERC4626 继承了 ERC20,金库代币就是用 ERC20 代币代表的;底层资产(比如 WETH)可以是任何有价值的代币,提前存入金库;
用户将特定的 ERC20 底层资产存进金库,金库会给他铸造特定数量的金库代币;相关函数为 deposit() 和 mint()。deposit(uint assets, address receiver) 函数让用户存入 assets 数量的资产,并铸造相应数量的金库代币给 receiver 地址。mint(uint shares, address receiver) 与它类似,只不过是以将铸造的金库代币数量作为参数
当用户从金库中提取底层资产时,会销毁相应数量的金库代币。相关函数为 withdraw() 和 redeem(),前者以取出底层资产数量为参数,后者以销毁的金库代币数量为参数。
接口中定义
- asset(): 返回金库的底层资产代币地址。
- totalAssets(): 返回金库中管理的底层资产总额。
- convertToShares(): 返回利用一定数量底层资产可以换取的金库代币。
- convertToAssets(): 返回利用一定数量金库代币可以换取的底层资产。
- deposit(): 存款,向金库存入 assets 数量的底层资产,然后增发相应比例的shares 金库代币给 receiver 。触发 Deposit 事件。
- maxDeposit(): 返回单次存款可存的最大底层资产数额。
- previewDeposit(): 在当前链上环境模拟存款一定数额的底层资产能够获得的金库代币。
- mint(): 铸造,指定想获得的 shares 数量的金库代币,计算出需要存入的 assets 数量的底层资产金库从用户账户转出 assets 数量的底层资产,再给 receiver 铸造相应数量的金库代币。触发 Deposit 事件。
- maxMint(): 返回单次可以铸造的最大金库代币额度。
- previewMint(): 用于用户在当前链上环境模拟铸造一定数额的金库额度需要存款的基础资产数量。
- withdraw(): 提款,金库将assets数量的底层资产发送给 receiver ,owner 销毁相应数量的金库代币。触发 Withdraw 事件。
- maxWithdraw(): 返回某个用户地址单次取款可以提取的最大基础资产额度。
- previewWithdraw(): 当前链上环境模拟提款一定数量的底层资产需要的金库代币数量。
- redeem(): 赎回,owner 销毁shares 数量的金库代币,然后金库将相应数量的底层资产发给 receiver,触发 Withdraw 事件。
- maxRedeem(): 返回单次赎回,可以销毁的最大金库代币。
- previewRedeem(): 在当前链上环境模拟销毁制定数量的金库代币能够赎回的底层资产数量。
IERC4626 接口合约共包含 2 个事件:
- Deposit : 存款时触发事件。
- Withdraw : 取款时触发事件。
代码分析
这里直接使用openzeppelin库的源码
abstract contract ERC4626 is ERC20, IERC4626 {
using Math for uint256;
IERC20 private immutable _asset; //底层资产地址
uint8 private immutable _decimals; //shares的decimal
//初始化
constructor(IERC20 asset_) {
(bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
_decimals = success ? assetDecimals : super.decimals();
_asset = asset_;
}
//获取底层资产decimal
function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) {
//使用底层资产地址通过staticcall,获取decimal
(bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
abi.encodeWithSelector(IERC20Metadata.decimals.selector)
);
//返回成功,且有数据,就判断数据是否<=255,满足就返回该值;
if (success && encodedDecimals.length >= 32) {
uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
if (returnedDecimals <= type(uint8).max) {
return (true, uint8(returnedDecimals));
}
}
return (false, 0);
}
//获取金库代币的decimal,注意这是对ERC20中函数的重写;
function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
return _decimals;
}
//底层资产地址
function asset() public view virtual override returns (address) {
return address(_asset);
}
//金库中底层资产的数量
function totalAssets() public view virtual override returns (uint256) {
return _asset.balanceOf(address(this));
}
//指定数量的底层资产可换取金库代币的数量
function convertToShares(uint256 assets) public view virtual override returns (uint256 shares) {
return _convertToShares(assets, Math.Rounding.Down);
}
//指定数量的金库代币可换取底层资产的数量
function convertToAssets(uint256 shares) public view virtual override returns (uint256 assets) {
return _convertToAssets(shares, Math.Rounding.Down);
}
//如何金库底层资产数量>0 || 金库代币=0,则返回2^256-1 否则为0;
function maxDeposit(address) public view virtual override returns (uint256) {
return _isVaultCollateralized() ? type(uint256).max : 0;
}
//单次最大铸造金库代币,返回2^256-1
function maxMint(address) public view virtual override returns (uint256) {
return type(uint256).max;
}
//返回owner账户下金库代币,可以兑换出来的底层资产数量
function maxWithdraw(address owner) public view virtual override returns (uint256) {
return _convertToAssets(balanceOf(owner), Math.Rounding.Down);
}
//返回owner账户下金库代币数量
function maxRedeem(address owner) public view virtual override returns (uint256) {
return balanceOf(owner);
}
//计算此时存入指定数量的底层资产,可以铸造多少金库代币;实际金库代币要低于理论值
function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
return _convertToShares(assets, Math.Rounding.Down);
}
//计算此时要铸造指定数量的金库代币,可以质押的底层资产数量;实际质押的底层资产数量要高于理论值
function previewMint(uint256 shares) public view virtual override returns (uint256) {
return _convertToAssets(shares, Math.Rounding.Up);
}
//计算此时赎回指定数量的底层资产,需要多少金库代币;实际金库代币要高于理论值
function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
return _convertToShares(assets, Math.Rounding.Up);
}
//计算此时指定数量的金库代币,可以赎回的底层资产数量;实际兑换的底层资产数量要小于理论值
function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
return _convertToAssets(shares, Math.Rounding.Down);
}
//存入指定数量的底层资产,并为receiver铸造相应的金库代币
function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
//校验参数,质押的底层资产数量,不可大于receiver能质押的最大值
require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
//计算可获取金库代币数量
uint256 shares = previewDeposit(assets);
//用户向金库质押assets数量的底层资产,为receiver增发shares数量的金库代币
_deposit(_msgSender(), receiver, assets, shares);
return shares;
}
//要铸造指定数量的金库代币,并扣除相应的底层资产;
function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
//校验参数
require(shares <= maxMint(receiver), "ERC4626: mint more than max");
//计算增发shares数量的代币金库,需要多少底层资产
uint256 assets = previewMint(shares);
//用户向金库质押assets数量的底层资产,为receiver增发shares数量的金库代币
_deposit(_msgSender(), receiver, assets, shares);
return assets;
}
//赎回指定数量的底层资产,并扣除相应的金库代币
function withdraw(
uint256 assets,
address receiver,
address owner
) public virtual override returns (uint256) {
require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max");
//计算赎回指定的底层资产,需要的金库代币数量
uint256 shares = previewWithdraw(assets);
//销毁owner名下,shares数量的金库代币,给receiver转入assets数量的底层资产
_withdraw(_msgSender(), receiver, owner, assets, shares);
return shares;
}
//销毁指定数量的金库代币,获取想要数量的底层资产
function redeem(
uint256 shares,
address receiver,
address owner
) public virtual override returns (uint256) {
require(shares <= maxRedeem(owner), "ERC4626: redeem more than max");
//计算指定数量的金库代币,可以赎回底层资产的数量
uint256 assets = previewRedeem(shares);
//销毁owner名下,shares数量的金库代币,给receiver转入assets数量的底层资产
_withdraw(_msgSender(), receiver, owner, assets, shares);
return assets;
}
//计算此时指定数量的底层资产,返回可以铸造多少金库代币;
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256 shares) {
uint256 supply = totalSupply();
return
(assets == 0 || supply == 0)
? _initialConvertToShares(assets, rounding)
: assets.mulDiv(supply, totalAssets(), rounding);//返回assets * 金库代币总量 /底层资产总量
}
//金库代币总量为0时或者底层资产为0,金库代币增发数量
function _initialConvertToShares(
uint256 assets,
Math.Rounding /*rounding*/
) internal view virtual returns (uint256 shares) {
return assets;//默认1:1
}
//计算此时指定数量的金库代币,返回可以兑换的底层资产数量;
function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256 assets) {
uint256 supply = totalSupply();
return
(supply == 0) ?
_initialConvertToAssets(shares, rounding)
: shares.mulDiv(totalAssets(), supply, rounding);//返回shares* 底层资产总量 /金库代币总量
}
//金库代币总量为0时,可兑换底层资产的数量
function _initialConvertToAssets(
uint256 shares,
Math.Rounding /*rounding*/
) internal view virtual returns (uint256 assets) {
return shares; //默认1:1
}
//caller向金库质押assets数量的底层资产,为receiver增发shares数量的金库代币
function _deposit(
address caller,
address receiver,
uint256 assets,
uint256 shares
) internal virtual {
//使用safeerc20的safetransferfrom方法,进行底层资产的转账;
SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
//增发金库代币
_mint(receiver, shares);
emit Deposit(caller, receiver, assets, shares);
}
//销毁owner名下,shares数量的金库代币,给receiver转入assets数量的底层资产
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal virtual {
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
//销毁金库代币
_burn(owner, shares);
//使用safeerc20的safetransfer方法,进行底层资产的转账
SafeERC20.safeTransfer(_asset, receiver, assets);
emit Withdraw(caller, receiver, owner, assets, shares);
}
//检测金库是否还有底层资产 || 金库代币=0,这两种情况,都认为是正常
function _isVaultCollateralized() private view returns (bool) {
return totalAssets() > 0 || totalSupply() == 0;
}
}
测试
测试使用foundry,可以直接看-----github代码,就不做过多分析了;