2017-07-05 10:28:15 +00:00
|
|
|
pragma solidity ^0.4.4;
|
|
|
|
|
|
|
|
|
|
|
|
/// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution.
|
|
|
|
/// @author Stefan George - <stefan.george@consensys.net>
|
|
|
|
contract MultiSigWallet {
|
|
|
|
|
|
|
|
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 OwnerAddition(address indexed owner);
|
|
|
|
event OwnerRemoval(address indexed owner);
|
|
|
|
event RequirementChange(uint required);
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier onlyWallet() {
|
|
|
|
if (msg.sender != address(this))
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier ownerDoesNotExist(address owner) {
|
|
|
|
if (isOwner[owner])
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier ownerExists(address owner) {
|
|
|
|
if (!isOwner[owner])
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier transactionExists(uint transactionId) {
|
2018-06-12 09:05:49 +00:00
|
|
|
if (transactions[transactionId].destination == address(0))
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier confirmed(uint transactionId, address owner) {
|
|
|
|
if (!confirmations[transactionId][owner])
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier notConfirmed(uint transactionId, address owner) {
|
|
|
|
if (confirmations[transactionId][owner])
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier notExecuted(uint transactionId) {
|
|
|
|
if (transactions[transactionId].executed)
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier notNull(address _address) {
|
2018-06-12 09:05:49 +00:00
|
|
|
if (_address == address(0))
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
modifier validRequirement(uint ownerCount, uint _required) {
|
|
|
|
if ( ownerCount > MAX_OWNER_COUNT
|
|
|
|
|| _required > ownerCount
|
|
|
|
|| _required == 0
|
|
|
|
|| ownerCount == 0)
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
_;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Fallback function allows to deposit ether.
|
|
|
|
function()
|
2018-06-28 16:08:45 +00:00
|
|
|
external
|
2017-07-05 10:28:15 +00:00
|
|
|
payable
|
|
|
|
{
|
|
|
|
if (msg.value > 0)
|
2018-06-27 08:35:38 +00:00
|
|
|
emit Deposit(msg.sender, msg.value);
|
2017-07-05 10:28:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Public functions
|
|
|
|
*/
|
|
|
|
/// @dev Contract constructor sets initial owners and required number of confirmations.
|
|
|
|
/// @param _owners List of initial owners.
|
|
|
|
/// @param _required Number of required confirmations.
|
2018-07-14 21:42:01 +00:00
|
|
|
constructor(address[] memory _owners, uint _required)
|
2017-07-05 10:28:15 +00:00
|
|
|
public
|
|
|
|
validRequirement(_owners.length, _required)
|
|
|
|
{
|
|
|
|
for (uint i=0; i<_owners.length; i++) {
|
2018-06-12 09:05:49 +00:00
|
|
|
if (isOwner[_owners[i]] || _owners[i] == address(0))
|
2018-07-11 23:49:00 +00:00
|
|
|
revert();
|
2017-07-05 10:28:15 +00:00
|
|
|
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);
|
2018-06-27 08:35:38 +00:00
|
|
|
emit OwnerAddition(owner);
|
2017-07-05 10:28:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @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);
|
2018-06-27 08:35:38 +00:00
|
|
|
emit OwnerRemoval(owner);
|
2017-07-05 10:28:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @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;
|
2018-06-27 08:35:38 +00:00
|
|
|
emit OwnerRemoval(owner);
|
|
|
|
emit OwnerAddition(newOwner);
|
2017-07-05 10:28:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @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;
|
2018-06-27 08:35:38 +00:00
|
|
|
emit RequirementChange(_required);
|
2017-07-05 10:28:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @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.
|
2018-07-14 21:42:01 +00:00
|
|
|
function submitTransaction(address destination, uint value, bytes memory data)
|
2017-07-05 10:28:15 +00:00
|
|
|
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;
|
2018-06-27 08:35:38 +00:00
|
|
|
emit Confirmation(msg.sender, transactionId);
|
2017-07-05 10:28:15 +00:00
|
|
|
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;
|
2018-06-27 08:35:38 +00:00
|
|
|
emit Revocation(msg.sender, transactionId);
|
2017-07-05 10:28:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Allows anyone to execute a confirmed transaction.
|
|
|
|
/// @param transactionId Transaction ID.
|
|
|
|
function executeTransaction(uint transactionId)
|
|
|
|
public
|
|
|
|
notExecuted(transactionId)
|
|
|
|
{
|
|
|
|
if (isConfirmed(transactionId)) {
|
2018-07-11 11:30:46 +00:00
|
|
|
Transaction storage tx = transactions[transactionId];
|
2017-07-05 10:28:15 +00:00
|
|
|
tx.executed = true;
|
|
|
|
if (tx.destination.call.value(tx.value)(tx.data))
|
2018-06-27 08:35:38 +00:00
|
|
|
emit Execution(transactionId);
|
2017-07-05 10:28:15 +00:00
|
|
|
else {
|
2018-06-27 08:35:38 +00:00
|
|
|
emit ExecutionFailure(transactionId);
|
2017-07-05 10:28:15 +00:00
|
|
|
tx.executed = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// @dev Returns the confirmation status of a transaction.
|
|
|
|
/// @param transactionId Transaction ID.
|
|
|
|
/// @return Confirmation status.
|
|
|
|
function isConfirmed(uint transactionId)
|
|
|
|
public
|
2018-07-02 09:14:28 +00:00
|
|
|
view
|
2017-07-05 10:28:15 +00:00
|
|
|
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.
|
2018-07-14 21:42:01 +00:00
|
|
|
function addTransaction(address destination, uint value, bytes memory data)
|
2017-07-05 10:28:15 +00:00
|
|
|
internal
|
|
|
|
notNull(destination)
|
|
|
|
returns (uint transactionId)
|
|
|
|
{
|
|
|
|
transactionId = transactionCount;
|
|
|
|
transactions[transactionId] = Transaction({
|
|
|
|
destination: destination,
|
|
|
|
value: value,
|
|
|
|
data: data,
|
|
|
|
executed: false
|
|
|
|
});
|
|
|
|
transactionCount += 1;
|
2018-06-27 08:35:38 +00:00
|
|
|
emit Submission(transactionId);
|
2017-07-05 10:28:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Web3 call functions
|
|
|
|
*/
|
|
|
|
/// @dev Returns number of confirmations of a transaction.
|
|
|
|
/// @param transactionId Transaction ID.
|
|
|
|
/// @return Number of confirmations.
|
|
|
|
function getConfirmationCount(uint transactionId)
|
|
|
|
public
|
2018-07-02 09:14:28 +00:00
|
|
|
view
|
2017-07-05 10:28:15 +00:00
|
|
|
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
|
2018-07-02 09:14:28 +00:00
|
|
|
view
|
2017-07-05 10:28:15 +00:00
|
|
|
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
|
2018-07-02 09:14:28 +00:00
|
|
|
view
|
2018-07-14 21:42:01 +00:00
|
|
|
returns (address[] memory)
|
2017-07-05 10:28:15 +00:00
|
|
|
{
|
|
|
|
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
|
2018-07-02 09:14:28 +00:00
|
|
|
view
|
2018-07-14 21:42:01 +00:00
|
|
|
returns (address[] memory _confirmations)
|
2017-07-05 10:28:15 +00:00
|
|
|
{
|
|
|
|
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
|
2018-07-02 09:14:28 +00:00
|
|
|
view
|
2018-07-14 21:42:01 +00:00
|
|
|
returns (uint[] memory _transactionIds)
|
2017-07-05 10:28:15 +00:00
|
|
|
{
|
|
|
|
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];
|
|
|
|
}
|
|
|
|
}
|