Created
February 23, 2017 11:02
-
-
Save pprados/19b6998e1de2b919a157d741e69818ba 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.8; // Enable Optimization | |
/** | |
* Multi owned events. | |
*/ | |
contract MultiOwnedEvents { | |
// EVENTS | |
// this contract only has six types of events: it can accept a confirmation, in which case | |
// we record owner and operation (hash) alongside it. | |
event Confirmation(address owner, bytes32 operation); | |
event Revoke(address owner, bytes32 operation); | |
// some others are in the case of an owner changing. | |
event OwnerChanged(address oldOwner, address newOwner); | |
event OwnerAdded(address newOwner); | |
event OwnerRemoved(address oldOwner); | |
// the last one is emitted if the required signatures change | |
event RequirementChanged(uint newRequirement); | |
event AlreadySubmitted(bytes32 operation); | |
} | |
/** | |
* Multi owned contract. | |
* Propose some modifiers to delay an invocation, after a confirmation from n owners. | |
*/ | |
// @see https://github.com/ethereum/dapp-bin/blob/master/wallet/wallet.sol | |
contract MultiOwned is MultiOwnedEvents { | |
// struct for the status of a pending operation. | |
struct PendingState { | |
uint yetNeeded; | |
uint ownersDone; | |
uint index; | |
} | |
// The number of owners that must confirm the same operation before it is run. | |
uint public required; | |
// Pointer used to find a free slot in owners | |
uint public numOwners; | |
// List of owners | |
uint[256] public owners; | |
// Maximum number of owners | |
uint constant maxOwners = 250; | |
// Index on the list of owners to allow reverse lookup | |
mapping(uint => uint) public ownerIndexes; | |
// The ongoing operations. | |
mapping(bytes32 => PendingState) public pendings; | |
bytes32[] public pendingIndexes; | |
bool private isInit; // FIXME Refuse double init | |
/** | |
* Simple single-owner function modifier. | |
*/ | |
modifier onlyOwner { | |
if (isInit==false) throw; | |
if (isOwner(msg.sender)) | |
_; | |
} | |
/** | |
* Multi-owner function modifier: the operation must have an intrinsic hash in order | |
* that later attempts can be realised as the same underlying operation and | |
* thus count as confirmations. | |
*/ | |
modifier onlyManyOwners { | |
if (isInit==false) throw; | |
if (confirmAndCheck(sha3(msg.data))) | |
_; | |
} | |
/** | |
* Initialize a given number of sigs required to do protected "onlymanyowners" transactions | |
* as well as the selection of addresses capable of confirming them. | |
* | |
* @param _owners A list of validate owner. The creator is automatically added. | |
* @param _required A required owners to invoke a method. | |
*/ | |
function initMultiOwned(address[] _owners, uint _required) internal { | |
if (isInit==true) throw; | |
numOwners = _owners.length + 1; | |
owners[1] = uint(msg.sender); | |
ownerIndexes[uint(msg.sender)] = 1; | |
for (uint i = 0; i < _owners.length; ++i) | |
{ | |
owners[2 + i] = uint(_owners[i]); | |
ownerIndexes[uint(_owners[i])] = 2 + i; | |
} | |
required = _required; | |
isInit=true; | |
} | |
/** | |
* Revokes a prior confirmation of the given operation. | |
* @param _operation the hash of the operation. | |
*/ | |
function revoke(bytes32 _operation) external { | |
if (isInit==false) throw; | |
uint ownerIndex = ownerIndexes[uint(msg.sender)]; | |
// make sure they're an owner | |
if (ownerIndex == 0) return; | |
uint ownerIndexBit = 2**ownerIndex; | |
var pending = pendings[_operation]; | |
if (pending.ownersDone & ownerIndexBit > 0) { | |
pending.yetNeeded++; | |
pending.ownersDone -= ownerIndexBit; | |
Revoke(msg.sender, _operation); | |
} | |
} | |
/** | |
* Replaces an owner `_from` with another `_to`. | |
* Must be confirmed by required owners. | |
* @param _from The owner to change. | |
* @param _to The new owner. | |
*/ | |
function changeOwner(address _from, address _to) onlyManyOwners external { | |
if (isInit==false) throw; | |
if (isOwner(_to)) return; | |
uint ownerIndex = ownerIndexes[uint(_from)]; | |
if (ownerIndex == 0) return; | |
clearPending(); | |
owners[ownerIndex] = uint(_to); | |
ownerIndexes[uint(_from)] = 0; | |
ownerIndexes[uint(_to)] = ownerIndex; | |
OwnerChanged(_from, _to); | |
} | |
/** | |
* Add a new owner. | |
* Must be confirmed by required owners. | |
* @param _owner The owner to add. | |
*/ | |
function addOwner(address _owner) onlyManyOwners external { | |
if (isInit==false) throw; | |
if (isOwner(_owner)) return; | |
clearPending(); | |
if (numOwners >= maxOwners) | |
reorganizeOwners(); | |
if (numOwners >= maxOwners) | |
return; | |
numOwners++; | |
owners[numOwners] = uint(_owner); | |
ownerIndexes[uint(_owner)] = numOwners; | |
OwnerAdded(_owner); | |
} | |
/** | |
* Remove an owner. | |
* Must be confirmed by required owners. | |
* @param _owner The ownver to remove. | |
*/ | |
function removeOwner(address _owner) onlyManyOwners external { | |
if (isInit==false) throw; | |
uint ownerIndex = ownerIndexes[uint(_owner)]; | |
if (ownerIndex == 0) return; | |
if (required > numOwners - 1) return; | |
owners[ownerIndex] = 0; | |
ownerIndexes[uint(_owner)] = 0; | |
clearPending(); | |
reorganizeOwners(); //make sure m_numOwner is equal to the number of owners and always points to the optimal free slot | |
OwnerRemoved(_owner); | |
} | |
/** | |
* Change the required owners to validate a call. | |
* Must be confirmed by required owners. | |
* @param _newRequired The new required owners. | |
* | |
*/ | |
function changeRequirement(uint _newRequired) onlyManyOwners external { | |
if (isInit==false) throw; | |
if (_newRequired > numOwners) return; | |
required = _newRequired; | |
clearPending(); | |
RequirementChanged(_newRequired); | |
} | |
/** | |
* Gets an owner by 0-indexed position (using numOwners as the count) | |
* @param _ownerIndex The position. | |
* @return The address at this position. | |
*/ | |
function getOwner(uint _ownerIndex) public constant returns (address) { | |
return address(owners[_ownerIndex + 1]); | |
} | |
/** | |
* Check if the the address is an owner. | |
* @param _addr The address to check. | |
* @return true if this address is an owner. | |
*/ | |
function isOwner(address _addr) returns (bool) { | |
return ownerIndexes[uint(_addr)] > 0; | |
} | |
/** | |
* Check if the operation has confirmed by the owner. | |
* @param _operation The hash of the operation. | |
* @param _owner The owner to check. | |
* @return true if the owner has validate this operation. | |
*/ | |
function hasConfirmed(bytes32 _operation, address _owner) constant returns (bool) { | |
if (isInit==false) throw; | |
var pending = pendings[_operation]; | |
uint ownerIndex = ownerIndexes[uint(_owner)]; | |
// make sure they're an owner | |
if (ownerIndex == 0) return false; | |
// determine the bit to set for this owner. | |
uint ownerIndexBit = 2**ownerIndex; | |
return !(pending.ownersDone & ownerIndexBit == 0); | |
} | |
// INTERNAL METHODS | |
function confirmAndCheck(bytes32 _operation) private returns (bool) { | |
// determine what index the present sender is: | |
uint ownerIndex = ownerIndexes[uint(msg.sender)]; | |
// make sure they're an owner | |
if (ownerIndex == 0) return; | |
var pending = pendings[_operation]; | |
// if we're not yet working on this operation, switch over and reset the confirmation status. | |
if (pending.yetNeeded == 0) { | |
// reset count of confirmations needed. | |
pending.yetNeeded = required; | |
// reset which owners have confirmed (none) - set our bitmap to 0. | |
pending.ownersDone = 0; | |
pending.index = pendingIndexes.length++; | |
pendingIndexes[pending.index] = _operation; | |
} | |
// determine the bit to set for this owner. | |
uint ownerIndexBit = 2**ownerIndex; | |
// make sure we (the message sender) haven't confirmed this operation previously. | |
if ((pending.ownersDone & ownerIndexBit) == 0) { | |
Confirmation(msg.sender, _operation); | |
// ok - check if count is enough to go ahead. | |
if (pending.yetNeeded <= 1) { | |
// enough confirmations: reset and run interior. | |
delete pendingIndexes[pendings[_operation].index]; | |
delete pendings[_operation]; | |
return true; | |
} | |
else | |
{ | |
// not enough: record that this owner in particular confirmed. | |
pending.yetNeeded--; | |
pending.ownersDone |= ownerIndexBit; | |
} | |
} | |
else { | |
AlreadySubmitted(_operation); | |
} | |
} | |
function reorganizeOwners() private { | |
uint free = 1; | |
while (free < numOwners) | |
{ | |
while (free < numOwners && owners[free] != 0) ++free; | |
while (numOwners > 1 && owners[numOwners] == 0) numOwners--; | |
if (free < numOwners && owners[numOwners] != 0 && owners[free] == 0) | |
{ | |
owners[free] = owners[numOwners]; | |
ownerIndexes[owners[free]] = free; | |
owners[numOwners] = 0; | |
} | |
} | |
} | |
/** | |
* Clear all pending operation. | |
*/ | |
function clearPending() internal { | |
uint length = pendingIndexes.length; | |
for (uint i = 0; i < length; ++i) | |
if (pendingIndexes[i] != 0) | |
delete pendings[pendingIndexes[i]]; | |
delete pendingIndexes; | |
} | |
} | |
//--------------------------------------------------------------------------- | |
contract Versionable is MultiOwned { | |
event VersionChanged(Versionable version); | |
/** The current version. */ | |
Versionable internal currentVersion; | |
} | |
/** | |
* A proxy to delegate all call to the current implementation. | |
* All the attributs updated by the implementation are set in this proxy. | |
*/ | |
contract Proxy is Versionable { | |
/** | |
* Create a proxy to delegate call to the current version of contract. | |
* @param _currentVersion The first version to use. | |
*/ | |
function Proxy(Versionable _currentVersion,address[] _owners, uint _required) { | |
currentVersion=_currentVersion; | |
initMultiOwned(_owners,_required); | |
} | |
/** | |
* Change the current version. | |
* @param _newVersion The new version. | |
*/ | |
function changeVersion(Versionable _newVersion) external onlyManyOwners { | |
currentVersion=_newVersion; | |
clearPending(); | |
VersionChanged(_newVersion); | |
} | |
/** | |
* Propagate the current call to another contract. | |
* Use TARGET code with THIS storage, but also keep caller and callvalue. | |
* Invoke delegateCall(). | |
* In order for this to work with callcode, the data-members needs to be identical to the target contract. | |
* | |
* @param target The target contract. | |
* @param returnSize The maximum return size of all methods invoked. | |
*/ | |
function propagateDelegateCall(address target, int returnSize) internal { | |
assembly { | |
let brk := mload(0x40) // Special solidity slot with top memory | |
calldatacopy(brk, 0, calldatasize) // Copy data to memory at offset brk | |
let retval := delegatecall(sub(gas,150) | |
,target //address | |
,brk // memory in | |
,calldatasize // input size | |
,brk // reuse mem | |
,returnSize) // arbitrary return size | |
// 0 == it threw, by jumping to bad destination (00) | |
jumpi(0x00,iszero(retval)) // Throw (access invalid code) | |
return(brk,returnSize) // Return returnSize from memory to the caller | |
} | |
} | |
/** | |
* Fall-back function. | |
* Delegate all call to the current version, but use this instance to save datas. | |
*/ | |
function () payable { | |
// 32 is the maximum return size for all methods in all versions. | |
propagateDelegateCall(currentVersion,32); | |
} | |
} | |
// ---------- Sample usage ---------- | |
/** | |
* The version 1 of the contract. | |
* The attr is initialized to 1000. | |
* The method doSomething() return attr + version = 1001 | |
*/ | |
contract ContractV1 is Versionable { | |
uint constant private version=1; | |
uint public attr; | |
/** return attr+version (1001). */ | |
function doSomething() constant external returns(uint) { | |
return attr+version; // 1001 | |
} | |
/** Post-construction. */ | |
function init() { | |
attr=1000; | |
} | |
} | |
/** | |
* The version 2 of the contract. | |
* To preserve all the attributs from the v1 version, this version IS a ContractV1. | |
* All methods can be rewrite, new one can be added and some attributs can be added. | |
* | |
* The newAttr is initialized to 100. | |
* The method doSomething() return attr + newAtttr + version = 1102 | |
*/ | |
contract ContractV2 is ContractV1 { | |
uint constant private version=2; | |
uint public newAttr; | |
/** return attr+newAttr+version (1102). */ | |
function doSomething() constant external returns(uint) { | |
return attr+newAttr+version; // 1102 | |
} | |
/** return 42. Another method in version 2. */ | |
function doOtherThing() external returns(uint) { | |
return 42; | |
} | |
/** Post-construction. */ | |
function init() { | |
super.init(); | |
newAttr=100; | |
} | |
} | |
// ------------------------------------------------------------------- | |
// Unit test | |
/** For the UnitTest, this the shared instances by both users. */ | |
contract A_UnitTestBase is MultiOwnedEvents { | |
/** Event to confirm the version changed. */ | |
event VersionChanged(Versionable version); | |
/** All the owners of the contract. */ | |
address[] public owners; | |
/** A reference to a version of contract, via a proxy. */ | |
ContractV1 public useContract; | |
ContractV2 public contractv2=new ContractV2(); | |
} | |
/** Simulate the user1. */ | |
contract User_1 { | |
A_UnitTestBase private base; | |
function User_1(A_UnitTestBase _base) { | |
base=_base; | |
} | |
/** Change the version of the contract. | |
* The msg.sender is this instance. | |
*/ | |
function changeToV2() { | |
// Cast the current contract to be a Proxy | |
Proxy proxy=Proxy(base.useContract()); | |
// Change the delegate instance to v2. Kill the version 1. | |
proxy.changeVersion(base.contractv2()); | |
// Here the version is not yet changed. It's must be valide with another user. | |
} | |
} | |
/** Simulate the user2. */ | |
contract User_2 { | |
A_UnitTestBase /*private*/public base; | |
function User_2(A_UnitTestBase _base) { | |
base=_base; | |
} | |
/** Change the version of the contract. | |
* The msg.sender is this instance. | |
*/ | |
function changeToV2() { | |
// Cast the current contract to be a Proxy | |
Proxy proxy=Proxy(base.useContract()); | |
// Change the delegate instance to v2. Kill the version 1. | |
proxy.changeVersion(base.contractv2()); | |
// Here the version is changed. | |
// Initialize the version 2 via the proxy. | |
base.useContract().init(); | |
} | |
} | |
/** | |
* Unit test. | |
* | |
* After created an instance, | |
* - call 'init()' | |
* - call 'doSomething()' return 1001 | |
* - call 'user1_changeToV2' | |
* - call 'user2_changeToV2' | |
* - call 'doSomething()' return 1102 | |
* - call 'doOtherthing()' return 42 | |
*/ | |
contract A_UnitTest is A_UnitTestBase { | |
/** Mininum required owner to invoke a protected method. */ | |
uint private constant MIN_REQUIRED=2; | |
bool private isInit; | |
User_1 private user1=new User_1(this); | |
User_2 private user2=new User_2(this); | |
/** | |
* Initialise useContract with a ContractV1 encapsulated by a proxy. | |
*/ | |
function A_UnitTest() { | |
owners.push(user1); | |
owners.push(user2); | |
// Out of gas (see http://ethereum.stackexchange.com/questions/7436/large-contract-bytecode-45k-causes-out-of-gas-on-deploy) | |
// init(); | |
// WARNING: You must call init() after the creation. | |
} | |
function init() { | |
isInit=true; | |
// Create an instance of version 1 | |
ContractV1 v1=new ContractV1(); | |
// Encapsulate this instance in a proxy | |
Proxy proxy=new Proxy(v1,owners,MIN_REQUIRED); | |
// Cast the proxy to ContractV1 | |
useContract=ContractV1(proxy); | |
// Init the instance via the proxy | |
useContract.init(); | |
} | |
/** Invoke the method 'doSomething()', valide with a contract v1 or v2. */ | |
function doSomething() constant returns(uint) { | |
if (!isInit) throw; | |
return useContract.doSomething(); | |
} | |
/** Invoke a method 'doOtherThing()', only valide with a contract v2. */ | |
function doOtherThing() constant returns(uint) { | |
if (!isInit) throw; | |
return ContractV2(useContract).doOtherThing(); | |
} | |
/** User1 ask to change to V2. */ | |
function user1_changeToV2() { | |
if (!isInit) throw; | |
user1.changeToV2(); | |
} | |
/** User2 ask to change to V2. */ | |
function user2_changeToV2() { | |
if (!isInit) throw; | |
user2.changeToV2(); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment