Created
November 11, 2018 01:20
-
-
Save rfikki/2affaf89b49361c0506bf0340d827f1b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.0; | |
import './SafeMath.sol'; | |
/** | |
* Here is an example of upgradable contract, consisting of three parts: | |
* - Data contract keeps the resources (data) and is controlled by the Handler contract; | |
* - Handler contract (implements Handler interface) defines operations and provides services. This contract can be upgraded; | |
* - Upgrader contract (optional) deals with the voting mechanism and upgrades the Handler contract. The voters are pre-defined | |
* by the contract owner. | |
* | |
* @author Kaidong Wu ([email protected]) | |
* @version 0.1.6 | |
*/ | |
/** | |
* The example of Data contract. | |
* There are three parts in the Data contract: | |
* - Administrator Data: owner’s address, Handler contract’s address and an 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 mange. | |
*/ | |
contract DataContract { | |
using SafeMath for uint256; | |
/** Management data */ | |
// Owner and Handler contract | |
address private owner; | |
address private handlerAddr; | |
// Getter permitted | |
mapping(address => bool) private addressPermissions; | |
// 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: examples */ | |
string private exmStr; | |
uint256 private exmInt; | |
mapping(address => uint256) private exmMapping; | |
uint16[] private exmArray; | |
struct ExmStruct { | |
uint16 key; | |
string value; | |
uint[] list; | |
mapping(uint16 => uint16) map; | |
} | |
mapping(uint16 => ExmStruct) private exmStructMapping; | |
/** | |
* Constructor. | |
* Set the period of upgrading proposal. | |
* | |
* @param _period default value of this.proposalPeriod. | |
*/ | |
constructor (uint256 _period) public { | |
owner = msg.sender; | |
upgraderAddr = address(0); | |
proposalBlockNumber = 0; | |
proposalPeriod = _period; | |
valid = false; | |
addressPermissions[msg.sender] = true; | |
} | |
/** Modifiers */ | |
/** | |
* Check if msg.sender is the Handler contract. It is used for setters. | |
* If fail, throw PermissionException. | |
*/ | |
modifier onlyHandler { | |
require(msg.sender == handlerAddr, "Only Handler contract can call this function!"); | |
_; | |
} | |
/** | |
* Check if msg.sender is not permitted to call getters. It is used for getters (if necessary). | |
* If fail, throw GetterPermissionException. | |
*/ | |
modifier allowedAddress { | |
require(addressPermissions[msg.sender], "Do not have the permission!"); | |
_; | |
} | |
/** | |
* Check if the contract is working. | |
* If fail, throw UninitializationException. | |
*/ | |
modifier ready { | |
require(valid, "Data contract has not been initialized!"); | |
_; | |
} | |
/** 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) { | |
require(!valid, "Data contarct has been initialized!"); | |
exmStr = _str; | |
exmInt = _int; | |
exmArray = _array; | |
valid = true; | |
return true; | |
} | |
/** | |
* 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) { | |
// If Handler contract can be just upgraded by Upgrader contract except the first one, use this requirement. | |
require((!valid && msg.sender == owner) || msg.sender == upgraderAddr, "Permission error!"); | |
// // The other version of requirement, owner can always upgrade the Handler contract as well. | |
// require(msg.sender == owner || msg.sender == upgraderAddr, "Permission error!"); | |
// Check if the Upgrader contract is right. | |
require(handlerAddr == address(0) || handlerAddr == _originalHandlerAddr, "Upgrader contract error!"); | |
// Allow Handler contract to use getters, and remove original Handler contract's permission. | |
addressPermissions[_handlerAddr] = true; | |
if (handlerAddr != address(0)) { | |
addressPermissions[handlerAddr] = false; | |
} | |
handlerAddr = _handlerAddr; | |
upgraderAddr = address(0); | |
return true; | |
} | |
/** 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 registed. | |
*/ | |
function startUpgrading (address _upgraderAddr) public returns(bool) { | |
require(msg.sender == owner, "Just owner can start upgrading Handler contract!"); | |
require(upgraderAddr == address(0) || | |
proposalBlockNumber.add(proposalPeriod) < block.number, | |
"Another proposal is in the progress!"); | |
upgraderAddr = _upgraderAddr; | |
proposalBlockNumber = block.number; | |
return true; | |
} | |
/** | |
* 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 ready allowedAddress returns(uint256) { | |
return proposalPeriod; | |
} | |
/** | |
* 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 ready returns(bool) { | |
require(msg.sender == owner, "Permission error!"); | |
proposalPeriod = _proposalPeriod; | |
return true; | |
} | |
/** | |
* 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 ready returns(UpgradingStatus) { | |
if (handlerAddr != _originalHandlerAddr) { | |
return UpgradingStatus.Error; | |
} | |
if (upgraderAddr == msg.sender) { | |
if (proposalBlockNumber.add(proposalPeriod) < block.number) { | |
return UpgradingStatus.Expired; | |
} else { | |
return UpgradingStatus.InProgress; | |
} | |
} else { | |
if (proposalBlockNumber.add(proposalPeriod) < block.number) { | |
return UpgradingStatus.Done; | |
} else { | |
return UpgradingStatus.Blocked; | |
} | |
} | |
} | |
/** | |
* Check if the contract has been initialized. | |
* | |
* @return if the contract has been initialized. | |
*/ | |
function live () external view returns(bool) { | |
return valid; | |
} | |
/** Getters and setters of data resources: examples */ | |
function getExmStr () external view ready allowedAddress returns(string) { | |
return exmStr; | |
} | |
function setExmStr (string _str) external ready onlyHandler returns(bool) { | |
exmStr = _str; | |
return true; | |
} | |
function getExmInt () external view ready allowedAddress returns(uint256) { | |
return exmInt; | |
} | |
function setExmInt (uint256 _int) external ready onlyHandler returns(bool) { | |
exmInt = _int; | |
return true; | |
} | |
function getExmMappingValue (address _key) external view ready allowedAddress returns(uint256) { | |
return exmMapping[_key]; | |
} | |
function setExmMappingValue (address _key, uint256 _value) external ready onlyHandler returns(bool) { | |
exmMapping[_key] = _value; | |
return true; | |
} | |
function deleteExmMappingValue (address _key) external ready onlyHandler returns(bool) { | |
delete exmMapping[_key]; | |
return true; | |
} | |
function getExmArray () external view ready allowedAddress returns(uint16[]) { | |
return exmArray; | |
} | |
function addElementInExmArray (uint16 _e) external ready onlyHandler returns(bool) { | |
exmArray.push(_e); | |
return true; | |
} | |
function deleteElementByIndex (uint256 _index) external ready onlyHandler returns(bool) { | |
require(exmArray.length > _index, "Index error!"); | |
delete exmArray[_index]; | |
return true; | |
} | |
function deleteElementByEle (uint16 _e) external ready onlyHandler returns(bool) { | |
uint i = 0; | |
while (i < exmArray.length && exmArray[i] != _e) { | |
i++; | |
} | |
if (i != exmArray.length) { | |
delete exmArray[i]; | |
} | |
return true; | |
} | |
function deleteAllElementsByEle (uint16 _e) external ready onlyHandler returns(bool) { | |
for (uint i = exmArray.length; i > 0; i--) { | |
if (exmArray[i-1] == _e) { | |
delete exmArray[i-1]; | |
} | |
} | |
return true; | |
} | |
function getExmStruct (uint16 _key) external view ready allowedAddress returns(uint16, string, uint256[]) { | |
ExmStruct storage tmp = exmStructMapping[_key]; | |
return (tmp.key, tmp.value, tmp.list); | |
} | |
function setExmStruct (uint16 _k, uint16 _key, string _value, uint256[] _list) external ready onlyHandler returns(bool) { | |
ExmStruct storage tmp = exmStructMapping[_k]; | |
tmp.key = _key; | |
tmp.value = _value; | |
tmp.list = _list; | |
return true; | |
} | |
function getMapValueInExmStruct (uint16 _k, uint16 _key) external view ready allowedAddress returns(uint16) { | |
return exmStructMapping[_k].map[_key]; | |
} | |
function setMapInExmStruct (uint16 _k, uint16 _key, uint16 _value) external ready onlyHandler returns(bool) { | |
exmStructMapping[_k].map[_key] = _value; | |
return true; | |
} | |
function deleteMapInExmStruct (uint16 _k, uint16 _key) external ready onlyHandler returns(bool) { | |
delete exmStructMapping[_k].map[_key]; | |
return true; | |
} | |
} | |
/** | |
* Handler interface. | |
* Handler defines bussiness 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 is deployed as following steps: | |
* 1. Deploy Data contract; | |
* 2. Deploy a Handler contract at a given address specified in the data | |
* contract; | |
* 3. 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; | |
* 4. Initialize Data contract if haven’t done it already. | |
*/ | |
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 prepare2BUpgraded (address _upgraderAddr) external; | |
/** | |
* Upgrader contract calls this to check if it is registered. | |
* | |
* @return if the Upgrader contract is registered. | |
*/ | |
function isPrepared4Upgrading () 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 */ | |
// Some example functions. | |
function a () view external returns(string); | |
function b (string _str) external; | |
/** Events: add events here */ | |
// Some example events. | |
event Create(address contractAddress); | |
} | |
/** | |
* An example implementation of Handler contract interface | |
*/ | |
contract Handler is IHandler { | |
using SafeMath for uint256; | |
/** Management data */ | |
// Owner. | |
address private owner; | |
// Data conract. | |
DataContract private data; | |
// Cache of data.live(). | |
bool private valid; | |
/** Upgrader data */ | |
address private upgraderAddr; | |
/** | |
* Constructor. | |
* | |
* @param _dataAddr address of the Data contract. | |
*/ | |
constructor (address _dataAddr) public { | |
owner = msg.sender; | |
data = DataContract(_dataAddr); | |
valid = data.live(); | |
} | |
/** | |
* 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. | |
* | |
* exception PermissionException msg.sender is not the owner. | |
* exception ReInitializationException Data contract has been initialized. | |
* | |
* event Create service is created. | |
*/ | |
function initialize (string _str, uint256 _int, uint16 [] _array) public { | |
require(msg.sender == owner, "Permission error!"); | |
require(data.initialize(_str, _int, _array), | |
"Initialization failed! Check if the Data contract has been initialized!"); | |
valid = true; | |
// Example event is used. | |
emit Create(address(this)); | |
} | |
/** | |
* Register upgrader address. | |
* | |
* @param _upgraderAddr address of the upgrader. | |
* | |
* exception PermissionException msg.sender is not the owner. | |
*/ | |
function prepare2BUpgraded (address _upgraderAddr) external { | |
require(msg.sender == owner, "Permission error!"); | |
upgraderAddr = _upgraderAddr; | |
} | |
/** | |
* Upgrader calls this to check if it is registered. | |
* | |
* @return if the upgrader is registered. | |
*/ | |
function isPrepared4Upgrading () external view returns(bool) { | |
return upgraderAddr == msg.sender; | |
} | |
/** | |
* Handler has been upgraded so the original one has to self-destruct. | |
* | |
* exception PermissionException msg.sender is not the owner nor the upgrader. | |
*/ | |
function done() external{ | |
require(msg.sender == owner || msg.sender == upgraderAddr, "Permission error!"); | |
selfdestruct(owner); | |
} | |
/** Functions */ | |
// Example functons. | |
function a () view external returns(string) { | |
return data.getExmStr(); | |
} | |
function b (string _str) external { | |
data.setExmStr(_str); | |
} | |
} | |
/** | |
* Handler upgrader. The upgrader works in following steps: | |
* 1. Verify the Data contract, its corresponding Handler contract and the new Handler contract have all been deployed; | |
* 2. Deploy an Upgrader contract using Data contract address, previous Handler contract address and new Handler contract | |
* address; | |
* 3. Register upgrader address in the new Handler contract first, then the original hander and finally the Data contract; | |
* 4. Call startProposal() to start the voting process; | |
* 5. Call getResolution() before the expiration; | |
* 6. Upgrade succeed or proposal is expired. | |
* * 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. | |
*/ | |
contract Upgrader { | |
using SafeMath for uint256; | |
address private owner; | |
// 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; | |
/** Voting mechanism related */ | |
uint256 private percentage; | |
mapping(address => bool) public voting; | |
mapping(address => bool) private voterRegistered; | |
uint256 private numOfVoters = 0; | |
uint256 private numOfAgreements = 0; | |
/** | |
* 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!"); | |
} | |
_; | |
} | |
/** | |
* Constructor. | |
* | |
* @param _dataAddr address of the Data contract. | |
* @param _originalAddr address of the original Handler contract. | |
* @param _newAddr address of the new Handler contract. | |
* @param _voters addresses of voters. | |
* @param _percentage value of this.percentage. | |
* | |
* exception UninitializationException _dataAddr does not belong to a deployed Data contract having been initialization. | |
* exception UpgraderConflictException another upgrader is working. | |
* exception InvalidHandlerException _originalAddr or _newAddr does not belong to a deployed Handler contract. | |
*/ | |
constructor (address _dataAddr, address _originalAddr, address _newAddr, address[] _voters, uint256 _percentage) public { | |
// Check if the Data contract can be upgarded. | |
data = DataContract(_dataAddr); | |
require(data.live(), | |
"Can not upgrade Handler contract for an uninitialized Data contract!"); | |
require(data.canBeUpgraded(_originalAddr) == DataContract.UpgradingStatus.Done, | |
"Can not upgrade Handler contract!"); | |
// Check if the Handler contracts are valid. | |
originalHandler = IHandler(_originalAddr); | |
require(originalHandler.live(), "Invlid original Handler contract!"); | |
newHandlerAddr = _newAddr; | |
require(IHandler(_newAddr).live(), "Invlid new Handler contract!"); | |
owner = msg.sender; | |
_addVoters(_voters); | |
_setPercentage(_percentage); | |
// Mark the contract as preparing. | |
status = UpgraderStatus.Preparing; | |
} | |
/** | |
* Start voting. | |
* Upgrader must check 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 { | |
require(status == UpgraderStatus.Preparing, "Proposal has been already started!"); | |
require(msg.sender == owner, "Permission error!"); | |
// Check if contracts are prepared. | |
require(data.canBeUpgraded(address(originalHandler)) == DataContract.UpgradingStatus.InProgress, | |
"Have not registered upgrader in Data contract!"); | |
require(originalHandler.isPrepared4Upgrading(), | |
"Have not registered upgrader in original Handler contract!"); | |
require(IHandler(newHandlerAddr).isPrepared4Upgrading(), | |
"Have not registered upgrader in new Handler contract!"); | |
// Mark the contract as voting. | |
status = UpgraderStatus.Voting; | |
} | |
/** | |
* Add unique voters. | |
* If expired, self-destruct. | |
* | |
* @param _voters addresses of voters. | |
* | |
* exception PermissionException msg.sender is not the owner. | |
* | |
* see this.notExpired | |
*/ | |
function addVoters (address[] _voters) public notExpired { | |
require(msg.sender == owner, "Permission error!"); | |
_addVoters(_voters); | |
} | |
function _addVoters (address[] _voters) internal { | |
for (uint256 i = 0; i < _voters.length; i++) { | |
if (!voterRegistered[_voters[i]]) { | |
voterRegistered[_voters[i]] = true; | |
numOfVoters++; | |
} | |
} | |
} | |
/** | |
* Vote. | |
* If expired, self-destruct. | |
* | |
* @param _choose if the voter agrees with the proposal. | |
* | |
* exception PermissionException msg.sender is not a voter. | |
* | |
* see this.notExpired | |
*/ | |
function vote (bool _choose) external notExpired { | |
require(voterRegistered[msg.sender], "Do not have the permission!"); | |
if (voting[msg.sender] != _choose) { | |
if (_choose) { | |
numOfAgreements++; | |
} else { | |
numOfAgreements--; | |
} | |
voting[msg.sender] = _choose; | |
} | |
} | |
/** | |
* Set percentage. | |
* If percentage is over 100, it will be fixed automatically. | |
* | |
* @param _percentage value of this.percentage. | |
* | |
* exception PermissionException msg.sender is not the owner. | |
* | |
* see this.notExpired | |
*/ | |
function setPercentage(uint256 _percentage) external notExpired { | |
require(msg.sender == owner, "Permission error!"); | |
_setPercentage(_percentage); | |
} | |
function _setPercentage(uint256 _percentage) internal { | |
percentage = _percentage; | |
if (percentage > 100) { | |
percentage = 100; | |
} | |
} | |
/** | |
* 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. | |
* | |
* @return status of proposal. | |
* | |
* see this.notExpired | |
*/ | |
function getResolution() external notExpired returns(UpgraderStatus) { | |
if (numOfAgreements > numOfVoters.mul(percentage).div(100)) { | |
data.setHandler(newHandlerAddr, address(originalHandler)); | |
originalHandler.done(); | |
status = UpgraderStatus.Success; | |
} | |
return status; | |
} | |
/** | |
* Destruct itself. | |
* | |
* exception PermissionException msg.sender is not the owner. | |
*/ | |
function done() external { | |
require(msg.sender == owner, "Permission error!"); | |
selfdestruct(owner); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment