mirror of
https://github.com/status-im/topic-democracy.git
synced 2025-02-25 16:45:20 +00:00
278 lines
9.7 KiB
Solidity
278 lines
9.7 KiB
Solidity
pragma solidity >=0.5.0 <0.6.0;
|
|
|
|
import "../../common/MessageSigned.sol";
|
|
import "../../common/MerkleProof.sol";
|
|
import "./ProposalAbstract.sol";
|
|
|
|
/**
|
|
* @title ProposalBase
|
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
|
* Store votes and tabulate results for Democracy. Cannot be used stand alone, only as base of Instance.
|
|
*/
|
|
contract ProposalBase is ProposalAbstract, MessageSigned {
|
|
|
|
/**
|
|
* @notice constructs a "ProposalAbstract Library Contract" for Instance and ProposalFactory
|
|
*/
|
|
constructor()
|
|
public
|
|
{
|
|
blockStart = uint256(-1);
|
|
voteBlockEnd = uint256(-1);
|
|
}
|
|
|
|
/**
|
|
* @notice include a merkle root of vote signatures
|
|
* @dev votes can be included as bytes32 hash(signature), in a merkle tree format,
|
|
* makes possible:
|
|
* - include multiple signatures by the same cost
|
|
* - voters don't have to pay anything to vote
|
|
* - the cost of ballot processing can be subsided to the party interested in the outcome
|
|
* @param _signatures merkle root of keccak256(address(this),uint8(vote))` leaf
|
|
*/
|
|
function voteSigned(bytes32 _signatures)
|
|
external
|
|
votingPeriod
|
|
{
|
|
emit VoteSignatures(
|
|
signatures.length,
|
|
_signatures
|
|
);
|
|
signatures.push(_signatures);
|
|
}
|
|
|
|
/**
|
|
* @notice include `msg.sender` vote
|
|
* @dev votes can be included by a direct call for contracts to vote directly
|
|
* contracts can also delegate to a externally owned account and submit by voteSigned method
|
|
* still important that contracts are able to vote directly is to allow a multisig to take a decision
|
|
* this is important because the safest delegates would be a Multisig
|
|
* @param _vote vote
|
|
*/
|
|
function voteDirect(Vote _vote)
|
|
external
|
|
votingPeriod
|
|
{
|
|
require(_vote != Vote.Null, "Bad _vote parameter");
|
|
voteMap[msg.sender] = _vote;
|
|
emit Voted(_vote, msg.sender);
|
|
}
|
|
|
|
/**
|
|
* @notice tabulates influence of a direct vote
|
|
* @param _voter address which called voteDirect
|
|
*/
|
|
function tabulateDirect(address _voter)
|
|
external
|
|
tabulationPeriod
|
|
{
|
|
Vote vote = voteMap[_voter];
|
|
require(vote != Vote.Null, "Not voted");
|
|
setTabulation(_voter, _voter, vote );
|
|
}
|
|
|
|
/**
|
|
* @notice tabulate influence of signed vote
|
|
* @param _vote vote used in signature
|
|
* @param _position position where the signature is sealed
|
|
* @param _proof merkle proof
|
|
* @param _signature plain signature used to produce the merkle leaf
|
|
*/
|
|
function tabulateSigned(Vote _vote, uint256 _position, bytes32[] calldata _proof, bytes calldata _signature)
|
|
external
|
|
tabulationPeriod
|
|
{
|
|
require(MerkleProof.verifyProof(_proof, signatures[_position], keccak256(_signature)), "Invalid proof");
|
|
address _voter = recoverAddress(getSignHash(voteHash(_vote)), _signature);
|
|
require(voteMap[_voter] == Vote.Null, "Already voted");
|
|
voteMap[_voter] = _vote;
|
|
emit Voted(_vote, _voter);
|
|
setTabulation(_voter, _voter, _vote);
|
|
}
|
|
|
|
/**
|
|
* @notice tabulates influence of non voter to his nearest delegate that voted
|
|
* @dev might run out of gas, to prevent this, precompute the delegation
|
|
* Should be called every time a nearer delegate tabulate their vote
|
|
* @param _source holder which not voted but have a delegate that voted
|
|
*/
|
|
function tabulateDelegated(address _source)
|
|
external
|
|
tabulationPeriod
|
|
{
|
|
(address _claimer, Vote _vote) = findNearestDelegatable(_source); // try finding first delegate from chain which voted
|
|
setTabulation(_source, _claimer, _vote);
|
|
}
|
|
|
|
/**
|
|
* @notice precomputes a delegate vote based on current votes tabulated
|
|
* @dev to precompute a very long delegate chain, go from the end to start with _clean false.
|
|
* @param _start who will have delegate precomputed
|
|
* @param _clean if true dont use precomputed results
|
|
* TODO: fix long delegate chain recompute in case new votes
|
|
*/
|
|
function precomputeDelegation(
|
|
address _start,
|
|
bool _clean
|
|
)
|
|
external
|
|
tabulationPeriod
|
|
{
|
|
cacheDelegation(_start,_clean);
|
|
}
|
|
|
|
/**
|
|
* TODO:
|
|
* cannot have votes claimed for votes: accumulators(hold more than 1%) and burned SNT (held by TokenController address).
|
|
* when informed to contract, these accounts reduces totalSupply used for Qualified and Absolute quorums.
|
|
* if someone rich wants to use their influence, they will have to devide their balance in multiple addresses and delegate them to one address
|
|
* the objective is to make one rule for all on how to remove "out of circulation" in addresses like "Dev Reserve"
|
|
* this enhances the democracy, otherwise this locked accounts will end up influence of defaultDelegate
|
|
* function invalidate(address _accumulator) external;
|
|
*/
|
|
|
|
/**
|
|
* @notice finalizes and set result
|
|
*/
|
|
function finalize()
|
|
external
|
|
tabulationFinished
|
|
{
|
|
require(result == Vote.Null, "Already finalized");
|
|
Vote finalResult = calculateResult();
|
|
emit FinalResult(finalResult);
|
|
result = finalResult;
|
|
}
|
|
|
|
/**
|
|
* @notice wipes all from state
|
|
* @dev once the proposal result was read, it might be cleared up to free up state
|
|
*/
|
|
function clear()
|
|
external
|
|
onlyController
|
|
{
|
|
require(result != Vote.Null, "Not finalized");
|
|
selfdestruct(controller);
|
|
}
|
|
|
|
function isApproved() external view returns (bool) {
|
|
require(result != Vote.Null, "Not finalized");
|
|
return result == Vote.Approve;
|
|
}
|
|
|
|
function isFinalized() external view returns (bool) {
|
|
return result != Vote.Null;
|
|
}
|
|
|
|
function checkSignedVote(
|
|
Vote _vote,
|
|
uint256 _position,
|
|
bytes32[] calldata _proof,
|
|
bytes calldata _signature
|
|
)
|
|
external
|
|
view
|
|
returns (address)
|
|
{
|
|
require(MerkleProof.verifyProof(_proof, signatures[_position], keccak256(_signature)), "Invalid proof");
|
|
return recoverAddress(getSignHash(voteHash(_vote)), _signature);
|
|
}
|
|
|
|
function getVoteHash(Vote _vote) external view returns (bytes32) {
|
|
return voteHash(_vote);
|
|
}
|
|
|
|
function getVotePrefixedHash(Vote _vote) external view returns (bytes32) {
|
|
return getSignHash(voteHash(_vote));
|
|
}
|
|
|
|
/**
|
|
* @notice get result
|
|
*/
|
|
function calculateResult()
|
|
public
|
|
view
|
|
returns(Vote finalResult)
|
|
{
|
|
uint256 approvals = results[uint8(Vote.Approve)];
|
|
bool approved;
|
|
|
|
if(quorum == QuorumType.Simple){
|
|
uint256 rejects = results[uint8(Vote.Reject)];
|
|
approved = approvals > rejects;
|
|
} else {
|
|
uint256 totalTokens = token.totalSupplyAt(voteBlockEnd);
|
|
if(quorum == QuorumType.Absolute) {
|
|
approved = approvals > (totalTokens / 2);
|
|
} else if(quorum == QuorumType.Qualified) {
|
|
approved = approvals > (totalTokens * 3) / 5;
|
|
}
|
|
}
|
|
finalResult = approved ? Vote.Approve : Vote.Reject;
|
|
}
|
|
|
|
function setTabulation(address _source, address _claimer, Vote _vote) internal {
|
|
require(_vote != Vote.Null, "Cannot be Vote.Null");
|
|
uint256 voterBalance = token.balanceOfAt(_source, voteBlockEnd);
|
|
if(voterBalance == 0) {
|
|
return;
|
|
}
|
|
address currentClaimer = tabulated[_source];
|
|
tabulated[_source] = _claimer;
|
|
emit Claimed(_vote, _claimer, _source);
|
|
if(currentClaimer != address(0))
|
|
{
|
|
require(currentClaimer != _source, "Voter already tabulated");
|
|
require(currentClaimer != _claimer, "Claimer already tabulated");
|
|
Vote oldVote = voteMap[currentClaimer];
|
|
if(oldVote == _vote) {
|
|
return;
|
|
}
|
|
emit PartialResult(oldVote, results[uint8(oldVote)] -= voterBalance);
|
|
}
|
|
emit PartialResult(_vote, results[uint8(_vote)] += voterBalance);
|
|
lastTabulationBlock = block.number;
|
|
}
|
|
|
|
function voteHash(Vote _vote) internal view returns (bytes32) {
|
|
require(_vote != Vote.Null, "Bad _vote parameter");
|
|
return keccak256(abi.encodePacked(address(this), _vote));
|
|
}
|
|
|
|
function findNearestDelegatable(address _source) internal view returns (address claimer, Vote vote){
|
|
vote = voteMap[_source];
|
|
require(vote == Vote.Null, "Not delegatable");
|
|
claimer = _source; // try finding first delegate from chain which voted
|
|
while(vote == Vote.Null) {
|
|
address claimerDelegate = delegationOf[claimer];
|
|
if(claimerDelegate == address(0)){
|
|
claimerDelegate = delegation.delegatedToAt(claimer, voteBlockEnd);
|
|
}
|
|
require(claimer != claimerDelegate, "No delegate vote found");
|
|
claimer = claimerDelegate;
|
|
vote = voteMap[claimer]; //loads delegate vote.
|
|
}
|
|
}
|
|
|
|
function cacheDelegation(address _source, bool _clean) private returns (address delegate) {
|
|
delegate = _source;
|
|
if(voteMap[_source] == Vote.Null) {
|
|
if(!_clean) {
|
|
delegate = delegationOf[delegate];
|
|
}
|
|
if(delegate == address(0)){
|
|
delegate = delegation.delegatedToAt(_source, voteBlockEnd); //get delegate chain tail
|
|
}
|
|
}
|
|
|
|
require(delegate != address(0), "No delegate vote found");
|
|
if(voteMap[delegate] == Vote.Null) {
|
|
delegate = cacheDelegation(delegate, _clean);
|
|
}
|
|
delegationOf[_source] = delegate;
|
|
return delegate;
|
|
|
|
}
|
|
|
|
} |