pragma solidity 0.4.24; /// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution. /// @author Stefan George - <stefan.george@consensys.net> contract MultiSigWallet { /* * Events */ event Confirmation(address indexed sender, uint indexed transactionId); event Revocation(address indexed sender, address destination, uint value, bytes data); 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); event SubmitTransaction(bytes32, address[]); /* * Constants */ uint constant public MAX_OWNER_COUNT = 50; /* * Storage */ mapping (address => bool) public isOwner; address[] public owners; uint public required; mapping (bytes32 => address[]) txHashToConfirmersMap; /* * Modifiers */ modifier onlyWallet() { require(msg.sender == address(this)); _; } modifier ownerDoesNotExist(address owner) { require(!isOwner[owner]); _; } modifier ownerExists(address owner) { require(isOwner[owner]); _; } modifier notNull(address _address) { require(_address != 0); _; } modifier validRequirement(uint ownerCount, uint _required) { require(ownerCount <= MAX_OWNER_COUNT && _required <= ownerCount && _required != 0 && ownerCount != 0); _; } /// @dev Fallback function allows to deposit ether. function() public payable { if (msg.value > 0) emit Deposit(msg.sender, msg.value); } /* * 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. constructor(address[] _owners, uint _required) public validRequirement(_owners.length, _required) { for (uint i=0; i<_owners.length; i++) { require(!isOwner[_owners[i]] && _owners[i] != 0); 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); emit OwnerAddition(owner); } /// @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); 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<owners.length; i++) if (owners[i] == owner) { owners[i] = newOwner; break; } isOwner[owner] = false; isOwner[newOwner] = true; emit OwnerRemoval(owner); emit OwnerAddition(newOwner); } /// @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; emit RequirementChange(_required); } /// @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. function submitTransaction(address destination, uint value, bytes data) public ownerExists(msg.sender) { bytes32 txHash = keccak256(abi.encodePacked(destination, value, data)); txHashToConfirmersMap[txHash]; for (uint i = 0; i < txHashToConfirmersMap[txHash].length; i++){ require(msg.sender != txHashToConfirmersMap[txHash][i]); } txHashToConfirmersMap[txHash].push(msg.sender); if (txHashToConfirmersMap[txHash].length == required) { external_call(destination, value, data.length, data); delete txHashToConfirmersMap[txHash]; } emit SubmitTransaction(txHash, txHashToConfirmersMap[txHash]); } function revokeConfirmation(address destination, uint value, bytes data) public ownerExists(msg.sender) { bytes32 txHash = keccak256(abi.encodePacked(destination, value, data)); for (uint i = 0; i < txHashToConfirmersMap[txHash].length; i++){ if(msg.sender == txHashToConfirmersMap[txHash][i]){ if(i != txHashToConfirmersMap[txHash].length - 1) { txHashToConfirmersMap[txHash][i] = txHashToConfirmersMap[txHash][txHashToConfirmersMap[txHash].length - 1]; } txHashToConfirmersMap[txHash].length--; emit Revocation(msg.sender, destination, value, data); } } } function getConfirmersList(bytes32 txHash) public view returns(address[]) { return txHashToConfirmersMap[txHash]; } // call has been separated into its own function in order to take advantage // of the Solidity's code generator to produce a loop that copies tx.data into memory. function external_call(address destination, uint value, uint dataLength, bytes data) private returns (bool) { bool result; assembly { let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention) let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that result := call( sub(gas, 34710), // 34710 is the value that solidity is currently emitting // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) + // callNewAccountGas (25000, in case the destination address does not exist and needs creating) destination, value, d, dataLength, // Size of the input (in bytes) - this is what fixes the padding problem x, 0 // Output is ignored, therefore the output size is zero ) } return result; } }
0.4.24