快速实现一个多重调用合约

339 阅读3分钟

前言

多重调用合约设计在于一次交易中执行多个函数调用,这样可以显著降低交易费用并提高效率。

多重调用

特点

  • 降低gas费:多个交易合并成一次交易中的多个调用,从而节省gas;
  • 提高效率:在一次交易中对不同合约的不同函数进行调用,同时这些调用还可以使用不同的参数;

合约开发

测试合约(一个ERC20代币合约)

说明:一个名字MockToken 符号为 "MTK" 余额为1000000 MTK标准的代币,具备铸造等功能

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken1 is ERC20 {
    constructor() ERC20("Mock Token", "MTK") {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }
    function mint(address to, uint amount) external {
        _mint(to, amount);
    }
}
# 编译指令
# npx hardhat compile
多重调用合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "hardhat/console.sol";
contract Multicall {
    // Call结构体,包含目标合约target,是否允许调用失败allowFailure,和call data
    struct Call {
        address target;
        bool allowFailure;
        bytes callData;
    }

    // Result结构体,包含调用是否成功和return data
    struct Result {
        bool success;
        bytes returnData;
    }

    /// @notice 将多个调用(支持不同合约/不同方法/不同参数)合并到一次调用
    /// @param calls Call结构体组成的数组
    /// @return returnData Result结构体组成的数组
    function multicall(Call[] calldata calls) public returns (Result[] memory returnData) {
        uint256 length = calls.length;
        returnData = new Result[](length);
        Call calldata calli;
        
        // 在循环中依次调用
        for (uint256 i = 0; i < length; i++) {
            Result memory result = returnData[i];
            calli = calls[i];
            (result.success, result.returnData) = calli.target.call(calli.callData);
            // 如果 calli.allowFailure 和 result.success 均为 false,则 revert
            if (!(calli.allowFailure || result.success)){
                revert("Multicall: call failed");
            }
        }
    }
    /**
     * @dev 批量调用合约(只读)
     * @param calls 调用数组
     * @return blockNumber 区块号
     * @return returnData 返回结果数组
     */
    function multiStaticCall(Call[] calldata calls) 
        public 
        view 
        returns(
            uint256 blockNumber,
            Result[] memory returnData
        ) 
    {
        returnData = new Result[](calls.length);
        
        for(uint256 i = 0; i < calls.length; i++) {
            (bool success, bytes memory result) = calls[i].target.staticcall(calls[i].callData);
            
            if (!success && !calls[i].allowFailure) {
                revert("MultiCall: static call failed");
            }
            
            returnData[i] = Result(success, result);
        }
        
        return (block.number, returnData);
    }
}
# 编译指令
# npx hardhat compile

合约测试

const {ethers,getNamedAccounts,deployments} = require("hardhat");
const { assert,expect } = require("chai");
describe("MultiCall",function(){
    let Multicall;//合约地址
    let token;//合约地址
    let firstAccount//第一个账户
    let secondAccount//第二个账户
    let addr1,addr2,addr3,addr4,addr5,addr6,addr7,addr8,addr9,addr10;//10个账户
    beforeEach(async function(){
        await deployments.fixture(["token1","MultiCall"]);
        [addr1,addr2,addr3,addr4,addr5,addr6,addr7,addr8,addr9,addr10]=await ethers.getSigners();
        firstAccount=(await getNamedAccounts()).firstAccount;
        secondAccount=(await getNamedAccounts()).secondAccount;
        const tokenDeployment = await deployments.get("MyToken1");
        token = await ethers.getContractAt("MyToken1",tokenDeployment.address);//已经部署的合约交互
        const MulticallDeployment = await deployments.get("Multicall");
        Multicall = await ethers.getContractAt("Multicall",MulticallDeployment.address);//已经部署的合约交互
    })
    describe("MultiCall合约",function(){
        it("多重调用",async function(){
            const calls=[
                {
                    target:token.address,//合约地址
                    allowFailure:true,//失败是否继续
                    callData:token.interface.encodeFunctionData("mint",[firstAccount,ethers.utils.parseEther("100")])//调用铸造的方法
                },
                {
                    target:token.address,
                    allowFailure:true,
                    callData:token.interface.encodeFunctionData("mint",[secondAccount,ethers.utils.parseEther("200")])
                },
                {
                    target:token.address,
                    allowFailure:true,
                    callData:token.interface.encodeFunctionData("mint",[addr3.address,ethers.utils.parseEther("300")])
                }
            ]
            //动态多重调用
            await Multicall.multicall(calls)
            let FirstAccount=await token.balanceOf(firstAccount)
            let SecondAccount=await token.balanceOf(secondAccount)
            let ThirdAccount=await token.balanceOf(addr3.address)
            
            console.log("账号1余额",`${ethers.utils.formatEther(FirstAccount.toString())} MTK`)
            console.log("账号2的余额",`${ethers.utils.formatEther(SecondAccount.toString())} MTK`)
            console.log("账号3的余额",`${ethers.utils.formatEther(ThirdAccount.toString())} MTK`)

           let multiStaticCalls=[
                {
                    target:token.address,
                    allowFailure:true,
                    callData:token.interface.encodeFunctionData("balanceOf",[firstAccount])
                },
                // {
                //     target:token.address,
                //     allowFailure:true,
                //     callData:token.interface.encodeFunctionData("balanceOf",[secondAccount])
                // },
                // {
                //     target:token.address,
                //     allowFailure:true,
                //     callData:token.interface.encodeFunctionData("balanceOf",[addr3.address])
                // }
            ]
            //静态调用只读
            let multiStaticCallArr=await Multicall.multiStaticCall(multiStaticCalls)
            console.log(multiStaticCallArr.returnData)
        })
    })
})
# 测试指令
# npx hardhat test ./test/xxx.js

合约部署

多签合约部署
module.exports = async function ({getNamedAccounts,deployments}) {
    const firstAccount = (await getNamedAccounts()).firstAccount;
    const { deploy, log } = deployments;
    const MultiCall = await deploy("Multicall", {
        contract: "Multicall",
        from: firstAccount,
        args: [],//参数 owner
        log: true,
        // waitConfirmations: 1,
    })
console.log("MultiCall合约地址",MultiCall.address)
}
module.exports.tags = ["all", "MultiCall"]
# 部署指令
# npx hardhat deploy
# 代币部署同上修改各个关键参数即可

总结

以上就是多重调用合约从开发、测试、部署的全部流程,此合约的设计目的节省gas费。