Time is passing by and we are getting more experienced in writing smart contracts with Sophia ML language on æ
In this tutorial, I’d like to share with you a simple voting decentralized
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
- Add candidates to vote for
- Allow users to give their vote for a certain candidate
- Count the votes that every candidate gathered
You can
Setting up project and development environment
forgae
. Take a look at the Build first
Smart contract
As you probably already know (if you’ve followed the previous tutorial), in
The first thing we do is to define our variables and types that we are going to use in the smart contract. And init()
contract Vote =
type candidate = address
type votes = list(address)
record state =
{ vote : map(candidate, votes) }
public function init() : state =
{ vote = { } }
We are defining candidate
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 Call.caller
::
public function count_votes(count_for : candidate) =
length(state.vote[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 'a
int
length'
private function length'(l : list('a), x : int) : int = switch(l) [] => x _ :: l' => length'(l', x + 1)
And here the magic happens, we use switch
[] => 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
The whole smart contract code looks like this in the end
Conclusion
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.