7. eosio.forum - simple forum, messaging and voting system for BOS
A simple forum, messaging and voting system for BOS
The purpose of this contract is to support the EOS Referendum system by storing proposals and their related votes in-RAM in the blockchain's state.
It's also possible to create related posts and statuses, but they are not stored in-RAM in the blockchain's state. It allows authenticated messages to go through, where they are visible in the transaction history of the chain. Off-chain tools are needed to sort, display, aggregate, and report on the outputs of the post and status actions.
Lifecycle
The propose
action is first called providing the proposer's account, proposal's name (its id among all other proposals), proposal's title, a JSON string for extra metadata (specification not defined yet) that can be left empty.
Once the proposal has been created, people can start to vote on it via the vote
action. The vote action is called using the voter's account, proposal's name, vote's value (0
for negative vote and 1
for a positive vote) and a JSON string for extra metadata (specification not defined yet), which can be left empty.
A vote overwrites any previous value if present. That means that if you voted initially with a vote value of 0
(negative vote) and you perform a second vote
action on the same proposal this time with a value of 1
(positive vote), your current vote for the proposal is now 1
.
There is no decay once you have voted. Once you vote, it does not change, nor is it removed until you either call unvote
.
Once a vote has been cast, a user can remove its vote via the unvote
action. The unvote
action is called using the voter's account and proposal's name. An unvote
action completely removes your vote from the proposal and clears the RAM usage associated to that vote.
Development
Prerequisites:
Docker 17+ (or eosio.cdt 1.6+ installed locally)
We assume the docker
binary is available in your PATH
environment as well as eosc
and eos-bios
. The eos-bios
and eosc
binaries are required to correctly boot the local development & test node as well as running the automated test suite.
Building
Simply call the build.sh
script which launches a Docker container and compiles the contract:
Local Toolchain
For compilation purposes, it's possible to use the local eosio.cdt instead of pulling a Docker image containing it. Simply call compile.sh
script to compile using the local EOSIO.CDT toolchain:
Running
You can easily start a development node using the run.sh
script, which uses eos-bios and Docker to launch a fully configured sandboxed nodeos
development node:
This creates the following accounts:
eosio.forum
proposer1
proposer2
poster1
poster2
voter1
voter2
zzzzzzzzzzzz
All accounts created in the development node above use the following public/private key pair:
Public:
EOS5MHPYyhjBjnQZejzZHqHewPWhGTfQWSVTWYEhDmJu4SXkzgweP
Private:
5JpjqdhVCQTegTjrLtCSXHce7c9M8w7EXYZS7xC13jVFF4Phcrx
You can pre-fill your environment with some proposals and votes easily by simply calling the ./tests/data.sh
script.
Once you are done with the nodeos
development node, simply call stop.sh
to stop the running instance:
Environment
To easily interact with the development node via eosc
on your terminal, simply export the following environment variables:
The direnv tool can be used to automatically import those variables when you cd
in the project's root directory.
Tests
Running the full automatic test suite is easy as doing:
This launches nodeos
development node (via ./run.sh
) and then executes all the integration tests found in tests
folder (see all.sh for exact files picked up).
To correctly run the tests, you will need to switch the freeze period of a proposal for 2 seconds (waiting 3 days could be a bit too long!). The tests.sh
script takes also care of this changing the freeze period automatically for you to 2 seconds so it looks like this instead when the tests runs:
Important The tests.sh
script automatically revert back the changes once the test script finishes (either in error or successfully). You should check just in case before sending your changes to be 100% sure that changes were effectively reverted back. You would not like to push a freeze period of 2 seconds in the repository!
Deployment
The latest version of this code lives on the eosio.forum
account on the EOS Mainnet.
There is also a few accounts that were used for development purposes as well as for testing updates to the contract. Here the list with some details about the status:
eosforumrcpp
on EOS Mainnet (status:release candidates
)cancancan345
on Kylin network (status:unmaintained
)cancancan123
on Kylin network (status:unmaintained
)eosforumdapp
on EOS Mainnet (status:unmaintained
)
Reference
Here is the list of possible actions:
Action propose
Propose a new proposal to the community.
Parameters
proposer
(typename
) - The actual proposer's accountproposal_name
(typename
) - The proposal's name, its ID among all proposalstitle
(typestring
) - The proposal's title (must be less than 1024 characters)proposal_json
(typestring
) - The proposal's JSON metadata, no specification yet, see Proposal JSON Structure
Rejections
When missing signature of
proposer
When
proposal_name
already existsWhen
title
is longer than 1024 charactersWhen
proposal_json
JSON is invalid or too large (must be a JSON object and be less than 32768 characters)
Example
OR
Action vote
Vote for a given proposal using your account.
Parameters
voter
(typename
) - The actual voter's accountproposal_name
(typename
) - The proposal's name to vote onvote
(typeuint8
) - Your vote on the proposal,0
means a negative vote,1
means a positive votevote_json
(typestring
) - The vote's JSON metadata, no specification yet, see General JSON Structure Guidelines
Rejections
When missing signature of
voter
When
proposal_name
does not existWhen
proposal_name
is already expiredWhen the
vote_json
JSON is invalid or too large (must be a JSON object and be less than 8192 characters)
Example
OR
Action unvote
Remove your current active vote, effectively reclaiming the stored RAM of the vote. Of course, your vote will not count anymore (neither positively or negatively) on the current proposal's voting statistics.
Parameters
voter
(typename
) - The actual voter's accountproposal_name
(typename
) - The proposal's name to remove your vote from
Rejections
When missing signature of
voter
When
proposal_name
does not exist
Example
OR
Action cancel
Is used to cancel a proposal_name
authorized by the proposer
.
Parameters
proposer
(typename
) - The original proposerproposal_name
(typename
) - The proposal's name to cancel
Rejections
When missing signatures of proposal's
proposer
When
proposal_name
does not exist
Example
Note proposer1
must be the same as the one that created initially the example
proposal.
Action post
Parameters
poster
(typename
) - The poster's accountpost_uuid
(typestring
) - The postUUID
(for reply purposes)content
(typestring
) - The actual content of the postreply_to_poster
(typename
) - The initial post's poster your post replies toreply_to_post_uuid
(typestring
) - The initial post'sUUID
your post replies tocertify
(typebool
) - Reserved for future usejson_metadata
(typestring
) - The post's JSON metadata, no specification yet, see General JSON Structure Guidelines
Rejections
When missing signature of
poster
When
content
is an empty stringWhen
content
is bigger than 10240 charactersWhen
post_uuid
is an empty stringWhen
post_uuid
is bigger than 128 charactersWhen
reply_to_poster
is not set butreply_to_post_uuid
isWhen
reply_to_poster
is not an existing accountWhen
reply_to_poster
is set andreply_to_post_uuid
is an empty stringWhen
reply_to_poster
is set andreply_to_post_uuid
is bigger than 128 charactersWhen
json_metadata
JSON is invalid or too large (must be a JSON object and be less than 8192 characters)
Example
OR
Action unpost
Parameters
poster
(typename
) - Remove a previous post you didpost_uuid
(typestring
) - TheUUID
of the post to remove
Rejections
When missing signature of
poster
When
post_uuid
is an empty stringWhen
post_uuid
is bigger than 128 characters
Example
OR
Action status
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 for the account
using the content
received.
Parameters
account
(typename
) - The account to add a status tocontent
(typestring
) - The content associated to the status
Rejections
When missing signature of
account
When
post_uuid
is bigger than 256 charactersWhen
content
is the empty string and no previousstatus
existed foraccount
Example (add status):
Example (remove previous status):
OR
Example (remove previous status):
Table proposals
Row
proposal_name
(typename
) - The proposal's name, its ID among all proposalsproposer
(typename
) - The actual proposer's accounttitle
(typestring
) - The proposal's title, a brief description of the proposalproposal_json
(typestring
) - The proposal's JSON metadata, no specification yet, see Proposal JSON Structure Guidelinescreated_at
(typetime_point_sec
) - The date at which the proposal's was created, ISO 8601 string format (in UTC) without a timezone modifier.
Indexes
First (
1
typename
) - Index byproposal_name
fieldSecond (
2
typename
) - Index byproposer
Example (get all proposals):
OR
Example (get all proposals for a given proposer):
Caveats Right now, eosc
does not support searching giving only a direct key. Instead, it really requires a lower and upper bound. The upper bound being exclusive, to correctly get the upper bound, take the account name and change the last character to the next one in the EOS name alphabet (order is a-z1-5.
).
So, looking for all proposals proposed by testusertest
, the lower bound key would be testusertest
and the upper bound key would be testusertesu
(last character t
bumped to next one u
).
OR
Table status
Row
account
(typename
) - The status' postercontent
(typestring
) - The content of the statusupdated_at
(typetime_point_sec
) - The date at which the status was last updated, ISO 8601 string format (in UTC) without a timezone modifier.
Example
Table vote
Row
id
(typeuint64
) - The unique ID of thevoter
/proposal_name
pairproposal_name
(typename
) - Theproposal_name
on which the vote appliesvoter
(typename
) - Thevoter
that votedvote
(typeuint8
) - The vote value of thevoter
(0
means negative vote,1
means a positive vote)vote_json
(typestring
) - The vote's JSON metadata, no specification yet, see General JSON Structure Guidelinesupdated_at
(typetime_point_sec
) - The date at which the vote was last updated, ISO 8601 string format (in UTC) without a timezone modifier.
Indexes
First (
1
typei64
) - Index byid
fieldSecond (
2
typei128
input in hexadecimal little-endian format) - Index by proposal name, the key is composed in the high bytes using theproposal_name
and the low bytes are thevoter
.Third (
3
typei128
input in hexadecimal little-endian format) - Index by voter, the key is composed in the high bytes using thevoter
and the low bytes are theproposal_name
.
Example (get all votes):
Example (get all votes for a given proposal):
The idea is to turn the proposal_name
into an integer, convert it to hexadecimal, and compute the lowest possible key for voter
(lower bound) as well as the highest possible key for voter
(upper bound).
Note The hexadecimal values below are all in little-endian format, so high bytes are on the right side and low bytes on the left side.
Here are the steps to compute the lower/upper bounds for the table query:
Convert
ramusetest
EOS name to hexadecimal usingeosc tools name
.
Create the
lower_bound
key by prepending0000000000000000
to thehex
value shown above:0x00000000000000000040c62a2baca5b9
Create the
upper_bound
key by prependingffffffffffffffff
to thehex
value shown above:0xffffffffffffffff0040c62a2baca5b9
.
Now that we have the lower and upper bound keys, simply perform your query:
You will see only the votes against the proposal ramusetest
.
Example (get all proposals a voter voted for):
The idea is to turn the voter
into an integer, convert it to hexadecimal, and compute the lowest possible key for proposal_name
(lower bound) as well as the highest possible key for proposal_name
(upper bound).
Note The hexadecimal values below are all in little-endian format, so high bytes are on the right side and low bytes on the left side.
Here the steps to compute the lower/upper bounds for the table query:
Convert
testusertest
EOS name to hexadecimal usingeosc tools name
.
Create the
lower_bound
key by prepending0000000000000000
to thehex
value shown above:0x000000000000000090b1ca57619db1ca
Create the
upper_bound
key by prependingffffffffffffffff
to thehex
value shown above:0xffffffffffffffff90b1ca57619db1ca
.
Now that we have the lower and upper bound keys, simply perform your query:
You will see only the proposals that voter testusertest
voted for.
Proposal JSON Structure Guidelines
The proposal_json
should be structured against the EOS Enhancement Proposal 4 (EEP-4) which describes how the proposal_json
field should be structured based on a predefined set of proposal types.
While it's not strictly required to follow the guidelines in EEP-4, it's strongly suggested to do so as UI, vote tallies and related tools use the guidelines in EEP-4 to provide their services.
If you decide to not follow the guidelines and instead create you own type(s), it's highly encouraged to have a type
field in your JSON string describing your proposal type. Be sure that it does not collapse with the ones defined in EEP-4.
Of course, if you think your new type could be beneficial to the broader community of EOS, you are invited to submit changes to EEP-4 via a GitHub pull request on the EEP Repository.
General JSON Structure Guidelines
You can use any vocabulary you want when creating posts and votes, there is no specification yet for the JSON of those actions. However, by following some simple guidelines, you can simplify your life and the life of those building UIs around these messages.
For all json
prefixed or suffixed fields in vote
and post
, the type
field should determine a higher order protocol, and determines what other sibling fields will be required.
In a vote's vote_json field
type
is optional. Defaults tosimple
if not present.
type values
simple
is the same as notype
at all. The value of the vote is the booleanvote
field of the action.
In a post's json_metadata field
type
is a required field to distinguish protocol. See below for sample types
The following fields attempt to standardize the meaning of certain keys. If you specify your own type
, you can define whatever you want.
title
is a title that will be shown above a message, often used in clickable headlines. Similar to a Reddit post's title.tags
is a list of strings, prefixed or not with a#
.
type values
chat
, which is a simple chat, pushing a message out.eos-bps-roll-call
, this is used within EOS Block Producers calls to indicate they are present.eos-bps-emergency
, once 3 block producers send a message of this type within an hour, all block producers can trigger a wake-up alarm within 1h. Do not abuse this message to avoid alert fatigue. ##### Example serious vulnerability requires mitigation, serious network issues, immediate action required, etc..eos-bps-notify
, once 7 block producers send a message of this type within an hour, other block producers can trigger a notification to get their attention in the next 24h. ##### Example new ECAF order requires attention.eos-arbitration-order
, BPs can watch for known Arbitration forums accounts, and alert themselves of required action. Further fields could be defined like a link to the PDF format order; a reference to a ready-madeeosio.msig
transaction proposition; etc.
Last updated