@ -47,12 +47,18 @@ static char const* walletCode = R"DELIMITER(
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.
contract multiowned {
// TYPES
// struct for the status of a pending operation.
struct PendingState {
uint yetNeeded ;
uint ownersDone ;
uint index ;
}
// EVENTS
// this contract only has five 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 ) ;
@ -63,14 +69,9 @@ contract multiowned {
event OwnerRemoved ( address oldOwner ) ;
// the last one is emitted if the required signatures change
event RequirementChanged ( uint newRequirement ) ;
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function multiowned ( ) {
m_required = 1 ;
m_numOwners = 1 ;
m_owners [ m_numOwners ] = uint ( msg . sender ) ;
m_ownerIndex [ uint ( msg . sender ) ] = m_numOwners ;
}
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if ( isOwner ( msg . sender ) )
@ -80,9 +81,21 @@ contract multiowned {
// that later attempts can be realised as the same underlying operation and
// thus count as confirmations.
modifier onlymanyowners ( bytes32 _operation ) {
if ( confirmed ( _operation ) )
if ( confirmAndCheck ( _operation ) )
_
}
// METHODS
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function multiowned ( ) {
m_required = 1 ;
m_numOwners = 1 ;
m_owners [ m_numOwners ] = uint ( msg . sender ) ;
m_ownerIndex [ uint ( msg . sender ) ] = m_numOwners ;
}
// Revokes a prior confirmation of the given operation
function revoke ( bytes32 _operation ) external {
uint ownerIndex = m_ownerIndex [ uint ( msg . sender ) ] ;
@ -96,7 +109,75 @@ contract multiowned {
Revoke ( msg . sender , _operation ) ;
}
}
function confirmed ( bytes32 _operation ) internal returns ( bool ) {
// Replaces an owner `_from` with another `_to`.
function changeOwner ( address _from , address _to ) onlymanyowners ( sha3 ( msg . data , block . number ) ) external {
if ( isOwner ( _to ) ) return ;
uint ownerIndex = m_ownerIndex [ uint ( _from ) ] ;
if ( ownerIndex = = 0 ) return ;
clearPending ( ) ;
m_owners [ ownerIndex ] = uint ( _to ) ;
m_ownerIndex [ uint ( _from ) ] = 0 ;
m_ownerIndex [ uint ( _to ) ] = ownerIndex ;
OwnerChanged ( _from , _to ) ;
}
function addOwner ( address _owner ) onlymanyowners ( sha3 ( msg . data , block . number ) ) external {
if ( isOwner ( _owner ) ) return ;
clearPending ( ) ;
if ( m_numOwners > = c_maxOwners )
reorganizeOwners ( ) ;
if ( m_numOwners > = c_maxOwners )
return ;
m_numOwners + + ;
m_owners [ m_numOwners ] = uint ( _owner ) ;
m_ownerIndex [ uint ( _owner ) ] = m_numOwners ;
OwnerAdded ( _owner ) ;
}
function removeOwner ( address _owner ) onlymanyowners ( sha3 ( msg . data , block . number ) ) external {
uint ownerIndex = m_ownerIndex [ uint ( _owner ) ] ;
if ( ownerIndex = = 0 ) return ;
if ( m_required > m_numOwners - 1 ) return ;
m_owners [ ownerIndex ] = 0 ;
m_ownerIndex [ 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 ) ;
}
function changeRequirement ( uint _newRequired ) onlymanyowners ( sha3 ( msg . data , block . number ) ) external {
if ( _newRequired > m_numOwners ) return ;
m_required = _newRequired ;
clearPending ( ) ;
RequirementChanged ( _newRequired ) ;
}
function isOwner ( address _addr ) returns ( bool ) {
return m_ownerIndex [ uint ( _addr ) ] > 0 ;
}
function hasConfirmed ( bytes32 _operation , address _owner ) constant returns ( bool ) {
var pending = m_pending [ _operation ] ;
uint ownerIndex = m_ownerIndex [ 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 ;
if ( pending . ownersDone & ownerIndexBit = = 0 ) {
return false ;
} else {
return true ;
}
}
// INTERNAL METHODS
function confirmAndCheck ( bytes32 _operation ) internal returns ( bool ) {
// determine what index the present sender is:
uint ownerIndex = m_ownerIndex [ uint ( msg . sender ) ] ;
// make sure they're an owner
@ -132,42 +213,7 @@ contract multiowned {
}
}
}
// Replaces an owner `_from` with another `_to`.
function changeOwner ( address _from , address _to ) onlymanyowners ( sha3 ( msg . data ) ) external {
if ( isOwner ( _to ) ) return ;
uint ownerIndex = m_ownerIndex [ uint ( _from ) ] ;
if ( ownerIndex = = 0 ) return ;
clearPending ( ) ;
m_owners [ ownerIndex ] = uint ( _to ) ;
m_ownerIndex [ uint ( _from ) ] = 0 ;
m_ownerIndex [ uint ( _to ) ] = ownerIndex ;
OwnerChanged ( _from , _to ) ;
}
function addOwner ( address _owner ) onlymanyowners ( sha3 ( msg . data ) ) external {
if ( isOwner ( _owner ) ) return ;
clearPending ( ) ;
if ( m_numOwners > = c_maxOwners )
reorganizeOwners ( ) ;
if ( m_numOwners > = c_maxOwners )
return ;
m_numOwners + + ;
m_owners [ m_numOwners ] = uint ( _owner ) ;
m_ownerIndex [ uint ( _owner ) ] = m_numOwners ;
OwnerAdded ( _owner ) ;
}
function removeOwner ( address _owner ) onlymanyowners ( sha3 ( msg . data ) ) external {
uint ownerIndex = m_ownerIndex [ uint ( _owner ) ] ;
if ( ownerIndex = = 0 ) return ;
if ( m_required > m_numOwners - 1 ) return ;
m_owners [ ownerIndex ] = 0 ;
m_ownerIndex [ 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 ) ;
}
function reorganizeOwners ( ) private returns ( bool ) {
uint free = 1 ;
while ( free < m_numOwners )
@ -182,6 +228,7 @@ contract multiowned {
}
}
}
function clearPending ( ) internal {
uint length = m_pendingIndex . length ;
for ( uint i = 0 ; i < length ; + + i )
@ -189,46 +236,54 @@ contract multiowned {
delete m_pending [ m_pendingIndex [ i ] ] ;
delete m_pendingIndex ;
}
function changeRequirement ( uint _newRequired ) onlymanyowners ( sha3 ( msg . data ) ) external {
if ( _newRequired > m_numOwners ) return ;
m_required = _newRequired ;
clearPending ( ) ;
RequirementChanged ( _newRequired ) ;
}
function isOwner ( address _addr ) returns ( bool ) {
return m_ownerIndex [ uint ( _addr ) ] > 0 ;
}
// FIELDS
// the number of owners that must confirm the same operation before it is run.
uint public m_required ;
// pointer used to find a free slot in m_owners
uint public m_numOwners ;
// list of owners
uint [ 256 ] public m_owners ;
uint [ 256 ] m_owners ;
uint constant c_maxOwners = 250 ;
// index on the list of owners to allow reverse lookup
mapping ( uint = > uint ) public m_ownerIndex ;
mapping ( uint = > uint ) m_ownerIndex ;
// the ongoing operations.
mapping ( bytes32 = > PendingState ) public m_pending ;
bytes32 [ ] public m_pendingIndex ;
mapping ( bytes32 = > PendingState ) m_pending ;
bytes32 [ ] m_pendingIndex ;
}
// inheritable "property" contract that enables methods to be protected by placing a linear limit (specifiable)
// on a particular resource per calendar day. is multiowned to allow the limit to be altered. resource that method
// uses is specified in the modifier.
contract daylimit is multiowned {
// MODIFIERS
// simple modifier for daily limit.
modifier limitedDaily ( uint _value ) {
if ( underLimit ( _value ) )
_
}
// METHODS
// constructor - just records the present day's index.
function daylimit ( ) {
m_lastDay = today ( ) ;
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit ( uint _newLimit ) onlymanyowners ( sha3 ( msg . data ) ) external {
function setDailyLimit ( uint _newLimit ) onlymanyowners ( sha3 ( msg . data , block . number ) ) external {
m_dailyLimit = _newLimit ;
}
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function resetSpentToday ( ) onlymanyowners ( sha3 ( msg . data ) ) external {
function resetSpentToday ( ) onlymanyowners ( sha3 ( msg . data , block . number ) ) external {
m_spentToday = 0 ;
}
// INTERNAL METHODS
// checks to see if there is at least `_value` left from the daily limit today. if there is, subtracts it and
// returns true. otherwise just returns false.
function underLimit ( uint _value ) internal onlyowner returns ( bool ) {
@ -244,60 +299,76 @@ contract daylimit is multiowned {
}
return false ;
}
// simple modifier for daily limit.
modifier limitedDaily ( uint _value ) {
if ( underLimit ( _value ) )
_
}
// determines today's index.
function today ( ) private constant returns ( uint ) { return now / 1 days ; }
uint public m_spentToday ;
// FIELDS
uint public m_dailyLimit ;
uint public m_lastDay ;
uint m_spentToday ;
uint m_lastDay ;
}
// interface contract for multisig proxy contracts; see below for docs.
contract multisig {
// EVENTS
// logged events:
// Funds has arrived into the wallet (record how much).
event Deposit ( address from , uint value ) ;
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact ( address owner , uint value , address to , bytes data ) ;
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact ( address owner , bytes32 operation , uint value , address to , bytes data ) ;
// Confirmation still needed for a transaction.
event ConfirmationNeeded ( bytes32 operation , address initiator , uint value , address to , bytes data ) ;
// FUNCTIONS
// TODO: document
function changeOwner ( address _from , address _to ) external ;
function execute ( address _to , uint _value , bytes _data ) external returns ( bytes32 ) ;
function confirm ( bytes32 _h ) returns ( bool ) ;
}
// usage:
// bytes32 h = Wallet(w).from(oneOwner).transact(to, value, data);
// Wallet(w).from(anotherOwner).confirm(h);
contract Wallet is multisig , multiowned , daylimit {
// TYPES
// Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction {
address to ;
uint value ;
bytes data ;
}
/*
// logged events:
// Funds has arrived into the wallet (record how much).
event Deposit ( address from , uint value ) ;
// Single transaction going out of the wallet (record who signed for it, how much, and to whom it's going).
event SingleTransact ( address owner , uint value , address to , bytes data ) ;
// Multi-sig transaction going out of the wallet (record who signed for it last, the operation hash, how much, and to whom it's going).
event MultiTransact ( address owner , bytes32 operation , uint value , address to , bytes data ) ; */
// EVENTS
event Created ( bytes32 indexed identifier ) ;
// METHODS
// constructor - just pass on the owner arra to the multiowned.
event Created ( ) ;
function Wallet ( ) {
Created ( ) ;
function Wallet ( bytes32 identifier ) {
Created ( identifier ) ;
}
// kills the contract sending everything to `_to`.
function kill ( address _to ) onlymanyowners ( sha3 ( msg . data ) ) external {
function kill ( address _to ) onlymanyowners ( sha3 ( msg . data , block . number ) ) external {
suicide ( _to ) ;
}
// gets called when no other function matches
function ( ) {
// just being sent some cash?
if ( msg . value > 0 )
Deposit ( msg . sender , msg . value ) ;
}
// Outside-visible transact entry point. Executes transacion immediately if below daily spend limit.
// If not, goes into multisig process. We provide a hash on return to allow the sender to provide
// shortcuts for the other confirmations (allowing them to avoid replicating the _to, _value
@ -311,7 +382,7 @@ contract Wallet is multisig, multiowned, daylimit {
return 0 ;
}
// determine our operation hash.
_r = sha3 ( msg . data ) ;
_r = sha3 ( msg . data , block . number ) ;
if ( ! confirm ( _r ) & & m_txs [ _r ] . to = = 0 ) {
m_txs [ _r ] . to = _to ;
m_txs [ _r ] . value = _value ;
@ -319,6 +390,7 @@ contract Wallet is multisig, multiowned, daylimit {
ConfirmationNeeded ( _r , msg . sender , _value , _to , _data ) ;
}
}
// confirm a transaction through just the hash. we use the previous transactions map, m_txs, in order
// to determine the body of the transaction from the hash provided.
function confirm ( bytes32 _h ) onlymanyowners ( _h ) returns ( bool ) {
@ -329,20 +401,20 @@ contract Wallet is multisig, multiowned, daylimit {
return true ;
}
}
// INTERNAL METHODS
function clearPending ( ) internal {
uint length = m_pendingIndex . length ;
for ( uint i = 0 ; i < length ; + + i )
delete m_txs [ m_pendingIndex [ i ] ] ;
super . clearPending ( ) ;
}
// // internally confirm transaction with all of the info. returns true iff confirmed good and executed.
// function confirmVerbose(bytes32 _h, address _to, uint _value, bytes _data) private onlymanyowners(_h) returns (bool) {
// _to.call.value(_value)(_data);
// MultiTransact("out", msg.sender, _h, _value, _to);
// return true;
// }
// FIELDS
// pending transactions we have at present.
mapping ( bytes32 = > Transaction ) public m_txs ;
mapping ( bytes32 = > Transaction ) m_txs ;
}
) DELIMITER " ;
@ -443,7 +515,7 @@ BOOST_AUTO_TEST_CASE(multisig_value_transfer)
// 4 owners, set required to 3
BOOST_REQUIRE ( callContractFunction ( " changeRequirement(uint256) " , u256 ( 3 ) ) = = encodeArgs ( ) ) ;
// check that balance is and stays zero at destination address
h256 opHash ( " f916231db11c12e0142dc51f23632bc655de87c63f83fc928c443e90f7aa364a " ) ;
h256 opHash ( " 8f27f478ebcfaf28b0c354f4809ace8087000d668b89c8bc3b1b608bfdbe6654 " ) ;
BOOST_CHECK_EQUAL ( m_state . balance ( Address ( 0x05 ) ) , 0 ) ;
m_sender = Address ( 0x12 ) ;
BOOST_REQUIRE ( callContractFunction ( " execute(address,uint256,bytes) " , h256 ( 0x05 ) , 100 , 0x60 , 0x00 ) = = encodeArgs ( opHash ) ) ;