Building Voting æpp with Sophia ML on æternity blockchain

Time is passing by and we are getting more experienced in writing smart contracts with Sophia ML language on æternity blockchain.

In this tutorial, I’d like to share with you a simple voting decentralized aepp solution that we’ve built during the æpps summit in Turkey, which I took part in.

photo by nikita fuchs

This was one of my first times interacting with Sophia ML smart contracts, and while at the æpps summit I’ve gathered some cool and enthusiastic people, and made ourselves sort of a studying group. We decided to brainstorm and learn together while developing this voting aepp. This is the short summary of what we’ve decided our voting aepp will be doing:

  1. Add candidates to vote for
  2. Allow users to give their vote for a certain candidate
  3. Count the votes that every candidate gathered

You can try push the aepp’s functionalities further, with more sophisticated functions and use case, you can try doing it yourself, like we did. Try it – its fun.

Setting up project and development environment

First we have to initialize our project where we will write the smart contract. In order to do that we will be using forgae. Take a look at the Build first aeternity application with Sophia ML tutorial and follow the steps to initialize your project.

Smart contract

As you probably already know (if you’ve followed the previous tutorial), in Sophia ML we have a state which is the place where we store data on-chain, and it is the only thing in the smart contract that can be mutated (overwritten).

The first thing we do is to define our variables and types that we are going to use in the smart contract. And the init() function which is the constructor basically, if we compare this to a Solidity smart contract.

contract Vote =
   type candidate = address
   type votes = list(address)

   record state = 
      { vote : map(candidate, votes) }

   public function init() : state = 
      { vote = { } }

We are defining the candidate type as address, and votes as a list of addresses. And the state record will be storing a map (key-value-pair) of candidate to votes.

Starting with the first functionality for the aepp – adding candidates:

  public stateful function add_candidate(candidate: address) : bool =       is_candidate(candidate)       true

What this does is passing the candidate to the is_candiate() function – taking a candidate’s address as a parameter, checking if there is a candidate defined at with this address and saving it to the votes mapping in the state with the initial empty list of voters if not.

Here are the helper functions we are using for this:

   private stateful function is_candidate(candidate: address) =       let candidate_found = lookupByAddress(candidate, state.votes, { voters = [] })       if (size(candidate_found.voters) == 0)          put(state{             votes[candidate] = { voters = [] } })    private function lookupByAddress(k : address, m, v) =       switch(Map.lookup(k,m))          None => v          Some(x) => x

We are doing this because in Sophia ML we don’t have a default value of 0x0/false as in Solidity for example. So, in order for us to cast a vote, we need to first have added the candidates which we can vote for.

If we don’t add the candidate first, before voting, we will hit out of gas error.

Next we create the vote function which looks like this:

public stateful function vote(vote_for: candidate) =
      put(state{ vote[vote_for = []] @ vs = Call.caller :: vs })

We access the transaction initiator’s address by the built in Call.caller and prepend it :: to the current list of voters. Using this syntx we dont need to worry whether the candidate was initialized or not.

Last thing is the get votes count function.

public function count_votes(count_for : candidate) =
      length([count_for = []])

Here we are using a custom length function which we define as a helper function below. Here is the code and below we can see the explanation.

private function length(l : list('a)) : int = length'(l, 0)

This is where things get a bit more complicated, so I will try to explain what is happening here.

Since in Sophia ML we don’t have .count or .length to get the list length, we need to make ourselves a helper function which will make a recursion and will iterate over the list while incrementing a counter.

The length function is defined to accept a list of 'a which is the convention for a generic type, and a return type int . In the function’s body we are calling the length' function, while passing the list and an initial value for the counter.

private function length'(l : list('a), x : int) : int =       switch(l)          [] => x          _ :: l' => length'(l', x + 1)

And here the magic happens, we use the switch statement with 2 cases [] => x – which returns the value of the counter and breaks the recursion if the list is empty. And _ :: l' => length'(l', x+1) – meaning we are using a pattern matching and we are separating the first element from the list and the remainder and then recursively passing the list’s remainder to the same function, while incrementing the counter.

The whole smart contract code looks like this in the end

Sophia ML basic voting aepp


It is fairly simple to create a basic aepp on æternity blockchain using Sophia ML. In our case with the ae-vote we stumbled upon some tricky parts like the recursive iteration, we had to make above, but when you familiarize yourself with the language it is easier.

Useful links

Also published on Medium.

About the author


Milen Radkov has experience building and delivering successful complex software systems and projects for big enterprises and small startups. Software developed by him and his colleagues is being used by over 1000+ retail stores today. Milen has also extensive experience in blockchain development and is a well-known figure in Bulgaria’s blockchain ecosystem.

Milen Radkov

Let's build the decentralized future together!

Subscribe for updates from the blog

Leave a Reply

Your email address will not be published. Required fields are marked *