commit
13d380863e
|
@ -0,0 +1,72 @@
|
||||||
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title DelegatedCall
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* Abstract contract that delegates all calls to contract returned by abstract function `_getDelegatedContract`
|
||||||
|
*/
|
||||||
|
contract DelegatedCall {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev delegates the call of this function
|
||||||
|
*/
|
||||||
|
modifier delegated
|
||||||
|
{
|
||||||
|
uint inSize = msg.data.length;
|
||||||
|
bytes32 inDataPtr = _malloc(inSize);
|
||||||
|
|
||||||
|
assembly {
|
||||||
|
calldatacopy(inDataPtr, 0x0, inSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes32 outDataPtr;
|
||||||
|
uint256 outSize;
|
||||||
|
|
||||||
|
(outDataPtr, outSize) = _delegatecall(inDataPtr, inSize);
|
||||||
|
_;
|
||||||
|
assembly {
|
||||||
|
return(outDataPtr, outSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev defines the address for delegation of calls
|
||||||
|
*/
|
||||||
|
function _getDelegatedContract()
|
||||||
|
internal
|
||||||
|
returns(address);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev allocates memory to a pointer
|
||||||
|
*/
|
||||||
|
function _malloc(uint size)
|
||||||
|
internal
|
||||||
|
returns(bytes32 ptr)
|
||||||
|
{
|
||||||
|
assembly {
|
||||||
|
ptr := mload(0x40)
|
||||||
|
mstore(0x40, add(ptr, size))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev delegates the data in pointer
|
||||||
|
*/
|
||||||
|
function _delegatecall(bytes32 inDataPtr, uint inSize)
|
||||||
|
internal
|
||||||
|
returns(bytes32 outDataPtr, uint256 outSize)
|
||||||
|
{
|
||||||
|
address target = _getDelegatedContract();
|
||||||
|
bool failed;
|
||||||
|
assembly {
|
||||||
|
failed := iszero(delegatecall(sub(gas, 10000), target, inDataPtr, inSize, 0, 0))
|
||||||
|
outSize := returndatasize
|
||||||
|
}
|
||||||
|
require(!failed);
|
||||||
|
outDataPtr = _malloc(outSize);
|
||||||
|
assembly {
|
||||||
|
returndatacopy(outDataPtr, 0, outSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
pragma solidity ^0.4.15;
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
contract ERC20 {
|
contract ERC20 {
|
||||||
uint256 public totalSupply;
|
uint256 public totalSupply;
|
||||||
function balanceOf(address who) constant returns (uint256);
|
function balanceOf(address who) public constant returns (uint256);
|
||||||
function allowance(address owner, address spender) constant returns (uint256);
|
function allowance(address owner, address spender) public constant returns (uint256);
|
||||||
function transfer(address to, uint256 value) returns (bool ok);
|
function transfer(address to, uint256 value) public returns (bool ok);
|
||||||
function transferFrom(address from, address to, uint256 value) returns (bool ok);
|
function transferFrom(address from, address to, uint256 value) public returns (bool ok);
|
||||||
function approve(address spender, uint256 value) returns (bool ok);
|
function approve(address spender, uint256 value) public returns (bool ok);
|
||||||
event Transfer(address indexed from, address indexed to, uint256 value);
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
||||||
event Approval(address indexed owner, address indexed spender, uint256 value);
|
event Approval(address indexed owner, address indexed spender, uint256 value);
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
pragma solidity ^0.4.15;
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
import "./MultiSigStub.sol";
|
import "./MultiSigStub.sol";
|
||||||
|
|
||||||
|
@ -6,7 +6,10 @@ contract MultiSigFactory {
|
||||||
|
|
||||||
event Create(address indexed caller, address createdContract);
|
event Create(address indexed caller, address createdContract);
|
||||||
|
|
||||||
function create(address[] owners, uint256 required) returns (address wallet) {
|
function create(address[] owners, uint256 required)
|
||||||
|
public
|
||||||
|
returns (address wallet)
|
||||||
|
{
|
||||||
wallet = new MultiSigStub(owners, required);
|
wallet = new MultiSigStub(owners, required);
|
||||||
Create(msg.sender, wallet);
|
Create(msg.sender, wallet);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,13 @@
|
||||||
pragma solidity ^0.4.15;
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
|
import "./DelegatedCall.sol";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @title MultiSigStub
|
* @title MultiSigStub
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
* Contract that delegates calls to a library to build a full MultiSigWallet that is cheap to create.
|
* Contract that delegates calls to a library to build a full MultiSigWallet that is cheap to create.
|
||||||
*/
|
*/
|
||||||
contract MultiSigStub {
|
contract MultiSigStub is DelegatedCall {
|
||||||
|
|
||||||
address[] public owners;
|
|
||||||
address[] public tokens;
|
|
||||||
mapping (uint => Transaction) public transactions;
|
|
||||||
mapping (uint => mapping (address => bool)) public confirmations;
|
|
||||||
uint public transactionCount;
|
|
||||||
|
|
||||||
struct Transaction {
|
|
||||||
address destination;
|
|
||||||
uint value;
|
|
||||||
bytes data;
|
|
||||||
bool executed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function MultiSigStub(address[] _owners, uint256 _required) {
|
function MultiSigStub(address[] _owners, uint256 _required) {
|
||||||
//bytes4 sig = bytes4(sha3("constructor(address[],uint256)"));
|
//bytes4 sig = bytes4(sha3("constructor(address[],uint256)"));
|
||||||
|
@ -34,21 +24,6 @@ contract MultiSigStub {
|
||||||
_delegatecall(mData, size);
|
_delegatecall(mData, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier delegated {
|
|
||||||
uint size = msg.data.length;
|
|
||||||
bytes32 mData = _malloc(size);
|
|
||||||
|
|
||||||
assembly {
|
|
||||||
calldatacopy(mData, 0x0, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
bytes32 mResult = _delegatecall(mData, size);
|
|
||||||
_;
|
|
||||||
assembly {
|
|
||||||
return(mResult, 0x20)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function()
|
function()
|
||||||
payable
|
payable
|
||||||
delegated
|
delegated
|
||||||
|
@ -56,34 +31,6 @@ contract MultiSigStub {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitTransaction(address destination, uint value, bytes data)
|
|
||||||
public
|
|
||||||
delegated
|
|
||||||
returns (uint)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmTransaction(uint transactionId)
|
|
||||||
public
|
|
||||||
delegated
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function watch(address _tokenAddr)
|
|
||||||
public
|
|
||||||
delegated
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMyTokenList(address[] _tokenList)
|
|
||||||
public
|
|
||||||
delegated
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @dev Returns the confirmation status of a transaction.
|
/// @dev Returns the confirmation status of a transaction.
|
||||||
/// @param transactionId Transaction ID.
|
/// @param transactionId Transaction ID.
|
||||||
/// @return Confirmation status.
|
/// @return Confirmation status.
|
||||||
|
@ -99,15 +46,6 @@ contract MultiSigStub {
|
||||||
/*
|
/*
|
||||||
* Web3 call functions
|
* Web3 call functions
|
||||||
*/
|
*/
|
||||||
function tokenBalances(address tokenAddress)
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
delegated
|
|
||||||
returns (uint)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @dev Returns number of confirmations of a transaction.
|
/// @dev Returns number of confirmations of a transaction.
|
||||||
/// @param transactionId Transaction ID.
|
/// @param transactionId Transaction ID.
|
||||||
|
@ -139,19 +77,10 @@ contract MultiSigStub {
|
||||||
function getOwners()
|
function getOwners()
|
||||||
public
|
public
|
||||||
constant
|
constant
|
||||||
|
delegated
|
||||||
returns (address[])
|
returns (address[])
|
||||||
{
|
{
|
||||||
return owners;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Returns list of tokens.
|
|
||||||
/// @return List of token addresses.
|
|
||||||
function getTokenList()
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns (address[])
|
|
||||||
{
|
|
||||||
return tokens;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Returns array with owner addresses, which confirmed transaction.
|
/// @dev Returns array with owner addresses, which confirmed transaction.
|
||||||
|
@ -160,21 +89,10 @@ contract MultiSigStub {
|
||||||
function getConfirmations(uint transactionId)
|
function getConfirmations(uint transactionId)
|
||||||
public
|
public
|
||||||
constant
|
constant
|
||||||
|
delegated
|
||||||
returns (address[] _confirmations)
|
returns (address[] _confirmations)
|
||||||
{
|
{
|
||||||
address[] memory confirmationsTemp = new address[](owners.length);
|
|
||||||
uint count = 0;
|
|
||||||
uint i;
|
|
||||||
for (i = 0; i < owners.length; i++) {
|
|
||||||
if (confirmations[transactionId][owners[i]]) {
|
|
||||||
confirmationsTemp[count] = owners[i];
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_confirmations = new address[](count);
|
|
||||||
for (i = 0; i < count; i++) {
|
|
||||||
_confirmations[i] = confirmationsTemp[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @dev Returns list of transaction IDs in defined range.
|
/// @dev Returns list of transaction IDs in defined range.
|
||||||
|
@ -186,47 +104,17 @@ contract MultiSigStub {
|
||||||
function getTransactionIds(uint from, uint to, bool pending, bool executed)
|
function getTransactionIds(uint from, uint to, bool pending, bool executed)
|
||||||
public
|
public
|
||||||
constant
|
constant
|
||||||
|
delegated
|
||||||
returns (uint[] _transactionIds)
|
returns (uint[] _transactionIds)
|
||||||
{
|
{
|
||||||
uint[] memory transactionIdsTemp = new uint[](transactionCount);
|
|
||||||
uint count = 0;
|
|
||||||
uint i;
|
|
||||||
for (i = 0; i < transactionCount; i++) {
|
|
||||||
if (pending && !transactions[i].executed || executed && transactions[i].executed) {
|
|
||||||
transactionIdsTemp[count] = i;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_transactionIds = new uint[](to - from);
|
|
||||||
for (i = from; i < to; i++) {
|
|
||||||
_transactionIds[i - from] = transactionIdsTemp[i];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _getDelegatedContract()
|
||||||
function _malloc(uint size)
|
internal
|
||||||
private
|
returns(address)
|
||||||
returns(bytes32 mData)
|
|
||||||
{
|
{
|
||||||
assembly {
|
return 0xCaFFE810d0dF52E27DC580AD4a3C6283B0094291; //hardcoded multinetwork address
|
||||||
mData := mload(0x40)
|
|
||||||
mstore(0x40, add(mData, size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _delegatecall(bytes32 mData, uint size)
|
|
||||||
private
|
|
||||||
returns(bytes32 mResult)
|
|
||||||
{
|
|
||||||
address target = 0xc0FFeEE61948d8993864a73a099c0E38D887d3F4; //Multinetwork
|
|
||||||
mResult = _malloc(32);
|
|
||||||
bool failed;
|
|
||||||
|
|
||||||
assembly {
|
|
||||||
failed := iszero(delegatecall(sub(gas, 10000), target, mData, size, mResult, 0x20))
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(!failed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,114 +1,30 @@
|
||||||
pragma solidity ^0.4.15;
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
|
import "./MultiSigWallet.sol";
|
||||||
import "./ERC20.sol";
|
import "./ERC20.sol";
|
||||||
|
|
||||||
contract MultiSigTokenWallet {
|
/**
|
||||||
|
* @title MultiSigTokenWallet
|
||||||
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
||||||
|
* MultiSigWallet that supports withdrawing all ERC20 tokens at once.
|
||||||
|
*/
|
||||||
|
contract MultiSigTokenWallet is MultiSigWallet {
|
||||||
|
|
||||||
address[] public owners;
|
event TokenDeposit(address indexed token, address indexed sender, uint value);
|
||||||
address[] public tokens;
|
|
||||||
mapping (uint => Transaction) public transactions;
|
|
||||||
mapping (uint => mapping (address => bool)) public confirmations;
|
|
||||||
uint public transactionCount;
|
|
||||||
|
|
||||||
mapping (address => uint) public tokenBalances;
|
|
||||||
mapping (address => bool) public isOwner;
|
|
||||||
mapping (address => address[]) public userList;
|
|
||||||
uint public required;
|
|
||||||
uint public nonce;
|
|
||||||
|
|
||||||
struct Transaction {
|
|
||||||
address destination;
|
|
||||||
uint value;
|
|
||||||
bytes data;
|
|
||||||
bool executed;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint constant public MAX_OWNER_COUNT = 50;
|
|
||||||
|
|
||||||
event Confirmation(address indexed _sender, uint indexed _transactionId);
|
|
||||||
event Revocation(address indexed _sender, uint indexed _transactionId);
|
|
||||||
event Submission(uint indexed _transactionId);
|
|
||||||
event Execution(uint indexed _transactionId);
|
|
||||||
event ExecutionFailure(uint indexed _transactionId);
|
|
||||||
event Deposit(address indexed _sender, uint _value);
|
|
||||||
event TokenDeposit(address _token, address indexed _sender, uint _value);
|
|
||||||
event OwnerAddition(address indexed _owner);
|
|
||||||
event OwnerRemoval(address indexed _owner);
|
|
||||||
event RequirementChange(uint _required);
|
|
||||||
|
|
||||||
modifier onlyWallet() {
|
|
||||||
require (msg.sender == address(this));
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier ownerDoesNotExist(address owner) {
|
|
||||||
require (!isOwner[owner]);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier ownerExists(address owner) {
|
|
||||||
require (isOwner[owner]);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier transactionExists(uint transactionId) {
|
|
||||||
require (transactions[transactionId].destination != 0);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier confirmed(uint transactionId, address owner) {
|
|
||||||
require (confirmations[transactionId][owner]);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier notConfirmed(uint transactionId, address owner) {
|
|
||||||
require(!confirmations[transactionId][owner]);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier notExecuted(uint transactionId) {
|
|
||||||
require (!transactions[transactionId].executed);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier notNull(address _address) {
|
|
||||||
require (_address != 0);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifier validRequirement(uint ownerCount, uint _required) {
|
|
||||||
require (ownerCount <= MAX_OWNER_COUNT && _required <= ownerCount && _required != 0 && ownerCount != 0);
|
|
||||||
_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Fallback function allows to deposit ether.
|
|
||||||
function()
|
|
||||||
payable
|
|
||||||
{
|
|
||||||
if (msg.value > 0)
|
|
||||||
Deposit(msg.sender, msg.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Public functions
|
* Public functions
|
||||||
*
|
*
|
||||||
**/
|
**/
|
||||||
/// @dev Contract constructor sets initial owners and required number of confirmations.
|
/**
|
||||||
/// @param _owners List of initial owners.
|
* @dev only call parent constructor
|
||||||
/// @param _required Number of required confirmations.
|
*/
|
||||||
function constructor(address[] _owners, uint _required)
|
function MultiSigTokenWallet(address[] _owners, uint _required)
|
||||||
|
MultiSigWallet(_owners,_required)
|
||||||
public
|
public
|
||||||
validRequirement(_owners.length, _required)
|
|
||||||
{
|
{
|
||||||
require(owners.length == 0 && required == 0);
|
//does nothing
|
||||||
for (uint i = 0; i < _owners.length; i++) {
|
|
||||||
require(!isOwner[_owners[i]] && _owners[i] != 0);
|
|
||||||
isOwner[_owners[i]] = true;
|
|
||||||
}
|
}
|
||||||
owners = _owners;
|
|
||||||
required = _required;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @notice deposit a ERC20 token. The amount of deposit is the allowance set to this contract.
|
* @notice deposit a ERC20 token. The amount of deposit is the allowance set to this contract.
|
||||||
* @param _token the token contract address
|
* @param _token the token contract address
|
||||||
|
@ -132,38 +48,9 @@ contract MultiSigTokenWallet {
|
||||||
{
|
{
|
||||||
if (_from == address(this))
|
if (_from == address(this))
|
||||||
return;
|
return;
|
||||||
uint _nonce = nonce;
|
|
||||||
bool result = ERC20(_token).transferFrom(_from, this, _amount);
|
bool result = ERC20(_token).transferFrom(_from, this, _amount);
|
||||||
assert(result);
|
require(result);
|
||||||
//ERC23 not executed _deposited tokenFallback by
|
TokenDeposit(_token, _from, _amount);
|
||||||
if (nonce == _nonce) {
|
|
||||||
_deposited(_from, _amount, _token, _data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @notice watches for balance in a token contract
|
|
||||||
* @param _tokenAddr the token contract address
|
|
||||||
**/
|
|
||||||
function watch(address _tokenAddr)
|
|
||||||
ownerExists(msg.sender)
|
|
||||||
{
|
|
||||||
uint oldBal = tokenBalances[_tokenAddr];
|
|
||||||
uint newBal = ERC20(_tokenAddr).balanceOf(this);
|
|
||||||
if (newBal > oldBal) {
|
|
||||||
_deposited(0x0, newBal-oldBal, _tokenAddr, new bytes(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMyTokenList(address[] _tokenList)
|
|
||||||
public
|
|
||||||
{
|
|
||||||
userList[msg.sender] = _tokenList;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTokenList(address[] _tokenList)
|
|
||||||
onlyWallet
|
|
||||||
{
|
|
||||||
tokens = _tokenList;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -171,10 +58,16 @@ contract MultiSigTokenWallet {
|
||||||
* @param _from address incoming token
|
* @param _from address incoming token
|
||||||
* @param _amount incoming amount
|
* @param _amount incoming amount
|
||||||
**/
|
**/
|
||||||
function tokenFallback(address _from, uint _amount, bytes _data)
|
function tokenFallback(
|
||||||
|
address _from,
|
||||||
|
uint _amount,
|
||||||
|
bytes _data
|
||||||
|
)
|
||||||
public
|
public
|
||||||
|
returns (bool)
|
||||||
{
|
{
|
||||||
_deposited(_from, _amount, msg.sender, _data);
|
TokenDeposit(msg.sender, _from, _amount);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -184,366 +77,50 @@ contract MultiSigTokenWallet {
|
||||||
* @param _token the token contract address
|
* @param _token the token contract address
|
||||||
* @param _data (might be used by child classes)
|
* @param _data (might be used by child classes)
|
||||||
*/
|
*/
|
||||||
function receiveApproval(address _from, uint256 _amount, address _token, bytes _data) {
|
function receiveApproval(
|
||||||
|
address _from,
|
||||||
|
uint256 _amount,
|
||||||
|
address _token,
|
||||||
|
bytes _data
|
||||||
|
)
|
||||||
|
public
|
||||||
|
{
|
||||||
deposit(_from, _amount, _token, _data);
|
deposit(_from, _amount, _token, _data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// @dev Allows to add a new owner. Transaction has to be sent by wallet.
|
|
||||||
/// @param owner Address of new owner.
|
|
||||||
function addOwner(address owner)
|
|
||||||
public
|
|
||||||
onlyWallet
|
|
||||||
ownerDoesNotExist(owner)
|
|
||||||
notNull(owner)
|
|
||||||
validRequirement(owners.length + 1, required)
|
|
||||||
{
|
|
||||||
isOwner[owner] = true;
|
|
||||||
owners.push(owner);
|
|
||||||
OwnerAddition(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Allows to remove an owner. Transaction has to be sent by wallet.
|
|
||||||
/// @param owner Address of owner.
|
|
||||||
function removeOwner(address owner)
|
|
||||||
public
|
|
||||||
onlyWallet
|
|
||||||
ownerExists(owner)
|
|
||||||
{
|
|
||||||
isOwner[owner] = false;
|
|
||||||
uint _len = owners.length - 1;
|
|
||||||
for (uint i = 0; i < _len; i++) {
|
|
||||||
if (owners[i] == owner) {
|
|
||||||
owners[i] = owners[owners.length - 1];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
owners.length -= 1;
|
|
||||||
if (required > owners.length)
|
|
||||||
changeRequirement(owners.length);
|
|
||||||
OwnerRemoval(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet.
|
|
||||||
/// @param owner Address of owner to be replaced.
|
|
||||||
/// @param owner Address of new owner.
|
|
||||||
function replaceOwner(address owner, address newOwner)
|
|
||||||
public
|
|
||||||
onlyWallet
|
|
||||||
ownerExists(owner)
|
|
||||||
ownerDoesNotExist(newOwner)
|
|
||||||
{
|
|
||||||
for (uint i = 0; i < owners.length; i++) {
|
|
||||||
if (owners[i] == owner) {
|
|
||||||
owners[i] = newOwner;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isOwner[owner] = false;
|
|
||||||
isOwner[newOwner] = true;
|
|
||||||
OwnerRemoval(owner);
|
|
||||||
OwnerAddition(newOwner);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev gives full ownership of this wallet to `_dest` removing older owners from wallet
|
* @dev withdraw all tokens in list and ether to `_dest`
|
||||||
* @param _dest the address of new controller
|
|
||||||
**/
|
|
||||||
function releaseWallet(address _dest)
|
|
||||||
public
|
|
||||||
notNull(_dest)
|
|
||||||
ownerDoesNotExist(_dest)
|
|
||||||
onlyWallet
|
|
||||||
{
|
|
||||||
address[] memory _owners = owners;
|
|
||||||
uint numOwners = _owners.length;
|
|
||||||
addOwner(_dest);
|
|
||||||
for (uint i = 0; i < numOwners; i++) {
|
|
||||||
removeOwner(_owners[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet.
|
|
||||||
/// @param _required Number of required confirmations.
|
|
||||||
function changeRequirement(uint _required)
|
|
||||||
public
|
|
||||||
onlyWallet
|
|
||||||
validRequirement(owners.length, _required)
|
|
||||||
{
|
|
||||||
required = _required;
|
|
||||||
RequirementChange(_required);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Allows an owner to submit and confirm a transaction.
|
|
||||||
/// @param destination Transaction target address.
|
|
||||||
/// @param value Transaction ether value.
|
|
||||||
/// @param data Transaction data payload.
|
|
||||||
/// @return Returns transaction ID.
|
|
||||||
function submitTransaction(address destination, uint value, bytes data)
|
|
||||||
public
|
|
||||||
returns (uint transactionId)
|
|
||||||
{
|
|
||||||
transactionId = addTransaction(destination, value, data);
|
|
||||||
confirmTransaction(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Allows an owner to confirm a transaction.
|
|
||||||
/// @param transactionId Transaction ID.
|
|
||||||
function confirmTransaction(uint transactionId)
|
|
||||||
public
|
|
||||||
ownerExists(msg.sender)
|
|
||||||
transactionExists(transactionId)
|
|
||||||
notConfirmed(transactionId, msg.sender)
|
|
||||||
{
|
|
||||||
confirmations[transactionId][msg.sender] = true;
|
|
||||||
Confirmation(msg.sender, transactionId);
|
|
||||||
executeTransaction(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Allows an owner to revoke a confirmation for a transaction.
|
|
||||||
/// @param transactionId Transaction ID.
|
|
||||||
function revokeConfirmation(uint transactionId)
|
|
||||||
public
|
|
||||||
ownerExists(msg.sender)
|
|
||||||
confirmed(transactionId, msg.sender)
|
|
||||||
notExecuted(transactionId)
|
|
||||||
{
|
|
||||||
confirmations[transactionId][msg.sender] = false;
|
|
||||||
Revocation(msg.sender, transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Allows anyone to execute a confirmed transaction.
|
|
||||||
/// @param transactionId Transaction ID.
|
|
||||||
function executeTransaction(uint transactionId)
|
|
||||||
public
|
|
||||||
notExecuted(transactionId)
|
|
||||||
{
|
|
||||||
if (isConfirmed(transactionId)) {
|
|
||||||
Transaction storage txx = transactions[transactionId];
|
|
||||||
txx.executed = true;
|
|
||||||
if (txx.destination.call.value(txx.value)(txx.data)) {
|
|
||||||
Execution(transactionId);
|
|
||||||
} else {
|
|
||||||
ExecutionFailure(transactionId);
|
|
||||||
txx.executed = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev withdraw all recognized tokens balances and ether to `_dest`
|
|
||||||
* @param _dest the address of receiver
|
* @param _dest the address of receiver
|
||||||
|
* @param _tokenList the list of tokens to withdraw all balance
|
||||||
**/
|
**/
|
||||||
function withdrawEverything(address _dest)
|
function withdrawEverything(address _dest, address[] _tokenList)
|
||||||
public
|
public
|
||||||
notNull(_dest)
|
notNull(_dest)
|
||||||
onlyWallet
|
onlyWallet
|
||||||
{
|
{
|
||||||
withdrawAllTokens(_dest);
|
withdrawAllTokens(_dest, _tokenList);
|
||||||
_dest.transfer(this.balance);
|
_dest.transfer(this.balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @dev withdraw all recognized tokens balances to `_dest`
|
* @dev withdraw all listed tokens balances to `_dest`
|
||||||
* @param _dest the address of receiver
|
* @param _dest the address of receiver
|
||||||
|
* @param _tokenList the list of tokens to withdraw all balance
|
||||||
**/
|
**/
|
||||||
function withdrawAllTokens(address _dest)
|
function withdrawAllTokens(address _dest, address[] _tokenList)
|
||||||
public
|
public
|
||||||
notNull(_dest)
|
notNull(_dest)
|
||||||
onlyWallet
|
onlyWallet
|
||||||
{
|
{
|
||||||
address[] memory _tokenList;
|
|
||||||
if (userList[_dest].length > 0) {
|
|
||||||
_tokenList = userList[_dest];
|
|
||||||
} else {
|
|
||||||
_tokenList = tokens;
|
|
||||||
}
|
|
||||||
uint len = _tokenList.length;
|
uint len = _tokenList.length;
|
||||||
for (uint i = 0;i < len; i++) {
|
for (uint i = 0;i < len; i++) {
|
||||||
address _tokenAddr = _tokenList[i];
|
address _tokenAddr = _tokenList[i];
|
||||||
uint _amount = tokenBalances[_tokenAddr];
|
uint _amount = ERC20(_tokenAddr).balanceOf(address(this));
|
||||||
if (_amount > 0) {
|
if (_amount > 0) {
|
||||||
delete tokenBalances[_tokenAddr];
|
|
||||||
ERC20(_tokenAddr).transfer(_dest, _amount);
|
ERC20(_tokenAddr).transfer(_dest, _amount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev withdraw `_tokenAddr` `_amount` to `_dest`
|
|
||||||
* @param _tokenAddr the address of the token
|
|
||||||
* @param _dest the address of receiver
|
|
||||||
* @param _amount the number of tokens to send
|
|
||||||
**/
|
|
||||||
function withdrawToken(address _tokenAddr, address _dest, uint _amount)
|
|
||||||
public
|
|
||||||
notNull(_dest)
|
|
||||||
onlyWallet
|
|
||||||
{
|
|
||||||
require(_amount > 0);
|
|
||||||
uint _balance = tokenBalances[_tokenAddr];
|
|
||||||
require(_amount <= _balance);
|
|
||||||
tokenBalances[_tokenAddr] = _balance - _amount;
|
|
||||||
bool result = ERC20(_tokenAddr).transfer(_dest, _amount);
|
|
||||||
assert(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Returns the confirmation status of a transaction.
|
|
||||||
/// @param transactionId Transaction ID.
|
|
||||||
/// @return Confirmation status.
|
|
||||||
function isConfirmed(uint transactionId)
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns (bool)
|
|
||||||
{
|
|
||||||
uint count = 0;
|
|
||||||
for (uint i = 0; i < owners.length; i++) {
|
|
||||||
if (confirmations[transactionId][owners[i]])
|
|
||||||
count += 1;
|
|
||||||
if (count == required)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Internal functions
|
|
||||||
*/
|
|
||||||
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
|
|
||||||
/// @param destination Transaction target address.
|
|
||||||
/// @param value Transaction ether value.
|
|
||||||
/// @param data Transaction data payload.
|
|
||||||
/// @return Returns transaction ID.
|
|
||||||
function addTransaction(address destination, uint value, bytes data)
|
|
||||||
internal
|
|
||||||
notNull(destination)
|
|
||||||
returns (uint transactionId)
|
|
||||||
{
|
|
||||||
transactionId = transactionCount;
|
|
||||||
transactions[transactionId] = Transaction({
|
|
||||||
destination: destination,
|
|
||||||
value: value,
|
|
||||||
data: data,
|
|
||||||
executed: false
|
|
||||||
});
|
|
||||||
transactionCount += 1;
|
|
||||||
Submission(transactionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @dev register the deposit
|
|
||||||
**/
|
|
||||||
function _deposited(address _from, uint _amount, address _tokenAddr, bytes)
|
|
||||||
internal
|
|
||||||
{
|
|
||||||
TokenDeposit(_tokenAddr,_from,_amount);
|
|
||||||
nonce++;
|
|
||||||
if (tokenBalances[_tokenAddr] == 0) {
|
|
||||||
tokens.push(_tokenAddr);
|
|
||||||
tokenBalances[_tokenAddr] = ERC20(_tokenAddr).balanceOf(this);
|
|
||||||
} else {
|
|
||||||
tokenBalances[_tokenAddr] += _amount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Web3 call functions
|
|
||||||
*/
|
|
||||||
/// @dev Returns number of confirmations of a transaction.
|
|
||||||
/// @param transactionId Transaction ID.
|
|
||||||
/// @return Number of confirmations.
|
|
||||||
function getConfirmationCount(uint transactionId)
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns (uint count)
|
|
||||||
{
|
|
||||||
for (uint i = 0; i < owners.length; i++) {
|
|
||||||
if (confirmations[transactionId][owners[i]])
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Returns total number of transactions after filters are applied.
|
|
||||||
/// @param pending Include pending transactions.
|
|
||||||
/// @param executed Include executed transactions.
|
|
||||||
/// @return Total number of transactions after filters are applied.
|
|
||||||
function getTransactionCount(bool pending, bool executed)
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns (uint count)
|
|
||||||
{
|
|
||||||
for (uint i = 0; i < transactionCount; i++) {
|
|
||||||
if (pending && !transactions[i].executed || executed && transactions[i].executed)
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Returns list of owners.
|
|
||||||
/// @return List of owner addresses.
|
|
||||||
function getOwners()
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns (address[])
|
|
||||||
{
|
|
||||||
return owners;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Returns list of tokens.
|
|
||||||
/// @return List of token addresses.
|
|
||||||
function getTokenList()
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns (address[])
|
|
||||||
{
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Returns array with owner addresses, which confirmed transaction.
|
|
||||||
/// @param transactionId Transaction ID.
|
|
||||||
/// @return Returns array of owner addresses.
|
|
||||||
function getConfirmations(uint transactionId)
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns (address[] _confirmations)
|
|
||||||
{
|
|
||||||
address[] memory confirmationsTemp = new address[](owners.length);
|
|
||||||
uint count = 0;
|
|
||||||
uint i;
|
|
||||||
for (i = 0; i < owners.length; i++) {
|
|
||||||
if (confirmations[transactionId][owners[i]]) {
|
|
||||||
confirmationsTemp[count] = owners[i];
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_confirmations = new address[](count);
|
|
||||||
for (i = 0; i < count; i++) {
|
|
||||||
_confirmations[i] = confirmationsTemp[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @dev Returns list of transaction IDs in defined range.
|
|
||||||
/// @param from Index start position of transaction array.
|
|
||||||
/// @param to Index end position of transaction array.
|
|
||||||
/// @param pending Include pending transactions.
|
|
||||||
/// @param executed Include executed transactions.
|
|
||||||
/// @return Returns array of transaction IDs.
|
|
||||||
function getTransactionIds(uint from, uint to, bool pending, bool executed)
|
|
||||||
public
|
|
||||||
constant
|
|
||||||
returns (uint[] _transactionIds)
|
|
||||||
{
|
|
||||||
uint[] memory transactionIdsTemp = new uint[](transactionCount);
|
|
||||||
uint count = 0;
|
|
||||||
uint i;
|
|
||||||
for (i = 0; i < transactionCount; i++) {
|
|
||||||
if (pending && !transactions[i].executed || executed && transactions[i].executed) {
|
|
||||||
transactionIdsTemp[count] = i;
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_transactionIds = new uint[](to - from);
|
|
||||||
for (i = from; i < to; i++) {
|
|
||||||
_transactionIds[i - from] = transactionIdsTemp[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,203 @@
|
||||||
|
pragma solidity ^0.4.15;
|
||||||
|
|
||||||
|
import "./MultiSigWallet.sol";
|
||||||
|
import "./ERC20.sol";
|
||||||
|
// @dev This contract is deprecated, use the new version.
|
||||||
|
contract MultiSigTokenWalletV1 is MultiSigWallet {
|
||||||
|
|
||||||
|
address[] public tokens;
|
||||||
|
mapping (address => uint) public tokenBalances;
|
||||||
|
mapping (address => address[]) public userList;
|
||||||
|
uint public nonce;
|
||||||
|
|
||||||
|
event TokenDeposit(address _token, address indexed _sender, uint _value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public functions
|
||||||
|
*
|
||||||
|
**/
|
||||||
|
/**
|
||||||
|
* @notice deposit a ERC20 token. The amount of deposit is the allowance set to this contract.
|
||||||
|
* @param _token the token contract address
|
||||||
|
* @param _data might be used by child implementations
|
||||||
|
**/
|
||||||
|
function depositToken(address _token, bytes _data)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
address sender = msg.sender;
|
||||||
|
uint amount = ERC20(_token).allowance(sender, this);
|
||||||
|
deposit(sender, amount, _token, _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice deposit a ERC20 token. The amount of deposit is the allowance set to this contract.
|
||||||
|
* @param _token the token contract address
|
||||||
|
* @param _data might be used by child implementations
|
||||||
|
**/
|
||||||
|
function deposit(address _from, uint256 _amount, address _token, bytes _data)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
if (_from == address(this))
|
||||||
|
return;
|
||||||
|
uint _nonce = nonce;
|
||||||
|
bool result = ERC20(_token).transferFrom(_from, this, _amount);
|
||||||
|
assert(result);
|
||||||
|
//ERC23 not executed _deposited tokenFallback by
|
||||||
|
if (nonce == _nonce) {
|
||||||
|
_deposited(_from, _amount, _token, _data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @notice watches for balance in a token contract
|
||||||
|
* @param _tokenAddr the token contract address
|
||||||
|
**/
|
||||||
|
function watch(address _tokenAddr)
|
||||||
|
ownerExists(msg.sender)
|
||||||
|
{
|
||||||
|
uint oldBal = tokenBalances[_tokenAddr];
|
||||||
|
uint newBal = ERC20(_tokenAddr).balanceOf(this);
|
||||||
|
if (newBal > oldBal) {
|
||||||
|
_deposited(0x0, newBal-oldBal, _tokenAddr, new bytes(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMyTokenList(address[] _tokenList)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
userList[msg.sender] = _tokenList;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTokenList(address[] _tokenList)
|
||||||
|
onlyWallet
|
||||||
|
{
|
||||||
|
tokens = _tokenList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice ERC23 Token fallback
|
||||||
|
* @param _from address incoming token
|
||||||
|
* @param _amount incoming amount
|
||||||
|
**/
|
||||||
|
function tokenFallback(address _from, uint _amount, bytes _data)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
_deposited(_from, _amount, msg.sender, _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice Called MiniMeToken approvesAndCall to this contract, calls deposit.
|
||||||
|
* @param _from address incoming token
|
||||||
|
* @param _amount incoming amount
|
||||||
|
* @param _token the token contract address
|
||||||
|
* @param _data (might be used by child classes)
|
||||||
|
*/
|
||||||
|
function receiveApproval(address _from, uint256 _amount, address _token, bytes _data) {
|
||||||
|
deposit(_from, _amount, _token, _data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev gives full ownership of this wallet to `_dest` removing older owners from wallet
|
||||||
|
* @param _dest the address of new controller
|
||||||
|
**/
|
||||||
|
function releaseWallet(address _dest)
|
||||||
|
public
|
||||||
|
notNull(_dest)
|
||||||
|
ownerDoesNotExist(_dest)
|
||||||
|
onlyWallet
|
||||||
|
{
|
||||||
|
address[] memory _owners = owners;
|
||||||
|
uint numOwners = _owners.length;
|
||||||
|
addOwner(_dest);
|
||||||
|
for (uint i = 0; i < numOwners; i++) {
|
||||||
|
removeOwner(_owners[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev withdraw all recognized tokens balances and ether to `_dest`
|
||||||
|
* @param _dest the address of receiver
|
||||||
|
**/
|
||||||
|
function withdrawEverything(address _dest)
|
||||||
|
public
|
||||||
|
notNull(_dest)
|
||||||
|
onlyWallet
|
||||||
|
{
|
||||||
|
withdrawAllTokens(_dest);
|
||||||
|
_dest.transfer(this.balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev withdraw all recognized tokens balances to `_dest`
|
||||||
|
* @param _dest the address of receiver
|
||||||
|
**/
|
||||||
|
function withdrawAllTokens(address _dest)
|
||||||
|
public
|
||||||
|
notNull(_dest)
|
||||||
|
onlyWallet
|
||||||
|
{
|
||||||
|
address[] memory _tokenList;
|
||||||
|
if (userList[_dest].length > 0) {
|
||||||
|
_tokenList = userList[_dest];
|
||||||
|
} else {
|
||||||
|
_tokenList = tokens;
|
||||||
|
}
|
||||||
|
uint len = _tokenList.length;
|
||||||
|
for (uint i = 0;i < len; i++) {
|
||||||
|
address _tokenAddr = _tokenList[i];
|
||||||
|
uint _amount = tokenBalances[_tokenAddr];
|
||||||
|
if (_amount > 0) {
|
||||||
|
delete tokenBalances[_tokenAddr];
|
||||||
|
ERC20(_tokenAddr).transfer(_dest, _amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev withdraw `_tokenAddr` `_amount` to `_dest`
|
||||||
|
* @param _tokenAddr the address of the token
|
||||||
|
* @param _dest the address of receiver
|
||||||
|
* @param _amount the number of tokens to send
|
||||||
|
**/
|
||||||
|
function withdrawToken(address _tokenAddr, address _dest, uint _amount)
|
||||||
|
public
|
||||||
|
notNull(_dest)
|
||||||
|
onlyWallet
|
||||||
|
{
|
||||||
|
require(_amount > 0);
|
||||||
|
uint _balance = tokenBalances[_tokenAddr];
|
||||||
|
require(_amount <= _balance);
|
||||||
|
tokenBalances[_tokenAddr] = _balance - _amount;
|
||||||
|
bool result = ERC20(_tokenAddr).transfer(_dest, _amount);
|
||||||
|
assert(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev register the deposit
|
||||||
|
**/
|
||||||
|
function _deposited(address _from, uint _amount, address _tokenAddr, bytes)
|
||||||
|
internal
|
||||||
|
{
|
||||||
|
TokenDeposit(_tokenAddr,_from,_amount);
|
||||||
|
nonce++;
|
||||||
|
if (tokenBalances[_tokenAddr] == 0) {
|
||||||
|
tokens.push(_tokenAddr);
|
||||||
|
tokenBalances[_tokenAddr] = ERC20(_tokenAddr).balanceOf(this);
|
||||||
|
} else {
|
||||||
|
tokenBalances[_tokenAddr] += _amount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Web3 call functions
|
||||||
|
*/
|
||||||
|
/// @dev Returns list of tokens.
|
||||||
|
/// @return List of token addresses.
|
||||||
|
function getTokenList()
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (address[])
|
||||||
|
{
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,378 @@
|
||||||
|
pragma solidity ^0.4.18;
|
||||||
|
|
||||||
|
|
||||||
|
/// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution.
|
||||||
|
/// @author Stefan George - <stefan.george@consensys.net>
|
||||||
|
contract MultiSigWallet {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Events
|
||||||
|
*/
|
||||||
|
event Confirmation(address indexed sender, uint indexed transactionId);
|
||||||
|
event Revocation(address indexed sender, uint indexed transactionId);
|
||||||
|
event Submission(uint indexed transactionId);
|
||||||
|
event Execution(uint indexed transactionId);
|
||||||
|
event ExecutionFailure(uint indexed transactionId);
|
||||||
|
event Deposit(address indexed sender, uint value);
|
||||||
|
event OwnerAddition(address indexed owner);
|
||||||
|
event OwnerRemoval(address indexed owner);
|
||||||
|
event RequirementChange(uint required);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constants
|
||||||
|
*/
|
||||||
|
uint constant public MAX_OWNER_COUNT = 50;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Storage
|
||||||
|
*/
|
||||||
|
mapping (uint => Transaction) public transactions;
|
||||||
|
mapping (uint => mapping (address => bool)) public confirmations;
|
||||||
|
mapping (address => bool) public isOwner;
|
||||||
|
address[] public owners;
|
||||||
|
uint public required;
|
||||||
|
uint public transactionCount;
|
||||||
|
|
||||||
|
struct Transaction {
|
||||||
|
address destination;
|
||||||
|
uint value;
|
||||||
|
bytes data;
|
||||||
|
bool executed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Modifiers
|
||||||
|
*/
|
||||||
|
modifier onlyWallet() {
|
||||||
|
require (msg.sender == address(this));
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier ownerDoesNotExist(address owner) {
|
||||||
|
require (!isOwner[owner]);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier ownerExists(address owner) {
|
||||||
|
require (isOwner[owner]);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier transactionExists(uint transactionId) {
|
||||||
|
require (transactions[transactionId].destination != 0);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier confirmed(uint transactionId, address owner) {
|
||||||
|
require (confirmations[transactionId][owner]);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier notConfirmed(uint transactionId, address owner) {
|
||||||
|
require(!confirmations[transactionId][owner]);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier notExecuted(uint transactionId) {
|
||||||
|
require (!transactions[transactionId].executed);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier notNull(address _address) {
|
||||||
|
require (_address != 0);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier validRequirement(uint ownerCount, uint _required) {
|
||||||
|
require (ownerCount <= MAX_OWNER_COUNT && _required <= ownerCount && _required != 0 && ownerCount != 0);
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Fallback function allows to deposit ether.
|
||||||
|
function ()
|
||||||
|
public
|
||||||
|
payable
|
||||||
|
{
|
||||||
|
if (msg.value > 0)
|
||||||
|
Deposit(msg.sender, msg.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public functions
|
||||||
|
*/
|
||||||
|
/// @dev Constructor calls function `constructor(address[],uint256)`
|
||||||
|
/// @param _owners List of initial owners.
|
||||||
|
/// @param _required Number of required confirmations.
|
||||||
|
function MultiSigWallet(address[] _owners, uint _required)
|
||||||
|
public
|
||||||
|
{
|
||||||
|
constructor(_owners, _required);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Contract constructor sets initial owners and required number of confirmations, can only be called once.
|
||||||
|
/// @param _owners List of initial owners.
|
||||||
|
/// @param _required Number of required confirmations.
|
||||||
|
function constructor(address[] _owners, uint _required)
|
||||||
|
public
|
||||||
|
validRequirement(_owners.length, _required)
|
||||||
|
{
|
||||||
|
require(owners.length == 0 && required == 0);
|
||||||
|
for (uint i = 0; i < _owners.length; i++) {
|
||||||
|
require(!isOwner[_owners[i]] && _owners[i] != 0);
|
||||||
|
isOwner[_owners[i]] = true;
|
||||||
|
}
|
||||||
|
owners = _owners;
|
||||||
|
required = _required;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Allows to add a new owner. Transaction has to be sent by wallet.
|
||||||
|
/// @param owner Address of new owner.
|
||||||
|
function addOwner(address owner)
|
||||||
|
public
|
||||||
|
onlyWallet
|
||||||
|
ownerDoesNotExist(owner)
|
||||||
|
notNull(owner)
|
||||||
|
validRequirement(owners.length + 1, required)
|
||||||
|
{
|
||||||
|
isOwner[owner] = true;
|
||||||
|
owners.push(owner);
|
||||||
|
OwnerAddition(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Allows to remove an owner. Transaction has to be sent by wallet.
|
||||||
|
/// @param owner Address of owner.
|
||||||
|
function removeOwner(address owner)
|
||||||
|
public
|
||||||
|
onlyWallet
|
||||||
|
ownerExists(owner)
|
||||||
|
{
|
||||||
|
isOwner[owner] = false;
|
||||||
|
for (uint i=0; i<owners.length - 1; i++)
|
||||||
|
if (owners[i] == owner) {
|
||||||
|
owners[i] = owners[owners.length - 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
owners.length -= 1;
|
||||||
|
if (required > owners.length)
|
||||||
|
changeRequirement(owners.length);
|
||||||
|
OwnerRemoval(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Allows to replace an owner with a new owner. Transaction has to be sent by wallet.
|
||||||
|
/// @param owner Address of owner to be replaced.
|
||||||
|
/// @param newOwner Address of new owner.
|
||||||
|
function replaceOwner(address owner, address newOwner)
|
||||||
|
public
|
||||||
|
onlyWallet
|
||||||
|
ownerExists(owner)
|
||||||
|
ownerDoesNotExist(newOwner)
|
||||||
|
{
|
||||||
|
for (uint i=0; i<owners.length; i++)
|
||||||
|
if (owners[i] == owner) {
|
||||||
|
owners[i] = newOwner;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
isOwner[owner] = false;
|
||||||
|
isOwner[newOwner] = true;
|
||||||
|
OwnerRemoval(owner);
|
||||||
|
OwnerAddition(newOwner);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Allows to change the number of required confirmations. Transaction has to be sent by wallet.
|
||||||
|
/// @param _required Number of required confirmations.
|
||||||
|
function changeRequirement(uint _required)
|
||||||
|
public
|
||||||
|
onlyWallet
|
||||||
|
validRequirement(owners.length, _required)
|
||||||
|
{
|
||||||
|
required = _required;
|
||||||
|
RequirementChange(_required);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Allows an owner to submit and confirm a transaction.
|
||||||
|
/// @param destination Transaction target address.
|
||||||
|
/// @param value Transaction ether value.
|
||||||
|
/// @param data Transaction data payload.
|
||||||
|
/// @return Returns transaction ID.
|
||||||
|
function submitTransaction(address destination, uint value, bytes data)
|
||||||
|
public
|
||||||
|
returns (uint transactionId)
|
||||||
|
{
|
||||||
|
transactionId = addTransaction(destination, value, data);
|
||||||
|
confirmTransaction(transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Allows an owner to confirm a transaction.
|
||||||
|
/// @param transactionId Transaction ID.
|
||||||
|
function confirmTransaction(uint transactionId)
|
||||||
|
public
|
||||||
|
ownerExists(msg.sender)
|
||||||
|
transactionExists(transactionId)
|
||||||
|
notConfirmed(transactionId, msg.sender)
|
||||||
|
{
|
||||||
|
confirmations[transactionId][msg.sender] = true;
|
||||||
|
Confirmation(msg.sender, transactionId);
|
||||||
|
executeTransaction(transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Allows an owner to revoke a confirmation for a transaction.
|
||||||
|
/// @param transactionId Transaction ID.
|
||||||
|
function revokeConfirmation(uint transactionId)
|
||||||
|
public
|
||||||
|
ownerExists(msg.sender)
|
||||||
|
confirmed(transactionId, msg.sender)
|
||||||
|
notExecuted(transactionId)
|
||||||
|
{
|
||||||
|
confirmations[transactionId][msg.sender] = false;
|
||||||
|
Revocation(msg.sender, transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Allows anyone to execute a confirmed transaction.
|
||||||
|
/// @param transactionId Transaction ID.
|
||||||
|
function executeTransaction(uint transactionId)
|
||||||
|
public
|
||||||
|
ownerExists(msg.sender)
|
||||||
|
confirmed(transactionId, msg.sender)
|
||||||
|
notExecuted(transactionId)
|
||||||
|
{
|
||||||
|
if (isConfirmed(transactionId)) {
|
||||||
|
Transaction storage trx = transactions[transactionId];
|
||||||
|
trx.executed = true;
|
||||||
|
if (trx.destination.call.value(trx.value)(trx.data))
|
||||||
|
Execution(transactionId);
|
||||||
|
else {
|
||||||
|
ExecutionFailure(transactionId);
|
||||||
|
trx.executed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Returns the confirmation status of a transaction.
|
||||||
|
/// @param transactionId Transaction ID.
|
||||||
|
/// @return Confirmation status.
|
||||||
|
function isConfirmed(uint transactionId)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (bool)
|
||||||
|
{
|
||||||
|
uint count = 0;
|
||||||
|
for (uint i=0; i<owners.length; i++) {
|
||||||
|
if (confirmations[transactionId][owners[i]])
|
||||||
|
count += 1;
|
||||||
|
if (count == required)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Internal functions
|
||||||
|
*/
|
||||||
|
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
|
||||||
|
/// @param destination Transaction target address.
|
||||||
|
/// @param value Transaction ether value.
|
||||||
|
/// @param data Transaction data payload.
|
||||||
|
/// @return Returns transaction ID.
|
||||||
|
function addTransaction(address destination, uint value, bytes data)
|
||||||
|
internal
|
||||||
|
notNull(destination)
|
||||||
|
returns (uint transactionId)
|
||||||
|
{
|
||||||
|
transactionId = transactionCount;
|
||||||
|
transactions[transactionId] = Transaction({
|
||||||
|
destination: destination,
|
||||||
|
value: value,
|
||||||
|
data: data,
|
||||||
|
executed: false
|
||||||
|
});
|
||||||
|
transactionCount += 1;
|
||||||
|
Submission(transactionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Web3 call functions
|
||||||
|
*/
|
||||||
|
/// @dev Returns number of confirmations of a transaction.
|
||||||
|
/// @param transactionId Transaction ID.
|
||||||
|
/// @return Number of confirmations.
|
||||||
|
function getConfirmationCount(uint transactionId)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (uint count)
|
||||||
|
{
|
||||||
|
for (uint i=0; i<owners.length; i++)
|
||||||
|
if (confirmations[transactionId][owners[i]])
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Returns total number of transactions after filers are applied.
|
||||||
|
/// @param pending Include pending transactions.
|
||||||
|
/// @param executed Include executed transactions.
|
||||||
|
/// @return Total number of transactions after filters are applied.
|
||||||
|
function getTransactionCount(bool pending, bool executed)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (uint count)
|
||||||
|
{
|
||||||
|
for (uint i=0; i<transactionCount; i++)
|
||||||
|
if ( pending && !transactions[i].executed
|
||||||
|
|| executed && transactions[i].executed)
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Returns list of owners.
|
||||||
|
/// @return List of owner addresses.
|
||||||
|
function getOwners()
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (address[])
|
||||||
|
{
|
||||||
|
return owners;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Returns array with owner addresses, which confirmed transaction.
|
||||||
|
/// @param transactionId Transaction ID.
|
||||||
|
/// @return Returns array of owner addresses.
|
||||||
|
function getConfirmations(uint transactionId)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (address[] _confirmations)
|
||||||
|
{
|
||||||
|
address[] memory confirmationsTemp = new address[](owners.length);
|
||||||
|
uint count = 0;
|
||||||
|
uint i;
|
||||||
|
for (i=0; i<owners.length; i++)
|
||||||
|
if (confirmations[transactionId][owners[i]]) {
|
||||||
|
confirmationsTemp[count] = owners[i];
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
_confirmations = new address[](count);
|
||||||
|
for (i=0; i<count; i++)
|
||||||
|
_confirmations[i] = confirmationsTemp[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @dev Returns list of transaction IDs in defined range.
|
||||||
|
/// @param from Index start position of transaction array.
|
||||||
|
/// @param to Index end position of transaction array.
|
||||||
|
/// @param pending Include pending transactions.
|
||||||
|
/// @param executed Include executed transactions.
|
||||||
|
/// @return Returns array of transaction IDs.
|
||||||
|
function getTransactionIds(uint from, uint to, bool pending, bool executed)
|
||||||
|
public
|
||||||
|
constant
|
||||||
|
returns (uint[] _transactionIds)
|
||||||
|
{
|
||||||
|
uint[] memory transactionIdsTemp = new uint[](transactionCount);
|
||||||
|
uint count = 0;
|
||||||
|
uint i;
|
||||||
|
for (i=0; i<transactionCount; i++)
|
||||||
|
if ( pending && !transactions[i].executed
|
||||||
|
|| executed && transactions[i].executed)
|
||||||
|
{
|
||||||
|
transactionIdsTemp[count] = i;
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
_transactionIds = new uint[](to - from);
|
||||||
|
for (i=from; i<to; i++)
|
||||||
|
_transactionIds[i - from] = transactionIdsTemp[i];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue