Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-1504: 可升级智能合约

Authors Kaidong Wu <wukd94@pku.edu.cn>, Chuqiao Ren <cr025@bucknell.edu>, Ruthia He <rujiahe@gmail.com>, Yun Ma <mayun@pku.edu.cn>, Xuanzhe Liu <liuxuanzhe@pku.edu.cn>
Created 2018-10-17
Discussion Link https://github.com/ethereum/EIPs/issues/1503

简单总结

一个使智能合约可升级的标准接口/指南。

摘要

在过去的几年里,以太坊智能合约遭受了许多安全问题。修复智能合约中的此类错误的成本很高;例如,2016 年 6 月的 The DAO 攻击造成的巨大经济损失以及以太坊区块链的硬分叉。

以下标准使得可以升级智能合约中的标准 API。该标准提供了升级合约操作的基本功能,而无需数据迁移。为了确保去中心化/社区利益,它还包含一个投票机制来控制升级过程。

动机

智能合约在部署后是不可变的。如果发现任何安全风险或检测到程序错误,开发人员总是必须销毁旧合约,部署新合约,并可能将数据迁移(硬分叉)到新合约。在某些情况下,部署带有错误和潜在安全漏洞的智能合约可能会导致大量的经济损失。

我们提出了这个可升级合约来解决当前的情况。借助可升级合约,开发人员可以在之前部署后部署新版本的智能合约,并同时保留数据。

例如,在部署了符合 ERC20 标准的代币合约后,用户利用了源代码中的漏洞。如果没有可升级合约的支持,开发人员必须通过部署新的、安全的合约来解决此问题,否则攻击者将利用该安全漏洞,这可能会导致巨大的经济损失。一个挑战是如何将数据从旧合约迁移到新合约。有了下面的可升级合约,这将变得相对容易,因为开发人员只需升级 Handler 合约即可修复错误,而 Data 合约将保持不变。

规范

可升级合约由三个部分组成:

  • Handler 合约(实现 Handler 接口)定义操作并提供服务。此合约可以升级;
  • Data 合约保存资源(数据)并由 Handler 合约控制;
  • Upgrader 合约(可选)处理投票机制并升级 Handler 合约。投票者由合约所有者预先定义。

以下代码是 ERC-1504 Upgradable Smart Contract 的精确副本。

Handler 合约和 Handler 接口

Handler 合约的功能随需求而变化,因此开发人员最好为 Handler 合约设计接口以限制它们,并确保始终支持外部应用程序。

以下是 Handler 接口的规范。在 Handler 接口中,我们定义了以下操作:

  • 初始化 Data 合约;
  • 注册 Upgrader 合约地址;
  • 在升级完成后销毁 Handler 合约;
  • 验证当前的 Handler 是否是正在工作的那个 → 它应该总是返回 true。

开发人员还必须定义其业务相关的功能。

/// Handler interface.
/// Handler defines business related functions.
/// Use the interface to ensure that your external services are always supported.
/// Because of function live(), we design IHandler as an abstract contract rather than a true interface.
// Handler 接口。
// Handler 定义了与业务相关的功能。
// 使用该接口以确保始终支持您的外部服务。
// 由于函数 live() 的存在,我们将 IHandler 设计为抽象合约而不是真正的接口。
contract IHandler {

    /// Initialize the data contarct.
    /// @param  _str    value of exmStr of Data contract.
    /// @param  _int    value of exmInt of Data contract.
    /// @param  _array  value of exmArray of Data contract.
    // 初始化数据合约。
    // @param  _str    Data 合约中 exmStr 的值。
    // @param  _int    Data 合约中 exmInt 的值。
    // @param  _array  Data 合约中 exmArray 的值。
    function initialize (string _str, uint256 _int, uint16 [] _array) public;

    /// Register Upgrader contract address.
    /// @param  _upgraderAddr   address of the Upgrader contract.
    // 注册 Upgrader 合约地址。
    // @param  _upgraderAddr   Upgrader 合约的地址。
    function registerUpgrader (address _upgraderAddr) external;

    /// Upgrader contract calls this to check if it is registered.
    /// @return if the Upgrader contract is registered.
    // Upgrader 合约调用此函数以检查它是否已注册。
    // @return 如果 Upgrader 合约已注册。
    function isUpgraderRegistered () external view returns(bool);

    /// Handler has been upgraded so the original one has to self-destruct.
    // Handler 已被升级,因此原始 Handler 必须自毁。
    function done() external;

    /// Check if the Handler contract is a working Handler contract.
    /// It is used to prove the contract is a Handler contract.
    /// @return always true.
    // 检查 Handler 合约是否是正在工作的 Handler 合约。
    // 它用于证明该合约是 Handler 合约。
    // @return 总是 true。
    function live() external pure returns(bool) {
        return true;
    }

    /** Functions - define functions here */
    /** 函数 - 在此处定义函数 */

    /** Events - add events here */
    /** 事件 - 在此处添加事件 */
}

部署 Handler 合约的过程:

  1. 部署 Data 合约;
  2. 在 Data 合约中指定的给定地址部署 Handler 合约;
  3. 通过调用 Data 合约中的 setHandler() 来注册 Handler 合约地址,或者使用 Upgrader 合约来切换 Handler 合约,这要求 Data 合约已初始化;
  4. 如果尚未完成,则初始化 Data 合约。

Data 合约

以下是 Data 合约的规范。Data 合约中有三个部分:

  • Administrator Data:所有者的地址,Handler 合约的地址以及一个布尔值,指示合约是否已初始化;
  • Upgrader Data:Upgrader 合约的地址,升级提案的提交时间戳和提案的时间段;
  • Resource Data:合约需要保留和管理的所有其他资源。
/// Data Contract
// 数据合约
contract DataContract {

    /** Management data */
    /** 管理数据 */
    /// Owner and Handler contract
    // 所有者和 Handler 合约
    address private owner;
    address private handlerAddr;

    /// Ready?
    // 准备好了吗?
    bool private valid;

    /** Upgrader data */
    /** Upgrader 数据 */
    address private upgraderAddr;
    uint256 private proposalBlockNumber;
    uint256 private proposalPeriod;
    /// Upgrading status of the Handler contract
    // Handler 合约的升级状态
    enum UpgradingStatus {
        /// Can be upgraded
        // 可以升级
        Done,
        /// In upgrading
        // 升级中
        InProgress,
        /// Another proposal is in progress
        // 另一个提案正在进行中
        Blocked,
        /// Expired
        // 已过期
        Expired,
        /// Original Handler contract error
        // 原始 Handler 合约错误
        Error
    }

    /** Data resources - define variables here */
    /** 数据资源 - 在此处定义变量 */

    /** Modifiers */
    /** 修饰器 */

    /// Check if msg.sender is the Handler contract. It is used for setters.
    /// If fail, throw PermissionException.
    // 检查 msg.sender 是否是 Handler 合约。它用于 setter。
    // 如果失败,则抛出 PermissionException。
    modifier onlyHandler;

    /// Check if msg.sender is not permitted to call getters. It is used for getters (if necessary).
    /// If fail, throw GetterPermissionException.
    // 检查是否允许 msg.sender 调用 getter。它用于 getter(如果需要)。
    // 如果失败,则抛出 GetterPermissionException。
    modifier allowedAddress;

    /// Check if the contract is working.
    /// It is used for all functions providing services after initialization.
    /// If fail, throw UninitializationException.
    // 检查合约是否正在工作。
    // 它用于初始化后提供服务的所有函数。
    // 如果失败,则抛出 UninitializationException。
    modifier ready;

    /** Management functions */
    /** 管理功能 */

    /// Initializer. Just the Handler contract can call it. 
    /// @param  _str    default value of this.exmStr.
    /// @param  _int    default value of this.exmInt.
    /// @param  _array  default value of this.exmArray.
    /// exception   PermissionException msg.sender is not the Handler contract.
    /// exception   ReInitializationException   contract has been initialized.
    /// @return if the initialization succeeds.
    // 初始化器。只有 Handler 合约可以调用它。
    // @param  _str    this.exmStr 的默认值。
    // @param  _int    this.exmInt 的默认值。
    // @param  _array  this.exmArray 的默认值。
    // exception   PermissionException msg.sender 不是 Handler 合约。
    // exception   ReInitializationException   合约已初始化。
    // @return 如果初始化成功。
    function initialize (string _str, uint256 _int, uint16 [] _array) external onlyHandler returns(bool);

    /// Set Handler contract for the contract. Owner must set one to initialize the Data contract.
    /// Handler can be set by owner or Upgrader contract.
    /// @param  _handlerAddr    address of a deployed Handler contract.
    /// @param  _originalHandlerAddr    address of the original Handler contract, only used when an Upgrader contract want to set the Handler contract.
    /// exception   PermissionException msg.sender is not the owner nor a registered Upgrader contract.
    /// exception   UpgraderException   Upgrader contract does not provide a right address of the original Handler contract.
    /// @return if Handler contract is successfully set.
    // 为合约设置 Handler 合约。所有者必须设置一个才能初始化 Data 合约。
    // Handler 可以由所有者或 Upgrader 合约设置。
    // @param  _handlerAddr    已部署的 Handler 合约的地址。
    // @param  _originalHandlerAddr    原始 Handler 合约的地址,仅当 Upgrader 合约要设置 Handler 合约时才使用。
    // exception   PermissionException msg.sender 不是所有者也不是已注册的 Upgrader 合约。
    // exception   UpgraderException   Upgrader 合约未提供原始 Handler 合约的正确地址。
    // @return 如果 Handler 合约已成功设置。
    function setHandler (address _handlerAddr, address _originalHandlerAddr) external returns(bool);

    /** Upgrader contract functions */
    /** Upgrader 合约功能 */

    /// Register an Upgrader contract in the contract.
    /// If a proposal has not been accepted until proposalBlockNumber + proposalPeriod, it can be replaced by a new one.
    /// @param  _upgraderAddr  address of a deployed Upgrader contract.
    /// exception   PermissionException msg.sender is not the owner.
    /// exception   UpgraderConflictException   Another Upgrader contract is working.
    /// @return if Upgrader contract is successfully registered.
    // 在合约中注册 Upgrader 合约。
    // 如果在 proposalBlockNumber + proposalPeriod 之前未接受提案,则可以将其替换为新提案。
    // @param  _upgraderAddr  已部署的 Upgrader 合约的地址。
    // exception   PermissionException msg.sender 不是所有者。
    // exception   UpgraderConflictException   另一个 upgrader 正在工作。
    // @return 如果 Upgrader 合约已成功注册。
    function startUpgrading (address _upgraderAddr) public returns(bool);

    /// Getter of proposalPeriod.
    /// exception   UninitializationException   uninitialized contract.
    /// exception   GetterPermissionException   msg.sender is not permitted to call the getter.
    /// @return this.proposalPeriod.
    // proposalPeriod 的 Getter。
    // exception   UninitializationException   未初始化的合约。
    // exception   GetterPermissionException   不允许 msg.sender 调用 getter。
    // @return this.proposalPeriod。
    function getProposalPeriod () public view isReady allowedAddress returns(uint256);

    /// Setter of proposalPeriod.
    /// @param  _proposalPeriod new value of this.proposalPeriod.
    /// exception   UninitializationException   uninitialized contract.
    /// exception   PermissionException msg.sender is not the owner.
    /// @return if this.proposalPeriod is successfully set.
    // proposalPeriod 的 Setter。
    // @param  _proposalPeriod this.proposalPeriod 的新值。
    // exception   UninitializationException   未初始化的合约。
    // exception   PermissionException msg.sender 不是所有者。
    // @return 如果 this.proposalPeriod 已成功设置。
    function setProposalPeriod (uint256 _proposalPeriod) public isReady returns(bool);

    /// Return upgrading status for Upgrader contracts.
    /// @param  _originalHandlerAddr    address of the original Handler contract.
    /// exception   UninitializationException   uninitialized contract.
    /// @return Handler contract's upgrading status.
    // 返回 Upgrader 合约的升级状态。
    // @param  _originalHandlerAddr    原始 Handler 合约的地址。
    // exception   UninitializationException   未初始化的合约。
    // @return Handler 合约的升级状态。
    function canBeUpgraded (address _originalHandlerAddr) external view isReady returns(UpgradingStatus);

    /// Check if the contract has been initialized.
    /// @return if the contract has been initialized.
    // 检查合约是否已初始化。
    // @return 如果合约已初始化。
    function live () external view returns(bool);

    /** Getters and setters of data resources: define functions here */
    /** 数据资源的 Getter 和 Setter:在此处定义函数 */
}

Upgrader 合约(可选)

可以通过调用 Data 合约的 setHandler() 来升级 Handler 合约。如果所有者想从用户那里收集想法,则 Upgrader 合约将帮助他/她管理投票和升级。

以下是 Upgrader 合约的规范:

  • Upgrader 合约具有从已注册的投票者那里获得投票的能力。
    • 合约所有者可以在提案到期前的任何时间添加投票者;
    • 投票者可以检查提案的当前状态(成功或已过期)。
  • 开发人员可以通过在部署后随时调用 done() 来删除此 Upgrader 合约。

Upgrader 合约的工作方式如下:

  1. 验证 Data 合约,其对应的 Handler 合约和新的 Handler 合约都已部署;
  2. 使用 Data 合约地址,先前的 Handler 合约地址和新的 Handler 合约地址来部署 Upgrader 合约;
  3. 首先在新 Handler 合约中注册 upgrader 地址,然后注册原始 handler,最后注册 Data 合约;
  4. 调用 startProposal() 以启动投票过程;
  5. 在到期前调用 getResolution();
  6. 升级成功或提案已过期。

注意:

  • 可以随时调用函数 done(),以使 upgrader 自行销毁。
  • 可以随时调用函数 status(),以显示 upgrader 的调用者状态。
/// Handler upgrader
// Handler 升级器
contract Upgrader {
    // Data contract
    // 数据合约
    DataContract public data;
    // Original Handler contract
    // 原始 Handler 合约
    IHandler public originalHandler;
    // New Handler contract
    // 新的 Handler 合约
    address public newHandlerAddr;

    /** Marker */
    /** 标记 */
    enum UpgraderStatus {
        Preparing,
        Voting,
        Success,
        Expired,
        End
    }
    UpgraderStatus public status;

    /// Check if the proposal is expired.
    /// If so, contract would be marked as expired.
    /// exception   PreparingUpgraderException  proposal has not been started.
    /// exception   ReupgradingException    upgrading has been done.
    /// exception   ExpirationException proposal is expired.
    // 检查提案是否已过期。
    // 如果是这样,合约将被标记为已过期。
    // exception   PreparingUpgraderException  提案尚未启动。
    // exception   ReupgradingException    已完成升级。
    // exception   ExpirationException 提案已过期。
    modifier notExpired {
        require(status != UpgraderStatus.Preparing, "Invalid proposal!");
        require(status != UpgraderStatus.Success, "Upgrading has been done!");
        require(status != UpgraderStatus.Expired, "Proposal is expired!");
        if (data.canBeUpgraded(address(originalHandler)) != DataContract.UpgradingStatus.InProgress) {
            status = UpgraderStatus.Expired;
            require(false, "Proposal is expired!");
        }
        _;
    }

    /// Start voting.
    /// Upgrader must do upgrading check, namely checking if Data contract and 2 Handler contracts are ok.
    /// exception   RestartingException proposal has been already started.
    /// exception   PermissionException msg.sender is not the owner.
    /// exception   UpgraderConflictException   another upgrader is working.
    /// exception   NoPreparationException  original or new Handler contract is not prepared.
    // 开始投票。
    // Upgrader 必须进行升级检查,即检查 Data 合约和 2 个 Handler 合约是否正常。
    // exception   RestartingException 提案已经开始。
    // exception   PermissionException msg.sender 不是所有者。
    // exception   UpgraderConflictException   另一个 upgrader 正在工作。
    // exception   NoPreparationException  原始或新的 Handler 合约未准备好。
    function startProposal () external;

    /// Anyone can try to get resolution.
    /// If voters get consensus, upgrade the Handler contract.
    /// If expired, self-destruct.
    /// Otherwise, do nothing.
    /// exception   PreparingUpgraderException  proposal has not been started.
    /// exception   ExpirationException proposal is expired.
    /// @return     status of proposal.
    // 任何人都可以尝试获得解决方案。
    // 如果投票者达成共识,则升级 Handler 合约。
    // 如果过期,则自毁。
    // 否则,不执行任何操作。
    // exception   PreparingUpgraderException  提案尚未启动。
    // exception   ExpirationException 提案已过期。
    // @return     提案的状态。
    function getResolution() external returns(UpgraderStatus);

    /// Destruct itself.
    /// exception   PermissionException msg.sender is not the owner.
    // 自行销毁。
    // exception   PermissionException msg.sender 不是所有者。
    function done() external;

    /** Other voting mechanism related variables and functions */
    /** 其他与投票机制相关的变量和函数 */
}

注意事项

由于 ERC-1504 中的 Upgrader 合约具有简单的投票机制,因此它容易受到投票合约面临的所有限制:

  • 管理员只能是数据和 Handler 合约的所有者。此外,只有管理员有权添加投票者并启动提案。
  • 它要求投票者始终保持活跃、知情和专心,以使升级器成功。
  • 投票仅在给定的时间段内有效。如果在给定的时间段内,合约无法收集到足够的“是”以继续进行,则提案将被标记为已过期。

理由

Data 合约和 Handler 合约

智能合约实际上是一种软件,它提供某种服务。从软件工程的角度来看,服务包含 资源,这些资源抽象数据,以及 操作,这些操作抽象数据上的过程逻辑。升级的要求主要在逻辑部分。因此,为了使智能合约可升级,我们将其分为两部分:

  1. Data 合约保存资源;
  2. Handler 合约包含操作。

Handler 合约可以在将来升级,而 Data 合约是永久性的。Handler 合约可以通过 Data 合约提供的 getter 和 setter 函数来操纵 Data 合约中的变量。

Upgrader 合约和投票机制

为了防止中心化并保护社区和利益相关者的利益,我们还在 Upgrader 合约中设计了投票机制。Upgrader 合约包含 Data 合约和两个 Handler 合约的地址,并从预定义的投票者那里收集投票,以在满足预设条件时升级 Handler 合约。

为简单起见,可升级合约附带了非常低版本的投票机制。如果合约所有者想实现更复杂的投票机制,则他/她可以修改现有的投票机制以结合可升级性。到期机制(请参阅 Upgrader 合约中的修饰符 notExpried 和 Data 合约中的相关函数)和升级检查(请参阅 Upgrader 合约中的函数 startProposal())对于合约是强制性的。

Gas 和复杂性(关于枚举扩展)

使用升级器会花费一些 gas。如果 Handler 合约由所有者升级,则仅花费合约调用将花费的 gas,这通常明显低于创建和部署新合约。

尽管升级合约可能需要一些努力和 gas,但它比弃用不安全的合约/创建新合约或硬分叉(例如 DAO 攻击)要容易得多。合约创建需要大量的精力和 gas。可升级合约的优势之一是合约所有者不必创建新合约;相反,他们只需要升级导致问题的合约部分,与数据丢失和区块链不一致相比,这便宜得多。换句话说,可升级合约使 Data 合约更具可扩展性和灵活性。

社区共识

感谢那些帮助审查和修改提案的人:

该提案由 Renaissance 团队和北京大学操作系统中心区块链系统研究组发起和开发。

我们在此过程中非常具有包容性,并邀请任何有疑问或贡献的人参与我们的讨论。但是,此标准的编写仅是为了支持此处列出的已确定的用例。

实现

  1. Renaissance - 一个在财务上连接创作者和粉丝的协议
  2. ERC-1504 - 参考实现

版权

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

Citation

Please cite this document as:

Kaidong Wu <wukd94@pku.edu.cn>, Chuqiao Ren <cr025@bucknell.edu>, Ruthia He <rujiahe@gmail.com>, Yun Ma <mayun@pku.edu.cn>, Xuanzhe Liu <liuxuanzhe@pku.edu.cn>, "ERC-1504: 可升级智能合约 [DRAFT]," Ethereum Improvement Proposals, no. 1504, October 2018. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-1504.