Created
December 3, 2018 18:10
-
-
Save awsum/e2a374e8e873fed264eb9cbebe8c6455 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.4.21+commit.dfe3193c.js&optimize=false&gist=
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.21; | |
import "./ERC20Interface.sol"; | |
import "./Ownable.sol"; | |
import "./Pausable.sol"; | |
/** | |
* @title Mutlisig wallet for ether and ERC20 tokens. | |
*/ | |
contract MultiSigWallet is Ownable, Pausable { | |
// Signers data | |
uint public constant MAX_SIGNERS = 8; | |
uint public constant MAX_PENDING_TRANSFERS = 20; | |
address[] private signers; | |
mapping(address => bool) private isSigner; | |
mapping(address => uint8) private signerIndex; | |
uint8 private requiredSignatures; | |
// Pending transfers data | |
uint256 private sequence = 0; | |
uint256[] private pendingTransfers; | |
mapping(uint256 => PendingTransfer) private pendingTransfersById; | |
/** | |
* The contract constructor | |
* | |
* @param _signers Wallet signers. | |
* @param _requiredSignatures Number of required signatures for a transfer. | |
*/ | |
function MultiSigWallet(address[] _signers, uint8 _requiredSignatures) public payable { | |
_checkSigners(_signers, _requiredSignatures); | |
_setSigners(_signers, _requiredSignatures); | |
owner = msg.sender; | |
} | |
/** | |
* Get wallet signers. | |
* | |
* @return _signers Array of signers. | |
*/ | |
function getSigners() public view returns(address[] _signers) { | |
return signers; | |
} | |
/** | |
* Get number of signatures required for a transfer. | |
*/ | |
function getRequiredSignatures() public view returns(uint8) { | |
return requiredSignatures; | |
} | |
/** | |
* Get an array of pending transfers IDs. | |
*/ | |
function getPendingTransfers() public view returns(uint[] _pendingTransfersIds) { | |
return pendingTransfers; | |
} | |
/** | |
* Get number of signatures required for a transfer. | |
*/ | |
function getPendingTransfer(uint256 transferId) public view | |
returns( | |
uint256 id, | |
address to, | |
uint256 amount, | |
address token, | |
TransferType transferType, | |
uint8 numberOrSignatures | |
) | |
{ | |
PendingTransfer storage transfer = pendingTransfersById[transferId]; | |
uint8 numberOfSignatures = _countSignatures(transfer); | |
// Break the struct into a tuple to make it work with web3.js | |
return ( | |
transfer.id, | |
transfer.to, | |
transfer.amount, | |
transfer.token, | |
transfer.transferType, | |
numberOfSignatures | |
); | |
} | |
/** | |
* Change the signers and the required number of signatures. | |
* | |
* @param _signers Wallet signers. | |
* @param _requiredSignatures Number of required signatures for a transfer. | |
*/ | |
function changeSigners( | |
address[] _signers, | |
uint8 _requiredSignatures | |
) public onlyOwner whenPaused { | |
_checkSigners(_signers, _requiredSignatures); | |
// Clean pending transfers | |
if (pendingTransfers.length > 0) { | |
for (uint i = 0; i < pendingTransfers.length; i++) { | |
delete pendingTransfersById[pendingTransfers[i]]; | |
} | |
} | |
delete pendingTransfers; | |
// Clean old signers | |
for (i = 0; i < signers.length; i++) { | |
delete isSigner[signers[i]]; | |
delete signerIndex[signers[i]]; | |
} | |
delete signers; | |
_setSigners(_signers, _requiredSignatures); | |
emit SignersChanged(owner, _signers, requiredSignatures); | |
} | |
/** | |
* Create and sign a pending ether transfer. | |
* | |
* @param _to Recipient. | |
* @param _amount Amount of ether to be transferred. | |
* | |
* @return _transferId Pending transfer ID. | |
*/ | |
function createEtherTransfer(address _to, uint256 _amount) public | |
onlySigner limitPendingTransfers whenNotPaused | |
returns(uint256 _transferId) | |
{ | |
require(_to != 0x0); | |
require(_amount > 0); | |
uint256 transferId = _createTransfer(TransferType.Ether, _to, 0x0, _amount); | |
emit PendingEtherTransferCreated(msg.sender, _to, _amount, transferId); | |
return transferId; | |
} | |
/** | |
* Create and sign a pending token transfer. | |
* | |
* @param _to Recipient. | |
* @param _token Token contract address. | |
* @param _amount Amount of tokens to be transferred. | |
* | |
* @return _transferId Pending transfer ID. | |
*/ | |
function createTokenTransfer(address _to, address _token, uint256 _amount) public | |
onlySigner limitPendingTransfers whenNotPaused | |
returns(uint256 _transferId) | |
{ | |
require(_to != 0x0); | |
require(_token != 0x0); | |
require(_amount > 0); | |
uint256 transferId = _createTransfer(TransferType.ERC20Token, _to, _token, _amount); | |
emit PendingTokenTransferCreated(msg.sender, _to, _token, _amount, transferId); | |
return transferId; | |
} | |
/** | |
* Sign the pending transfer. Perform the transfer if it has enough signatures. | |
* | |
* @param _transferId ID of the transfer to be signed. | |
*/ | |
function confirmTransfer(uint256 _transferId) public onlySigner whenNotPaused { | |
PendingTransfer storage pendingTransfer = pendingTransfersById[_transferId]; | |
require(pendingTransfer.transferType != TransferType.Null); | |
_signTransfer(pendingTransfer); | |
uint8 signaturesCount = _countSignatures(pendingTransfer); | |
emit PendingTransferConfirmed(_transferId, msg.sender, signaturesCount); | |
if (signaturesCount >= requiredSignatures) { | |
// Copy data and remove the pending transfer to avoid reentrancy attacks | |
TransferType transferType = pendingTransfer.transferType; | |
address to = pendingTransfer.to; | |
uint256 amount = pendingTransfer.amount; | |
ERC20 token = pendingTransfer.token; | |
_removeTransfer(pendingTransfer); | |
if (transferType == TransferType.Ether) { | |
to.transfer(amount); | |
emit EtherTransferred(_transferId, to, amount); | |
} else if (transferType == TransferType.ERC20Token) { | |
require(token.transfer(to, amount)); | |
emit TokensTransferred(_transferId, to, token, amount); | |
} | |
} | |
} | |
function _checkSigners(address[] _signers, uint8 _requiredSignatures) private pure { | |
require(_signers.length > 1 && _signers.length <= MAX_SIGNERS); | |
require(_requiredSignatures > 0 && _requiredSignatures <= _signers.length); | |
} | |
function _setSigners(address[] _signers, uint8 _requiredSignatures) private onlyOwner { | |
for (uint8 i = 0; i < _signers.length; i++) { | |
require(_signers[i] != 0x0); | |
require(isSigner[_signers[i]] == false); | |
isSigner[_signers[i]] = true; | |
signerIndex[_signers[i]] = i; | |
signers.push(_signers[i]); | |
} | |
requiredSignatures = _requiredSignatures; | |
} | |
function _countSignatures(PendingTransfer storage transfer) view private | |
returns (uint8 _signaturesCount) | |
{ | |
uint8 result = 0; | |
uint8 mask = 0x1; | |
for (uint8 i = 0; i < 8; i++) { | |
if ((transfer.signatures & (mask << i)) > 0) { | |
result++; | |
} | |
} | |
return result; | |
} | |
function _generateTransferId() private returns(uint256) { | |
sequence++; | |
return uint256(keccak256(block.number, sequence, msg.data)); | |
} | |
function _signTransfer(PendingTransfer storage transfer) onlySigner private { | |
uint8 signatureBit = uint8(0x1) << signerIndex[msg.sender]; | |
transfer.signatures = transfer.signatures | signatureBit; | |
} | |
function _createTransfer( | |
TransferType _transferType, | |
address _to, | |
address _token, | |
uint256 _amount | |
) onlySigner private returns(uint256 _transferId) { | |
uint256 transferId = _generateTransferId(); | |
PendingTransfer memory transfer = PendingTransfer({ | |
id: transferId, | |
to: _to, | |
amount: _amount, | |
token: ERC20(_token), | |
transferType: _transferType, | |
signatures: 0x0 | |
}); | |
pendingTransfers.push(transfer.id); | |
pendingTransfersById[transferId] = transfer; | |
_signTransfer(pendingTransfersById[transferId]); | |
if (pendingTransfers.length == MAX_PENDING_TRANSFERS) { | |
emit PendingTransfersLimitReached(MAX_PENDING_TRANSFERS); | |
} | |
return transferId; | |
} | |
function _removeTransfer(PendingTransfer storage transfer) onlySigner private { | |
bool deleted = false; | |
for (uint i = 0; i < pendingTransfers.length; i++) { | |
if (deleted) { | |
pendingTransfers[i - 1] = pendingTransfers[i]; | |
delete pendingTransfers[i]; | |
} else { | |
if (pendingTransfers[i] == transfer.id) { | |
delete pendingTransfers[i]; | |
deleted = true; | |
} | |
} | |
} | |
pendingTransfers.length = pendingTransfers.length - 1; | |
assert(deleted); | |
delete pendingTransfersById[transfer.id]; | |
} | |
/** | |
* Accept ether on contract address. | |
*/ | |
function() public payable { | |
if (msg.value > 0) { | |
emit Deposit(msg.sender, msg.value); | |
} | |
} | |
/** | |
* Check if transaction sender is a signer. | |
*/ | |
modifier onlySigner() { | |
require(isSigner[msg.sender]); | |
_; | |
} | |
modifier limitPendingTransfers() { | |
require(pendingTransfers.length < MAX_PENDING_TRANSFERS); | |
_; | |
} | |
// Events | |
event SignersChanged( | |
address indexed owner, | |
address[] signers, | |
uint8 requiredSignatures | |
); | |
event Deposit( | |
address indexed from, | |
uint256 amount | |
); | |
event PendingEtherTransferCreated( | |
address indexed creator, | |
address indexed to, | |
uint256 amount, | |
uint256 transferId | |
); | |
event PendingTokenTransferCreated( | |
address indexed creator, | |
address indexed to, | |
address indexed token, | |
uint256 amount, | |
uint256 transferId | |
); | |
event PendingTransferConfirmed( | |
uint256 indexed transferId, | |
address indexed signer, | |
uint signaturesCount | |
); | |
event EtherTransferred( | |
uint256 indexed transferId, | |
address indexed to, | |
uint256 amount | |
); | |
event TokensTransferred( | |
uint256 indexed transferId, | |
address indexed to, | |
address indexed token, | |
uint256 amount | |
); | |
event PendingTransfersLimitReached( | |
uint limit | |
); | |
// Types | |
enum TransferType { | |
Null, // a hack to check existance of a struct instance in a mapping | |
Ether, | |
ERC20Token | |
} | |
struct PendingTransfer { | |
uint256 id; | |
address to; | |
uint256 amount; | |
ERC20 token; | |
TransferType transferType; | |
uint8 signatures; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.21; | |
/** | |
* @title ERC20Basic | |
* @dev Simpler version of ERC20 interface | |
*/ | |
interface ERC20 { | |
function balanceOf(address who) external view returns (uint256); | |
function transfer(address to, uint256 value) external returns (bool); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.21; | |
/** | |
* @title Ownable | |
* @dev The Ownable contract has an owner address, and provides basic authorization control | |
* functions, this simplifies the implementation of "user permissions". | |
*/ | |
contract Ownable { | |
address public owner; | |
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); | |
/** | |
* @dev The Ownable constructor sets the original `owner` of the contract to the sender | |
* account. | |
*/ | |
function Ownable() public { | |
owner = msg.sender; | |
} | |
/** | |
* @dev Throws if called by any account other than the owner. | |
*/ | |
modifier onlyOwner() { | |
require(msg.sender == owner); | |
_; | |
} | |
/** | |
* @dev Allows the current owner to transfer control of the contract to a newOwner. | |
* @param newOwner The address to transfer ownership to. | |
*/ | |
function transferOwnership(address newOwner) public onlyOwner { | |
require(newOwner != address(0)); | |
emit OwnershipTransferred(owner, newOwner); | |
owner = newOwner; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.21; | |
import "./Ownable.sol"; | |
/** | |
* @title Pausable | |
* @dev Base contract which allows children to implement an emergency stop mechanism. | |
*/ | |
contract Pausable is Ownable { | |
event Pause(); | |
event Unpause(); | |
bool public paused = false; | |
/** | |
* @dev Modifier to make a function callable only when the contract is not paused. | |
*/ | |
modifier whenNotPaused() { | |
require(!paused); | |
_; | |
} | |
/** | |
* @dev Modifier to make a function callable only when the contract is paused. | |
*/ | |
modifier whenPaused() { | |
require(paused); | |
_; | |
} | |
/** | |
* @dev called by the owner to pause, triggers stopped state | |
*/ | |
function pause() onlyOwner whenNotPaused public { | |
paused = true; | |
emit Pause(); | |
} | |
/** | |
* @dev called by the owner to unpause, returns to normal state | |
*/ | |
function unpause() onlyOwner whenPaused public { | |
paused = false; | |
emit Unpause(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment