49fdd23d58
* Move secureApi to shell * Extract isTestnet test * Use mobx + subscriptions for status * Re-add status indicator * Add lerna * Move intial packages to js/packages * Move 3rdparty/{email,sms}-verification to correct location * Move package.json & README to library src * Move tests for library packages * Move views & dapps to packages * Move i18n to root * Move shell to actual src (main app) * Remove ~ references * Change ~ to root (explicit imports) * Finalise convert of ~ * Move views into dapps as well * Move dapps to packages/ * Fix references * Update css * Update test spec locations * Update tests * Case fix * Skip flakey tests * Update enzyme * Skip previously ignored tests * Allow empty api for hw * Re-add theme for embed
465 lines
16 KiB
Solidity
465 lines
16 KiB
Solidity
//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.4.9;
|
|
|
|
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) constant returns (bool);
|
|
|
|
function hasConfirmed(bytes32 _operation, address _owner) external constant 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 _data) external returns (bytes32 o_hash);
|
|
function confirm(bytes32 _h) 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() payable {
|
|
// just being sent some cash?
|
|
if (msg.value > 0)
|
|
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[] _owners, uint _required) 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;
|
|
var pending = m_pending[_operation];
|
|
if (pending.ownersDone & ownerIndexBit > 0) {
|
|
pending.yetNeeded++;
|
|
pending.ownersDone -= ownerIndexBit;
|
|
Revoke(msg.sender, _operation);
|
|
}
|
|
}
|
|
|
|
// 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 changeRequirement(uint _newRequired) onlymanyowners(sha3(msg.data)) external {
|
|
if (_newRequired > m_numOwners) return;
|
|
m_required = _newRequired;
|
|
clearPending();
|
|
RequirementChanged(_newRequired);
|
|
}
|
|
|
|
// Gets an owner by 0-indexed position (using numOwners as the count)
|
|
function getOwner(uint ownerIndex) external constant returns (address) {
|
|
return address(m_owners[ownerIndex + 1]);
|
|
}
|
|
|
|
function isOwner(address _addr) constant returns (bool) {
|
|
return m_ownerIndex[uint(_addr)] > 0;
|
|
}
|
|
|
|
function hasConfirmed(bytes32 _operation, address _owner) external 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;
|
|
return !(pending.ownersDone & ownerIndexBit == 0);
|
|
}
|
|
|
|
// constructor - stores initial daily limit and records the present day's index.
|
|
function initDaylimit(uint _limit) 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(sha3(msg.data)) external {
|
|
m_dailyLimit = _newLimit;
|
|
}
|
|
// resets the amount already spent today. needs many of the owners to confirm.
|
|
function resetSpentToday() onlymanyowners(sha3(msg.data)) external {
|
|
m_spentToday = 0;
|
|
}
|
|
|
|
// throw unless the contract is not yet initialized.
|
|
modifier only_uninitialized { if (m_numOwners > 0) throw; _; }
|
|
|
|
// constructor - just pass on the owner array to the multiowned and
|
|
// the limit to daylimit
|
|
function initWallet(address[] _owners, uint _required, uint _daylimit) only_uninitialized {
|
|
initDaylimit(_daylimit);
|
|
initMultiowned(_owners, _required);
|
|
}
|
|
|
|
// kills the contract sending everything to `_to`.
|
|
function kill(address _to) onlymanyowners(sha3(msg.data)) external {
|
|
suicide(_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 _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 == 0) {
|
|
created = create(_value, _data);
|
|
} else {
|
|
if (!_to.call.value(_value)(_data))
|
|
throw;
|
|
}
|
|
SingleTransact(msg.sender, _value, _to, _data, created);
|
|
} else {
|
|
// determine our operation hash.
|
|
o_hash = sha3(msg.data, block.number);
|
|
// store if it's new
|
|
if (m_txs[o_hash].to == 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)) {
|
|
ConfirmationNeeded(o_hash, msg.sender, _value, _to, _data);
|
|
}
|
|
}
|
|
}
|
|
|
|
function create(uint _value, bytes _code) internal returns (address o_addr) {
|
|
assembly {
|
|
o_addr := create(_value, add(_code, 0x20), mload(_code))
|
|
jumpi(invalidJumpLabel, iszero(extcodesize(o_addr)))
|
|
}
|
|
}
|
|
|
|
// 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 o_success) {
|
|
if (m_txs[_h].to != 0 || m_txs[_h].value != 0 || m_txs[_h].data.length != 0) {
|
|
address created;
|
|
if (m_txs[_h].to == 0) {
|
|
created = create(m_txs[_h].value, m_txs[_h].data);
|
|
} else {
|
|
if (!m_txs[_h].to.call.value(m_txs[_h].value)(m_txs[_h].data))
|
|
throw;
|
|
}
|
|
|
|
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;
|
|
|
|
var 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) {
|
|
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 constant 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 constant _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 constant 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;
|
|
}
|
|
|
|
contract Wallet is WalletEvents {
|
|
|
|
// WALLET CONSTRUCTOR
|
|
// calls the `initWallet` method of the Library in this context
|
|
function Wallet(address[] _owners, uint _required, uint _daylimit) {
|
|
// Signature of the Wallet Library's init function
|
|
bytes4 sig = bytes4(sha3("initWallet(address[],uint256,uint256)"));
|
|
address target = _walletLibrary;
|
|
|
|
// Compute the size of the call data : arrays has 2
|
|
// 32bytes for offset and length, plus 32bytes per element ;
|
|
// plus 2 32bytes for each uint
|
|
uint argarraysize = (2 + _owners.length);
|
|
uint argsize = (2 + argarraysize) * 32;
|
|
|
|
assembly {
|
|
// Add the signature first to memory
|
|
mstore(0x0, sig)
|
|
// Add the call data, which is at the end of the
|
|
// code
|
|
codecopy(0x4, sub(codesize, argsize), argsize)
|
|
// Delegate call to the library
|
|
delegatecall(sub(gas, 10000), target, 0x0, add(argsize, 0x4), 0x0, 0x0)
|
|
}
|
|
}
|
|
|
|
// METHODS
|
|
|
|
// gets called when no other function matches
|
|
function() payable {
|
|
// just being sent some cash?
|
|
if (msg.value > 0)
|
|
Deposit(msg.sender, msg.value);
|
|
else if (msg.data.length > 0)
|
|
_walletLibrary.delegatecall(msg.data);
|
|
}
|
|
|
|
// Gets an owner by 0-indexed position (using numOwners as the count)
|
|
function getOwner(uint ownerIndex) constant returns (address) {
|
|
return address(m_owners[ownerIndex + 1]);
|
|
}
|
|
|
|
// As return statement unavailable in fallback, explicit the method here
|
|
|
|
function hasConfirmed(bytes32 _operation, address _owner) external constant returns (bool) {
|
|
return _walletLibrary.delegatecall(msg.data);
|
|
}
|
|
|
|
function isOwner(address _addr) constant returns (bool) {
|
|
return _walletLibrary.delegatecall(msg.data);
|
|
}
|
|
|
|
// FIELDS
|
|
address constant _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;
|
|
}
|