Compound V3 中的 bulker 合约是类似 multicall 的合约,用于批量处理多个交易。
例如,如果我们想在单笔交易中提供 Ether、LINK 和 wBTC 作为抵押品,并以此借出 USDC,我们是可以做到的。
正如下方截图所示,我们也可以在单笔交易中减少抵押品持有量并提取贷款。当然,这前提是我们保持在抵押因子限制范围内。

invoke()
bulker 的行为不像传统的 multicall 那样接受任意 calldata 列表。相反,它接收两个参数:一个操作列表(有 6 种选择)以及要提供给这些操作的参数。该函数如下所示。具体来说,我们可以:
- 提供 ERC-20 资产 (
ACTION_SUPPLY_ASSET) - 提供 ETH (
ACTION_SUPPLY_NATIVE_TOKEN) - 转移资产 (
ACTION_TRANSFER_ASSET),查看我们关于 Compound V3 如何像 rebasing ERC-20 代币一样运作 的文章以了解其工作原理 - 提取 ERC-20 资产 (
ACTION_WITHDRAW_ASSET) - 提取 ETH (
ACTION_WITHDRAW_NATIVE_TOKEN) - 领取累积的 COMP 奖励。底层函数 claimReward 将与奖励合约交互,而不是与主借贷合约(Comet.sol)交互。

invoke 将遍历这些操作,并使用提供的参数调用 Comet(或奖励合约)。
尽管将此代码放入主合约可能会更节省 gas,但在循环中使用 msg.value 是不安全的,尤其是在执行 delegatecall 时。请参考本文末尾的练习题。
Compound 谨慎地确保 msg.value 会被扣减而不是重复使用,因为重复使用可能导致双重支付(双花)——参见黄色框。
如果使用 1 字节的指示符而不是 32 字节的 ASCII 字符串指示符来决定操作,这点按理说可以进行 gas 优化。
Bulker 是非托管的
bulker 从不持有用户的代币。相反,用户向 bulker 授予授权(approval),bulker 则代表用户向 Compound 提供资产。Compound 并不预设 msg.sender 就是存款人;这样做通常不是一个好的设计模式,因为它破坏了可组合性——这会阻止其他合约代表用户执行操作。
挽救代币
在用户意外将代币转入 bulker 的情况下,管理员可以将其移出。请注意,我们有单独的函数来移除被困的 ETH 和被困的 ERC-20 代币。

处理非标准 ERC-20 代币
与 ERC-20 代币规范最常见的偏差之一是不返回布尔值。有几种 ERC-20 代币不返回布尔值,而是在失败时发生 revert。
为了在处理 ERC-20 资产时兼顾这两种情况,Compound 实现了以下代码:

IERC20NonStandard 没有返回值——在处理非标准 ERC-20 代币时它会发生 revert。为了应对它实际上是一个标准 ERC-20 代币的可能性,当返回数据大小为 32 字节时,将表示代币确实返回了一个值。如果返回值为 1,则表示成功,否则表示失败。不同的场景总结在下方的矩阵中。

从本质上讲,我们在(代币没有 revert 且未返回任何内容)或(代币没有 revert 且返回 true)的情况下判定为成功。我们在(代币发生 revert)或(代币没有 revert 且返回 false)的情况下判定为失败。
IERC20Nonstandard 接口只是定义了一个不返回任何布尔值的 ERC20 代币。

练习题
在循环内部使用 msg.value 可能会发生严重的问题。请看这两个练习题:
通过 RareSkills 了解更多
请参阅我们的 Web3 训练营 以了解更多信息。
最初发布于 2024 年 1 月 9 日