Alert Source Discuss
Standards Track: ERC

ERC-2470: 单例工厂

Authors Ricardo Guilherme Schmidt (@3esmit)
Created 2020-01-15
Discussion Link https://ethereum-magicians.org/t/erc-2470-singleton-factory/3933
Requires EIP-1014

简单总结

一些 DApp 需要一个且仅一个合约实例,该实例在任何链上都具有相同的地址。

一个无需许可的工厂,用于部署基于其字节码的无密钥确定性合约地址。

摘要

一些合约被设计为单例,无论它们在哪个链上都具有相同的地址,这意味着对于所有合约都应该存在一个实例,例如 EIP-1820EIP-2429。这些合约通常使用一种称为 Nick 方法的方法进行部署,因此任何人都可以将这些合约部署在任何链上,并且它们具有确定性的地址。 本标准建议使用此方法创建一个 CREATE2 工厂,以便其他需要此功能的项目可以在任何链上使用相同的设置(即使在开发链中)使用此工厂。

动机

代码重用,使用该工厂可以更轻松地部署单例。

规范

[ERC-2470] 单例工厂

这是 [ERC2470 工厂智能合约] 代码的精确副本。

pragma solidity 0.6.2;


/**
 * @title Singleton Factory (EIP-2470)
 * @notice Exposes CREATE2 (EIP-1014) to deploy bytecode on deterministic addresses based on initialization code and salt.
 * @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
 */
contract SingletonFactory {
    /**
     * @notice Deploys `_initCode` using `_salt` for defining the deterministic address.
     * @param _initCode Initialization code.
     * @param _salt Arbitrary value to modify resulting address.
     * @return createdContract Created contract address.
     */
    function deploy(bytes memory _initCode, bytes32 _salt)
        public
        returns (address payable createdContract)
    {
        assembly {
            createdContract := create2(0, add(_initCode, 0x20), mload(_initCode), _salt)
        }
    }
}
// IV is a value changed to generate the vanity address.
// IV: 6583047

部署交易

以下是必须用于在任何链上部署智能合约的原始交易。

0xf9016c8085174876e8008303c4d88080b90154608060405234801561001057600080fd5b50610134806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634af63f0214602d575b600080fd5b60cf60048036036040811015604157600080fd5b810190602081018135640100000000811115605b57600080fd5b820183602082011115606c57600080fd5b80359060200191846001830284011164010000000083111715608d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925060eb915050565b604080516001600160a01b039092168252519081900360200190f35b6000818351602085016000f5939250505056fea26469706673582212206b44f8a82cb6b156bfcc3dc6aadd6df4eefd204bc928a4397fd15dacf6d5320564736f6c634300060200331b83247000822470

交易末尾的 2470 字符串是签名的 rs。 从这种(由人生成的)确定性模式中,任何人都可以推断出没有人知道部署帐户的私钥。

部署方法

该合约将使用无密钥部署方法(也称为 Nick 的方法)进行部署,该方法依赖于一次性地址。 (有关更多详细信息,请参见 [Nick 的文章])。此方法的工作方式如下:

  1. 生成一个从新随机帐户部署合约的交易。
    • 此交易不得使用 EIP-155,以便在任何链上运行。
    • 此交易必须具有相对较高的 gas 价格才能部署在任何链上。在这种情况下,它将是 100 Gwei。
  2. 伪造一个具有以下参数的交易:
     {
         nonce: 0,
         gasPrice: 100000000000,
         value: 0,
         data: '0x608060405234801561001057600080fd5b50610134806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634af63f0214602d575b600080fd5b60cf60048036036040811015604157600080fd5b810190602081018135640100000000811115605b57600080fd5b820183602082011115606c57600080fd5b80359060200191846001830284011164010000000083111715608d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925060eb915050565b604080516001600160a01b039092168252519081900360200190f35b6000818351602085016000f5939250505056fea26469706673582212206b44f8a82cb6b156bfcc3dc6aadd6df4eefd204bc928a4397fd15dacf6d5320564736f6c63430006020033',
         gasLimit: 247000,
         v: 27,
         r: '0x247000',
         s: '0x2470'
     }
    

    2470 开头的 rs 值显然是人为确定的值,而不是真实的签名。

  3. 我们恢复此交易的发送者,即一次性部署帐户。

    因此,我们获得了一个可以广播该交易的帐户,但我们也可以保证没有人知道该帐户的私钥。

  4. 将 0.0247 以太坊精确地发送到此一次性部署帐户。

  5. 广播部署交易。

    注意:247000 是部署智能合约所需 gas 的两倍,这确保了 OPCODE 定价的未来变化不太可能导致此部署交易因 gas 不足而失败。大约 0.01 ETH 的剩余 gas 将永远锁定在一次性使用地址中。

生成的交易哈希为 0x803351deb6d745e91545a6a3e1c0ea3e9a6a02a1a4193b70edfcd2f40f71a01c

此操作可以在任何链上完成,从而保证合约地址始终相同,并且没有人可以使用具有不同合约的该地址。

单次使用工厂部署帐户

0xBb6e024b9cFFACB947A71991E386681B1Cd1477D

此帐户是通过从其交易签名中进行逆向工程生成的。 这样,没有人知道私钥,但众所周知,它是部署交易的有效签名者。

要部署注册表,必须首先将 0.0247 以太坊发送到此帐户。

工厂合约地址

0xce0042B868300000d44A59004Da54A005ffdcf9f

对于部署的每个链,合约都具有上述地址。

SingletonFactory 的 ABI:

[
    {
        "constant": false,
        "inputs": [
            {
                "internalType": "bytes",
                "name": "_initCode",
                "type": "bytes"
            },
            {
                "internalType": "bytes32",
                "name": "_salt",
                "type": "bytes32"
            }
        ],
        "name": "deploy",
        "outputs": [
            {
                "internalType": "address payable",
                "name": "createdContract",
                "type": "address"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    }
]

理由

SingletonFactory 不允许在 create2 上发送 value,这样做是为了防止创建的对象出现不同的结果。 SingletonFactory 允许用户定义的 salt,以方便为其他项目创建自定义地址。如果不需要自定义地址,则应使用 salt bytes(0)。 由 SingletonFactory 构造的合约不得在其构造函数中使用 msg.sender,所有变量都必须通过初始化数据传入。这是有意的,因为如果允许在创建后进行回调以帮助初始化状态,将导致具有相同地址(但不同的链)的合约具有相同的地址但不同的初始状态。 可以使用以下公式在链中通过任何合约计算生成的地址:address(keccak256(bytes1(0xff), 0xce0042B868300000d44A59004Da54A005ffdcf9f, _salt, keccak256(_code)) << 96) 或者在 javascript 中使用 https://github.com/ethereumjs/ethereumjs-util/blob/master/docs/README.md#const-generateaddress2。

向后兼容性

不适用,因为没有正在使用的 Singleton Factory 的过去版本。

测试用例

待定

实现

https://github.com/3esmit/ERC2470

安全注意事项

某些合约可能不支持部署在任何链上,或者需要每个链上的不同地址,这可以通过在构造函数中使用 EIP-1344 中的比较来安全地完成。 从每个用户的角度来看,帐户合约都是单例,当钱包想要指示预期的链 ID 时,应使用 EIP-1191。 在工厂部署的合约不得在构造函数中使用 msg.sender,而应使用构造函数参数,否则工厂最终将成为这些合约的控制器/唯一所有者。

版权

版权和相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Ricardo Guilherme Schmidt (@3esmit), "ERC-2470: 单例工厂," Ethereum Improvement Proposals, no. 2470, January 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2470.