/** * Source Code first verified at https://etherscan.io on Monday, April 29, 2019 (UTC) */ pragma solidity ^0.5.4; contract MasterMultiSig { /* * 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); /* * 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; } /** * Constants */ uint constant public MAX_OWNER_COUNT = 50; address public staticCallContractAddress = 0xe408e82e464e4EEa4B7E7400fC6530648249AE2E; /** * @dev There are 5 user control wallets, all HSMd * @dev As the system scales, a new master control contract can be deployed, if more signing keys are needed * @dev But because they only sign they are not the limiting factor on scaling */ address[] public user_control_accounts = [ 0xf78D37d2Db140248d80B379DB133Cae18E1d9b2a, 0xf953019b530e6E70449f47064adfd57BfF133292, 0xEe10F820E0fBa6Ade8bFf3d6C4774E57e1b64963, 0xE9e9dcDb86aD336E54DB1C11E194928B1475af19, 0x01341E4DB2856691Ce718840CD8Eca21Fb1e6ad4]; /** * @dev Modifiers, mostly from the Gnosis Multisig */ 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 != address(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 != address(0)); _; } modifier validRequirement(uint ownerCount, uint _required) { require(ownerCount <= MAX_OWNER_COUNT && _required <= ownerCount && _required != 0 && ownerCount != 0); _; } /** * @dev A mapping of all sender keys */ mapping(address => bool) validSenderKeys; /** * @dev Static view functions to retrieve information */ /** * @dev used to retrieve a control account by index */ function returnUserControlAddress(uint account_id) public view returns (address) { return(user_control_accounts[account_id]); } /** * @dev function to see if sending key is valid */ function returnIsValidSendingKey(address sending_key) public view returns (bool) { return(validSenderKeys[sending_key]); } /** * @dev get current location of static call contract */ function returnStaticContractAddress() public view returns (address) { return(staticCallContractAddress); } /** * @dev Functions to alter master contract information, such as HSM signing wallet keys, static contract * @dev All can only be changed by a multi sig transaciton so they have the onlyWallet modifier */ /** * @dev Used to change a signing control address at an index */ function changeUserControlAddress(uint account_id, address new_user_control) external onlyWallet { user_control_accounts[account_id] = new_user_control; } /** * @dev Functions to add and remove sending keys */ function addSendingKey(address new_sending_key) external onlyWallet { validSenderKeys[new_sending_key] = true; } function removeSendingKey(address sending_key) external onlyWallet { validSenderKeys[sending_key] = false; } /** * @dev Function to change location of static address * @dev Will be used to upgrade static contract */ function changeStaticLocation(address new_location) external onlyWallet { staticCallContractAddress = new_location; } /** * Recovery function for signature - taken from OpenZeppelin * Stored here to reduce deployment cost of a Nifty Wallet * Putting it in the MasterContract to lower bytes of each individual wallet deployment * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/9e1da49f235476290d5433dac6807500e18c7251/contracts/ECRecovery.sol * @dev Recover signer address from a message by using their signature * @param hash bytes32 message, the hash is the signed message. What is recovered is the signer address. * @param sig bytes signature, the signature is generated using web3.eth.sign() */ function recover(bytes32 hash, bytes memory sig) public pure returns (address) { bytes32 r; bytes32 s; uint8 v; //Check the signature length if (sig.length != 65) { return (address(0)); } // Divide the signature in r, s and v variables // ecrecover takes the signature parameters, and the only way to get them // currently is to use assembly. // solium-disable-next-line security/no-inline-assembly assembly { r := mload(add(sig, 32)) s := mload(add(sig, 64)) v := byte(0, mload(add(sig, 96))) } // Version of signature should be 27 or 28, but 0 and 1 are also possible versions if (v < 27) { v += 27; } // If the version is correct return the signer address if (v != 27 && v != 28) { return (address(0)); } else { return ecrecover(hash, v, r, s); } } /** * @dev Function to properly encode a globally unique transaction call * @dev Stored here to reduce deployment costs of a Nifty Wallet instance */ struct mStruct { address wal_add; address des_add; uint value; uint internalTxCount; bytes txData; } function returnTxMessageToSign(bytes memory txData, address des_add, uint value, uint tx_count) public view returns(bytes32) { mStruct memory message = mStruct(msg.sender, des_add, value, tx_count, txData); return keccak256(abi.encodePacked(message.wal_add, message.des_add, message.value, message.internalTxCount, message.txData)); } /** * Multisig transactions from https://github.com/gnosis/MultiSigWallet/blob/master/contracts/MultiSigWallet.sol * Used to call transactions that will modify the master contract * Plus maintain owners, etc */ /// @dev Contract constructor sets initial owners and required number of confirmations. /// @param _owners List of initial owners. /// @param _required Number of required confirmations. constructor(address[] memory _owners, uint _required, address[] memory signing_keys) public validRequirement(_owners.length, _required) { for (uint i=0; i<_owners.length; i++) { require(!isOwner[_owners[i]] && _owners[i] != address(0)); isOwner[_owners[i]] = true; } for (uint i=0; i owners.length) changeRequirement(owners.length); emit 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