Skip to content

Instantly share code, notes, and snippets.

@awsum
Created December 3, 2018 18:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save awsum/e2a374e8e873fed264eb9cbebe8c6455 to your computer and use it in GitHub Desktop.
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=
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;
}
}
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);
}
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;
}
}
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