2019-02-20 02:45:21 -03:00

171 lines
5.5 KiB
Solidity

pragma solidity >=0.5.0 <0.6.0;
import "../common/Controlled.sol";
import "../common/MessageSigned.sol";
import "../common/MerkleProof.sol";
import "../token/MiniMeToken.sol";
import "./Delegation.sol";
import "./TrustNetworkInterface.sol";
/**
* @title Proposal
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
* Store votes and tabulate results for Democracy
*/
contract Proposal is Controlled, MessageSigned {
MiniMeToken public token;
Delegation public delegation;
uint256 public tabulationBlockDelay;
bytes32 topic;
bytes32 txHash;
uint blockStart;
uint voteBlockEnd;
//votes storage
bytes32[] signatures;
mapping(address => Vote) voteMap;
//tabulation process
uint256 lastTabulationBlock;
mapping(address => address) delegationOf;
mapping(address => address) tabulated;
mapping(uint8 => uint256) results;
Vote result;
enum Vote {
Null,
Reject,
Approve
}
constructor(
MiniMeToken _token,
Delegation _delegation,
bytes32 _topic,
bytes32 _txHash,
uint256 _tabulationBlockDelay,
uint256 _blockStart,
uint256 _blockEndDelay
)
public
{
delegation = _delegation;
token = _token;
tabulationBlockDelay = _tabulationBlockDelay;
topic = _topic;
txHash = _txHash;
blockStart = _blockStart;
voteBlockEnd = blockStart + _blockEndDelay;
}
function addVotes(bytes32 _signatures)
external
{
require(block.number >= blockStart, "Voting not started");
require(block.number <= voteBlockEnd, "Voting ended");
signatures.push(_signatures);
}
function tabulateSigned(Vote _vote, uint256 _position, bytes32[] calldata _proof, bytes calldata _signature)
external
{
require(block.number > voteBlockEnd, "Voting running");
require(MerkleProof.verifyProof(_proof, signatures[_position], keccak256(_signature)), "Invalid proof");
address _voter = recoverAddress(keccak256(abi.encodePacked(address(this), _vote)), _signature);
require(voteMap[_voter] == Vote.Null, "Already tabulated");
voteMap[_voter] = _vote;
setTabulation(_voter, _voter, _vote);
}
function tabulateDelegated(address _voter)
external
{
require(block.number > voteBlockEnd, "Voting running");
(address _claimer, Vote _vote) = findNearestDelegatable(_voter); // try finding first delegate from chain which voted
setTabulation(_voter, _claimer, _vote);
}
function precomputeDelegateChain(address _start, bool _clean) external {
cacheDelegation(_start,_clean);
}
function finalize()
external
{
require(block.number > voteBlockEnd, "Voting running");
require(lastTabulationBlock + tabulationBlockDelay > block.number, "Tabulation running");
require(result == Vote.Null, "Already finalized");
uint256 totalTokens = token.totalSupplyAt(voteBlockEnd);
uint256 approvals = results[uint8(Vote.Approve)];
uint256 rejects = results[uint8(Vote.Reject)];
uint256 approvalQuorum = (totalTokens / 2);
if(approvals-rejects >= approvalQuorum) {
result = Vote.Approve;
} else {
result = Vote.Reject;
}
}
function clear()
external
onlyController
{
require(result != Vote.Null, "Not finalized");
selfdestruct(controller);
}
function setTabulation(address _voter, address _claimer, Vote _vote) internal {
require(_vote != Vote.Null, "Cannot be Vote.Null");
uint256 voterBalance = token.balanceOfAt(_voter, voteBlockEnd);
address currentClaimer = tabulated[_voter];
if(currentClaimer != address(0))
{
require(currentClaimer != _voter, "Voter already tabulated");
require(currentClaimer != _claimer, "Claimer already tabulated");
Vote oldVote = voteMap[currentClaimer];
require(oldVote != _vote, "Doesn't changes tabulation");
results[uint8(oldVote)] -= voterBalance;
}
tabulated[_voter] = _claimer;
results[uint8(_vote)] += voterBalance;
lastTabulationBlock = block.number;
}
function findNearestDelegatable(address _voter) internal view returns (address claimer, Vote vote){
vote = voteMap[_voter];
require(vote == Vote.Null, "Not delegatable");
claimer = _voter; // try finding first delegate from chain which voted
while(vote == Vote.Null) {
claimer = delegationOf[claimer];
if(claimer == address(0)){
claimer = delegation.delegatedToAt(claimer, voteBlockEnd);
}
require(claimer != address(0), "No delegate vote found");
vote = voteMap[claimer]; //loads delegate vote.
}
}
function cacheDelegation(address _delegator, bool _clean) private returns (address) {
address delegate;
if(!_clean) {
delegate = delegationOf[_delegator];
}
if(delegate == address(0)){
delegate = delegation.delegatedToAt(_delegator, voteBlockEnd); //get delegate chain tail
}
require(delegate != address(0), "No delegate vote found");
if(voteMap[delegate] == Vote.Null) {
delegate = cacheDelegation(delegate, _clean);
}
delegationOf[_delegator] = delegate;
return delegate;
}
}