ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

如何用 OpenZeppelin Contracts 的 Multicall 把多个调用合并成一次原子交易?

如何用 OpenZeppelin Contracts 的 Multicall 把多个调用合并成一次原子交易? 如何用 OpenZeppelin Contracts 的 Multicall 把多个调用合并成一次原子交易【免费下载链接】openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development.项目地址: https://gitcode.com/GitHub_Trending/op/openzeppelin-contracts如果你的合约需要用户在单笔交易里完成多个操作例如连续多次 ERC-20transfer单独发多笔交易会留下中间状态而且后一笔失败时前面几笔已经确认。OpenZeppelin Contracts 的Multicall抽象合约把这个问题收敛为一个multicall(bytes[])入口外部账户只发一次交易合约按顺序执行所有子调用任何一步失败时整笔交易回滚之前的状态修改全部撤销。本文基于仓库中的 Multicall 实现、Utilities 文档 中的示例和 官方测试走通继承 → 编码参数 → 发起交易 → 验证结果这条路径。前提Multicall 是抽象合约必须被继承Multicall位于 contracts/utils/Multicall.sol是一个abstract contract文件头部的 pragma 为^0.8.20仓库 package.json 中当前库版本为 5.7.0。它不能单独部署你需要在自己的合约里继承它然后外部账户直接调用继承下来的multicall函数function multicall(bytes[] calldata data) public virtual returns (bytes[] memory results) { bytes memory context msg.sender _msgSender() ? new bytes(0) : msg.data[msg.data.length - _contextSuffixLength():]; results new bytes[](data.length); for (uint256 i 0; i data.length; i) { results[i] Address.functionDelegateCall(address(this), bytes.concat(data[i], context)); } return results; }实现要点data的每一项是一条编码好的函数调用数据multicall按数组顺序逐条执行每个子调用通过Address.functionDelegateCall(address(this), ...)对本合约自身执行因此子调用的状态修改直接作用于当前合约的存储并且整条调用链共享同一次交易一步失败全部回滚返回值results是bytes[]第i项是第i个子调用的返回数据供调用方自行解码。第一步写一个继承 Multicall 的合约Utilities 文档 的 Multicall 一节给出的示例合约对应仓库中的 contracts/mocks/docs/utilities/Multicall.sol如下// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Multicall} from ../../../utils/Multicall.sol; contract Box is Multicall { function foo() public { // ... } function bar() public { // ... } }foo/bar是文档里的占位函数。在你的项目中把import路径换成contracts/utils/Multicall.sol在你工程里的实际位置foo/bar换成你真正要打包执行的函数即可。合约里不需要为multicall写任何额外代码——继承即生效且该函数是virtual的必要时可以重写。仓库里的 ERC20MulticallMock 展示了最典型的一种组合一个既继承ERC20又继承Multicall的合约abstract contract ERC20MulticallMock is ERC20, Multicall {}用于测试对transfer等 ERC-20 函数的批量调用。第二步用 Ethers.js 把多个调用编码进一笔交易文档给出的调用方式Ethers.js是// scripts/foobar.js const instance await ethers.deployContract(Box); await instance.multicall([ instance.interface.encodeFunctionData(foo), instance.interface.encodeFunctionData(bar) ]);instance.interface.encodeFunctionData(foo)生成foo的编码调用数据带参数时写成encodeFunctionData(transfer, [recipient, amount])。数组里有几条编码数据这一笔交易就会执行几条子调用。第三步验证执行结果与原子性验证分两层仓库自己的测试 test/utils/Multicall.test.js 就是按这两层断言的。检查每个子调用的返回值。multicall返回的results[i]是第i个子调用的原始返回数据需要按该函数的返回类型解码。仓库的 MulticallHelper 展示了这种检查方式对transfer返回bool批量执行后逐条解码并要求为真bytes[] memory results multicallToken.multicall(calls); for (uint256 i 0; i results.length; i) { require(abi.decode(results[i], (bool))); }检查事件、余额与回滚行为。测试batches function calls中把两笔transfer分别转amount / 2和amount / 3放进同一个multicall断言会按顺序发出两条Transfer事件且两个收款地址的balanceOf分别等于对应金额——这对应文档示例中foo/bar被同一次交易执行的预期。测试reverts previous calls则验证原子性第一个子调用转账amount、第二个子调用再转amount余额不够整笔交易以自定义错误ERC20InsufficientBalance回滚而第一个子调用本应增加的余额保持为 0——即后面的调用失败时前面的调用一并撤销。回滚原因会原样冒泡到最外层交易方便你在ethers报错中直接看到是哪个子调用失败测试bubbles up revert reasons用的正是同一场景。使用边界两点限制来自 Multicall.sol 的源码注释使用这个合约时应当知道调用方对 calldata 的校验假设可能失效。注释明确提醒如果发送方假设交易 calldata 会被某种方式校验例如按函数选择器过滤的 relay 地址它不会过滤multicall内部的嵌套调用。也就是说子调用不受外层选择器白名单的约束。与ERC2771Context的兼容性。自 5.0.1 / 4.9.4 起合约会识别非规范上下文msg.sender与Context._msgSender()不一致的情形例如经 meta-transaction 中继时并在自delegatecall时把msg.data末尾的上下文后缀追加到子调用数据中使其可安全地与ERC2771Context搭配使用不影响_msgSender解析的上下文则不会传递给子调用。如果后续还要处理子调用返回的复杂数据解码逻辑放在合约外如MulticallHelper所示或合约内均可关键是逐项解码results后再使用而不是假设整个批量一定全部成功——批量整体要么成功、要么整体回滚。【免费下载链接】openzeppelin-contractsOpenZeppelin Contracts is a library for secure smart contract development.项目地址: https://gitcode.com/GitHub_Trending/op/openzeppelin-contracts创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表