Skip to content

Instantly share code, notes, and snippets.

@andrecronje
Created October 24, 2020 11:24
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save andrecronje/6f6098e37ff28c95dfca2b6d1790f088 to your computer and use it in GitHub Desktop.
Save andrecronje/6f6098e37ff28c95dfca2b6d1790f088 to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;
pragma experimental ABIEncoderV2;
interface IERC20 {
function transfer(address to, uint value) external returns (bool);
function approve(address to, uint value) external returns (bool);
}
contract MetaWallet {
string public constant name = "MetaWallet";
address private _owner0;
address private _owner1;
address private _pendingOwner0;
address private _pendingOwner1;
constructor(address owner0, address owner1) public {
_owner0 = owner0;
_owner1 = owner1;
}
// @notice The EIP-712 typehash for the contract's domain
bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint chainId,address verifyingContract)");
/// @notice The EIP-712 typehash for the execute struct used by the contract
bytes32 public constant EXECUTE_TYPEHASH = keccak256("Execute(address to,uint value,bytes data,uint nonces,uint deadline)");
/// @notice The EIP-712 typehash for the send struct used by the contract
bytes32 public constant SEND_TYPEHASH = keccak256("Send(address to,uint value,uint nonces,uint deadline)");
/// @notice The EIP-712 typehash for the transfer struct used by the contract
bytes32 public constant TRANSFER_TYPEHASH = keccak256("Transfer(address token,address to,uint value,uint nonces,uint deadline)");
/// @notice The EIP-712 typehash for the approve struct used by the contract
bytes32 public constant APPROVE_TYPEHASH = keccak256("Approve(address token,address to,uint value,uint nonces,uint deadline)");
function transferOwnership(address _newOwner) external {
require(msg.sender == _owner0 || msg.sender == _owner1, "MetaWallet::transferOwnership: !owner");
if (msg.sender == _owner0) {
_pendingOwner0 = _newOwner;
} else if (msg.sender == _owner1) {
_pendingOwner1 = _newOwner;
}
}
function acceptOwnership() external {
require(msg.sender == _pendingOwner0 || msg.sender == _pendingOwner1, "MetaWallet::acceptOwnership: !pendingOwner");
if (msg.sender == _pendingOwner0) {
_owner0 = _pendingOwner0;
} else if (msg.sender == _pendingOwner1) {
_owner1 = _pendingOwner1;
}
}
/// @notice A record of states for signing / validating signatures
uint public nonces;
fallback() external {}
function send(address payable to, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external returns (bool, bytes memory) {
require(_verify(keccak256(abi.encode(SEND_TYPEHASH, to, value, nonces++, deadline)), deadline, v, r, s));
return to.call{value: value}("");
}
function transfer(address token, address to, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) internal returns (bool) {
require(_verify(keccak256(abi.encode(TRANSFER_TYPEHASH, token, to, value, nonces++, deadline)), deadline, v, r, s));
return IERC20(token).transfer(to, value);
}
function approve(address token, address to, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) internal returns (bool) {
require(_verify(keccak256(abi.encode(APPROVE_TYPEHASH, token, to, value, nonces++, deadline)), deadline, v, r, s));
return IERC20(token).approve(to, value);
}
function _verify(bytes32 structHash, uint deadline, uint8 v, bytes32 r, bytes32 s) internal view returns (bool) {
bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), _getChainId(), address(this)));
bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
address signatory = ecrecover(digest, v, r, s);
return ((signatory == _owner0 || signatory == _owner1) && now <= deadline);
}
struct stack {
address recipients;
uint values;
bytes datas;
uint deadlines;
uint8 v;
bytes32 r;
bytes32 s;
}
function batch(stack[] calldata stacks) external returns (bool[] memory results, bytes[] memory responses) {
for (uint i = 0; i < stacks.length; i++) {
(results[i], responses[i]) = _exec(stacks[i].recipients, stacks[i].values, stacks[i].datas, stacks[i].deadlines, stacks[i].v, stacks[i].r, stacks[i].s);
}
}
function execute(address to, uint value, bytes calldata data, uint deadline, uint8 v, bytes32 r, bytes32 s) external returns (bool, bytes memory) {
return _exec(to, value, data, deadline, v, r, s);
}
function _exec(address to, uint value, bytes memory data, uint deadline, uint8 v, bytes32 r, bytes32 s) internal returns (bool, bytes memory) {
require(_verify(keccak256(abi.encode(EXECUTE_TYPEHASH, to, value, data, nonces++, deadline)), deadline, v, r, s));
return to.call{value: value}(data);
}
function _getChainId() internal pure returns (uint) {
uint chainId;
assembly { chainId := chainid() }
return chainId;
}
}
contract MetaWalletFactory {
struct stack {
address wallet;
address to;
uint value;
bytes data;
uint deadline;
uint8 v;
bytes32 r;
bytes32 s;
}
struct id {
uint index0;
address owner0;
uint index1;
address owner1;
}
mapping(address => address[]) public lookups;
mapping(address => id) public indexes;
function lookup(address wallet) external view returns (uint index0, address owner0, uint index1, address owner1) {
return (indexes[wallet].index0, indexes[wallet].owner0, indexes[wallet].index1, indexes[wallet].owner1);
}
function wallet(address owner, uint index) external view returns (address) {
return lookups[owner][index];
}
function wallets(address owner) external view returns (address[] memory) {
return lookups[owner];
}
function createWallet(address _owner0, address _owner1) external {
address _wallet = address(new MetaWallet(_owner0, _owner1));
indexes[_wallet] = id(lookups[_owner0].length, _owner0, lookups[_owner1].length, _owner1);
lookups[_owner0].push(_wallet);
lookups[_owner1].push(_wallet);
}
function batch(stack[] calldata stacks) external returns (bool[] memory results, bytes[] memory responses) {
for (uint i = 0; i < stacks.length; i++) {
(results[i], responses[i]) = MetaWallet(stacks[i].wallet).execute(stacks[i].to, stacks[i].value, stacks[i].data, stacks[i].deadline, stacks[i].v, stacks[i].r, stacks[i].s);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment