Contract Name:
WalletFactory
Contract Source Code:
File 1 of 1 : WalletFactory
// File: contracts/ERC721/ERC721ReceiverDraft.sol
pragma solidity ^0.4.24;
/// @title ERC721ReceiverDraft
/// @dev Interface for any contract that wants to support safeTransfers from
/// ERC721 asset contracts.
/// @dev Note: this is the interface defined from
/// https://github.com/ethereum/EIPs/commit/2bddd126def7c046e1e62408dc2b51bdd9e57f0f
/// to https://github.com/ethereum/EIPs/commit/27788131d5975daacbab607076f2ee04624f9dbb
/// and is not the final interface.
/// Due to the extended period of time this revision was specified in the draft,
/// we are supporting both this and the newer (final) interface in order to be
/// compatible with any ERC721 implementations that may have used this interface.
contract ERC721ReceiverDraft {
/// @dev Magic value to be returned upon successful reception of an NFT
/// Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`,
/// which can be also obtained as `ERC721ReceiverDraft(0).onERC721Received.selector`
/// @dev see https://github.com/ethereum/EIPs/commit/2bddd126def7c046e1e62408dc2b51bdd9e57f0f
bytes4 internal constant ERC721_RECEIVED_DRAFT = 0xf0b9e5ba;
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. This function MUST use 50,000 gas or less. Return of other
/// than the magic value MUST result in the transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _from The sending address
/// @param _tokenId The NFT identifier which is being transfered
/// @param data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
/// unless throwing
function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4);
}
// File: contracts/ERC721/ERC721ReceiverFinal.sol
pragma solidity ^0.4.24;
/// @title ERC721ReceiverFinal
/// @notice Interface for any contract that wants to support safeTransfers from
/// ERC721 asset contracts.
/// @dev Note: this is the final interface as defined at http://erc721.org
contract ERC721ReceiverFinal {
/// @dev Magic value to be returned upon successful reception of an NFT
/// Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`,
/// which can be also obtained as `ERC721ReceiverFinal(0).onERC721Received.selector`
/// @dev see https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.12.0/contracts/token/ERC721/ERC721Receiver.sol
bytes4 internal constant ERC721_RECEIVED_FINAL = 0x150b7a02;
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `safetransfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
function onERC721Received(
address _operator,
address _from,
uint256 _tokenId,
bytes _data
)
public
returns (bytes4);
}
// File: contracts/ERC721/ERC721Receivable.sol
pragma solidity ^0.4.24;
/// @title ERC721Receivable handles the reception of ERC721 tokens
/// See ERC721 specification
/// @author Christopher Scott
/// @dev These functions are public, and could be called by anyone, even in the case
/// where no NFTs have been transferred. Since it's not a reliable source of
/// truth about ERC721 tokens being transferred, we save the gas and don't
/// bother emitting a (potentially spurious) event as found in
/// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/5471fc808a17342d738853d7bf3e9e5ef3108074/contracts/mocks/ERC721ReceiverMock.sol
contract ERC721Receivable is ERC721ReceiverDraft, ERC721ReceiverFinal {
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `transfer`. This function MAY throw to revert and reject the
/// transfer. This function MUST use 50,000 gas or less. Return of other
/// than the magic value MUST result in the transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _from The sending address
/// @param _tokenId The NFT identifier which is being transfered
/// @param data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
/// unless throwing
function onERC721Received(address _from, uint256 _tokenId, bytes data) external returns(bytes4) {
_from;
_tokenId;
data;
// emit ERC721Received(_operator, _from, _tokenId, _data, gasleft());
return ERC721_RECEIVED_DRAFT;
}
/// @notice Handle the receipt of an NFT
/// @dev The ERC721 smart contract calls this function on the recipient
/// after a `safetransfer`. This function MAY throw to revert and reject the
/// transfer. Return of other than the magic value MUST result in the
/// transaction being reverted.
/// Note: the contract address is always the message sender.
/// @param _operator The address which called `safeTransferFrom` function
/// @param _from The address which previously owned the token
/// @param _tokenId The NFT identifier which is being transferred
/// @param _data Additional data with no specified format
/// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
function onERC721Received(
address _operator,
address _from,
uint256 _tokenId,
bytes _data
)
public
returns(bytes4)
{
_operator;
_from;
_tokenId;
_data;
// emit ERC721Received(_operator, _from, _tokenId, _data, gasleft());
return ERC721_RECEIVED_FINAL;
}
}
// File: contracts/ERC223/ERC223Receiver.sol
pragma solidity ^0.4.24;
/// @title ERC223Receiver ensures we are ERC223 compatible
/// @author Christopher Scott
contract ERC223Receiver {
bytes4 public constant ERC223_ID = 0xc0ee0b8a;
struct TKN {
address sender;
uint value;
bytes data;
bytes4 sig;
}
/// @notice tokenFallback is called from an ERC223 compatible contract
/// @param _from the address from which the token was sent
/// @param _value the amount of tokens sent
/// @param _data the data sent with the transaction
function tokenFallback(address _from, uint _value, bytes _data) public pure {
_from;
_value;
_data;
// TKN memory tkn;
// tkn.sender = _from;
// tkn.value = _value;
// tkn.data = _data;
// uint32 u = uint32(_data[3]) + (uint32(_data[2]) << 8) + (uint32(_data[1]) << 16) + (uint32(_data[0]) << 24);
// tkn.sig = bytes4(u);
/* tkn variable is analogue of msg variable of Ether transaction
* tkn.sender is person who initiated this token transaction (analogue of msg.sender)
* tkn.value the number of tokens that were sent (analogue of msg.value)
* tkn.data is data of token transaction (analogue of msg.data)
* tkn.sig is 4 bytes signature of function
* if data of token transaction is a function execution
*/
}
}
// File: contracts/ERC1271/ERC1271.sol
pragma solidity ^0.4.24;
contract ERC1271 {
/// @dev bytes4(keccak256("isValidSignature(bytes32,bytes)")
bytes4 internal constant ERC1271_VALIDSIGNATURE = 0x1626ba7e;
/// @dev Should return whether the signature provided is valid for the provided data
/// @param hash 32-byte hash of the data that is signed
/// @param _signature Signature byte array associated with _data
/// MUST return the bytes4 magic value 0x1626ba7e when function passes.
/// MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5)
/// MUST allow external calls
function isValidSignature(
bytes32 hash,
bytes _signature)
external
view
returns (bytes4);
}
// File: contracts/ECDSA.sol
pragma solidity ^0.4.24;
/// @title ECDSA is a library that contains useful methods for working with ECDSA signatures
library ECDSA {
/// @notice Extracts the r, s, and v components from the `sigData` field starting from the `offset`
/// @dev Note: does not do any bounds checking on the arguments!
/// @param sigData the signature data; could be 1 or more packed signatures.
/// @param offset the offset in sigData from which to start unpacking the signature components.
function extractSignature(bytes sigData, uint256 offset) internal pure returns (bytes32 r, bytes32 s, uint8 v) {
// 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 {
let dataPointer := add(sigData, offset)
r := mload(add(dataPointer, 0x20))
s := mload(add(dataPointer, 0x40))
v := byte(0, mload(add(dataPointer, 0x60)))
}
return (r, s, v);
}
}
// File: contracts/Wallet/CoreWallet.sol
pragma solidity ^0.4.24;
/// @title Core Wallet
/// @notice A basic smart contract wallet with cosigner functionality. The notion of "cosigner" is
/// the simplest possible multisig solution, a two-of-two signature scheme. It devolves nicely
/// to "one-of-one" (i.e. singlesig) by simply having the cosigner set to the same value as
/// the main signer.
///
/// Most "advanced" functionality (deadman's switch, multiday recovery flows, blacklisting, etc)
/// can be implemented externally to this smart contract, either as an additional smart contract
/// (which can be tracked as a signer without cosigner, or as a cosigner) or as an off-chain flow
/// using a public/private key pair as cosigner. Of course, the basic cosigning functionality could
/// also be implemented in this way, but (A) the complexity and gas cost of two-of-two multisig (as
/// implemented here) is negligable even if you don't need the cosigner functionality, and
/// (B) two-of-two multisig (as implemented here) handles a lot of really common use cases, most
/// notably third-party gas payment and off-chain blacklisting and fraud detection.
contract CoreWallet is ERC721Receivable, ERC223Receiver, ERC1271 {
using ECDSA for bytes;
/// @notice We require that presigned transactions use the EIP-191 signing format.
/// See that EIP for more info: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-191.md
byte public constant EIP191_VERSION_DATA = byte(0);
byte public constant EIP191_PREFIX = byte(0x19);
/// @notice This is the version of the contract.
string public constant VERSION = "1.0.0";
/// @notice A pre-shifted "1", used to increment the authVersion, so we can "prepend"
/// the authVersion to an address (for lookups in the authorizations mapping)
/// by using the '+' operator (which is cheaper than a shift and a mask). See the
/// comment on the `authorizations` variable for how this is used.
uint256 public constant AUTH_VERSION_INCREMENTOR = (1 << 160);
/// @notice The pre-shifted authVersion (to get the current authVersion as an integer,
/// shift this value right by 160 bits). Starts as `1 << 160` (`AUTH_VERSION_INCREMENTOR`)
/// See the comment on the `authorizations` variable for how this is used.
uint256 public authVersion;
/// @notice A mapping containing all of the addresses that are currently authorized to manage
/// the assets owned by this wallet.
///
/// The keys in this mapping are authorized addresses with a version number prepended,
/// like so: (authVersion,96)(address,160). The current authVersion MUST BE included
/// for each look-up; this allows us to effectively clear the entire mapping of its
/// contents merely by incrementing the authVersion variable. (This is important for
/// the emergencyRecovery() method.) Inspired by https://ethereum.stackexchange.com/a/42540
///
/// The values in this mapping are 256bit words, whose lower 20 bytes constitute "cosigners"
/// for each address. If an address maps to itself, then that address is said to have no cosigner.
///
/// The upper 12 bytes are reserved for future meta-data purposes. The meta-data could refer
/// to the key (authorized address) or the value (cosigner) of the mapping.
///
/// Addresses that map to a non-zero cosigner in the current authVersion are called
/// "authorized addresses".
mapping(uint256 => uint256) public authorizations;
/// @notice A per-key nonce value, incremented each time a transaction is processed with that key.
/// Used for replay prevention. The nonce value in the transaction must exactly equal the current
/// nonce value in the wallet for that key. (This mirrors the way Ethereum's transaction nonce works.)
mapping(address => uint256) public nonces;
/// @notice A special address that is authorized to call `emergencyRecovery()`. That function
/// resets ALL authorization for this wallet, and must therefore be treated with utmost security.
/// Reasonable choices for recoveryAddress include:
/// - the address of a private key in cold storage
/// - a physically secured hardware wallet
/// - a multisig smart contract, possibly with a time-delayed challenge period
/// - the zero address, if you like performing without a safety net ;-)
address public recoveryAddress;
/// @notice Used to track whether or not this contract instance has been initialized. This
/// is necessary since it is common for this wallet smart contract to be used as the "library
/// code" for an clone contract. See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1167.md
/// for more information about clone contracts.
bool public initialized;
/// @notice Used to decorate methods that can only be called directly by the recovery address.
modifier onlyRecoveryAddress() {
require(msg.sender == recoveryAddress, "sender must be recovery address");
_;
}
/// @notice Used to decorate the `init` function so this can only be called one time. Necessary
/// since this contract will often be used as a "clone". (See above.)
modifier onlyOnce() {
require(!initialized, "must not already be initialized");
initialized = true;
_;
}
/// @notice Used to decorate methods that can only be called indirectly via an `invoke()` method.
/// In practice, it means that those methods can only be called by a signer/cosigner
/// pair that is currently authorized. Theoretically, we could factor out the
/// signer/cosigner verification code and use it explicitly in this modifier, but that
/// would either result in duplicated code, or additional overhead in the invoke()
/// calls (due to the stack manipulation for calling into the shared verification function).
/// Doing it this way makes calling the administration functions more expensive (since they
/// go through a explict call() instead of just branching within the contract), but it
/// makes invoke() more efficient. We assume that invoke() will be used much, much more often
/// than any of the administration functions.
modifier onlyInvoked() {
require(msg.sender == address(this), "must be called from `invoke()`");
_;
}
/// @notice Emitted when an authorized address is added, removed, or modified. When an
/// authorized address is removed ("deauthorized"), cosigner will be address(0) in
/// this event.
///
/// NOTE: When emergencyRecovery() is called, all existing addresses are deauthorized
/// WITHOUT Authorized(addr, 0) being emitted. If you are keeping an off-chain mirror of
/// authorized addresses, you must also watch for EmergencyRecovery events.
/// @dev hash is 0xf5a7f4fb8a92356e8c8c4ae7ac3589908381450500a7e2fd08c95600021ee889
/// @param authorizedAddress the address to authorize or unauthorize
/// @param cosigner the 2-of-2 signatory (optional).
event Authorized(address authorizedAddress, uint256 cosigner);
/// @notice Emitted when an emergency recovery has been performed. If this event is fired,
/// ALL previously authorized addresses have been deauthorized and the only authorized
/// address is the authorizedAddress indicated in this event.
/// @dev hash is 0xe12d0bbeb1d06d7a728031056557140afac35616f594ef4be227b5b172a604b5
/// @param authorizedAddress the new authorized address
/// @param cosigner the cosigning address for `authorizedAddress`
event EmergencyRecovery(address authorizedAddress, uint256 cosigner);
/// @notice Emitted when the recovery address changes. Either (but not both) of the
/// parameters may be zero.
/// @dev hash is 0x568ab3dedd6121f0385e007e641e74e1f49d0fa69cab2957b0b07c4c7de5abb6
/// @param previousRecoveryAddress the previous recovery address
/// @param newRecoveryAddress the new recovery address
event RecoveryAddressChanged(address previousRecoveryAddress, address newRecoveryAddress);
/// @dev Emitted when this contract receives a non-zero amount ether via the fallback function
/// (i.e. This event is not fired if the contract receives ether as part of a method invocation)
/// @param from the address which sent you ether
/// @param value the amount of ether sent
event Received(address from, uint value);
/// @notice Emitted whenever a transaction is processed sucessfully from this wallet. Includes
/// both simple send ether transactions, as well as other smart contract invocations.
/// @dev hash is 0x101214446435ebbb29893f3348e3aae5ea070b63037a3df346d09d3396a34aee
/// @param hash The hash of the entire operation set. 0 is returned when emitted from `invoke0()`.
/// @param result A bitfield of the results of the operations. A bit of 0 means success, and 1 means failure.
/// @param numOperations A count of the number of operations processed
event InvocationSuccess(
bytes32 hash,
uint256 result,
uint256 numOperations
);
/// @notice The shared initialization code used to setup the contract state regardless of whether or
/// not the clone pattern is being used.
/// @param _authorizedAddress the initial authorized address, must not be zero!
/// @param _cosigner the initial cosigning address for `_authorizedAddress`, can be equal to `_authorizedAddress`
/// @param _recoveryAddress the initial recovery address for the wallet, can be address(0)
function init(address _authorizedAddress, uint256 _cosigner, address _recoveryAddress) public onlyOnce {
require(_authorizedAddress != _recoveryAddress, "Do not use the recovery address as an authorized address.");
require(address(_cosigner) != _recoveryAddress, "Do not use the recovery address as a cosigner.");
require(_authorizedAddress != address(0), "Authorized addresses must not be zero.");
require(address(_cosigner) != address(0), "Initial cosigner must not be zero.");
recoveryAddress = _recoveryAddress;
// set initial authorization value
authVersion = AUTH_VERSION_INCREMENTOR;
// add initial authorized address
authorizations[authVersion + uint256(_authorizedAddress)] = _cosigner;
emit Authorized(_authorizedAddress, _cosigner);
}
/// @notice The fallback function, invoked whenever we receive a transaction that doesn't call any of our
/// named functions. In particular, this method is called when we are the target of a simple send transaction
/// or when someone tries to call a method that we don't implement. We assume that a "correct" invocation of
/// this method only occurs when someone is trying to transfer ether to this wallet, in which case and the
/// `msg.data.length` will be 0.
///
/// NOTE: Some smart contracts send 0 eth as part of a more complex
/// operation (-cough- CryptoKitties -cough-) ; ideally, we'd `require(msg.value > 0)` here, but to work
/// with those kinds of smart contracts, we accept zero sends and just skip logging in that case.
function() external payable {
require(msg.data.length == 0, "Invalid transaction.");
if (msg.value > 0) {
emit Received(msg.sender, msg.value);
}
}
/// @notice Configures an authorizable address. Can be used in four ways:
/// - Add a new signer/cosigner pair (cosigner must be non-zero)
/// - Set or change the cosigner for an existing signer (if authorizedAddress != cosigner)
/// - Remove the cosigning requirement for a signer (if authorizedAddress == cosigner)
/// - Remove a signer (if cosigner == address(0))
/// @dev Must be called through `invoke()`
/// @param _authorizedAddress the address to configure authorization
/// @param _cosigner the corresponding cosigning address
function setAuthorized(address _authorizedAddress, uint256 _cosigner) external onlyInvoked {
// TODO: Allowing a signer to remove itself is actually pretty terrible; it could result in the user
// removing their only available authorized key. Unfortunately, due to how the invocation forwarding
// works, we don't actually _know_ which signer was used to call this method, so there's no easy way
// to prevent this.
// TODO: Allowing the backup key to be set as an authorized address bypasses the recovery mechanisms.
// Dapper can prevent this with offchain logic and the cosigner, but it would be nice to have
// this enforced by the smart contract logic itself.
require(_authorizedAddress != address(0), "Authorized addresses must not be zero.");
require(_authorizedAddress != recoveryAddress, "Do not use the recovery address as an authorized address.");
require(address(_cosigner) == address(0) || address(_cosigner) != recoveryAddress, "Do not use the recovery address as a cosigner.");
authorizations[authVersion + uint256(_authorizedAddress)] = _cosigner;
emit Authorized(_authorizedAddress, _cosigner);
}
/// @notice Performs an emergency recovery operation, removing all existing authorizations and setting
/// a sole new authorized address with optional cosigner. THIS IS A SCORCHED EARTH SOLUTION, and great
/// care should be taken to ensure that this method is never called unless it is a last resort. See the
/// comments above about the proper kinds of addresses to use as the recoveryAddress to ensure this method
/// is not trivially abused.
/// @param _authorizedAddress the new and sole authorized address
/// @param _cosigner the corresponding cosigner address, can be equal to _authorizedAddress
function emergencyRecovery(address _authorizedAddress, uint256 _cosigner) external onlyRecoveryAddress {
require(_authorizedAddress != address(0), "Authorized addresses must not be zero.");
require(_authorizedAddress != recoveryAddress, "Do not use the recovery address as an authorized address.");
require(address(_cosigner) != address(0), "The cosigner must not be zero.");
// Incrementing the authVersion number effectively erases the authorizations mapping. See the comments
// on the authorizations variable (above) for more information.
authVersion += AUTH_VERSION_INCREMENTOR;
// Store the new signer/cosigner pair as the only remaining authorized address
authorizations[authVersion + uint256(_authorizedAddress)] = _cosigner;
emit EmergencyRecovery(_authorizedAddress, _cosigner);
}
/// @notice Sets the recovery address, which can be zero (indicating that no recovery is possible)
/// Can be updated by any authorized address. This address should be set with GREAT CARE. See the
/// comments above about the proper kinds of addresses to use as the recoveryAddress to ensure this
/// mechanism is not trivially abused.
/// @dev Must be called through `invoke()`
/// @param _recoveryAddress the new recovery address
function setRecoveryAddress(address _recoveryAddress) external onlyInvoked {
require(
address(authorizations[authVersion + uint256(_recoveryAddress)]) == address(0),
"Do not use an authorized address as the recovery address."
);
address previous = recoveryAddress;
recoveryAddress = _recoveryAddress;
emit RecoveryAddressChanged(previous, recoveryAddress);
}
/// @notice Allows ANY caller to recover gas by way of deleting old authorization keys after
/// a recovery operation. Anyone can call this method to delete the old unused storage and
/// get themselves a bit of gas refund in the bargin.
/// @dev keys must be known to caller or else nothing is refunded
/// @param _version the version of the mapping which you want to delete (unshifted)
/// @param _keys the authorization keys to delete
function recoverGas(uint256 _version, address[] _keys) external {
// TODO: should this be 0xffffffffffffffffffffffff ?
require(_version > 0 && _version < 0xffffffff, "Invalid version number.");
uint256 shiftedVersion = _version << 160;
require(shiftedVersion < authVersion, "You can only recover gas from expired authVersions.");
for (uint256 i = 0; i < _keys.length; ++i) {
delete(authorizations[shiftedVersion + uint256(_keys[i])]);
}
}
/// @notice Should return whether the signature provided is valid for the provided data
/// See https://github.com/ethereum/EIPs/issues/1271
/// @dev This function meets the following conditions as per the EIP:
/// MUST return the bytes4 magic value `0x1626ba7e` when function passes.
/// MUST NOT modify state (using `STATICCALL` for solc < 0.5, `view` modifier for solc > 0.5)
/// MUST allow external calls
/// @param hash A 32 byte hash of the signed data. The actual hash that is hashed however is the
/// the following tightly packed arguments: `0x19,0x0,wallet_address,hash`
/// @param _signature Signature byte array associated with `_data`
/// @return Magic value `0x1626ba7e` upon success, 0 otherwise.
function isValidSignature(bytes32 hash, bytes _signature) external view returns (bytes4) {
// We 'hash the hash' for the following reasons:
// 1. `hash` is not the hash of an Ethereum transaction
// 2. signature must target this wallet to avoid replaying the signature for another wallet
// with the same key
// 3. Gnosis does something similar:
// https://github.com/gnosis/safe-contracts/blob/102e632d051650b7c4b0a822123f449beaf95aed/contracts/GnosisSafe.sol
bytes32 operationHash = keccak256(
abi.encodePacked(
EIP191_PREFIX,
EIP191_VERSION_DATA,
this,
hash));
bytes32[2] memory r;
bytes32[2] memory s;
uint8[2] memory v;
address signer;
address cosigner;
// extract 1 or 2 signatures depending on length
if (_signature.length == 65) {
(r[0], s[0], v[0]) = _signature.extractSignature(0);
signer = ecrecover(operationHash, v[0], r[0], s[0]);
cosigner = signer;
} else if (_signature.length == 130) {
(r[0], s[0], v[0]) = _signature.extractSignature(0);
(r[1], s[1], v[1]) = _signature.extractSignature(65);
signer = ecrecover(operationHash, v[0], r[0], s[0]);
cosigner = ecrecover(operationHash, v[1], r[1], s[1]);
} else {
return 0;
}
// check for valid signature
if (signer == address(0)) {
return 0;
}
// check for valid signature
if (cosigner == address(0)) {
return 0;
}
// check to see if this is an authorized key
if (address(authorizations[authVersion + uint256(signer)]) != cosigner) {
return 0;
}
return ERC1271_VALIDSIGNATURE;
}
/// @notice Query if a contract implements an interface
/// @param interfaceID The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceID` and
/// `interfaceID` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceID) external pure returns (bool) {
// I am not sure why the linter is complaining about the whitespace
return
interfaceID == this.supportsInterface.selector || // ERC165
interfaceID == ERC721_RECEIVED_FINAL || // ERC721 Final
interfaceID == ERC721_RECEIVED_DRAFT || // ERC721 Draft
interfaceID == ERC223_ID || // ERC223
interfaceID == ERC1271_VALIDSIGNATURE; // ERC1271
}
/// @notice A version of `invoke()` that has no explicit signatures, and uses msg.sender
/// as both the signer and cosigner. Will only succeed if `msg.sender` is an authorized
/// signer for this wallet, with no cosigner, saving transaction size and gas in that case.
/// @param data The data containing the transactions to be invoked; see internalInvoke for details.
function invoke0(bytes data) external {
// The nonce doesn't need to be incremented for transactions that don't include explicit signatures;
// the built-in nonce of the native ethereum transaction will protect against replay attacks, and we
// can save the gas that would be spent updating the nonce variable
// The operation should be approved if the signer address has no cosigner (i.e. signer == cosigner)
require(address(authorizations[authVersion + uint256(msg.sender)]) == msg.sender, "Invalid authorization.");
internalInvoke(0, data);
}
/// @notice A version of `invoke()` that has one explicit signature which is used to derive the authorized
/// address. Uses `msg.sender` as the cosigner.
/// @param v the v value for the signature; see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
/// @param r the r value for the signature
/// @param s the s value for the signature
/// @param nonce the nonce value for the signature
/// @param authorizedAddress the address of the authorization key; this is used here so that cosigner signatures are interchangeable
/// between this function and `invoke2()`
/// @param data The data containing the transactions to be invoked; see internalInvoke for details.
function invoke1CosignerSends(uint8 v, bytes32 r, bytes32 s, uint256 nonce, address authorizedAddress, bytes data) external {
// check signature version
require(v == 27 || v == 28, "Invalid signature version.");
// calculate hash
bytes32 operationHash = keccak256(
abi.encodePacked(
EIP191_PREFIX,
EIP191_VERSION_DATA,
this,
nonce,
authorizedAddress,
data));
// recover signer
address signer = ecrecover(operationHash, v, r, s);
// check for valid signature
require(signer != address(0), "Invalid signature.");
// check nonce
require(nonce == nonces[signer], "must use correct nonce");
// check signer
require(signer == authorizedAddress, "authorized addresses must be equal");
// Get cosigner
address requiredCosigner = address(authorizations[authVersion + uint256(signer)]);
// The operation should be approved if the signer address has no cosigner (i.e. signer == cosigner) or
// if the actual cosigner matches the required cosigner.
require(requiredCosigner == signer || requiredCosigner == msg.sender, "Invalid authorization.");
// increment nonce to prevent replay attacks
nonces[signer] = nonce + 1;
// call internal function
internalInvoke(operationHash, data);
}
/// @notice A version of `invoke()` that has one explicit signature which is used to derive the cosigning
/// address. Uses `msg.sender` as the authorized address.
/// @param v the v value for the signature; see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
/// @param r the r value for the signature
/// @param s the s value for the signature
/// @param data The data containing the transactions to be invoked; see internalInvoke for details.
function invoke1SignerSends(uint8 v, bytes32 r, bytes32 s, bytes data) external {
// check signature version
// `ecrecover` will infact return 0 if given invalid
// so perhaps this check is redundant
require(v == 27 || v == 28, "Invalid signature version.");
uint256 nonce = nonces[msg.sender];
// calculate hash
bytes32 operationHash = keccak256(
abi.encodePacked(
EIP191_PREFIX,
EIP191_VERSION_DATA,
this,
nonce,
msg.sender,
data));
// recover cosigner
address cosigner = ecrecover(operationHash, v, r, s);
// check for valid signature
require(cosigner != address(0), "Invalid signature.");
// Get required cosigner
address requiredCosigner = address(authorizations[authVersion + uint256(msg.sender)]);
// The operation should be approved if the signer address has no cosigner (i.e. signer == cosigner) or
// if the actual cosigner matches the required cosigner.
require(requiredCosigner == cosigner || requiredCosigner == msg.sender, "Invalid authorization.");
// increment nonce to prevent replay attacks
nonces[msg.sender] = nonce + 1;
internalInvoke(operationHash, data);
}
/// @notice A version of `invoke()` that has two explicit signatures, the first is used to derive the authorized
/// address, the second to derive the cosigner. The value of `msg.sender` is ignored.
/// @param v the v values for the signatures
/// @param r the r values for the signatures
/// @param s the s values for the signatures
/// @param nonce the nonce value for the signature
/// @param authorizedAddress the address of the signer; forces the signature to be unique and tied to the signers nonce
/// @param data The data containing the transactions to be invoked; see internalInvoke for details.
function invoke2(uint8[2] v, bytes32[2] r, bytes32[2] s, uint256 nonce, address authorizedAddress, bytes data) external {
// check signature versions
// `ecrecover` will infact return 0 if given invalid
// so perhaps these checks are redundant
require(v[0] == 27 || v[0] == 28, "invalid signature version v[0]");
require(v[1] == 27 || v[1] == 28, "invalid signature version v[1]");
bytes32 operationHash = keccak256(
abi.encodePacked(
EIP191_PREFIX,
EIP191_VERSION_DATA,
this,
nonce,
authorizedAddress,
data));
// recover signer and cosigner
address signer = ecrecover(operationHash, v[0], r[0], s[0]);
address cosigner = ecrecover(operationHash, v[1], r[1], s[1]);
// check for valid signatures
require(signer != address(0), "Invalid signature for signer.");
require(cosigner != address(0), "Invalid signature for cosigner.");
// check signer address
require(signer == authorizedAddress, "authorized addresses must be equal");
// check nonces
require(nonce == nonces[signer], "must use correct nonce for signer");
// Get Mapping
address requiredCosigner = address(authorizations[authVersion + uint256(signer)]);
// The operation should be approved if the signer address has no cosigner (i.e. signer == cosigner) or
// if the actual cosigner matches the required cosigner.
require(requiredCosigner == signer || requiredCosigner == cosigner, "Invalid authorization.");
// increment nonce to prevent replay attacks
nonces[signer]++;
internalInvoke(operationHash, data);
}
/// @dev Internal invoke call,
/// @param operationHash The hash of the operation
/// @param data The data to send to the `call()` operation
/// The data is prefixed with a global 1 byte revert flag
/// If revert is 1, then any revert from a `call()` operation is rethrown.
/// Otherwise, the error is recorded in the `result` field of the `InvocationSuccess` event.
/// Immediately following the revert byte (no padding), the data format is then is a series
/// of 1 or more tightly packed tuples:
/// `<target(20),amount(32),datalength(32),data>`
/// If `datalength == 0`, the data field must be omitted
function internalInvoke(bytes32 operationHash, bytes data) internal {
// keep track of the number of operations processed
uint256 numOps;
// keep track of the result of each operation as a bit
uint256 result;
// We need to store a reference to this string as a variable so we can use it as an argument to
// the revert call from assembly.
string memory invalidLengthMessage = "Data field too short";
string memory callFailed = "Call failed";
// At an absolute minimum, the data field must be at least 85 bytes
// <revert(1), to_address(20), value(32), data_length(32)>
require(data.length >= 85, invalidLengthMessage);
// Forward the call onto its actual target. Note that the target address can be `self` here, which is
// actually the required flow for modifying the configuration of the authorized keys and recovery address.
//
// The assembly code below loads data directly from memory, so the enclosing function must be marked `internal`
assembly {
// A cursor pointing to the revert flag, starts after the length field of the data object
let memPtr := add(data, 32)
// The revert flag is the leftmost byte from memPtr
let revertFlag := byte(0, mload(memPtr))
// A pointer to the end of the data object
let endPtr := add(memPtr, mload(data))
// Now, memPtr is a cursor pointing to the begining of the current sub-operation
memPtr := add(memPtr, 1)
// Loop through data, parsing out the various sub-operations
for { } lt(memPtr, endPtr) { } {
// Load the length of the call data of the current operation
// 52 = to(20) + value(32)
let len := mload(add(memPtr, 52))
// Compute a pointer to the end of the current operation
// 84 = to(20) + value(32) + size(32)
let opEnd := add(len, add(memPtr, 84))
// Bail if the current operation's data overruns the end of the enclosing data buffer
// NOTE: Comment out this bit of code and uncomment the next section if you want
// the solidity-coverage tool to work.
// See https://github.com/sc-forks/solidity-coverage/issues/287
if gt(opEnd, endPtr) {
// The computed end of this operation goes past the end of the data buffer. Not good!
revert(add(invalidLengthMessage, 32), mload(invalidLengthMessage))
}
// NOTE: Code that is compatible with solidity-coverage
// switch gt(opEnd, endPtr)
// case 1 {
// revert(add(invalidLengthMessage, 32), mload(invalidLengthMessage))
// }
// This line of code packs in a lot of functionality!
// - load the target address from memPtr, the address is only 20-bytes but mload always grabs 32-bytes,
// so we have to divide the result by 2^96 to effectively right-shift by 12 bytes.
// - load the value field, stored at memPtr+20
// - pass a pointer to the call data, stored at memPtr+84
// - use the previously loaded len field as the size of the call data
// - make the call (passing all remaining gas to the child call)
// - check the result (0 == reverted)
if eq(0, call(gas, div(mload(memPtr), exp(2, 96)), mload(add(memPtr, 20)), add(memPtr, 84), len, 0, 0)) {
switch revertFlag
case 1 {
revert(add(callFailed, 32), mload(callFailed))
}
default {
// mark this operation as failed
// create the appropriate bit, 'or' with previous
result := or(result, exp(2, numOps))
}
}
// increment our counter
numOps := add(numOps, 1)
// Update mem pointer to point to the next sub-operation
memPtr := opEnd
}
}
// emit single event upon success
emit InvocationSuccess(operationHash, result, numOps);
}
}
// File: contracts/Wallet/CloneableWallet.sol
pragma solidity ^0.4.24;
/// @title Cloneable Wallet
/// @notice This contract represents a complete but non working wallet.
/// It is meant to be deployed and serve as the contract that you clone
/// in an EIP 1167 clone setup.
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1167.md
/// @dev Currently, we are seeing approximatley 933 gas overhead for using
/// the clone wallet; use `FullWallet` if you think users will overtake
/// the transaction threshold over the lifetime of the wallet.
contract CloneableWallet is CoreWallet {
/// @dev An empty constructor that deploys a NON-FUNCTIONAL version
/// of `CoreWallet`
constructor () public {
initialized = true;
}
}
// File: contracts/Wallet/FullWallet.sol
pragma solidity ^0.4.24;
/// @title Full Wallet
/// @notice This contract represents a working contract, used if you want to deploy
/// the full code and not a clone shim
contract FullWallet is CoreWallet {
/// @notice A regular constructor that can be used if you wish to deploy a standalone instance of this
/// smart contract wallet. Useful if you anticipate that the lifetime gas savings of being able to call
/// this contract directly will outweigh the cost of deploying a complete copy of this contract.
/// Comment out this constructor and use the one above if you wish to save gas deployment costs by
/// using a clonable instance.
/// @param _authorized the initial authorized address
/// @param _cosigner the initial cosiging address for the `_authorized` address
/// @param _recoveryAddress the initial recovery address for the wallet
constructor (address _authorized, uint256 _cosigner, address _recoveryAddress) public {
init(_authorized, _cosigner, _recoveryAddress);
}
}
// File: contracts/Ownership/Ownable.sol
pragma solidity ^0.4.24;
/// @title Ownable is for contracts that can be owned.
/// @dev The Ownable contract keeps track of an owner address,
/// and provides basic authorization functions.
contract Ownable {
/// @dev the owner of the contract
address public owner;
/// @dev Fired when the owner to renounce ownership, leaving no one
/// as the owner.
/// @param previousOwner The previous `owner` of this contract
event OwnershipRenounced(address indexed previousOwner);
/// @dev Fired when the owner to changes ownership
/// @param previousOwner The previous `owner`
/// @param newOwner The new `owner`
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/// @dev sets the `owner` to `msg.sender`
constructor() public {
owner = msg.sender;
}
/// @dev Throws if the `msg.sender` is not the current `owner`
modifier onlyOwner() {
require(msg.sender == owner, "must be owner");
_;
}
/// @dev Allows the current `owner` to renounce ownership
function renounceOwnership() external onlyOwner {
emit OwnershipRenounced(owner);
owner = address(0);
}
/// @dev Allows the current `owner` to transfer ownership
/// @param _newOwner The new `owner`
function transferOwnership(address _newOwner) external onlyOwner {
_transferOwnership(_newOwner);
}
/// @dev Internal version of `transferOwnership`
/// @param _newOwner The new `owner`
function _transferOwnership(address _newOwner) internal {
require(_newOwner != address(0), "cannot renounce ownership");
emit OwnershipTransferred(owner, _newOwner);
owner = _newOwner;
}
}
// File: contracts/Ownership/HasNoEther.sol
pragma solidity ^0.4.24;
/// @title HasNoEther is for contracts that should not own Ether
contract HasNoEther is Ownable {
/// @dev This contructor rejects incoming Ether
constructor() public payable {
require(msg.value == 0, "must not send Ether");
}
/// @dev Disallows direct send by default function not being `payable`
function() external {}
/// @dev Transfers all Ether held by this contract to the owner.
function reclaimEther() external onlyOwner {
owner.transfer(address(this).balance);
}
}
// File: contracts/WalletFactory/CloneFactory.sol
pragma solidity ^0.4.24;
/// @title CloneFactory - a contract that creates clones
/// @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1167.md
/// @dev See https://github.com/optionality/clone-factory/blob/master/contracts/CloneFactory.sol
contract CloneFactory {
event CloneCreated(address indexed target, address clone);
function createClone(address target) internal returns (address result) {
bytes20 targetBytes = bytes20(target);
assembly {
let clone := mload(0x40)
mstore(clone, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000000000000000000000)
mstore(add(clone, 0x14), targetBytes)
mstore(add(clone, 0x28), 0x5af43d82803e903d91602b57fd5bf30000000000000000000000000000000000)
result := create(0, clone, 0x37)
}
}
}
// File: contracts/WalletFactory/WalletFactory.sol
pragma solidity ^0.4.24;
/// @title WalletFactory
/// @dev A contract for creating wallets.
contract WalletFactory is HasNoEther, CloneFactory {
/// @dev Pointer to a pre-deployed instance of the Wallet contract. This
/// deployment contains all the Wallet code.
address public cloneWalletAddress;
/// @notice Emitted whenever a wallet is created
/// @param wallet The address of the wallet created
/// @param authorizedAddress The initial authorized address of the wallet
/// @param full `true` if the deployed wallet was a full, self
/// contained wallet; `false` if the wallet is a clone wallet
event WalletCreated(address wallet, address authorizedAddress, bool full);
constructor(address _cloneWalletAddress) public {
cloneWalletAddress = _cloneWalletAddress;
}
/// @notice Sets the cloneable address in case you want to change it after
/// @dev No checks are done to see if this is a valid cloneable wallet
/// @param _newCloneWalletAddress the new clone source
function setCloneWalletAddress(address _newCloneWalletAddress) public onlyOwner {
cloneWalletAddress = _newCloneWalletAddress;
}
/// @notice Used to deploy a wallet clone
/// @dev Reasonably cheap to run (~100K gas)
/// @param _recoveryAddress the initial recovery address for the wallet
/// @param _authorizedAddress an initial authorized address for the wallet
/// @param _cosigner the cosigning address for the initial `_authorizedAddress`
function deployCloneWallet(
address _recoveryAddress,
address _authorizedAddress,
uint256 _cosigner
)
public
{
address clone = createClone(cloneWalletAddress);
CloneableWallet(clone).init(_authorizedAddress, _cosigner, _recoveryAddress);
emit WalletCreated(clone, _authorizedAddress, false);
}
/// @notice Used to deploy a full wallet
/// @dev This is potentially very gas intensive!
/// @param _recoveryAddress The initial recovery address for the wallet
/// @param _authorizedAddress An initial authorized address for the wallet
/// @param _cosigner The cosigning address for the initial `_authorizedAddress`
function deployFullWallet(
address _recoveryAddress,
address _authorizedAddress,
uint256 _cosigner
)
public
{
address full = new FullWallet(_authorizedAddress, _cosigner, _recoveryAddress);
emit WalletCreated(full, _authorizedAddress, true);
}
}