Last active
January 22, 2021 11:11
-
-
Save seniorjoinu/28601b8f80bf7fb05b288edb051223e2 to your computer and use it in GitHub Desktop.
Voting system for ERC777 with history
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
contract Voting { | |
using SafeMath for uint256; | |
enum VoteStatus { | |
NONE, ACCEPT, REJECT | |
} | |
enum VotingResult { | |
NONE, ACCEPT, REJECT, NOT_APPLIED | |
} | |
struct VotingDetails { | |
uint256 myTokenTotalSupplySnapshot; | |
uint256 createdAt; | |
uint256 duration; | |
uint256 totalAccepted; | |
uint256 totalRejected; | |
bool executed; | |
string description; | |
mapping(address => VoteStatus) voteByAccount; | |
} | |
event VotingStarted(uint256 indexed votingId); | |
event VotePlaced(uint256 indexed votingId, address voter, VoteStatus status, uint256 voteWeight); | |
event VotingExecuted(uint256 indexed votingId, VotingResult result); | |
uint256 votingIdsCounter; | |
mapping(uint256 => VotingDetails) votings; | |
address myToken; | |
constructor(address _myToken) public { | |
myToken = _myToken; | |
} | |
function startVoting(uint256 duration, string calldata description) external { | |
// checking inputs | |
require(IERC20(myToken).balanceOf(msg.sender) > 0, "You should possess at least some tokens to be able to start a voting"); | |
// creating new voting | |
uint256 votingId = votingIdsCounter++; | |
votings[votingId] = VotingDetails( | |
IERC20(myToken).totalSupply(), | |
block.timestamp, | |
duration, | |
0, | |
0, | |
false, | |
description | |
); | |
emit VotingStarted(votingId); | |
} | |
function vote(uint256 votingId, VoteStatus status) external { | |
VotingDetails storage votingDetails = votings[votingId]; | |
// checking inputs | |
require(votingDetails.createdAt > 0, "The voting does not exist"); | |
require(status != VoteStatus.NONE, "Invalid vote"); | |
require(votingDetails.createdAt != block.timestamp, "Unable to vote right after the voting's start"); | |
require(votingDetails.createdAt.add(votingDetails.duration) > block.timestamp, "Too late to vote"); | |
require(votingDetails.voteByAccount[msg.sender] == VoteStatus.NONE, "The voter already voted"); | |
uint256 accountWeight = IERC777WithHistory(myToken).balanceAt(msg.sender, votingDetails.createdAt); | |
require(accountWeight > 0, "The caller is unable to vote"); | |
votingDetails.voteByAccount[msg.sender] = status; | |
/* | |
Add here a subtracting of previous vote and remove 'already voted' require from above | |
if you want to implement re-vote scheme | |
*/ | |
/* | |
change this to votingDetails.totalAccepted.add(1) if you want '1 account = 1 vote' scheme | |
(dont forget to check for any required to vote balance and also change executeVoting() votingThreshold part) | |
*/ | |
if (status == VoteStatus.ACCEPT) { | |
votingDetails.totalAccepted = votingDetails.totalAccepted.add(accountWeight); | |
} else { | |
votingDetails.totalRejected = votingDetails.totalRejected.add(accountWeight); | |
} | |
emit VotePlaced(votingId, msg.sender, status, accountWeight); | |
} | |
function executeVoting(uint256 votingId) external { | |
VotingDetails storage votingDetails = votings[votingId]; | |
// checking inputs | |
require(votingDetails.createdAt > 0, "The voting does not exist"); | |
require(!votingDetails.executed, "The voting was already executed"); | |
require(votingDetails.createdAt.add(votingDetails.duration) < block.timestamp, "To early to execute"); | |
// calculating voting result | |
// the voting considered successful only when total voted weight is more than 20% of total supply | |
uint256 votingThreshold = votingDetails.myTokenTotalSupplySnapshot.mul(Utils.VOTING_THRESHOLD_PERCENT) / 100; | |
VotingResult result; | |
if (votingDetails.totalAccepted.add(votingDetails.totalRejected) >= votingThreshold) { | |
if (votingDetails.totalAccepted > votingDetails.totalRejected) { | |
result = VotingResult.ACCEPT; | |
} else { | |
result = VotingResult.REJECT; | |
} | |
} else { | |
result = VotingResult.NOT_APPLIED; | |
} | |
votingDetails.executed = true; | |
emit VotingExecuted(votingId, result); | |
/** | |
Put here any automatic action you want which would execute only when result == VotingResult.ACCEPT | |
*/ | |
} | |
function getVoteOf(uint256 votingId, address voter) external view returns (VoteStatus) { | |
VotingDetails storage votingDetails = votings[votingId]; | |
return votingDetails.voteByAccount[voter]; | |
} | |
function getVoting(uint256 votingId) external view returns ( | |
uint256 createdAt, | |
uint256 duration, | |
string memory description, | |
bool executed, | |
uint256 totalAccepted, | |
uint256 totalRejected | |
) { | |
VotingDetails storage votingDetails = votings[votingId]; | |
return ( | |
votingDetails.createdAt, | |
votingDetails.duration, | |
votingDetails.description, | |
votingDetails.executed, | |
votingDetails.totalAccepted, | |
votingDetails.totalRejected | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment