Skip to content

Instantly share code, notes, and snippets.

@pprados
Created February 23, 2017 11:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pprados/19b6998e1de2b919a157d741e69818ba to your computer and use it in GitHub Desktop.
Save pprados/19b6998e1de2b919a157d741e69818ba to your computer and use it in GitHub Desktop.
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