Skip to content

合约交互

在 Solidity 中,call、delegatecall、staticcall 和 multicall 是用于合约间交互的四种重要机制。

  • call 和 delegatecall 是用于执行外部合约调用的低级函数
  • staticcall 是一种只读调用,用于调用另一个合约的只读函数
  • Multicall 是一种实现,可以一次性执行多个合约调用,减少交易成本和提高效率。

call

call 是一种低级函数调用方式,用于调用另一个合约的函数。它是非常灵活的,但也更容易出错。

特点

  • 可以调用目标合约的任何函数,包括只读函数
  • 允许发送以太币(msg.value)
  • 返回布尔值,表示是否调用成功

用法

solidity
(bool success, bytes memory data) = targetContract.call(abi.encodeWithSignature("functionName()"));

注意:

  • 如果目标合约不存在或函数签名错误,call 不会抛出异常,而是返回 false。
  • 使用 call 时需要特别小心,避免重入攻击。

delegatecall

delegatecall 是一种特殊的 call 调用方式,用于调用目标合约的函数,使用当前合约的上下文(包括存储和余额),但不创建新的合约实例。

特点

  • 目标合约的代码在调用合约的上下文中执行。
  • 调用合约的存储、余额(msg.value)和 msg.sender 不会改变。
  • 常用于代理合约模式(Proxy Pattern)。
  • 支持合约升级。

用法

solidity
(bool success, bytes memory data) = targetContract.delegatecall(abi.encodeWithSignature("functionName()"));

staticcall

staticcall 是一种只读调用方式,用于调用另一个合约的函数,与 call 类似,但不会修改目标合约的状态。 用于防止调用中对状态进行修改,例如读取数据。

特点

  • 只能调用目标合约的只读函数,view 或 pure 函数
  • 不允许发送以太币(msg.value)
  • 返回布尔值,表示调用是否成功。

用法

solidity
(bool success, bytes memory data) = targetContract.staticcall(abi.encodeWithSignature("functionName()"));

Multicall

Multicall 是一种实现,可以一次性执行多个合约调用,减少交易成本和提高效率。 它通常用于聚合多个合约的只读函数调用,返回多个结果。

特点

  • 可以一次性执行多个合约调用
  • 减少交易成本

用法

  • TargetContract
solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract TargetContract {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}
  • TargetContractOne
solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

contract TargetContractOne {
    uint256 public number;

    function setNumber(uint256 newNumber) public {
        number = newNumber;
    }

    function increment() public {
        number++;
    }
}
  • MultiCall and MultiCaller
solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { console} from "forge-std/console.sol";


contract MultiCall {
    struct Call {
        address targetAddress;
        bytes  callData;
    }

    constructor() {
    }

    function multicall(Call[] memory calls) public {
        for(uint256 i = 0; i < calls.length; i++) {
            (bool success, ) = calls[i].targetAddress.call(calls[i].callData);
            require(success, "call item failed");
        }
    }
}


contract MultiCaller {
    MultiCall public multiCall;

    constructor(address _multiCall){
        multiCall = MultiCall(_multiCall);
    }

    function setNumbers(address target, uint256 number, address targetOne, uint256 numberOne) public {
        console.log("target==", target);
        console.log("data==", number);
        console.log("targetOne==", targetOne);
        console.log("dataOne==", numberOne);

        MultiCall.Call[] memory calls = new MultiCall.Call[](2);

        calls[0] = MultiCall.Call({
            targetAddress: target,
            callData: abi.encodeWithSignature("setNumber(uint256)", number)
        });

        calls[1] = MultiCall.Call({
            targetAddress: targetOne,
            callData: abi.encodeWithSignature("setNumber(uint256)", numberOne)
        });
        multiCall.multicall(calls);
    }
}
  • MultiCallerTest
solidity
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test, console} from "forge-std/Test.sol";
import { TargetContract } from "../src/TargetContract.sol";
import { TargetContractOne } from "../src/TargetContractOne.sol";

import {MultiCaller, MultiCall } from "../src/MultiCaller.sol";


contract MultiCallerTest is Test {
    TargetContract public targetContract;
    TargetContractOne public targetContractOne;
    MultiCall public multiCall;
    MultiCaller public multiCaller;

    function setUp() public {
        multiCall = new MultiCall();
        targetContract = new TargetContract();
        targetContractOne = new TargetContractOne();
        multiCaller = new MultiCaller(address(multiCall));
    }

    function test_SetNumbers() public {
        multiCaller.setNumbers(address(targetContract), 50, address(targetContractOne), 60);
        console.log("=============================");
        console.log("targetContract.number()=", targetContract.number());
        console.log("targetContractOne.number()=", targetContractOne.number());
        console.log("==============================");
    }
}

注意:

  • 如果某个调用失败,可能会导致整个交易回滚(取决于实现)。
  • 需要小心处理重入攻击和 gas 限制问题。
调用方式是否修改状态是否使用调用合约存储是否允许发送以太币
call
delegatecall
staticcall
multicall取决于实现取决于实现取决于实现