Friday, July 20, 2018

Building Ethereum DApps: Voting with Custom Tokens

In part 5 of this tutorial series on building DApps with Ethereum, we dealt with adding content to the story, looking at how to add the ability for participants to buy tokens from the DAO and to add submissions into the story. It’s now time for the DAO’s final form: voting, blacklisting/unblacklisting, and dividend distribution and withdrawal. We’ll throw in some additional helper functions for good measure.

If you get lost in all this, the full source code is available in the the repo.

Votes and Proposals

We’ll be issuing Proposals and voting with Votes. We need two new structs:

struct Proposal {
    string description;
    bool executed;
    int256 currentResult;
    uint8 typeFlag; // 1 = delete
    bytes32 target; // ID of the proposal target. I.e. flag 1, target XXXXXX (hash) means proposal to delete submissions[hash]
    uint256 creationDate;
    uint256 deadline;
    mapping (address => bool) voters;
    Vote[] votes;
    address submitter;
}

Proposal[] public proposals;
uint256 proposalCount = 0;
event ProposalAdded(uint256 id, uint8 typeFlag, bytes32 hash, string description, address submitter);
event ProposalExecuted(uint256 id);
event Voted(address voter, bool vote, uint256 power, string justification);

struct Vote {
    bool inSupport;
    address voter;
    string justification;
    uint256 power;
}

A Proposal will have a mapping of voters to prevent people from voting on a proposal twice, and some other metadata which should be self-explanatory. The Vote will either be a yes or no vote, and will remember the voter along with their justification for voting a certain way, and the voting power — the number of tokens they want to devote to voting for this proposal. We also added an array of Proposals so we can store them somewhere, and a counter for counting how many proposals there are.

Let’s build their accompanying functions now, starting with the voting function:

modifier tokenHoldersOnly() {
    require(token.balanceOf(msg.sender) >= 10**token.decimals());
    _;
}

function vote(uint256 _proposalId, bool _vote, string _description, uint256 _votePower) tokenHoldersOnly public returns (int256) {

    require(_votePower > 0, "At least some power must be given to the vote.");
    require(uint256(_votePower) <= token.balanceOf(msg.sender), "Voter must have enough tokens to cover the power cost.");

    Proposal storage p = proposals[_proposalId];

    require(p.executed == false, "Proposal must not have been executed already.");
    require(p.deadline > now, "Proposal must not have expired.");
    require(p.voters[msg.sender] == false, "User must not have already voted.");

    uint256 voteid = p.votes.length++;
    Vote storage pvote = p.votes[voteid];
    pvote.inSupport = _vote;
    pvote.justification = _description;
    pvote.voter = msg.sender;
    pvote.power = _votePower;

    p.voters[msg.sender] = true;

    p.currentResult = (_vote) ? p.currentResult + int256(_votePower) : p.currentResult - int256(_votePower);
    token.increaseLockedAmount(msg.sender, _votePower);

    emit Voted(msg.sender, _vote, _votePower, _description);
    return p.currentResult;
}

Notice the function modifier: by adding that modifier into our contract, we can attach it to any future function and make sure only token holders can execute that function. It’s a reusable security check!

The vote function does some sanity checks like the voting power being positive, the voter having enough tokens to actually vote etc. Then we fetch the proposal from storage and make sure it’s neither expired nor already executed. It wouldn’t make sense to vote on a proposal that’s already done. We also need to make sure this person hasn’t yet voted. We could allow changing the vote power, but this opens the DAO to some vulnerabilities like people withdrawing their votes at the last minute etc. Perhaps a candidate for a future version?

Then we register a new Vote into the proposal, change the current result for easy lookup of scores, and finally emit the Voted event. But what’s token.increaseLockedAmount?

This bit of logic increases the amount of locked tokens for a user. The function is only executable by the owner of the token contract (by this point that’s hopefully the DAO) and will prevent the user from sending an amount of tokens that exceeds the locked amount registered to their account. This lock is lifted after the proposal falls through or executes.

Let’s write the functions for proposing the deletion of an entry now.

The post Building Ethereum DApps: Voting with Custom Tokens appeared first on SitePoint.


by Bruno Skvorc via SitePoint

No comments:

Post a Comment