在Solidity中,calldelegatecall是两种用于在合约之间进行交互的低级函数。它们都可以调用合约的函数,但是它们的工作方式和用途有所不同。

call

call是一种低级函数,它可以调用合约的任何函数,包括非公开的函数。call函数接收一个函数签名和一些参数,然后在目标合约上执行该函数。call函数会创建一个新的执行环境,这意味着调用的函数有自己的thismsg.sender

delegatecall

delegatecallcall类似,也可以调用合约的任何函数。然而,delegatecall不会创建一个新的执行环境,而是在调用合约的上下文中执行函数。这意味着,被调用的函数可以访问和修改调用合约的存储。

delegatecall在升级合约和实现库函数等场景中非常有用,因为它允许一个合约借用另一个合约的代码,同时保持自己的状态。

对比

calldelegatecall函数在Solidity中都用于调用合约的函数,但它们在执行方式和用途上有一些关键的区别:

  1. 执行上下文:当使用call函数时,被调用的函数在被调用合约的上下文中执行,这意味着它有自己的thismsg.sender。而delegatecall函数则在调用合约的上下文中执行被调用的函数,这意味着thismsg.sender在被调用函数中与调用合约相同。
  2. 状态存储call函数在执行时不会改变调用合约的状态,它只会改变被调用合约的状态。而delegatecall函数则可以改变调用合约的状态,因为它在调用合约的上下文中执行。
  3. 用途call函数通常用于调用其他合约的函数,而delegatecall函数则常用于实现合约的升级和库函数。delegatecall允许一个合约借用另一个合约的代码,同时保持自己的状态,这在升级合约和实现库函数等场景中非常有用。

使用场景

在Solidity中,calldelegatecall函数都用于在合约之间进行交互,但它们的使用场景有所不同。

使用call的情况

call函数通常用于调用另一个合约的函数。当你需要在一个合约中调用另一个合约的函数,并且不需要改变当前合约的状态时,可以使用call函数。例如,你可能需要查询另一个合约的状态,或者调用其某个函数来执行某些操作。

使用delegatecall的情况

delegatecall函数通常用于实现合约的升级和库函数。当你需要在一个合约中调用另一个合约的函数,并且需要改变当前合约的状态时,可以使用delegatecall函数。delegatecall允许一个合约在其自身的上下文中执行另一个合约的代码,这意味着被调用的函数可以访问和修改调用合约的存储。

需要注意的是,calldelegatecall都是低级函数,使用它们需要非常小心,因为它们可能会导致意想不到的副作用。例如,如果被调用的函数抛出异常,那么整个交易都会被回滚。此外,如果你不正确地使用delegatecall,你可能会无意中修改合约的状态。因此,除非你完全理解它们的工作原理,否则最好避免直接使用它们。

不适用场景

在Solidity中,calldelegatecall都是低级函数,虽然它们在某些情况下非常有用,但在以下情况下应该避免直接使用它们:

  1. 安全性问题calldelegatecall都可能导致重入攻击。如果在调用外部合约后还有未完成的工作(例如,更新状态变量),并且未采取适当的防护措施,那么被调用的合约可能会再次调用原始合约,从而导致重入攻击。
  2. 异常处理calldelegatecall不会自动处理被调用函数抛出的异常。如果被调用的函数抛出异常,那么整个交易都会被回滚,除非你在调用calldelegatecall时进行了检查。
  3. 状态修改delegatecall可以修改调用合约的状态,这在某些情况下可能是有用的,但如果不小心使用,可能会导致意外的结果。例如,如果你不小心调用了一个会修改你不希望修改的状态变量的函数,那么可能会导致合约的状态被破坏。
  4. 函数可见性calldelegatecall可以调用任何函数,包括非公开的函数。这可能会导致意外的行为,如果你不小心调用了一个你不应该调用的函数,可能会导致问题。

call调用示例

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

// 接收者合约
contract Receiver {
    // 定义一个事件,当接收到资金时触发
    event Received(address caller, uint256 amount, string message);

    // 接收函数,用于接收ETH
    receive() external payable {}

    // fallback函数,当调用不存在的函数时触发
    fallback() external payable {
        emit Received(msg.sender, msg.value, "Fallback was called");
    }

    // foo函数,接收一个消息和一个数值,返回数值加一
    function foo(string memory _message, uint256 _x)
        public
        payable
        returns (uint256)
    {
        emit Received(msg.sender, msg.value, _message);

        return _x + 1;
    }
}

// 调用者合约
contract Caller {
    // 定义一个事件,当接收到响应时触发
    event Response(bool success, bytes data);

    // 假设调用者合约不知道接收者合约的源代码,
    // 但我们知道接收者合约的地址和要调用的函数。
    function testCallFoo(address payable _addr) public payable {
        // 你可以发送以太币并指定自定义的gas数量
        (bool success, bytes memory data) = _addr.call{
            value: msg.value,
            gas: 5000
        }(abi.encodeWithSignature("foo(string,uint256)", "call foo", 123));

        emit Response(success, data);
    }

    // 调用一个不存在的函数会触发fallback函数。
    function testCallDoesNotExist(address payable _addr) public payable {
        (bool success, bytes memory data) = _addr.call{value: msg.value}(
            abi.encodeWithSignature("doesNotExist()")
        );

        emit Response(success, data);
    }
}

孟斯特

声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。

Author: mengbin

blog: mengbin

Github: mengbin92

cnblogs: 恋水无意

腾讯云开发者社区:孟斯特