|
|
@ -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)); |
|
|
|