Solidity中的delegatecall

本文全面概述了Solidity中的“delegatecall”函数,详细介绍了它在一个智能合约的上下文中执行另一个智能合约中的代码的重要性。

在 Solidity 中,delegatecall 是一个低级函数,允许一个合约调用另一个合约,并在调用合约的上下文中运行其代码。它通常用于代理合约和可升级智能合约的上下文。当你没有目标智能合约的 ABI 以使用函数签名时,DelegateCall 可以派上用场。

还有其他类型的调用也被用于合约之间的交互,比如 StaticCall、CallCode 或 Call。它们与 DelegateCall 有什么不同?

Call: 通过合约直接调用一个函数,这个函数不会设置调用者的值,而是设置被调用者的值。在这种情况下,发送者即为调用者。

CallCode: 通过 CallCode 调用时,调用者调用被调用者的函数,并发送其自己的值(或通过调用参数更改自己的值),但被调用者的存储没有反映这些变化。在这里,发送者也是调用者。已弃用,并且在 Solidity 0.8 之后不能直接使用。

StaticCall: 这类似于 call,但不允许被调用的合约修改状态或存储。它主要用于从其他合约读取数据,而不对状态进行任何更改。

DelegateCall: 当第三个合约代表调用者调用被调用者的某个函数进行代理调用时,存储的更改将在调用者的值中进行,而不在被调用者的存储中反映出来。

DelegateCall 是如何工作的?

1. 合约 A 向合约 B 发起代理调用,指定要在合约 B 中执行的函数。

2. 在代理调用期间,合约 B 的代码像合约 A 的一部分一样运行。这意味着合约 B 可以访问和修改合约 A 的存储。

3. 代理调用完成后,合约 A 的存储将更新合约 B 进行的任何更改。

注意:在你的合约 B 中应定义相同的状态变量,并且顺序也应相同。

注意:当使用代理调用执行一个函数时,这些值不会发生变化。

\* address(this)

\* msg.sender

\* msg.value

注意:如果合约 A 利用代理调用调用合约 B 的一个函数,下面的两个语句是真实的:

1. 合约 A 的状态变量可以被读取和写入。

2. 合约 B 的状态变量永远不会被读取或写入。

在 DelegateCall 中“上下文”到底指什么?

在 Solidity 的 delegatecall 上下文中,“上下文”指的是在执行过程中传递到被调用合约的调用合约的状态和存储。当一个合约执行对另一个合约的 delegatecall 时,被调用的合约可以访问调用合约的状态变量和存储,就好像它在调用合约的上下文中执行一样。

这意味着,在 delegatecall 期间,被调用合约对状态变量和存储所做的任何更改将直接影响调用合约的状态。被调用合约可以读取、写入和修改调用合约的存储。

然而,重要的是要注意,被调用合约自身的代码执行和行为保持不变。只有被调用合约访问的存储和状态会受到调用合约上下文的影响。

如何进行简单的 DelegateCall?

下面是一个简单的例子,从合约 B 进行到合约 A 的代理调用。这里更新的是合约 B 的状态变量,而不是合约 A 的。

// 合约 A
pragma solidity ^0.8.0;

contract ContractA {
    address public implementation;
    uint256 public data;

    function setData(uint256 _data) public {
        data = _data;
    }

    function getData() view public returns(uint) {
        return data;
    }
}
// 合约 B
pragma solidity ^0.8.0;

contract ContractB {
    address public contractAAddress;
    uint256 public data;

    function setContractAAddress(address _address) public {
        contractAAddress = _address;
    }

    function delegateCallToContractA(uint256 _data) public {
        // 对合约 A 的 setData 函数执行代理调用
        (bool success, ) = contractAAddress.delegatecall(abi.encodeWithSignature("setData(uint256)", _data));
        require(success, "代理调用失败");
    }
}

请参考下面的视频链接以跟随步骤:https://github.com/bansaltushar014/web3Conf/assets/51528049/0049831c-58f0-4f6b-bc7f-3f71f12c4f4a

在代理中使用 DelegateCall?

// 合约 A
pragma solidity ^0.8.0;

contract ContractA {
    address public implementation;
    uint256 public data;

    function setData(uint256 _data) public {
        data = _data;
    }

    function getData() view public returns(uint) {
        return data;
    }
}
// Proxy.sol
pragma solidity ^0.8.0;

contract Proxy {
    address public implementation;
    uint256 public data;

    constructor(address _implementation) {
        implementation = _implementation;
    }

    function _delegate() private {
        (bool ok, ) = implementation.delegatecall(msg.data);
        require(ok, "代理调用失败");
    }

    fallback() external payable {
        _delegate();
    }

    receive() external payable {
        _delegate();
    }

    function setImplementation(address _implementation) external {
        implementation = _implementation;
    }
}
// 合约 A 升级
pragma solidity ^0.8.0;

contract ContractAUpgrade {
    address public contractAAddress;
    uint256 public data;

    function setData(uint256 _data) public {
        data = _data;
    }

    function getData() view public returns(uint) {
        return data;
    }

    function increment() public {
        data = data + 1;
    }
}

步骤:

  1. 部署合约 A。
  2. 部署代理智能合约,并在 setImplementation 函数中传入合约 A 的地址。
  3. 复制已部署代理智能合约的地址,并传递到 ContractA 的 AtAddress 中。
  4. 调用 setData 函数(注意代理合约 A 的数据是如何被更改的,而原合约 A 的数据没有被更改,同时 getData 函数返回错误)。
  5. 现在部署合约 A 升级智能合约。
  6. 复制合约 A 升级的地址,并设置到已部署代理智能合约的 setimplementation 函数中。
  7. 现在复制代理智能合约的地址,并传递到 ContractAUpgrade 的 AtAddress 中。

请参考下面的视频链接以跟随步骤:https://github.com/bansaltushar014/web3Conf/assets/51528049/0049831c-58f0-4f6b-bc7f-3f71f12c4f4a

使用 assembly 进行代理中的 DelegateCall?

以上,我们能够调用函数,但无法接收值。让我们修改我们的逻辑并使用 assembly 进行操作。执行步骤与上面相同。

// 合约 A
pragma solidity ^0.8.0;

contract ContractA {
    address public implementation;
    uint256 public data;

    function setData(uint256 _data) public {
        data = _data;
    }

    function getData() view public returns(uint) {
        return data;
    }
}
// 合约 A 升级
pragma solidity ^0.8.0;

contract ContractAUpgrade {
    address public contractAAddress;
    uint256 public data;

    function setData(uint256 _data) public {
        data = _data;
    }

    function getData() view public returns(uint) {
        return data;
    }

    function increment() public {
        data = data + 1;
    }
}
// Proxy.sol
pragma solidity ^0.8.0;

contract Proxy {
    address public implementation;

    constructor(address _implementation) {
        implementation = _implementation;
    }

    fallback() external {
        address target = implementation;

        // 执行代理调用到实现合约
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), target, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 {
                revert(0, returndatasize())
            }
            default {
                return(0, returndatasize())
            }
        }
    }

    function setImplementation(address _implementation) external {
        implementation = _implementation;
    }
}

请参考下面的视频链接以跟随步骤:https://github.com/bansaltushar014/Chapter-Solidity/assets/51528049/0f66e524-4f99-4e5c-bc7e-24413c61a505

我希望你对 Solidity 的 DelegateCall 有了一个清晰的理解。请参考 GitHub 链接获取代码 https://github.com/bansaltushar014/Chapter-Solidity/tree/master/DelegateCall

参考资料:

  • 原文链接: medium.com/@bansaltushar...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,在这里修改,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
bansaltushar014
bansaltushar014
江湖只有他的大名,没有他的介绍。