eosio.forum Part 2: Workflow of Voting

Obsidian Labs
EOS Smart Contracts in Depth
5 min readOct 17, 2019

The purpose of this tutorial series is to elaborate on some EOS smart contracts in depth. We will carefully select examples that are well designed and built, some are already widely used on the EOS Mainnet. Through this series, we hope to provide more learning materials for dApp developers and help them understand more design patterns and application scenarios of smart contracts.

In Part 1, we talked about the motivation of the eosio.forum and how it was developed and adopted on EOS Mainnet. In this article, we will demonstrate the basic workflow of how to create a proposal and cast a vote on it. This workflow will be divided into 4 stages: proposal, voting, freeze, and clearing the RAM.

Quickstart

We will use EOS Studio Web and dfuse’s On-Demand Network for the demonstration so that you will not need to set up a development environment.

Setups

  • Open eosio.forum at https://app.eosstudio.io/dfuseio/eosio.forum and click the fork button to make your own copy of the project.
  • Edit the waiting time from 3 days to 5 minutes so that we won’t need to wait that long in this demo. Open the file include/forum.hpp, go to line 70 and change the value ofFREEZE_PERIOD_IN_SECONDS to 300. The use of this value will be explained later.
  • Click the build button to rebuild the smart contract and regenerate the wasm and abi files.
  • Create a new account and deploy the contract you just built. For easy reference, we will assume the account name to be eosio.forum.
  • Create some other accounts, one for creating a proposal (proposer) and as many others for casting votes as you’d like (voter1, voter2, etc).

Stage 1: Proposal

Anybody with a valid EOS account can create a proposal for the community. Calling this operation will consume some RAM to save the content of the proposal. Before we create a new proposal, let’s open the account proposer and take note of its current RAM usage. Then, go to EOS Studio’s Contract Inspector and execute the propose() action

// Execute action *propose* with parameters
// Use proposer@active to sign
proposer: "proposer"
proposal_name: "usesys"
title: "Use SYS as the core symbol"
proposal_json: { "type": "referendum-v1", "content": "Should EOS change its symbol to SYS?" }
expires_at: "2020-01-01T00:00:00"

The transaction should be signed by proposer. All ongoing proposals are indexed by proposal_name, which is of type eosio::name and therefore can have a human-readable name. The content of the proposal contains a title (up to 1024 chars) and a proposal_json in the format of Proposal JSON Structure Guidelines. Every proposal also needs to have an expiration time expires_at, which should be a point between now and 6 months in the future. We will explain how it is used in Stage 3.

If you check proposer’s RAM usage now, you should see it has increased by about 480 bytes if you have the same parameters as provided above. Requirement of some RAM can prevent spam so that important issues can be discussed and voted on. Once a proposal has finished its entire lifecycle, eosio.forum allow you to safely remove the proposal and free up used RAM (Stage 4).

Published proposals are recorded in the proposals table. We can check it to see the proposal we just published.

Table proposals

Stage 2: Voting

Once the proposal has been created, people can start to vote on it via the vote() action. Let’s make some votes using the previously created accounts.

// Execute action *vote* with parameters
// Use voter1@active to sign
voter: "voter1"
proposal_name: "usesys"
vote: 1 // positively vote
vote_json: ""
// Execute action *vote* with parameters
// Use voter2@active to sign
voter: "voter2"
proposal_name: "usesys"
vote: 0 // negative vote
vote_json: ""
// Execute action *vote* with parameters
// Use voter3@active to sign
voter: "voter3"
proposal_name: "usesys"
vote: 255 // abstain
vote_json: ""

Notice that the vote value is used to represent yes (1) or no (0) to the proposal. It ranges from 0 to 255 so other values can be used to represent special meanings.

Votes are saved in table vote

Table vote

Similar to proposers, voters need to pay RAM to save their own votes. If you view the voters’ RAM usage, you will see it has increased by 430 bytes after executing the vote() action. This RAM will also be refunded to each voter when the proposal is removed in Stage 4.

Voters can change their votes at anytime by calling vote() again with a different vote value to override the old one. They can also remove the vote via the unvote() action, which would completely remove their vote data in the vote table and refund their RAM immediately.

Stage 3: Freeze

A proposal cannot be indefinitely continued, so an expiration time is needed to set a deadline for the voting time. That’s why the expires_at was predefined when the proposal was created. The proposer can also decide to end the proposal ahead of time by manually calling the expire()action. This amends the proposal’s expires_at field to the current time instead of waiting for its original expiration date to be reached.

Let’s expire our proposal now

// Execute action *expire* with parameters
// Use proposer@active to sign
proposal_name: "usesys"

Once a proposal is expired (be it manually or automatically if it passed its expiration date), the proposal will enter a 3 day freeze period. Within this freeze period, the proposal is locked and no actions can be called on it (no vote changes, no vote removal and no clean up). It is to allow a period that anyone can query the blockchain data, count votes and generate the voting result independently. The implementation of the proposal will be determined by the result.

Since we modified the freeze time in the sourced code, we just need to wait 5 minutes instead of 3 days. Within those 5 minutes, you will find that all attempts to execute the vote() or unvote()actions related to the freezing proposal will fail.

After the freeze period has passed, the process of handling a proposal through democratic voting has completed. Now, we can safely reclaim the RAM used in creating the proposal and generating votes.

Stage 4: Clean up

Once the freeze period has passed after a proposal’s expiry, any account can use the clnproposal() action to free all of the associated used RAM. This action effectively reclaims all RAM consumed for votes and for the proposal itself. The RAM is thus given back to voters (for their votes) and to the proposer (for the proposal).

The clnproposal() action can be called by anybody. There is no risk since only expired proposals that have passed their freeze period can be cleaned.

// Execute action *clnproposal* with parameters
// Use any account to sign
proposal_name: "usesys"
max_count: 100

If there are many voters, the action clnproposal() will remove up to max_count votes in one execution to prevent transaction timeout. Once all votes are removed on a proposal, the proposal itself will then be removed.

Now, if you look at accounts proposer and voter1 etc, their RAM usage should go back to the value before participating in the proposal and voting. Therefore, on completion of a proposal all used memory will be returned without a memory leak.

What’s next?

Don’t forget to clap and star if you find this tutorial helpful, and make sure to follow us if you’d like to be notified when we have new updates.

EOS Contract in Depth: eosio.forum

- Part 1: EOSIO Referendum

- Part 2: Workflow of Voting

- Part 3: Source Code Walkthrough

Many thanks to the dfuse team for their help in writing this tutorial!

--

--