//sol Wallet
// Multi-sig, daily-limited account proxy/wallet.
// @authors:
// Gav Wood <g@ethdev.com>
// inheritable "property" contract that enables methods to be protected by requiring the acquiescence of either a
// single, or, crucially, each of a number of, designated owners.
// usage:
// use modifiers onlyowner (just own owned) or onlymanyowners(hash), whereby the same hash must be provided by
// some number (specified in constructor) of the set of owners (specified in the constructor, modifiable) before the
// interior is executed.
pragma solidity 0 . 5 . 0 ;
contract WalletEvents {
// 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 ) ;
// 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 , address created ) ;
// 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 , address created ) ;
// Confirmation still needed for a transaction.
event ConfirmationNeeded ( bytes32 operation , address initiator , uint value , address to , bytes data ) ;
}
contract WalletAbi {
// Revokes a prior confirmation of the given operation
function revoke ( bytes32 _operation ) external ;
// Replaces an owner `_from` with another `_to`.
function changeOwner ( address _from , address _to ) external ;
function addOwner ( address _owner ) external ;
function removeOwner ( address _owner ) external ;
function changeRequirement ( uint _newRequired ) external ;
function isOwner ( address _addr ) public returns ( bool ) ;
function hasConfirmed ( bytes32 _operation , address _owner ) external returns ( bool ) ;
// (re)sets the daily limit. needs many of the owners to confirm. doesn't alter the amount already spent today.
function setDailyLimit ( uint _newLimit ) external ;
function execute ( address _to , uint _value , bytes calldata _data ) external returns ( bytes32 o_hash ) ;
function confirm ( bytes32 _h ) public returns ( bool o_success ) ;
}
contract WalletLibrary is WalletEvents {
// TYPES
// struct for the status of a pending operation.
struct PendingState {
uint yetNeeded ;
uint ownersDone ;
uint index ;
}
// Transaction structure to remember details of transaction lest it need be saved for a later call.
struct Transaction {
address to ;
uint value ;
bytes data ;
}
// MODIFIERS
// simple single-sig function modifier.
modifier onlyowner {
if ( isOwner ( msg . sender ) )
_ ;
}
// multi-sig 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 ( bytes32 _operation ) {
if ( confirmAndCheck ( _operation ) )
_ ;
}
// METHODS
// gets called when no other function matches
function ( ) external payable {
// just being sent some cash?
if ( msg . value > 0 )
emit Deposit ( msg . sender , msg . value ) ;
}
// constructor is given number of sigs required to do protected "onlymanyowners" transactions
// as well as the selection of addresses capable of confirming them.
function initMultiowned ( address [ ] memory _owners , uint _required ) public only_uninitialized {
m_numOwners = _owners . length + 1 ;
m_owners [ 1 ] = uint ( msg . sender ) ;
m_ownerIndex [ uint ( msg . sender ) ] = 1 ;
for ( uint i = 0 ; i < _owners . length ; ++ i )
{
m_owners [ 2 + i ] = uint ( _owners [ i ] ) ;
m_ownerIndex [ uint ( _owners [ i ] ) ] = 2 + i ;
}
m_required = _required ;
}
// Revokes a prior confirmation of the given operation
function revoke ( bytes32 _operation ) external {
uint ownerIndex = m_ownerIndex [ uint ( msg . sender ) ] ;
// make sure they're an owner
if ( ownerIndex == 0 ) return ;
uint ownerIndexBit = 2 ** ownerIndex ;
PendingState memory pending = m_pending [ _operation ] ;
if ( pending . ownersDone & ownerIndexBit > 0 ) {
pending . yetNeeded ++ ;
pending . ownersDone -= ownerIndexBit ;
emit Revoke ( msg . sender , _operation ) ;
}
}
// Replaces an owner `_from` with another `_to`.
function changeOwner ( address _from , address _to ) onlymanyowners ( keccak256 ( 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 ;
emit OwnerChanged ( _from , _to ) ;
}
function addOwner ( address _owner ) onlymanyowners ( keccak256 ( 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 ;
emit OwnerAdded ( _owner ) ;
}
function removeOwner ( address _owner ) onlymanyowners ( keccak256 ( 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
emit OwnerRemoved ( _owner ) ;
}
function changeRequirement ( uint _newRequired ) onlymanyowners ( keccak256 ( msg . data ) ) external {
if ( _newRequired > m_numOwners ) return ;
m_required = _newRequired ;
clearPending ( ) ;
emit RequirementChanged ( _newRequired ) ;
}
// Gets an owner by 0-indexed position (using numOwners as the count)
function getOwner ( uint ownerIndex ) external view returns ( address ) {
return address ( m_owners [ ownerIndex + 1 ] ) ;
}
function isOwner ( address _addr ) public view returns ( bool ) {
return m_ownerIndex [ uint ( _addr ) ] > 0 ;
}
function hasConfirmed ( bytes32 _operation , address _owner ) external view returns ( bool ) {
PendingState memory 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 ;
return ! ( pending . ownersDone & ownerIndexBit == 0 ) ;
}
// constructor - stores initial daily limit and records the present day's index.
function initDaylimit ( uint _limit ) public only_uninitialized {
m_dailyLimit = _limit ;
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 ( keccak256 ( msg . data ) ) external {
m_dailyLimit = _newLimit ;
}
// resets the amount already spent today. needs many of the owners to confirm.
function resetSpentToday ( ) onlymanyowners ( keccak256 ( msg . data ) ) external {
m_spentToday = 0 ;
}
// throw unless the contract is not yet initialized.
modifier only_uninitialized { require ( m_numOwners > 0 ) ; _ ; }
// constructor - just pass on the owner array to the multiowned and
// the limit to daylimit
function initWallet ( address [ ] memory _owners , uint _required , uint _daylimit ) public only_uninitialized {
initDaylimit ( _daylimit ) ;
initMultiowned ( _owners , _required ) ;
}
// kills the contract sending everything to `_to`.
function kill ( address payable _to ) onlymanyowners ( keccak256 ( msg . data ) ) external {
selfdestruct ( _to ) ;
}
// Outside-visible transact entry point. Executes transaction 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
// and _data arguments). They still get the option of using them if they want, anyways.
function execute ( address _to , uint _value , bytes calldata _data ) external onlyowner returns ( bytes32 o_hash ) {
// first, take the opportunity to check that we're under the daily limit.
if ( ( _data . length == 0 && underLimit ( _value ) ) || m_required == 1 ) {
// yes - just execute the call.
address created ;
if ( _to == address ( 0 ) ) {
created = create ( _value , _data ) ;
} else {
( bool success , bytes memory data ) = _to . call . value ( _value ) ( _data ) ;
require ( success ) ;
}
emit SingleTransact ( msg . sender , _value , _to , _data , created ) ;
} else {
// determine our operation hash.
o_hash = keccak256 ( abi . encode ( msg . data , block . number ) ) ;
// store if it's new
if ( m_txs [ o_hash ] . to == address ( 0 ) && m_txs [ o_hash ] . value == 0 && m_txs [ o_hash ] . data . length == 0 ) {
m_txs [ o_hash ] . to = _to ;
m_txs [ o_hash ] . value = _value ;
m_txs [ o_hash ] . data = _data ;
}
if ( ! confirm ( o_hash ) ) {
emit ConfirmationNeeded ( o_hash , msg . sender , _value , _to , _data ) ;
}
}
}
function create ( uint _value , bytes memory _code ) internal returns ( address o_addr ) {
uint256 o_size ;
assembly {
o_addr : = create ( _value , add ( _code , 0x20 ) , mload ( _code ) )
o_size : = extcodesize ( o_addr )
}
require ( o_size != 0 ) ;
}
// 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 ) public onlymanyowners ( _h ) returns ( bool o_success ) {
if ( m_txs [ _h ] . to != address ( 0 ) || m_txs [ _h ] . value != 0 || m_txs [ _h ] . data . length != 0 ) {
address created ;
if ( m_txs [ _h ] . to == address ( 0 ) ) {
created = create ( m_txs [ _h ] . value , m_txs [ _h ] . data ) ;
} else {
( bool success , bytes memory data ) = m_txs [ _h ] . to . call . value ( m_txs [ _h ] . value ) ( m_txs [ _h ] . data ) ;
require ( success ) ;
}
emit MultiTransact ( msg . sender , _h , m_txs [ _h ] . value , m_txs [ _h ] . to , m_txs [ _h ] . data , created ) ;
delete m_txs [ _h ] ;
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
if ( ownerIndex == 0 ) return false ;
PendingState memory pending = m_pending [ _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 = m_required ;
// reset which owners have confirmed (none) - set our bitmap to 0.
pending . ownersDone = 0 ;
pending . index = m_pendingIndex . length ++ ;
m_pendingIndex [ 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 ) {
emit Confirmation ( msg . sender , _operation ) ;
// ok - check if count is enough to go ahead.
if ( pending . yetNeeded <= 1 ) {
// enough confirmations: reset and run interior.
delete m_pendingIndex [ m_pending [ _operation ] . index ] ;
delete m_pending [ _operation ] ;
return true ;
}
else
{
// not enough: record that this owner in particular confirmed.
pending . yetNeeded -- ;
pending . ownersDone |= ownerIndexBit ;
}
}
}
function reorganizeOwners ( ) private {
uint free = 1 ;
while ( free < m_numOwners )
{
while ( free < m_numOwners && m_owners [ free ] != 0 ) free ++ ;
while ( m_numOwners > 1 && m_owners [ m_numOwners ] == 0 ) m_numOwners -- ;
if ( free < m_numOwners && m_owners [ m_numOwners ] != 0 && m_owners [ free ] == 0 )
{
m_owners [ free ] = m_owners [ m_numOwners ] ;
m_ownerIndex [ m_owners [ free ] ] = free ;
m_owners [ m_numOwners ] = 0 ;
}
}
}
// 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 ) {
// reset the spend limit if we're on a different day to last time.
if ( today ( ) > m_lastDay ) {
m_spentToday = 0 ;
m_lastDay = today ( ) ;
}
// check to see if there's enough left - if so, subtract and return true.
// overflow protection // dailyLimit check
if ( m_spentToday + _value >= m_spentToday && m_spentToday + _value <= m_dailyLimit ) {
m_spentToday += _value ;
return true ;
}
return false ;
}
// determines today's index.
function today ( ) private view returns ( uint ) { return now / 1 days ; }
function clearPending ( ) internal {
uint length = m_pendingIndex . length ;
for ( uint i = 0 ; i < length ; ++ i ) {
delete m_txs [ m_pendingIndex [ i ] ] ;
if ( m_pendingIndex [ i ] != 0 )
delete m_pending [ m_pendingIndex [ i ] ] ;
}
delete m_pendingIndex ;
}
// FIELDS
address _walletLibrary = 0xCAfEcAfeCAfECaFeCaFecaFecaFECafECafeCaFe ;
// 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 ;
uint public m_dailyLimit ;
uint public m_spentToday ;
uint public m_lastDay ;
// list of owners
uint [ 256 ] m_owners ;
uint c_maxOwners = 250 ;
// index on the list of owners to allow reverse lookup
mapping ( uint => uint ) m_ownerIndex ;
// the ongoing operations.
mapping ( bytes32 => PendingState ) m_pending ;
bytes32 [ ] m_pendingIndex ;
// pending transactions we have at present.
mapping ( bytes32 => Transaction ) m_txs ;
}