eosio.forum Part 2: Workflow of Voting
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
to300
. The use of this value will be explained later. - Click the build button to rebuild the smart contract and regenerate the
wasm
andabi
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.
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
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
Many thanks to the dfuse team for their help in writing this tutorial!
- EOS Studio Website: https://www.eosstudio.io
- EOS Studio Telegram: https://t.me/eosstudio
- dfuse Website: https://www.dfuse.io/
- dfuse Telegram: https://t.me/dfuseAPI