变形合约

本文针对初学者详细介绍了可变合约(metamorphic contracts)的概念和实现,重点讨论如何使用create和create2操作码在同一地址上重新部署合约。文章包含具体代码示例、交易哈希、观察结果以及对合约字节码的深入分析,适合希望了解智能合约更深层次的开发者。

分析变形合约的初学者指南

上一篇文章:https://medium.com/coinmonks/dark-side-of-create2-opcode-6b6838a42d71

上一篇文章概述:我们已经部署并销毁了两个合约,并使用 create 和 create2 操作码在相同地址上重新部署它们。

在继续之前,我想回答与上一篇文章相关的一个常见问题。

上一篇文章是想表达什么(非以太坊开发者)?

智能合约如果以这样的方式编写,则完全是可变的。

好了,现在让我们继续。首先,让我们开始将全新代码部署到目标地址。

按上一篇文章的步骤进行操作,直到第 4 步,然后重新部署 CreatorContract,代替目标合约的代码,可以部署任何代码,它仍会在同一地址上部署。

以下是其代码。

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.12;

contract Target {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function approve (address _spender, uint _value) public returns (string memory) {
        return "Thug Life BGM playing...";
    }

    function destroy() public {
        selfdestruct(payable(msg.sender));
    }
}

交易哈希:

目标合约自销毁 - 0x3cb50d84b48c2ec1b6d0c58a925fe6fb56d515aedbcd843d5e0bb9e562378a20

目标合约部署 - 0xb311cd648cdb08313e90abeefa27b54f68128543f5b484eb515d84ff974a5388

观察:

  1. 合约 nonce 仅在创建新合约时通过 create 或 create2 操作码增加。
  2. 新合约使用 nonce 1 初始化,而旧合约使用 nonce 0 初始化。(更多背景请阅读 → EIP-161
  3. 任何有自销毁的方法或通过委托调用与其他合约交互的合约都是不安全的。
  4. 当 Create2 构造函数参数更改时,地址会更改。

5. 为什么我们不能用这个替代代理?→ 答案是合约中存储的上一个状态被销毁,这不是我们所需要的。

变形合约:

  • 我们上面看到的是如何使用 create 操作码在同一地址重新部署不同代码的合约。
  • 现在让我们来看如何使用 create2 操作码在同一地址重新部署不同代码的合约。
  • 变形合约的要点 → 这个想法是部署一个智能合约,每当部署时,它用不同的字节码替换自己的字节码。所以,你通过 CREATE2 运行的字节码始终是相同的,并且在部署期间调用回工厂并替换自己。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.1;

contract Factory {
    mapping (address => address) _implementations;

    event Deployed(address _addr);

    function deploy(uint salt, bytes calldata bytecode) public {
        bytes memory implInitCode = bytecode; // 为变形合约分配初始化代码。

        bytes memory metamorphicCode  = (
          hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3"
        ); // 确定变形合约的地址。

        address metamorphicContractAddress = _getMetamorphicContractAddress(salt, metamorphicCode); // 声明实现合约的地址变量。
        address implementationContract; // 加载实现初始化代码和长度,然后通过 CREATE 部署。

        /* solhint-disable no-inline-assembly */
        assembly {
          let encoded_data := add(0x20, implInitCode) // 加载初始化代码。
          let encoded_size := mload(implInitCode)     // 加载初始化代码的长度。
          implementationContract := create(       // 使用 3 个参数调用 CREATE。
            0,                                    // 不转发任何资金。
            encoded_data,                         // 传入初始化代码。
            encoded_size                          // 传入初始化代码的长度。
          )
        } /* solhint-enable no-inline-assembly */

        // 首先,我们在单独的地址上部署我们要部署的代码
        // 存储要通过变形合约检索的实现。
        _implementations[metamorphicContractAddress] = implementationContract;
        address addr;

        assembly {
            let encoded_data := add(0x20, metamorphicCode) // 加载初始化代码。
            let encoded_size := mload(metamorphicCode)     // 加载初始化代码的长度。
            addr := create2(0, encoded_data, encoded_size, salt)
        }

        require(
          addr == metamorphicContractAddress,
          "Failed to deploy the new metamorphic contract."
        );

        emit Deployed(addr);
    }

    /**
    * @dev 计算给定特定盐值的变形合约地址的内部视图函数。
    */
    function _getMetamorphicContractAddress(
        uint256 salt,
        bytes memory metamorphicCode
    ) internal view returns (address) { // 确定变形合约的地址。
        return address(
          uint160(                      // 下降到匹配地址类型。
            uint256(                    // 转换为 uint 以截断高位数字。
              keccak256(                // 通过 4 个输入计算 CREATE2 哈希。
                abi.encodePacked(       // 将所有输入打包在一起以便计算哈希。
                  hex"ff",              // 以 0xff 开始以区分 RLP。
                  address(this),        // 此合约将是调用者。
                  salt,                 // 传入提供的盐值。
                  keccak256(
                      abi.encodePacked(
                        metamorphicCode
                      )
                    )     // 初始化代码哈希。
                )
              )
            )
          )
        );
    }

    // 这两个函数由变形合约调用
    function getImplementation() external view returns (address implementation) {
        return _implementations[msg.sender];
    }
}

contract Test1 {
    uint public myUint;

    function setUint(uint _myUint) public {
        myUint = _myUint;
    }

    function killme() public {
        selfdestruct(payable(msg.sender));
    }
}

contract Test2 {
    uint public myUint;

    function setUint(uint _myUint) public {
        myUint = 2 * _myUint;
    }

    function killme() public {
        selfdestruct(payable(msg.sender));
    }
}
  1. 部署 Factory
  2. 使用 salt=1 的 Test1 字节码部署 Test1。
  3. 告诉 Remix Test1 在变形合约的地址上运行
  4. 将 “myUint” 设置为你想要的任何值,它都能工作
  5. 销毁 Test1
  6. 使用相同的 salt=1 部署 Test2 字节码
  7. 它将以相同的地址部署不同的字节码!!
  8. 适应 setUint 现在翻倍输入值。

观察:

  1. 有两种类型的合约字节码(即)
  • CreationCode
  • RuntimeCode

2. 创建代码 →

  • 包含合约创建字节码的内存字节数组。
  • 可以在线内汇编中用于构建自定义创建例程,特别是通过使用 create2 操作码。
  • 此属性不能在合约本身或任何派生合约中访问。
  • 它导致字节码包含在调用位置的字节码中,因此不能有这样的循环引用。

3. 运行时代码 →

  • 包含合约运行时字节码的内存字节数组。
  • 这通常是 C 的构造函数所部署的代码。
  • 如果 C 有一个使用内联汇编的构造函数,这可能与实际部署的字节码不同。
  • 还需要注意,库在部署时会修改其运行时字节码以防止常规调用。
  • .creationCode 的相同限制也适用于此属性。

由于上述原因,我目前无法在 polyscan 上验证 create2 变形合约。在下一篇文章中,我将分析

“5860208158601c335a63aaf10f428752fa158151803b80938091923cf3” 这段字节码在 EVM Playground 上以获得更好的背景。

要深入理解,请访问 https://github.com/0age/metamorphic

变形合约工作详细分析在这个仓库中,我将发布下一篇文章,其中将分析这个仓库。(注意:它是一个有良好注释的仓库)

有关此主题的良好文章:

  1. https://medium.com/@jason.carver/defend-against-wild-magic-in-the-next-ethereum-upgrade-b008247839d2.
  2. https://learnblockchain.cn/article/11059.
  • 原文链接: medium.com/coinmonks/met...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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