pragma solidity 0.5.7;
pragma experimental ABIEncoderV2;
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
interface IERC20 {
function transfer(address to, uint256 value) external returns (bool);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
function totalSupply() external view returns (uint256);
function balanceOf(address who) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
/**
* @title SafeMath
* @dev Unsigned math operations with safety checks that revert on error
*/
library SafeMath {
/**
* @dev Multiplies two unsigned integers, reverts on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath::mul: Integer overflow");
return c;
}
/**
* @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, "SafeMath::div: Invalid divisor zero");
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath::sub: Integer underflow");
uint256 c = a - b;
return c;
}
/**
* @dev Adds two unsigned integers, reverts on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath::add: Integer overflow");
return c;
}
/**
* @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
* reverts when dividing by zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath::mod: Invalid divisor zero");
return a % b;
}
}
/**
* @title Void
* @dev Collects failed proposal fees.
*/
contract Void {}
/**
* @title Governance
* @dev Plutocratic voting system.
*/
contract Governance {
using SafeMath for uint;
event Execute(uint indexed proposalId);
event Propose(uint indexed proposalId, address indexed proposer, address indexed target, bytes data);
event RemoveVote(uint indexed proposalId, address indexed voter);
event Terminate(uint indexed proposalId);
event Vote(uint indexed proposalId, address indexed voter, bool approve, uint weight);
enum Result { Pending, Yes, No }
struct Proposal {
Result result;
address target;
bytes data;
address proposer;
address feeRecipient;
uint fee;
uint startTime;
uint yesCount;
uint noCount;
}
uint public constant OPEN_VOTE_PERIOD = 2 days;
uint public constant VETO_PERIOD = 2 days;
uint public constant TOTAL_VOTE_PERIOD = OPEN_VOTE_PERIOD + VETO_PERIOD;
uint public proposalFee;
IERC20 public token;
Void public void;
Proposal[] public proposals;
// Proposal Id => Voter => Yes Votes
mapping(uint => mapping(address => uint)) public yesVotes;
// Proposal Id => Voter => No Votes
mapping(uint => mapping(address => uint)) public noVotes;
// Voter => Deposit
mapping (address => uint) public deposits;
// Voter => Withdraw timestamp
mapping (address => uint) public withdrawTimes;
constructor(IERC20 _token, uint _initialProposalFee) public {
token = _token;
proposalFee = _initialProposalFee;
void = new Void();
}
function deposit(uint amount) public {
require(token.transferFrom(msg.sender, address(this), amount), "Governance::deposit: Transfer failed");
deposits[msg.sender] = deposits[msg.sender].add(amount);
}
function withdraw(uint amount) public {
require(time() > withdrawTimes[msg.sender], "Governance::withdraw: Voters with an active proposal cannot withdraw");
deposits[msg.sender] = deposits[msg.sender].sub(amount);
require(token.transfer(msg.sender, amount), "Governance::withdraw: Transfer failed");
}
function propose(address target, bytes memory data) public returns (uint) {
return proposeWithFeeRecipient(msg.sender, target, data);
}
function proposeWithFeeRecipient(address feeRecipient, address target, bytes memory data) public returns (uint) {
require(msg.sender != address(this) && target != address(token), "Governance::proposeWithFeeRecipient: Invalid proposal");
require(token.transferFrom(msg.sender, address(this), proposalFee), "Governance::proposeWithFeeRecipient: Transfer failed");
uint proposalId = proposals.length;
// Create a new proposal and vote yes
Proposal memory proposal;
proposal.target = target;
proposal.data = data;
proposal.proposer = msg.sender;
proposal.feeRecipient = feeRecipient;
proposal.fee = proposalFee;
proposal.startTime = time();
proposal.yesCount = proposalFee;
proposals.push(proposal);
emit Propose(proposalId, msg.sender, target, data);
return proposalId;
}
function voteYes(uint proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(time() <= proposal.startTime.add(OPEN_VOTE_PERIOD), "Governance::voteYes: Proposal is no longer accepting yes votes");
uint proposalEndTime = proposal.startTime.add(TOTAL_VOTE_PERIOD);
if (proposalEndTime > withdrawTimes[msg.sender]) withdrawTimes[msg.sender] = proposalEndTime;
uint weight = deposits[msg.sender].sub(yesVotes[proposalId][msg.sender]);
proposal.yesCount = proposal.yesCount.add(weight);
yesVotes[proposalId][msg.sender] = deposits[msg.sender];
emit Vote(proposalId, msg.sender, true, weight);
}
function voteNo(uint proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(proposal.result == Result.Pending, "Governance::voteNo: Proposal is already finalized");
uint proposalEndTime = proposal.startTime.add(TOTAL_VOTE_PERIOD);
uint _time = time();
require(_time <= proposalEndTime, "Governance::voteNo: Proposal is no longer in voting period");
uint _deposit = deposits[msg.sender];
uint weight = _deposit.sub(noVotes[proposalId][msg.sender]);
proposal.noCount = proposal.noCount.add(weight);
noVotes[proposalId][msg.sender] = _deposit;
emit Vote(proposalId, msg.sender, false, weight);
// Finalize the vote and burn the proposal fee if no votes outnumber yes votes and open voting has ended
if (_time > proposal.startTime.add(OPEN_VOTE_PERIOD) && proposal.noCount >= proposal.yesCount) {
proposal.result = Result.No;
require(token.transfer(address(void), proposal.fee), "Governance::voteNo: Transfer to void failed");
emit Terminate(proposalId);
} else if (proposalEndTime > withdrawTimes[msg.sender]) {
withdrawTimes[msg.sender] = proposalEndTime;
}
}
function removeVote(uint proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(proposal.result == Result.Pending, "Governance::removeVote: Proposal is already finalized");
require(time() <= proposal.startTime.add(TOTAL_VOTE_PERIOD), "Governance::removeVote: Proposal is no longer in voting period");
proposal.yesCount = proposal.yesCount.sub(yesVotes[proposalId][msg.sender]);
proposal.noCount = proposal.noCount.sub(noVotes[proposalId][msg.sender]);
delete yesVotes[proposalId][msg.sender];
delete noVotes[proposalId][msg.sender];
emit RemoveVote(proposalId, msg.sender);
}
function finalize(uint proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(proposal.result == Result.Pending, "Governance::finalize: Proposal is already finalized");
uint _time = time();
if (proposal.yesCount > proposal.noCount) {
require(_time > proposal.startTime.add(TOTAL_VOTE_PERIOD), "Governance::finalize: Proposal cannot be executed until end of veto period");
proposal.result = Result.Yes;
require(token.transfer(proposal.feeRecipient, proposal.fee), "Governance::finalize: Return proposal fee failed");
proposal.target.call(proposal.data);
emit Execute(proposalId);
} else {
require(_time > proposal.startTime.add(OPEN_VOTE_PERIOD), "Governance::finalize: Proposal cannot be terminated until end of yes vote period");
proposal.result = Result.No;
require(token.transfer(address(void), proposal.fee), "Governance::finalize: Transfer to void failed");
emit Terminate(proposalId);
}
}
function setProposalFee(uint fee) public {
require(msg.sender == address(this), "Governance::setProposalFee: Proposal fee can only be set via governance");
proposalFee = fee;
}
function time() public view returns (uint) {
return block.timestamp;
}
function getProposal(uint proposalId) external view returns (Proposal memory) {
return proposals[proposalId];
}
function getProposalsCount() external view returns (uint) {
return proposals.length;
}
}
/**
* @title HumanityGovernance
* @dev Plutocratic voting system that uses Humanity token for voting and proposal fees.
*/
contract HumanityGovernance is Governance {
constructor(IERC20 humanity, uint proposalFee) public
Governance(humanity, proposalFee) {}
}
{
"compilationTarget": {
"HumanityGovernance.sol": "HumanityGovernance"
},
"evmVersion": "petersburg",
"libraries": {},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"proposals","outputs":[{"name":"result","type":"uint8"},{"name":"target","type":"address"},{"name":"data","type":"bytes"},{"name":"proposer","type":"address"},{"name":"feeRecipient","type":"address"},{"name":"fee","type":"uint256"},{"name":"startTime","type":"uint256"},{"name":"yesCount","type":"uint256"},{"name":"noCount","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"finalize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"},{"name":"","type":"address"}],"name":"yesVotes","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"fee","type":"uint256"}],"name":"setProposalFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"time","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"voteNo","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"removeVote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"OPEN_VOTE_PERIOD","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"voteYes","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"TOTAL_VOTE_PERIOD","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getProposalsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"target","type":"address"},{"name":"data","type":"bytes"}],"name":"propose","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"void","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"withdrawTimes","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"deposit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"proposalFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"proposalId","type":"uint256"}],"name":"getProposal","outputs":[{"components":[{"name":"result","type":"uint8"},{"name":"target","type":"address"},{"name":"data","type":"bytes"},{"name":"proposer","type":"address"},{"name":"feeRecipient","type":"address"},{"name":"fee","type":"uint256"},{"name":"startTime","type":"uint256"},{"name":"yesCount","type":"uint256"},{"name":"noCount","type":"uint256"}],"name":"","type":"tuple"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"},{"name":"","type":"address"}],"name":"noVotes","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"feeRecipient","type":"address"},{"name":"target","type":"address"},{"name":"data","type":"bytes"}],"name":"proposeWithFeeRecipient","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"VETO_PERIOD","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"deposits","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"humanity","type":"address"},{"name":"proposalFee","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"proposalId","type":"uint256"}],"name":"Execute","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"proposalId","type":"uint256"},{"indexed":true,"name":"proposer","type":"address"},{"indexed":true,"name":"target","type":"address"},{"indexed":false,"name":"data","type":"bytes"}],"name":"Propose","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"proposalId","type":"uint256"},{"indexed":true,"name":"voter","type":"address"}],"name":"RemoveVote","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"proposalId","type":"uint256"}],"name":"Terminate","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"proposalId","type":"uint256"},{"indexed":true,"name":"voter","type":"address"},{"indexed":false,"name":"approve","type":"bool"},{"indexed":false,"name":"weight","type":"uint256"}],"name":"Vote","type":"event"}]