Appearance
Simple Summary ​
A standard interface/guideline that makes a smart contract upgradable.
Abstract ​
Ethereum smart contracts have suffered a number of security issues in the past few years. The cost of fixing such a bug in smart contract is significant; for example, the consequences of The DAO attack in June 2016 caused tremendous financial loss and the hard fork of Ethereum blockchain.
The following standard makes it possible to upgrade a standard API within smart contracts. This standard provides basic functionalities to upgrade the operations of the contract without data migration. To ensure the decentralization/community interests, it also contains a voting mechanism to control the upgrading process.
Motivation ​
Smart contract is immutable after deployment. If any security risk is identified or program bug is detected, developers always have to destruct the old contract, deploy a new one and potentially migrate the data (hard fork) to the new contract. In some cases, deploying a smart contract with bugs and potential security vulnerabilities can cause a significant amount of financial loss.
We propose this upgradable contract to fix the current situation. With the upgradable contract, developers can deploy a new version of smart contract after previous deployment and retain the data at the same time.
For example, after an ERC20-compliant token contract is deployed, the users exploit a vulnerability in the source code. Without the support of upgradable contract, developers have to fix this issue by deploy a new, secured contract otherwise the attackers would take advantage of the security hole, which may cause a tremendous financial loss. A challenge is how to migrate data from the old contract to a new one. With the upgradable contract below, this will become relatively easy as developers only have to upgrade the Handler contract to fix bugs while the Data contract will remain the same.
Specification ​
The upgradable contract consists of three parts:
- Handler contract (implements Handler interface) defines operations and provides services. This contract can be upgraded;
- Data contract keeps the resources (data) and is controlled by the Handler contract;
- Upgrader contract (optional) deals with the voting mechanism and upgrades the Handler contract. The voters are pre-defined by the contract owner.
The following codes are exact copies of the ERC-1504 Upgradable Smart Contract.
Handler contract and Handler interface ​
Functions of the Handler contract vary with requirements, so developers would better design interfaces for Handler contracts to limit them and make sure external applications are always supported.
Below is the specification of Handler interface. In the Handler interface we define the following actions:
- Initialize the Data contract;
- Register the Upgrader contract address;
- Destruct the Handler contract after upgrading is done;
- Verify the current Handler is the working one → it should always return true.
Developers have to define their business-related functions as well.
solidity
/// 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.
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.
function initialize (string _str, uint256 _int, uint16 [] _array) public;
/// Register Upgrader contract address.
/// @param _upgraderAddr address of the Upgrader contract.
function registerUpgrader (address _upgraderAddr) external;
/// Upgrader contract calls this to check if it is registered.
/// @return if the Upgrader contract is registered.
function isUpgraderRegistered () external view returns(bool);
/// Handler has been upgraded so the original one has to self-destruct.
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.
function live() external pure returns(bool) {
return true;
}
/** Functions - define functions here */
/** Events - add events here */
}
The process of deploying a Handler contract:
- Deploy Data contract;
- Deploy a Handler contract at a given address specified in the Data contract;
- Register the Handler contract address by calling setHandler() in the Data contract, or use an Upgrader contract to switch the Handler contract, which requires that Data contract is initialized;
- Initialize Data contract if haven’t done it already.
Data Contract ​
Below is the specification of Data contract. There are three parts in the Data contract:
- Administrator Data: owner’s address, Handler contract’s address and a boolean indicating whether the contract is initialized or not;
- Upgrader Data: Upgrader contract’s address, upgrade proposal’s submission timestamp and proposal’s time period;
- Resource Data: all other resources that the contract needs to keep and manage.
solidity
/// Data Contract
contract DataContract {
/** Management data */
/// Owner and Handler contract
address private owner;
address private handlerAddr;
/// Ready?
bool private valid;
/** Upgrader data */
address private upgraderAddr;
uint256 private proposalBlockNumber;
uint256 private proposalPeriod;
/// Upgrading status of the Handler contract
enum UpgradingStatus {
/// Can be upgraded
Done,
/// In upgrading
InProgress,
/// Another proposal is in progress
Blocked,
/// Expired
Expired,
/// Original Handler contract error
Error
}
/** Data resources - define variables here */
/** Modifiers */
/// Check if msg.sender is the Handler contract. It is used for setters.
/// If fail, throw PermissionException.
modifier onlyHandler;
/// Check if msg.sender is not permitted to call getters. It is used for getters (if necessary).
/// If fail, throw GetterPermissionException.
modifier allowedAddress;
/// Check if the contract is working.
/// It is used for all functions providing services after initialization.
/// If fail, throw 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.
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.
function setHandler (address _handlerAddr, address _originalHandlerAddr) external returns(bool);
/** Upgrader contract functions */
/// 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.
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.
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.
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.
function canBeUpgraded (address _originalHandlerAddr) external view isReady returns(UpgradingStatus);
/// Check if the contract has been initialized.
/// @return if the contract has been initialized.
function live () external view returns(bool);
/** Getters and setters of data resources: define functions here */
}
Upgrader Contract (Optional) ​
Handler contract can be upgraded by calling setHandler() of Data contract. If the owner wants to collect ideas from users, an Upgrader contract will help him/her manage voting and upgrading.
Below is the specification of Upgrader contract:
- The Upgrader contract has the ability to take votes from the registered voters.
- The contract owner is able to add voters any time before the proposal expires;
- Voter can check the current status of the proposal (succeed or expired).
- Developers are able to delete this Upgrader contract by calling done() any time after deployment.
The Upgrader contract works as follows:
- Verify the Data contract, its corresponding Handler contract and the new Handler contract have all been deployed;
- Deploy an Upgrader contract using Data contract address, previous Handler contract address and new Handler contract address;
- Register upgrader address in the new Handler contract first, then the original handler and finally the Data contract;
- Call startProposal() to start the voting process;
- Call getResolution() before the expiration;
- Upgrading succeed or proposal is expired.
Note:
- Function done() can be called at any time to let upgrader destruct itself.
- Function status() can be called at any time to show caller status of the upgrader.
solidity
/// Handler upgrader
contract Upgrader {
// Data contract
DataContract public data;
// Original Handler contract
IHandler public originalHandler;
// New Handler contract
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.
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.
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.
function getResolution() external returns(UpgraderStatus);
/// Destruct itself.
/// exception PermissionException msg.sender is not the owner.
function done() external;
/** Other voting mechanism related variables and functions */
}
Caveats ​
Since the Upgrader contract in ERC-1504 has a simple voting mechanism, it is prone to all the limitations that the voting contract is facing:
- The administrator can only be the owner of data and Handler contracts. Furthermore, only the administrator has the power to add voters and start a proposal.
- It requires voters to be constantly active, informative and attentive to make a upgrader succeed.
- The voting will only be valid in a given time period. If in a given time period the contract cannot collect enough “yes” to proceed, the proposal will be marked expired.
Rationale ​
Data Contract and Handler Contract ​
A smart contract is actually a kind of software, which provides some kind of services. From the perspective of software engineering, a service consists of resources that abstract the data and operations that abstract the process logic on the data. The requirement of upgrading is mostly on the logic part. Therefore, in order to make a smart contract upgradable, we divide it into two parts:
- Data contract keeps the resources;
- Handler contract contains operations.
The Handler contract can be upgraded in the future while the Data contract is permanent. Handler contract can manipulate the variables in Data contract through the getter and setter functions provided by Data contract.
Upgrader Contract and Voting Mechanism ​
In order to prevent centralization and protect the interests of the community and stakeholders, we also design a voting mechanism in the Upgrader contract. Upgrader contract contains addresses of Data contract and two Handler contracts, and collects votes from pre-defined voters to upgrade the Handler contract when the pre-set condition is fulfilled.
For simplicity, the upgradable contract comes with a very minimal version of the voting mechanism. If the contract owner wants to implement a more complex voting mechanism, he/she can modify the existing voting mechanism to incorporate upgradability. The expiration mechanism (see modifier notExpried in Upgrader contract and related functions in Data contract) and upgrading check (see function startProposal() in Upgrader contract) to the contract are mandatory.
Gas and Complexity (regarding the enumeration extension) ​
Using an upgrader will cost some gas. If the Handler contract is upgraded by the owner, it just costs gas that a contract call will cost, which is usually significantly lower than creating and deploying a new contract.
Although upgrading contract may take some efforts and gas, it is a much less painful than deprecating the insecure contract/creating a new contract or hard fork (e.g. DAO attack). Contract creation requires a significant amount of effort and gas. One of the advantages of upgradable contracts is that the contract owners don’t have to create new contracts; instead, they only need to upgrade parts of contract that cause issues, which is less expensive compared to data loss and blockchain inconsistency. In other words, upgradable contracts make Data contract more scalable and flexible.
Community Consensus ​
Thank you to those who helped on review and revise the proposal:
- @lsankar4033 from MIT
- more
The proposal is initiated and developed by the team Renaissance and the Research Group of Blockchain System @ Center for Operating System at Peking University.
We have been very inclusive in this process and invite anyone with questions or contributions into our discussion. However, this standard is written only to support the identified use cases which are listed herein.
Implementations ​
- Renaissance - a protocol that connect creators and fans financially
- ERC-1504 - a reference implementation
Copyright ​
Copyright and related rights waived via CC0.