Skip to content

Instantly share code, notes, and snippets.

@3esmit
Last active February 14, 2018 21:53
Show Gist options
  • Save 3esmit/cbfa213c87c151334f819864dce47d2c to your computer and use it in GitHub Desktop.
Save 3esmit/cbfa213c87c151334f819864dce47d2c to your computer and use it in GitHub Desktop.
MultiSigPreSigned.sol
pragma solidity ^0.4.17;
/// @title Multisignature wallet - Allows multiple parties to agree on transactions before execution.
/// @author Stefan George - <[email protected]> & Ricardo Guilherme Schmidt <[email protected]>
contract MultiSig {
uint constant public MAX_OWNER_COUNT = 50;
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);
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;
}
/*
* Modifiers
*/
modifier onlyMultiSig() {
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 != 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 != 0);
_;
}
modifier validRequirement(uint ownerCount, uint _required) {
require(ownerCount <= MAX_OWNER_COUNT && _required <= ownerCount && _required != 0 && ownerCount != 0);
_;
}
/*
* 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.
function MultiSig(address[] _owners, uint _required)
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;
}
owners = _owners;
required = _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)
external
returns (uint transactionId)
{
return submitTransaction(msg.sender, _destination, _value, _data);
}
/// @dev Allows an owner to confirm a transaction.
/// @param _transactionId Transaction ID.
function confirmTransaction(uint _transactionId)
external
{
confirmTransaction(msg.sender, _transactionId);
}
/// @dev Allows an owner to revoke a confirmation for a transaction.
/// @param _transactionId Transaction ID.
function revokeConfirmation(uint _transactionId)
external
{
revokeConfirmation(msg.sender, _transactionId);
}
/// @dev Allows anyone to execute a confirmed transaction.
/// @param _transactionId Transaction ID.
function executeTransaction(uint _transactionId)
public
notExecuted(_transactionId)
{
if (isConfirmed(_transactionId)) {
Transaction storage txn = transactions[_transactionId];
txn.executed = true;
if (txn.destination.call.value(txn.value)(txn.data))
Execution(_transactionId);
else {
ExecutionFailure(_transactionId);
txn.executed = false;
}
}
}
/// @dev Returns the confirmation status of a transaction.
/// @param _transactionId Transaction ID.
/// @return Confirmation status.
function isConfirmed(uint _transactionId)
public
constant
returns (bool)
{
uint count = 0;
for (uint i = 0; i < owners.length; i++) {
if (confirmations[_transactionId][owners[i]]) {
count += 1;
}
if (count == required) {
return true;
}
}
}
/*
* Web3 call functions
*/
/// @dev Returns number of confirmations of a transaction.
/// @param _transactionId Transaction ID.
/// @return Number of confirmations.
function getConfirmationCount(uint _transactionId)
public
constant
returns (uint count)
{
for (uint i = 0; i < owners.length; i++) {
if (confirmations[_transactionId][owners[i]]) {
count += 1;
}
}
}
/// @dev Returns total number of transactions after filers are applied.
/// @param _pending Include pending transactions.
/// @param _executed Include executed transactions.
/// @return Total number of transactions after filters are applied.
function getTransactionCount(bool _pending, bool _executed)
public
constant
returns (uint count)
{
for (uint i = 0; i < transactionCount; i++) {
if (_pending && !transactions[i].executed || _executed && transactions[i].executed) {
count += 1;
}
}
}
/// @dev Returns list of owners.
/// @return List of owner addresses.
function getOwners()
public
constant
returns (address[])
{
return owners;
}
/// @dev Returns array with owner addresses, which confirmed transaction.
/// @param _transactionId Transaction ID.
/// @return Returns array of owner addresses.
function getConfirmations(uint _transactionId)
public
constant
returns (address[] _confirmations)
{
address[] memory confirmationsTemp = new address[](owners.length);
uint count = 0;
uint i;
for (i = 0; i < owners.length; i++) {
if (confirmations[_transactionId][owners[i]]) {
confirmationsTemp[count] = owners[i];
count += 1;
}
}
_confirmations = new address[](count);
for (i = 0; i < count; i++) {
_confirmations[i] = confirmationsTemp[i];
}
}
/// @dev Returns list of transaction IDs in defined range.
/// @param _from Index start position of transaction array.
/// @param _to Index end position of transaction array.
/// @param _pending Include pending transactions.
/// @param _executed Include executed transactions.
/// @return Returns array of transaction IDs.
function getTransactionIds(uint _from, uint _to, bool _pending, bool _executed)
public
constant
returns (uint[] transactionIds)
{
uint[] memory transactionIdsTemp = new uint[](transactionCount);
uint count = 0;
uint i;
for (i = 0; i < transactionCount; i++) {
if (_pending && !transactions[i].executed || _executed && transactions[i].executed) {
transactionIdsTemp[count] = i;
count += 1;
}
}
transactionIds = new uint[](_to - _from);
for (i = _from; i < _to; i++) {
transactionIds[i - _from] = transactionIdsTemp[i];
}
}
/*
* Internal functions
*/
/// @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 _from, address _destination, uint _value, bytes _data)
internal
returns (uint transactionId)
{
transactionId = addTransaction(_destination, _value, _data);
confirmTransaction(_from, transactionId);
}
/// @dev Adds a new transaction to the transaction mapping, if transaction does not exist yet.
/// @param _destination Transaction target address.
/// @param _value Transaction ether value.
/// @param _data Transaction data payload.
/// @return Returns transaction ID.
function addTransaction(address _destination, uint _value, bytes _data)
internal
notNull(_destination)
returns (uint transactionId)
{
transactionId = transactionCount;
transactions[transactionId] = Transaction({
destination: _destination,
value: _value,
data: _data,
executed: false
});
transactionCount += 1;
Submission(transactionId);
}
/// @dev Allows an owner to confirm a transaction.
/// @param _transactionId Transaction ID.
function confirmTransaction(address _from, uint _transactionId)
internal
ownerExists(_from)
transactionExists(_transactionId)
notConfirmed(_transactionId, _from)
{
confirmations[_transactionId][_from] = true;
Confirmation(_from, _transactionId);
executeTransaction(_transactionId);
}
/// @dev Allows an owner to revoke a confirmation for a transaction.
/// @param _transactionId Transaction ID.
function revokeConfirmation(address _from, uint _transactionId)
internal
ownerExists(_from)
confirmed(_transactionId, _from)
notExecuted(_transactionId)
{
confirmations[_transactionId][_from] = false;
Revocation(_from, _transactionId);
}
}
pragma solidity ^0.4.17;
import "./MultiSig.sol";
contract MultiSigPreSigned is MultiSig {
mapping(address => uint256) presignNonce;
function callPreSigned(bytes _signature, bytes _data, uint256 _nonce) external {
address recovered = recoverCallPreSigned(_signature, _data, _nonce);
require(recovered != address(0));
require(presignNonce[recovered] == _nonce);
presignNonce[recovered]++;
internalCall(recovered, _data);
}
function internalCall(address _from, bytes _data) private {
// {size}{4bytes method}{position 32 bytes}[{lenght 32 bytes}{signature data}]
uint datasize = _data.length;
require(datasize > 4); // data must be at least 4 bytes for reading the call method
bytes4 calling;
assembly {
calling := mload(add(_data, 4))
}
if (calling == method("submitTransaction(address,uint256,bytes)")) {
_submitTransaction(_from, datasize, _data);
} else if (calling == method("confirmTransaction(uint256)")) {
_confirmTransaction(_from, datasize, _data);
} else if (calling == method("executeTransaction(uint256)")) {
_executeTransaction(datasize, _data);
} else {
revert();
}
}
/**
* @dev process the proxied call
*/
function _submitTransaction(address _from, uint256 datasize, bytes _data) private {
require(datasize > 100); //must contain at least array size and pos
address callDestination;
uint256 callValue;
bytes memory callData;
assembly {
callDestination := mload(add(_data, 36))
callValue := mload(add(_data, 68))
callData := mload(add(_data, 100))
}
submitTransaction(_from, callDestination, callValue, callData);
}
/**
* @dev process the proxied call
*/
function _confirmTransaction(address _from, uint256 datasize, bytes _data) private {
require(datasize == 36); //each additional parameter add 32
uint256 transactionId;
assembly {
transactionId := mload(add(_data, 36))
}
confirmTransaction(_from, transactionId);
}
/**
* @dev process the proxied call
*/
function _executeTransaction(uint256 datasize, bytes _data) private {
require(datasize == 36); //each additional parameter add 32
uint256 transactionId;
assembly {
transactionId := mload(add(_data, 36))
}
executeTransaction(transactionId);
}
/**
*
*/
function method(string s)
private
pure
returns(bytes4)
{
return bytes4(keccak256(s));
}
/**
* @notice get txHash of presigned `approve(address,uint256)`
* @param _data Presigned data
* @param _nonce Presigned transaction number.
* @return txHash Presigned `approve(address,uint256)` Hash
*/
function getCallHash(
bytes _data,
uint256 _nonce
)
constant
public
returns(bytes32 txHash)
{
//"095ea7b3": "approve(address,uint256)",
txHash = keccak256(address(this), _data, _nonce);
}
/**
* @notice recover the address which signed an approve
* @param signature
* @param _data
* @param _nonce Presigned transaction number.
*/
function recoverCallPreSigned(
bytes signature,
bytes _data,
uint256 _nonce
)
constant
public
returns (address recovered)
{
var (_sigV, _sigR, _sigS) = signatureSplit(signature);
recovered = ecrecover(getSignedHash(getCallHash(_data, _nonce)), _sigV, _sigR, _sigS);
}
/**
* @notice Hash a hash with `"\x19Ethereum Signed Message:\n32"`
* @param _hash Sign to hash.
* @return signHash Hash to be signed.
*/
function getSignedHash(
bytes32 _hash
)
pure
public
returns(bytes32 signHash)
{
signHash = keccak256("\x19Ethereum Signed Message:\n32", _hash);
}
/**
* @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`
*/
function signatureSplit(bytes signature)
pure
private
returns (uint8 v, bytes32 r, bytes32 s)
{
// The signature format is a compact form of:
// {bytes32 r}{bytes32 s}{uint8 v}
// Compact means, uint8 is not padded to 32 bytes.
assembly {
r := mload(add(signature, 32))
s := mload(add(signature, 64))
// Here we are loading the last 32 bytes, including 31 bytes
// of 's'. There is no 'mload8' to do this.
//
// 'byte' is not working due to the Solidity parser, so lets
// use the second best option, 'and'
v := and(mload(add(signature, 65)), 0xff)
}
require(v == 27 || v == 28);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment