// SPDX-License-Identifier: MIT
pragma solidity 0.8.3;
pragma abicoder v2;
contract EverPay {
// Event
event Submission(
bytes32 indexed id,
uint256 indexed proposalID,
bytes32 indexed everHash,
address owner,
address to,
uint256 value,
bytes data
);
event SubmissionFailure(
bytes32 indexed id,
uint256 indexed proposalID,
bytes32 indexed everHash,
address owner,
address to,
uint256 value,
bytes data
);
event Execution(
bytes32 indexed id,
uint256 indexed proposalID,
bytes32 indexed everHash,
address to,
uint256 value,
bytes data
);
event ExecutionFailure(
bytes32 indexed id,
uint256 indexed proposalID,
bytes32 indexed everHash,
address to,
uint256 value,
bytes data
);
// event Revocation(address indexed sender, bytes32 indexed id); // TODO
event Deposit(address indexed sender, uint256 value);
event OwnerAddition(address indexed owner);
event OwnerRemoval(address indexed owner);
event RequirementChange(uint256 required);
event OperatorChange(address indexed operator);
event PausedChange(bool paused);
// Event End
// Storage & Struct
uint256 public chainID;
bool public paused;
address public operator;
uint256 public required;
address[] public owners;
mapping(address => bool) public isOwner;
mapping(bytes32 => bool) public executed;// tx id => bool
mapping(bytes32 => mapping(address => bool)) public confirmations;
// Storage & Struct End
// Modifier
modifier validRequirement(uint256 ownerCount, uint256 _required) {
require(
ownerCount >= _required && ownerCount != 0 && _required != 0,
"invalid_required"
);
_;
}
modifier onlyWallet() {
require(msg.sender == address(this), "not_wallet");
_;
}
modifier onlyOperator() {
require(msg.sender == operator, "not_operator");
_;
}
modifier whenNotPaused() {
require(!paused, "paused");
_;
}
// Modifier End
// Manage
function getPaused() public view returns (bool) {
return paused;
}
function getOperator() public view returns (address) {
return operator;
}
function getOwners() public view returns (address[] memory) {
return owners;
}
function getRequire() public view returns (uint256) {
return required;
}
function setOperator(address _operator) public onlyWallet {
require(_operator != address(0), "null_address");
operator = _operator;
emit OperatorChange(operator);
}
function setPaused(bool _paused) public onlyOperator {
paused = _paused;
emit PausedChange(paused);
}
function addOwner(address owner) public onlyWallet {
require(owner != address(0), "null_address");
isOwner[owner] = true;
owners.push(owner);
emit OwnerAddition(owner);
}
function removeOwner(address owner) public onlyWallet {
require(isOwner[owner], "no_owner_found");
isOwner[owner] = false;
for (uint256 i = 0; i < owners.length - 1; i++) {
if (owners[i] == owner) {
owners[i] = owners[owners.length - 1];
break;
}
}
owners.pop();
if (required > owners.length) {
changeRequirement(owners.length);
}
OwnerRemoval(owner);
}
function replaceOwner(address owner, address newOwner) public onlyWallet {
require(isOwner[owner], "no_owner_found");
require(newOwner != address(0), "null_address");
for (uint256 i = 0; i < owners.length; i++) {
if (owners[i] == owner) {
owners[i] = newOwner;
break;
}
}
isOwner[owner] = false;
isOwner[newOwner] = true;
OwnerRemoval(owner);
OwnerAddition(newOwner);
}
function changeRequirement(uint256 _required)
public
onlyWallet
validRequirement(owners.length, _required)
{
required = _required;
emit RequirementChange(_required);
}
// Manage End
// Base
receive() external payable {
if (msg.value != 0) emit Deposit(msg.sender, msg.value);
}
constructor(address[] memory _owners, uint256 _required) validRequirement(_owners.length, _required)
{
for (uint256 i = 0; i < _owners.length; i++) {
isOwner[_owners[i]] = true;
}
owners = _owners;
required = _required;
uint256 _chainID;
assembly {
_chainID := chainid()
}
chainID = _chainID;
}
function submit(
uint256 proposalID, // ar tx id
bytes32 everHash,
address to,
uint256 value,
bytes memory data,
bytes[] memory sigs
) public whenNotPaused returns (bytes32, bool) {
bytes32 id = txHash(proposalID, everHash, to, value, data);
require(!executed[id], "tx_executed");
for (uint256 i = 0; i < sigs.length; i++) {
address owner = ecAddress(id, sigs[i]);
if (!isOwner[owner]) {
emit SubmissionFailure(id, proposalID, everHash, owner, to, value, data);
continue;
}
confirmations[id][owner] = true;
emit Submission(id, proposalID, everHash, owner, to, value, data);
}
if (!isConfirmed(id)) return (id, false);
executed[id] = true;
(bool ok, ) = to.call{value: value}(data);
if (ok) {
emit Execution(id, proposalID, everHash, to, value, data);
} else {
emit ExecutionFailure(id, proposalID, everHash, to, value, data);
}
return (id, true);
}
// execute multi calls
function executes(address[] memory tos, uint256[] memory values, bytes[] memory datas) payable public onlyWallet {
require(tos.length == values.length, "invalid_length");
require(tos.length == datas.length, "invalid_length");
for (uint256 i = 0; i < tos.length; i++) {
(bool ok, ) = tos[i].call{value: values[i]}(datas[i]);
require(ok, "executed_falied");
}
}
function isConfirmed(bytes32 id) public view returns (bool) {
uint256 count = 0;
for (uint256 i = 0; i < owners.length; i++) {
if (confirmations[id][owners[i]]) count += 1;
if (count >= required) return true;
}
return false;
}
// Base End
// Utils
function txHash(uint256 proposalID, bytes32 everHash, address to, uint256 value, bytes memory data) public view returns (bytes32) {
return keccak256(abi.encodePacked(chainID, address(this), proposalID, everHash, to, value, data));
}
function ecAddress(bytes32 id, bytes memory sig)
public
pure
returns (address)
{
require(sig.length == 65, "invalid_sig_len");
uint8 v;
bytes32 r;
bytes32 s;
assembly {
r := mload(add(sig, 0x20))
s := mload(add(sig, 0x40))
v := byte(0, mload(add(sig, 0x60)))
}
require(v == 27 || v == 28, "invalid_sig_v");
return
ecrecover(
keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", id)
), v, r, s
);
}
// Utils End
}
{
"compilationTarget": {
"EverPay.sol": "EverPay"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address[]","name":"_owners","type":"address[]"},{"internalType":"uint256","name":"_required","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"proposalID","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"everHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Execution","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"proposalID","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"everHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"ExecutionFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"operator","type":"address"}],"name":"OperatorChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"OwnerAddition","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"}],"name":"OwnerRemoval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"paused","type":"bool"}],"name":"PausedChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"required","type":"uint256"}],"name":"RequirementChange","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"proposalID","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"everHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"Submission","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"id","type":"bytes32"},{"indexed":true,"internalType":"uint256","name":"proposalID","type":"uint256"},{"indexed":true,"internalType":"bytes32","name":"everHash","type":"bytes32"},{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"data","type":"bytes"}],"name":"SubmissionFailure","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"addOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"chainID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_required","type":"uint256"}],"name":"changeRequirement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"}],"name":"confirmations","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"},{"internalType":"bytes","name":"sig","type":"bytes"}],"name":"ecAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"executed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"tos","type":"address[]"},{"internalType":"uint256[]","name":"values","type":"uint256[]"},{"internalType":"bytes[]","name":"datas","type":"bytes[]"}],"name":"executes","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"getOperator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOwners","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRequire","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"id","type":"bytes32"}],"name":"isConfirmed","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"operator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"owners","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"removeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"replaceOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"required","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_operator","type":"address"}],"name":"setOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"_paused","type":"bool"}],"name":"setPaused","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalID","type":"uint256"},{"internalType":"bytes32","name":"everHash","type":"bytes32"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"},{"internalType":"bytes[]","name":"sigs","type":"bytes[]"}],"name":"submit","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"proposalID","type":"uint256"},{"internalType":"bytes32","name":"everHash","type":"bytes32"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"txHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]