Skip to main content

Poll Contract

The poll contract stores polls' IPFS hash and users' answers. It guarantees that an account can only answer once to a poll. It also computes the number of times an answer has been selected.

When a user adds a poll (IPFS hash), it needs to be approved by a special account, called contract's owner. The owner can also remove any existing poll.

Repository


Author


Norms

Project

Poll contract project was created with the following Completium CLI command:

completium-cli create project poll-contract

Command npm i installs required packages:

  • typescript util packages
  • mocha for test suite
  • archetype's packages for binding

package.json file is created with utility commands, including:

  • npm run "gen-binding" to generate contract(s)' binding
  • npm test to launch tests in tests directory

Deployment

The following Completium CLI command is used to deploy the contract:

completium-cli deploy ./contracts/poll.arl --parameters '{ "owner": "tz1h4CiqWxNe4UxSpkwXy617RM6DaK6NU76P" }' --metadata-uri "ipfs://QmXbuUyyJXW1RRuL3k81Kpe2HULbYLj1sUUq44Nuxa5z8h"

where QmXbuUyyJXW1RRuL3k81Kpe2HULbYLj1sUUq44Nuxa5z8h is the IPFS hash of the contract metadata file:

{
"name": "Poll Dapp",
"description": "An example of Dapp built with Archetype & Completium",
"version": "2.0",
"license": { "name": "MIT" },
"authors": ["Completium team <contact@completium.com>"],
"homepage": "https://completium.github.io/poll-dapp/",
"interfaces": ["TZIP-016"]
}

Storage

owner

Contract owner's address, passed as contract parameter. Only the owner can:

  • approve a poll
  • remove a poll
  • transfer contract ownership
  • pause contract
  • unpause contract

Code


archetype poll(owner : address)
with metadata ""

archetype address with metadata

poll_counter

Number of polls added, used as poll key in poll asset collection (see approve entry point).

Code


variable polls_counter : nat = 0

variable nat

poll

Collection of polls.

A poll is identified by a natural integer rather than by its IPFS hash. This is to minimize the required storage of the responder information, that stores which polls an user has answered.

The responses field stores the numbers of responses to poll's possible answers.

Code


asset poll {
poll_pk : nat;
ipfs_hash : bytes;
creation : date = now;
responses : map<nat, nat> = []
}

asset nat bytes date now map

poll_to_approve

Collection of polls' IPFS hashes proposed by users. When approved by owner, a poll asset is created.

Note that the asset collection is created as a big_map, to be able to handle an arbitrary large amount of poll proposition.

Code


asset poll_to_approve to big_map {
ipfs_hash_to_approve : bytes;
poll_creator : address = caller
}

asset bytes address caller

responder

Collection of responders' lists (set) of answered polls. This is to decide whether a responder has already answered a poll or not (see respond entrypoint).

It is specified as a big_map to be able to handle an arbitrary large number of responders.

Code


asset responder to big_map {
res_addr : address;
polls : set<nat> = [];
}

asset address set nat

Entry

add_poll

Entry to call to propose a new poll. The poll's IPFS hash is added to the collection of hashes to approve.

Code


entry add_poll(h : bytes) {
require {
r1 : is_not_paused()
}
effect {
poll_to_approve.add({ ipfs_hash_to_approve = h });
emit<NewPoll>({ caller; h })
}
}

entry bytes require is_not_paused effect add caller emit

Parameter


h :

Poll's IPFS hash

Fails with

CONTRACT_PAUSED

When contract has been paused by owner


("KEY_EXISTS", "poll_to_approve")

When poll's IPFS hash h has already been proposed


Emits


Related

respond

Entry to call to answer a poll. It fails if:

  • the poll hash is not registered
  • the caller has already responded

The number of times someone has responded to the poll's answer (choice_id) is incremented, and the poll id is registered in the set of polls caller has already responded to.

Code


entry respond(pk : nat, choice_id : nat) {
constant {
selection_count is poll[pk] ? (the.responses[choice_id] ? the : 0) : 0;
}
require {
r2 : is_not_paused();
r3 : poll.contains(pk) otherwise POLL_NOT_FOUND;
}
fail if {
f1 : responder[caller] ? the.polls.contains(pk) : false with CANNOT_RESPOND_TWICE
}
effect {
responder.add_update(caller, { polls += [pk] } );
poll.update(pk, {
responses += [(choice_id, selection_count + 1)]
});
emit<Response>({ caller; pk; choice_id })
}
}

entry nat constant require is_not_paused contains fail if effect add_update += update emit caller

Parameters


pk :

Poll's primary key


choice_id :

Poll's choice id selected by user

Fails with

CONTRACT_PAUSED

When contract has been paused by owner


POLL_NOT_FOUND

When poll's primary key pk is not found in poll asset


CANNOT_RESPOND_TWICE

When poll's primary key pk is found in caller's set of already responded polls


Emits


Related

Called by owner

approve

Entry called by owner to approve a proposed poll:

  • a new poll is added to the poll asset collection
  • the proposed IPFS hash is removed from poll_to_approve

Code


entry approve(h : bytes) {
called by owner
constant {
creator_ ?is poll_to_approve[h]?.poll_creator otherwise POLL_NOT_FOUND
}
effect {
poll.add({ poll_pk = polls_counter; ipfs_hash = h });
polls_counter += 1;
poll_to_approve.remove(h);
emit<Approve>({ creator_; h })
}
}

entry bytes called by constant ?is ?. effect add += remove emit

Parameter


h :

Poll's IPFS hash

Fails with

POLL_NOT_FOUND

When poll's IPFS hash h is not found in poll_to_approve asset


Emits


Related

disapprove

Entry called by owner to disapprove a proposed poll:

Code


entry disapprove(h : bytes) {
called by owner
effect {
poll_to_approve.remove(h)
}
}

entry bytes called by effect remove

Parameter


h :

Poll's IPFS hash

Fails with

does not fail


Related

remove

Entry called by owner to remove a poll.

Code


entry remove(pk : nat) {
called by owner
effect {
poll.remove(pk)
}
}

entry nat called by effect remove

Parameter


pk :

Poll's primary key

Fails with

does not fail


Related

View

get_responses

Returns poll pk response statistics.

Code


view get_responses(pk : nat) : map<nat, nat> {
return poll[pk].responses
}

already_responded

Returns true if source has already answered poll pk.

Code


view already_responded(pk : nat) : bool {
return (responder[source] ? the.polls.contains(pk) : false)
}

Events

NewPoll

Emitted by add_poll with:

  • poll creator's address
  • poll's IPFS hash

Code


event NewPoll {
creator : address;
poll_id : bytes
}

event address bytes

Response

Emitted by respond with:

  • responder's address
  • poll's id
  • response's id

Code


event Response {
responder_addr : address;
poll_id : nat;
response : nat
}

event address nat

Approval

Emitted by approve with:

  • proposal issuer's address
  • poll's IPFS hash

Code


event Approval {
creator : address;
poll_id : bytes
}

event address bytes