Smart Contract

In this section we will look at the technical details of how the eosio.forum smart contract is implemented. This contract can be roughly divided into four parts: proposal, vote, status and post. All the actions and tables live in the same class forum.

Proposal

The proposal part contains a table proposals and a few actions to operate a proposal.

class forum
ACTION propose(eosio::name proposer, eosio::name proposal_name, string title, string proposal_json, eosio::time_point_sec expires_at)
[source]
TABLE proposals
[source]
eosio::name proposal_name; // primary key
eosio::name proposer; // secondary key
string title;
string proposal_json;
eosio::time_point_sec created_at;
eosio::time_point_sec expires_at;

Any account can execute the propose() action to create a new proposal. After some necessary parameter checks, a new proposal will be saved in table proposals with RAM charged to the proposer. Each action parameter corresponds to a column in the table:

  • proposer is the account who created the proposal;

  • proposal_name is the primary key for table proposals and used as the unique identifier for a proposal;

  • title is a string for the proposal title and should be less than 1024 characters;

  • proposal_json is a JSON string for the proposal description and should comply with Proposal JSON Structure Guidelines;

  • expires_at defines the deadline for the voting period, which needs to be a time point between the action execution time and the next 6 months.

Once a proposal is created, any account (including the proposer itself) can vote on it via the vote() action, as long as the current time is before expires_at.

ACTION expire(eosio::name proposal_name)
[source]

This action allows the proposer to manually expire his/her proposal and end the voting immediately. This is done by modifying the expires_at field to the current time.

forum.cpp
53
54
55
proposal_table.modify(itr, proposer, [&](auto& row) {
  row.expires_at = current_time_point_sec();
});

The action expire() can only be called by the original proposer. Calling it on a non-existant or already expired proposal will return an error.

ACTION clnproposal(eosio::name proposal_name, uint64_t max_count)
[source]

It’s possible to clean a proposal if it has expired and its freeze period of 3 days (set by FREEZE_PERIOD_IN_SECONDS) has fully elapsed.

forum.hpp
99
bool can_be_cleaned_up() const { return current_time_point_sec() > (expires_at + FREEZE_PERIOD_IN_SECONDS);  }

The action clnproposal() will clean up all votes related to a proposal. It works iteratively by removing as many as max_count votes, and can be executed multiple times until all votes are removed.

forum.cpp
119
120
121
122
123
124
125
126
127
128
129
130
131
auto index = vote_table.template get_index<"byproposal"_n>();

auto vote_key_lower_bound = compute_by_proposal_key(proposal_name, name(0x0000000000000000));
auto vote_key_upper_bound = compute_by_proposal_key(proposal_name, name(0xFFFFFFFFFFFFFFFF));

auto lower_itr = index.lower_bound(vote_key_lower_bound);
auto upper_itr = index.upper_bound(vote_key_upper_bound);

uint64_t count = 0;
while (count < max_count && lower_itr != upper_itr) {
  lower_itr = index.erase(lower_itr);
  count++;
}

Notice that the secondary index byproposal is used to query and iterate over all votes of a given proposal_name (see table vote). Once there are no more associated votes, the proposal itself will be deleted.

forum.cpp
134
135
136
if (lower_itr == upper_itr && itr != proposal_table.end()) {
  proposal_table.erase(itr);
}

This effectively clears all the RAM consumed for a proposal and all its votes. It’s safe to allow anybody to call clnproposal() since the action will only accept an expired proposal that has passed the freeze period, which means it has terminated its lifecycle. Voters, proposers, or any community member is invited to call clnproposal() to clean the RAM related to a proposal.

Vote

The vote part contains a table vote as well as vote() and unvote() actions.

class forum
ACTION vote(eosio::name voter, eosio::name proposal_name, uint8_t vote, string vote_json)
[source]
ACTION unvote(eosio::name voter, eosio::name proposal_name)
[source]
TABLE vote
uint64_t id; // primary key
eosio::name proposal_name; // secondary key
eosio::name voter; // secondary key
uint8_t vote;
string vote_json;
eosio::time_point_sec updated_at;

For a non-expired proposal, any accounts can use the vote() action to publish a vote. It will consume a little bit of RAM from the voter (430 bytes) to save the vote info in table vote.

The meaning of the vote is represented by the vote field.

  • 0 means no

  • 1 means yes

  • 255 means abstain

  • Other values can be used to represent other meanings

In table vote, the primary key id is generated automatically. Secondary keys are created for fields proposal_name and voter to support searching by proposal or voter. The field vote_json is designed to provide extra information for a vote, such as a comment explaining the thought behind the vote.

The voter can execute the vote() action again to change his/her vote, or call the unvote() action to delete his/her vote in table vote. Removing the current active vote reclaims the stored RAM of the vote. Of course, the vote will not count anymore.

The vote() and unvote() actions will first check whether the proposal is still active, and refuse the execution if the proposal is expired.

forum.hpp
98
bool is_expired() const { return current_time_point_sec() >= expires_at; }

Therefore, it is guaranteed that the vote statistics for a proposal will be fixed once the proposal is expired, so that people will be able to count the votes and compute the voting result.

Status

class forum
ACTION status(eosio::name account, string content)
[source]
TABLE status
// scope is self
eosio::name account; // primary key
string content;
eosio::time_point_sec updated_at;

The action status() will record a status for the associated account. If the content is empty, the action will remove a previous status. Otherwise, it will add a status entry or modify the existing entry for the account using the content received.

Todo

How this is used?

Post

class forum
ACTION post(eosio::name poster, string post_uuid, string content, eosio::name reply_to_poster, string reply_to_post_uuid, bool certify, string json_metadata)
[source]
ACTION unpost(eosio::name poster, string post_uuid)
[source]

Todo

How this is used?