In the previous post we started to look at how to implement a doodle using smart contracts written in DAML. The last code snippet we looked at was the IssueInvites choice, which is used to send out invites to each of the voters. There’s one more detail about the code in that choice that I’d like to point out.
Remember we mentioned that IssueInvites is a preconsuming choice. Exercising it will archive the current version of the contract before executing the choice code. Let’s see why this is important.
If the choice were not preconsuming, the version of the Doodle contract on which you exercise the choice would still exist when the contracts are created.
As a result of that, as shown on the screenshot on the left, every invite contract is going to be divulged to every voter.
The fact that the invite contract is divulged means that parties which are neither signatories or observers of the VotingInvite contract, will see a copy of it once, when the contract is created. That’s because the invite contract was created as a consequence of exercising the IssueInvites choice on the Doodle contract, on which the voters are observers.
It should be noted that the parties to which a contract is divulged will only see that contract once when it is created. Any further attempt by these parties to fetch or exercise choices on the divulged contract will fail.
Divulging the invites is not necessarily something we want to do, as “Voter1” has no business getting their hands on an invite meant for “Voter 2”.
In order to fix this problem we make the choice “preconsuming”, and the result can be seen in the screenshot on the left.
This is what is happening: the current version of the contract is archived before the choice is executed, and that means that there are no voters around to witness the creation of the invite contracts, and as such, the invite contracts are not divulged to unwanted parties. Each voter only sees their own invite because they are an observer thereof.
The Doodle Voting Invite
Now that we’ve seen how the doodle organizer issues voting invites, let’s have a look at what those invites look like:
The DoodleInvite template holds the doodle name, the organizer and the recipient voter.
Invite contracts are owned by the organizer because they decide who can vote in the doodle. This way, the organizer get the opportunity to withdraw an invite by archiving the invite contract.
The key of the invite contract is a triplet, made of the organizer, the voter and the doodle name. The organizer will therefore not be able to invite a voter more than once on the same doodle.
The voter is an observer on the invite contract. This will allow the voter party will be notified about the contract being created and, most importantly, act as a controller when exercising the “Vote” choice.
Let’s look into more detail at the “Vote” choice:
The choice takes just one parameter, and that’s the option we want to cast a vote for, and returns a Doodle updated with the vote.
You’ll also notice that the choice is “nonconsuming”, which means that the invite contract will not be archived after exercising this choice. That’s because a voter is allowed to use the invite to vote for more than one option, and so we need the invite contract around after casting the first vote.
First thing, the voter needs to verify that the doodle actually exists, so in line 6 we are looking for a Doodle contract signed by the organizer and having the name specified on the invite. This will return an optional wrapping a contract ID.
DAML Optionals work just like Java Optionals. Like in Java, an optional can either wrap something or be empty. But remember DAML is functional, not object oriented, so instead of calling methods on the optional itself to extract its value, you’ll use functions from the DA.Optional module.
In line 7 we attempt to extract the contract ID from the optional using the fromSome function and use it to fetch the contract payload. If the contract we are looking for does not exist, this is where it will fail.
You may be wondering why in lines 6 and 7 we are using the “←” operator for assignment. That is because the “lookupByKey” and “fetch” instructions are not executed on the participant node, but somewhere on the ledger. “a←b” means “execute the instruction b somewhere on the ledger, then retrieve the result on the machine where this code is running and assign it to variable a”.
On line 8, we are checking that the organizer on the invite matches the organizer on the doodle contract. This way a voter will not be able to impersonate the organizer on a doodle by issuing fake invites.
Ultimately, on line 9, we exercise the CastVote choice on doodle by specifying the voter, the option the voter is voting for and the ID of the invite contract itself. Passing the invite contract ID is necessary so that the doodle’s CastVote choice can ensure that the voter casting the vote was actually issued an invite to do so.
Fun fact about DAML contracts: remember how in various programing languages an object can reference itself using either the “this” (Java, C++) or “self” (Python, Objective-c) keywords ? Well, DAML has both, but they don’t mean the same thing:
“this” is a contract ID, very much like in Java or C++ where “this” is a pointer to an object. The analogy is even more clear if we think about the a ContractId as a pointer that points to a contract stored somewhere on the ledger, just like a java or C++ pointer points to an object somewhere in memory.
“self” can be used to reference the contract payload itself. It is literally the equivalent of “*this” in C++.
The Doodle’s CastVote Choice
To quickly recap: in order for a voter to effectively vote, they need to receive a DoodleInvite contract and exercise the Vote choice on it. The Vote choice will then look up he Doodle contract by name and organizer, and exercise CastVote on it.
So let’s see what this CastVote choice looks like.
CastVote takes three parameters:
- the voter
- the option the voter wants to cast a vote for
- the ID of the DoodleInvite the voter has received
It is obvious why we need to know who the voter is and what he intends to vote for in order to cast the vote. The invite contract ID however, is a little trick meant to prevent fraud.
Making sure the vote is legitimate
In line 8 we are fetching the invite contract. If we succeed, it will guarantee that the invite provided by the user actually exists. Once we get hold of the invite payload, we can go on and assert a certain number of conditions to make sure the vote is legitimate:
- in line 9 we ensure that the invite the voter has received was issued for the doodle the voter is trying to cast a vote on
- in line 10, we check that the party exercising the CastVote choice is the one who received the invite. This check is defending against malicious parties hijacking the Doodle and voting on behalf of somebody they are not. Only if the controller party is able to produce an invite in their name is the vote allowed to go through.
- in line 11 we make sure the invite provided by the voter was issued by the Doodle’s organizer. This defends against a situation where a voter with a valid invite for doodle A would try to use that to vote on a Doodle with the same name, but created by a different organizer.
- in line 12 we check that the Doodle is open for voting
- and finally in lines 13 and 14, we check that the controller is actually a registered voter and that the option they’re trying to cast a vote for is a valid one for this Doodle.
If all checks go trough it means we are confident enough that the vote is legitimate and the Doodle is ready to receive it.
Using TextMap to keep count of the votes
Let’s zoom into the code that’s actually casting the vote.
There are a few syntax elements here that we did not see before. First, in line 4 we start a “let” block. A “let” block lets you define values (variables) and bind them in the scope of the following code. All expressions in the let block are evaluated locally on the participant node.
So let’s have a closer look at what’s going on in there.
In line 5 we retrieve the current state of votes for the option we are about to cast a vote for.
The goal here is to retrieve the VotingSlot that holds the votes for the specified option. To do so, we call DA.TextMap.lookup on the votes TextMap, as you can see on the rightmost side of the expression.
fromOptional takes a fallback value and an optional. The fallback value will be returned if the optional — in our case, returned by DA.TextMap.lookup— is empty. This can be the case if the votes map does not contain the option in its key set and that happens if nobody has voted for that option yet. The fallback value is a VotingSlot with a votes count of zero and an empty voters array — because if the fallback value is returned, it means that nobody has voted for that option yet.
In line 6 we update the votes TextMap with a new VotingSlot that reflects the new vote:
Here we use DA.TextMap.insert to insert an updated VotingSlot into the votes TextMap.
DA.TextMap.insert takes three parameters: the key, the value associated with the key, the map in which the key / value pair will be inserted.
If the TextMap does not already contain an entry for the specified key, one will be created. If one exists, the value previously associated with the key will be replaced with the new one.
The value we insert is the updated VotingSlot:
The new VotingSlot is created with an incremented VotesCiount and an updated voters list.
In line 7 — 8 we create an updated version of the Doodle with a new votes map, but not before we check that the user has not already voted for the same option:
This should be read as: “create a new contract, identical to this one except for the votes argument, which should be replaced with updatedVotes”. Since the choice is not marked as “nonconsuming”, the contract will archive itself.
This wraps up the actual smart contract functionality for our Doodle. In the next iteration, we’ll be looking at how to properly and exhaustively test it.
Proofreading credits: Peter Schubert