ERC4626详解
Vault
Vault(保险库) 通常指一种资金管理和收益策略工具,主要用于 DeFi(去中心化金融)协议中,用来帮助用户自动化收益最大化,降低操作难度和 Gas 成本。
Vault 的主要功能
- 自动复投
Vault 会将挖矿奖励、利息等收益,自动复投到原策略中,提升复利效应,用户不需要手动操作。 - 策略自动化
比如 Yearn Finance 的 Vault,会根据链上数据动态调整策略,将资金投向收益更高的 DeFi 协议。 - 降低 Gas 成本
单个用户频繁操作会耗费大量 Gas,而 Vault 将资金集中,批量执行操作,摊薄 Gas 成本。 - 风险隔离
不同策略可以部署在不同的 Vault 中,用户可以根据风险偏好选择。 - yield farming/借贷/质押
Yield Farming(收益耕作) 是 DeFi(去中心化金融) 中的一种策略,指用户将自己的加密资产存入特定协议(如借贷、流动性池、收益聚合器)中,以赚取额外收益(通常是利息或平台代币奖励)的行为。
简单来说,Yield Farming 就是“把加密货币拿去生钱”,类似于传统金融里的“存款生息 + 活期理财”,但更复杂、收益更高、风险也更大。
典型项目
✅ Yearn Finance
最经典的 Vault 应用,用户存入稳定币、ETH 或 LP Token,Vault 会自动将资金部署到 Curve、Compound 等平台,执行收益优化策略。
✅ Beefy Finance
多链收益聚合器,Vault 帮助用户将流动性挖矿奖励复投,提升收益。
简单理解
如果把 DeFi 理解为“数字银行”,那么 Vault = 自动理财管家:
- 你把钱交给 Vault(智能合约)
- 它帮你找高收益、做复投
- 省去手动搬砖的麻烦
LP Token
LP Token(Liquidity Provider Token,流动性提供者代币) 是 用户向去中心化交易所(DEX)或流动性池提供流动性后获得的凭证代币,它代表着用户在池子中所占的份额。
LP Token = 你在流动性池的股份证明,可以提币、分红、甚至去挖矿。
为什么会有 LP Token?
在 Uniswap、SushiSwap、Curve 等 AMM(自动做市商)模型的 DEX 中,交易对(如 ETH/USDC)需要资金池提供流动性。
当你往池子里存入等价值的两种资产(例如 1 ETH + 2000 USDC),你就成为了 流动性提供者(Liquidity Provider, LP)。
为了证明你存入了多少,系统会给你发一个 LP Token,这就是你的 股份凭证。
LP Token 的作用
- 提现凭证
- 以后你要从池子里取出资金,必须用 LP Token 去兑换你的资产(本金 + 交易手续费分红)。
- 收益分配
- 交易产生的手续费会按 LP Token 占比分给 LP 持有人。
- 参与 DeFi 挖矿(Liquidity Mining)
- 你可以拿 LP Token 去 质押(Stake),获取额外奖励(比如治理代币)。
举例
- 你往 Uniswap 的 ETH/USDC 池子存了 1 ETH + 2000 USDC
- 系统给你 10 个 LP Token
- 池子总价值是 100 ETH + 200,000 USDC(假设 LP Token 总量 1000)
- 你的 LP Token 占比 = 10 / 1000 = 1%
- 所以,你拥有池子中 1% 的资金份额和手续费收益。
风险
- 无常损失(Impermanent Loss):如果 ETH 价格大涨或大跌,你最终取出的资产组合可能少于单纯持币的价值。
- 合约风险:LP Token 本身是由智能合约发行,如果合约被攻击,你可能损失资金。
- 代币贬值:如果奖励代币暴跌,实际收益降低。
Vault01实验
目的
实现一个基础的Vault合约,并结合ERC20代币,完成用户的充值(deposit)和取款(withdraw)逻辑。
准备
1. USDT合约
合约路径:ERC20_fortest.sol
功能描述:
该合约基于 OpenZeppelin 提供的 ERC20
标准,实现了一个名为 Tether USD(USDT) 的代币,主要用于模拟稳定币的发行。
代码
1 | // SPDX-License-Identifier: MIT |
功能特点:
- 使用 OpenZeppelin
ERC20
标准库,安全且兼容性强。 - 构造函数中通过
_mint()
为部署者铸造初始代币。
2. Vault01合约
合约路径:Vault01.sol
功能描述:
该合约实现一个资金池(Vault),用户可以存入 ERC20 代币并获得对应的 shares,shares 代表用户在池中的份额。用户可通过 shares 按比例赎回对应的代币。
核心逻辑
- 存款(deposit):
用户将代币转入合约地址,并按规则获得对应份额(shares)。
1 | /* |
- 取款(withdraw):
1 | /* |
核心代码
1 | // SPDX-License-Identifier: MIT |
实验步骤
部署 USDT 合约
- 在 Remix 中部署
ERC20_fortest.sol
,初始发行1000000 * 10^18
个代币。 - 记录 USDT 合约地址,例如
0xABC...123
。
- 在 Remix 中部署
部署 Vault01 合约
- 构造函数传入 USDT 合约地址
0xABC...123
。 - Vault01 合约部署完成,等待交互。
- 构造函数传入 USDT 合约地址
授权 Vault01 合约转账
- 在 USDT 合约中执行
approve(vaultAddress, amount)
。 - 授权 Vault 可转账指定数量的 USDT。
- 在 USDT 合约中执行
测试存款
- 调用
deposit(100e18)
。 - 验证:
Vault01.balanceOf(msg.sender)
是否增加。Vault01.totalSupply
是否更新。Vault01.token.balanceOf(address(Vault01))
是否为 100 USDT。
- 调用
测试取款
- 调用
withdraw(50e18)
。 - 验证:
- 用户账户 USDT 余额恢复。
Vault01.totalSupply
减少。
- 调用
ERC4626阅读
assets & shares
1 | // ERC4626 资产与份额接口 |
包含两个核心功能组:
- 资产信息查询(asset 和 totalAssets)
- 资产与份额的相互转换(convertToShares 和 convertToAssets)
存款/铸造相关函数接口
1 | // ERC4626 存款与铸造接口 |
receiver
的作用是什么?
receiver
参数的作用是 指定存款/铸造操作中接收份额(shares)的目标地址。它的设计目的是实现更灵活的资产托管和委托操作。
相当于充值给另外一个地址
与 msg.sender
的区别
msg.sender
代表实际调用合约的地址(操作者),通常是支付资产(transferFrom
)的地址。receiver
代表最终获得份额的地址,可能与msg.sender
相同,也可能不同。
提款与赎回接口
1 | // ERC4626 提款与赎回接口 |
核心区别:withdraw
vs redeem
withdraw
- 用户指定想要提取的资产数量(
assets
)。 - 系统自动计算需要销毁的份额(
shares
),通过previewWithdraw
预览。 - 适用场景:用户关注能拿到多少底层资产(如“我要提100 USDC”)。
- 用户指定想要提取的资产数量(
redeem
- 用户指定要销毁的份额数量(
shares
)。 - 系统计算可获得的资产数量(
assets
),通过previewRedeem
预览。 - 适用场景:用户关注销毁多少份额(如“我要赎回50 vaultShares”)。
- 用户指定要销毁的份额数量(
为什么需要 owner
和 receiver
?
owner
- 代表实际持有份额的地址(必须已授权调用者操作其份额)。
- 例如:智能合约代理可替用户操作,但份额属于用户地址。
receiver
- 允许将提取的资产发送到第三方地址(如归集钱包或另一个合约)。
- 例如:用户可指定将资产直接发送到交易所地址。
总体:
两个事件
1 | // ERC4626 存款/提款事件 |
在 withdraw
操作中,如果 msg.sender !=owner那么 msg.sender 需要先请 owner 调用什么方法,才可以让 msg.sender 来 withdraw 成功?
答: approve
Front - Running
Front-Running 是指恶意节点(FrontRunner)通过监控待处理交易池(Mempool),以更高 Gas 费抢在目标交易(User’s Transaction)之前执行自己的交易,从而获利的行为。以下是分步拆解:
1. 用户发起交易
- 用户提交一笔交易(如购买代币、调用智能合约)到以太坊网络。
- 交易进入待处理池(Mempool),等待矿工/验证者打包。
2. 恶意节点监控交易池
- FrontRunner(通常是机器人)实时扫描Mempool,识别有利可图的交易:
- 例如:用户的大额代币买单(可能推高价格)。
- 或套利机会(如DEX价格偏差)。
3. 发起抢先交易
- 策略:
- 复制用户交易逻辑(如购买同种代币)。
- 设置更高Gas费,吸引矿工优先打包。
- 结果:
- 恶意交易被优先执行,用户交易因Gas低而延后。
4. 获利手段
- 低买高卖:抢在用户买单前低价购入代币,待用户交易推高价格后卖出。
- 套利:利用DEX价格延迟,在用户交易前完成价差套利。
- 操纵合约:针对拍卖类合约,提前锁定优势条件。
技术实现依赖
- 交易透明性:以太坊Mempool公开可见,便于监控。
- Gas竞价机制:矿工优先打包高Gas交易。
- 智能合约可预测性:若合约逻辑固定,攻击者可模拟结果。
防御方案
- 隐私交易:使用Flashbots等隐私RPC,避免交易暴露在公开Mempool。
- 限价单/滑点控制:设置交易价格上限,减少被利用空间。
- 合约级防护:
- 提交-揭示模式(Commit-Reveal Scheme)。
- 随机化关键操作(如拍卖截止时间)。
典型案例
- DeFi套利:2020年Uniswap上多次出现抢跑套利,单笔获利超万美元。
- NFT铸造:热门项目公售时,机器人抢先铸造稀缺资产。
Front-Running本质是利用区块链透明性和激励机制的设计缺陷,是Web3中典型的”黑暗森林”攻击。
ERC4626 通胀攻击(Inflation Attack)
攻击背景
ERC4626 是代币化金库(Vault)标准,允许用户存入资产(assets)并获取份额(shares)。攻击者通过操纵金库的 资产-份额转换比例,在特定条件下实现“凭空增发份额”的漏洞。
攻击场景示例
假设初始状态:
- 用户存入资产:
assets_deposited = 1,000
- 当前总份额:
totalSupply() = 1,000
- 金库总资产:
totalAssets() = 1,000,000
此时,用户预期获得的份额应通过公式计算:1
2shares_received = assets_deposited * totalSupply() / totalAssets()
= 1,000 * 1,000 / 1,000,000 = 1 share
漏洞触发点
问题出在 _convertToShares
函数的实现:
1 | function _convertToShares(uint256 assets, Math.Rounding rounding) |
攻击步骤:
- 初始状态:金库刚部署,
totalSupply = 0
,totalAssets = 0
。 - 攻击者首次存款:
- 存入极少量资产(如 1 wei),此时:
1
shares = 1 * (0 + 1e18) / (0 + 1) = 1e18 shares
- 攻击者以 1 wei 的成本获得
1e18
份额(天文数字)。
- 存入极少量资产(如 1 wei),此时:
- 正常用户存款:
- 用户存入
1,000
资产时,因totalSupply
被攻击者通胀:1
shares = 1,000 * (1e18 + 1e18) / (1 + 1,000) ≈ 2e18 / 1,001 ≈ 2e15 shares
- 用户实际获得的份额远低于预期,大部分价值被攻击者稀释。
- 用户存入
攻击核心逻辑
- 分母操纵:
totalAssets() + 1
的设计在初始状态下(totalAssets=0
)会放大份额计算。 - 份额通胀:攻击者通过极低成本获取大量份额,后续用户的存款被严重稀释。
防御方案
初始化保护:
- 在金库部署时预铸少量份额给零地址(如
1e18
),避免totalSupply=0
的极端情况。1
2
3
4function __ERC4626_init(address asset, uint256 initialDeposit) internal {
_mint(address(0), 1e18); // 预铸份额
_deposit(initialDeposit, msg.sender); // 初始存款
}
- 在金库部署时预铸少量份额给零地址(如
公式修正:
- 移除分母的
+1
逻辑,直接使用assets * totalSupply / totalAssets
。
- 移除分母的
最小存款限制:
- 要求首次存款必须超过一定阈值(如
1e18
wei),提高攻击成本。
- 要求首次存款必须超过一定阈值(如
真实案例
- 2022 年多个未实现防护的 ERC4626 金库(如某些收益聚合器)因此漏洞被攻击,导致用户份额价值被稀释。
总结:ERC4626 通胀攻击利用了初始状态下的数学漏洞,开发者需严格检查资产-份额转换公式的边界条件!