Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ERC-4626: 代币化金库会计标准 (Tokenized Vault Standard)

ERC-4626 是以太坊针对**产生收益的代币化金库(Yield-bearing Vaults)**制定的统一会计接口标准。

在 ERC-4626 之前,各大借贷与质押协议(如 Compound 的 cToken、Aave 的 aToken、Yearn 的 yVault)各自维护了一套私有的份额(Shares)与底层资产(Assets)计算公式。接口割裂导致聚合器、机枪池与借贷衍生品必须为每个协议编写高度耦合的适配器代码。ERC-4626 确立了统一的存款、铸币、提取、赎回状态机与精度换算范式。


核心数学模型:资产与份额的动态换算

金库通常持有某种底层代币(asset),并向存入资产的用户按比例铸造代表金库索赔权的凭证代币(shares)。

1. 基础换算比例公式

$$ \text{shares} = \text{assets} \times \frac{\text{totalSupply}}{\text{totalAssets}} $$

$$ \text{assets} = \text{shares} \times \frac{\text{totalAssets}}{\text{totalSupply}} $$

  • totalAssets():当前金库所管理并拥有控制权的底层资产物理总量。
  • totalSupply():当前已发行的金库凭证份额总量。

2. 整数除法截断与舍入方向约束(Rounding Direction)

EVM 仅支持向零截断的整数算术。ERC-4626 明确规定了所有计算的舍入偏向,必须始终偏向保护金库账本完整性、惩罚提取套利:

操作接口核心算式规范舍入方向 (Rounding)经济学目的
previewDeposit(assets) / deposit计算获得的 shares向下取整(Round Down)避免用户存入资产时多得份额,防止凭空印钞
previewMint(shares) / mint计算需付出的 assets向上取整(Round Up)确保用户存足全额资产才可获得指定份额
previewWithdraw(assets) / withdraw计算需扣除的 shares向上取整(Round Up)提取资产时多销毁微量份额,保护剩余存款人
previewRedeem(shares) / redeem计算返还的 assets向下取整(Round Down)销毁份额时少给微量底层资产,收益沉淀于池中

核心状态机与标准接口

ERC-4626 扩展了 ERC-20 标准,包含四组对称的存款与提取方法:

interface IERC4626 is IERC20 {
    // 底层资产地址
    function asset() external view returns (address assetTokenAddress);

    // 金库总管理资产数
    function totalAssets() external view returns (uint256 totalManagedAssets);

    // 1. 基于资产数存入 (Deposit: 指定资产,换取份额)
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    // 2. 基于份额数存入 (Mint: 指定目标份额,支付资产)
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    // 3. 基于资产数提取 (Withdraw: 指定提取资产数,销毁份额)
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    // 4. 基于份额数赎回 (Redeem: 指定销毁份额数,返还资产)
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);

    // 查询与模拟接口 (无状态开销)
    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 maxMint(address receiver) external view returns (uint256 maxShares);
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);
    function maxRedeem(address owner) external view returns (uint256 maxShares);
    function previewDeposit(uint256 assets) external view returns (uint256 shares);
    function previewMint(uint256 shares) external view returns (uint256 assets);
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);
    function previewRedeem(uint256 shares) external view returns (uint256 assets);
}

初始存款通胀攻击与虚拟偏移量防御 (Virtual Offset)

1. 攻击根因复盘

在初始空池(\(\text{totalSupply} = 0\))状态下,攻击者先存入 1 wei 资产获得 1 wei 份额,随后通过直接 transfer 向金库地址强制捐赠 \(10^6\) 个代币拉升每股净值。后续真实用户存入资金时,由于向下截断,计算出的 shares 为 0,资金被全额吞并。

2. 虚拟资产与虚拟份额偏移量解法 (OpenZeppelin 方案)

OpenZeppelin ERC4626 实现中引入了虚拟偏移量(如增加 \(10^{\text{decimals}}\)):

$$ \text{shares} = \text{assets} \times \frac{\text{totalSupply} + 10^a}{\text{totalAssets} + 1} $$

$$ \text{assets} = \text{shares} \times \frac{\text{totalAssets} + 1}{\text{totalSupply} + 10^a} $$

通过人为在分母分子中注入虚拟底数,即使在极端空池状态下,1 wei 的小额建仓也无法将单位净值拉升至发生严重截断的区间。


生产级安全检查清单

  • 严格对齐舍入方向:所有换算函数必须严格遵循“存入少给份额,提取多扣份额”的原则,严禁使用原生除法默认向零截断造成方向颠倒。
  • 防范初始通胀与捐赠攻击:必须配置虚拟偏移量机制,或在初始化阶段铸造并死锁最小基础份额(如向 address(0) 锁定 1000 shares)。
  • 防范零份额铸造与充值:在 deposit 与 mint 逻辑中加入 require(shares > 0) 断言,防止用户资产被无偿吞没。
  • 脱钩直接余额物理依赖:避免单纯依赖 asset.balanceOf(address(this)) 计算 totalAssets(),采用内部记账抵御闪电贷或直接转账对汇率的单区块瞬态冲击。

参考文献与规范