Contract Name:
MultiSignWallet
Contract Source Code:
File 1 of 1 : MultiSignWallet
pragma solidity 0.5.12;
/**
* @author ESPAY PTY LTD.
*/
/**
* @title ERC223Interface
* @dev ERC223 Contract Interface
*/
contract ERC20Interface {
function transfer(address _to, uint256 _value) public returns (bool);
function balanceOf(address who)public view returns (uint);
}
/**
* @title Forwarder
* @dev Contract that will forward any incoming Ether & token to wallet
*/
contract Forwarder {
address payable public parentAddress;
event ForwarderDeposited(address from, uint value, bytes data);
event TokensFlushed(address forwarderAddress, uint value, address tokenContractAddress);
/**
* @dev Modifier that will execute internal code block only if the sender is the parent address
*/
modifier onlyParent {
require(msg.sender == parentAddress);
_;
}
/**
* @dev Create the contract, and sets the destination address to that of the creator
*/
constructor() public{
parentAddress = msg.sender;
}
/**
* @dev Default function; Gets called when Ether is deposited, and forwards it to the parent address.
* Credit eth to contract creator.
*/
function() external payable {
parentAddress.transfer(msg.value);
emit ForwarderDeposited(msg.sender, msg.value, msg.data);
}
/**
* @dev Execute a token transfer of the full balance from the forwarder token to the parent address
* @param tokenContractAddress the address of the erc20 token contract
*/
function flushTokens(address tokenContractAddress) public onlyParent {
ERC20Interface instance = ERC20Interface(tokenContractAddress);
uint forwarderBalance = instance.balanceOf(address(this));
require(forwarderBalance > 0);
require(instance.transfer(parentAddress, forwarderBalance));
emit TokensFlushed(address(this), forwarderBalance, tokenContractAddress);
}
/**
* @dev Execute a specified token transfer from the forwarder token to the parent address.
* @param _from the address of the erc20 token contract.
* @param _value the amount of token.
*/
function flushToken(address _from, uint _value) external{
require(ERC20Interface(_from).transfer(parentAddress, _value), "instance error");
}
/**
* @dev It is possible that funds were sent to this address before the contract was deployed.
* We can flush those funds to the parent address.
*/
function flush() public {
parentAddress.transfer(address(this).balance);
}
}
/**
* @title MultiSignWallet
*/
contract MultiSignWallet {
address[] public signers;
bool public safeMode;
uint private forwarderCount;
uint private lastsequenceId;
event Deposited(address from, uint value, bytes data);
event SafeModeActivated(address msgSender);
event SafeModeInActivated(address msgSender);
event ForwarderCreated(address forwarderAddress);
event Transacted(address msgSender, address otherSigner, bytes32 operation, address toAddress, uint value, bytes data);
event TokensTransfer(address tokenContractAddress, uint value);
/**
* @dev Modifier that will execute internal code block only if the
* sender is an authorized signer on this wallet
*/
modifier onlySigner {
require(isSigner(msg.sender));
_;
}
/**
* @dev Set up a simple multi-sig wallet by specifying the signers allowed to be used on this wallet.
* 2 signers will be required to send a transaction from this wallet.
* Note: The sender is NOT automatically added to the list of signers.
* Signers CANNOT be changed once they are set
* @param allowedSigners An array of signers on the wallet
*/
constructor(address[] memory allowedSigners) public {
require(allowedSigners.length == 3);
signers = allowedSigners;
}
/**
* @dev Gets called when a transaction is received without calling a method
*/
function() external payable {
if(msg.value > 0){
emit Deposited(msg.sender, msg.value, msg.data);
}
}
/**
* @dev Determine if an address is a signer on this wallet
* @param signer address to check
* @return boolean indicating whether address is signer or not
*/
function isSigner(address signer) public view returns (bool) {
for (uint i = 0; i < signers.length; i++) {
if (signers[i] == signer) {
return true;
}
}
return false;
}
/**
* @dev Irrevocably puts contract into safe mode. When in this mode,
* transactions may only be sent to signing addresses.
*/
function activateSafeMode() public onlySigner {
require(!safeMode);
safeMode = true;
emit SafeModeActivated(msg.sender);
}
/**
* @dev Irrevocably puts out contract into safe mode.
*/
function turnOffSafeMode() public onlySigner {
require(safeMode);
safeMode = false;
emit SafeModeInActivated(msg.sender);
}
/**
* @dev Create a new contract (and also address) that forwards funds to this contract
* returns address of newly created forwarder address
*/
function createForwarder() public returns (address) {
Forwarder f = new Forwarder();
forwarderCount += 1;
emit ForwarderCreated(address(f));
return(address(f));
}
/**
* @dev for return No of forwarder generated.
* @return total number of generated forwarder count.
*/
function getForwarder() public view returns(uint){
return forwarderCount;
}
/**
* @dev Execute a token flush from one of the forwarder addresses.
* This transfer needs only a single signature and can be done by any signer
* @param forwarderAddress the address of the forwarder address to flush the tokens from
* @param tokenContractAddress the address of the erc20 token contract
*/
function flushForwarderTokens(address payable forwarderAddress, address tokenContractAddress) public onlySigner {
Forwarder forwarder = Forwarder(forwarderAddress);
forwarder.flushTokens(tokenContractAddress);
}
/**
* @dev Gets the next available sequence ID for signing when using executeAndConfirm
* @return the sequenceId one higher than the highest currently stored
*/
function getNextSequenceId() public view returns (uint) {
return lastsequenceId+1;
}
/**
* @dev generate the hash for sendMultiSig
* same parameter as sendMultiSig
* @return the hash generated by parameters
*/
function getHash(address toAddress, uint value, bytes memory data, uint expireTime, uint sequenceId)public pure returns (bytes32){
return keccak256(abi.encodePacked("ETHER", toAddress, value, data, expireTime, sequenceId));
}
/**
* @dev Execute a multi-signature transaction from this wallet using 2 signers:
* one from msg.sender and the other from ecrecover.
* Sequence IDs are numbers starting from 1. They are used to prevent replay
* attacks and may not be repeated.
* @param toAddress the destination address to send an outgoing transaction
* @param value the amount in Wei to be sent
* @param data the data to send to the toAddress when invoking the transaction
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature see Data Formats
*/
function sendMultiSig(address payable toAddress, uint value, bytes memory data, uint expireTime, uint sequenceId, bytes memory signature) public payable onlySigner {
bytes32 operationHash = keccak256(abi.encodePacked("ETHER", toAddress, value, data, expireTime, sequenceId));
address otherSigner = verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);
toAddress.transfer(value);
emit Transacted(msg.sender, otherSigner, operationHash, toAddress, value, data);
}
/**
* @dev generate the hash for sendMultiSigToken and sendMultiSigForwarder.
* same parameter as sendMultiSigToken and sendMultiSigForwarder.
* @return the hash generated by parameters
*/
function getTokenHash( address toAddress, uint value, address tokenContractAddress, uint expireTime, uint sequenceId) public pure returns (bytes32){
return keccak256(abi.encodePacked("ERC20", toAddress, value, tokenContractAddress, expireTime, sequenceId));
}
/**
* @dev Execute a multi-signature token transfer from this wallet using 2 signers:
* one from msg.sender and the other from ecrecover.
* Sequence IDs are numbers starting from 1. They are used to prevent replay
* attacks and may not be repeated.
* @param toAddress the destination address to send an outgoing transaction
* @param value the amount in tokens to be sent
* @param tokenContractAddress the address of the erc20 token contract
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @param signature see Data Formats
*/
function sendMultiSigToken(address toAddress, uint value, address tokenContractAddress, uint expireTime, uint sequenceId, bytes memory signature) public onlySigner {
bytes32 operationHash = keccak256(abi.encodePacked("ERC20", toAddress, value, tokenContractAddress, expireTime, sequenceId));
verifyMultiSig(toAddress, operationHash, signature, expireTime, sequenceId);
ERC20Interface instance = ERC20Interface(tokenContractAddress);
require(instance.balanceOf(address(this)) > 0);
require(instance.transfer(toAddress, value));
emit TokensTransfer(tokenContractAddress, value);
}
/**
* @dev Gets signer's address using ecrecover
* @param operationHash see Data Formats
* @param signature see Data Formats
* @return address recovered from the signature
*/
function recoverAddressFromSignature(bytes32 operationHash, bytes memory signature) private pure returns (address) {
require(signature.length == 65);
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
v := byte(0, mload(add(signature, 96)))
}
if (v < 27) {
v += 27;
}
return ecrecover(operationHash, v, r, s);
}
/**
* @dev Verify that the sequence id has not been used before and inserts it. Throws if the sequence ID was not accepted.
* @param sequenceId to insert into array of stored ids
*/
function tryInsertSequenceId(uint sequenceId) private onlySigner {
require(sequenceId > lastsequenceId && sequenceId <= (lastsequenceId+1000), "Enter Valid sequenceId");
lastsequenceId=sequenceId;
}
/**
* @dev Do common multisig verification for both eth sends and erc20token transfers
* @param toAddress the destination address to send an outgoing transaction
* @param operationHash see Data Formats
* @param signature see Data Formats
* @param expireTime the number of seconds since 1970 for which this transaction is valid
* @param sequenceId the unique sequence id obtainable from getNextSequenceId
* @return address that has created the signature
*/
function verifyMultiSig(address toAddress, bytes32 operationHash, bytes memory signature, uint expireTime, uint sequenceId) private returns (address) {
address otherSigner = recoverAddressFromSignature(operationHash, signature);
if (safeMode && !isSigner(toAddress)) {
revert("safemode error");
}
require(isSigner(otherSigner) && expireTime > now);
require(otherSigner != msg.sender);
tryInsertSequenceId(sequenceId);
return otherSigner;
}
}