A Doodle In DAML — Part 1
In my previous post, DAML Smart Contracts Appetizer, I introduced the DAML smart contracts programming language and runtime and explained why I was so enthusiastic about it. I would now like to get into a slightly more complex example: a Doodle written in DAML.
If you looked up DAML code on the internet and if your previous programming experience is with imperative, object oriented programming languages such as Java or C++, the Haskell style, purely functional DAML syntax may not come naturally for you. To make it easier to understand, and as we go through the DAML Doodle, we’ll draw parallels with equivalent imperative language constructs.
What’s a Doodle?
A Doodle is an ad-hock voting system where an organizer invites people to vote on on a set of options. Each voter can vote exactly once for each option. Votes are visible, so all participants know who voted for what.
Our DAML doodle must be easy to use and secure:
- A user — hereafter referred to as the organizer— must be able to create a Doodle with a description, voting options and an initial set of voters. After creation, the Doodle will be in a “set-up” mode, that is, not open for voting.
- The organizer must be able to add / remove voters
- The organizer must be able to open the Doodle for voting and issue invites to voters.
- Each voter must be able to vote exactly once for one or more options
- Each voter must be able to observe the doodle
- A voter should not be able to vote on behalf of another voter
The DAML Doodle
Since we are doing this in DAML, the doodle will of course be modeled as a DAML template, which, when instantiated, will be signed into a DAML contract by the organizer.
VotingSlot — DAML Data Structures
Before looking at the template, we are going to define a data structure that the template will use to collect votes:
VotingSlot is used to count the votes for an option: it contains an integer that represents the vote count, and a list of parties containing the voters who have voted for that option.
The data keyword is used to define a pure data structure. This is very similar to a C/C++ struct, a Java record or a Scala case class, but strictly immutable.
Coming from other languages, you may be tempted to call the “voted” field an array, most likely because it’s surrounded by square brackets. In DAML vocabulary that’s not an array, it’s a list and as every data structure in DAML, it’s also immutable.
In line 5 we specify that our data structure derives the “Eq” and “Show” interfaces. This is the equivalent of implementing the “equals” and “toString” methods on a Java class.
Doodle Template Declaration
To declare the Doodle template we use the “template” keyword as in the code fragment below.
A DAML template is the equivalent of a class in object oriented languages such as Java, Scala or C++. And while a class is instantiated into an object, a DAML template is instantiated into a contract.
As opposed to a Java , Scala or C++ instance objects, DAML contracts are immutable. This means that once created, a contract cannot be changed. Instead, it can be archived and a new version of itself can be created.
If you are an imperative object oriented programmer, you should think about a template and its parameters as a class with a single constructor that takes all those parameters.
Let’s have a quick look at those parameters and their respective data types:
- name —of type Text, which in Java and Scala would map to a java.lang.String and in C++ it would be an std::string.
- organizer — of type Party. A Party is a native DAML type representing an entity that interacts with the ledger. It can be a natural person or an organization.
- voters — the list (not array) of type Party representing parties the organizer has invited to vote.
- options —a list of type String representing options voters can vote for
- votes —of type TextMap. This is a key-value map where the key is a Text and the value is of the specified type — in our case the VotingSlot data structure. In Java this would be a java.util.Map and in C++ a std:map.
- open — a boolean that controls the state of the doodle. When set to false, the organizer can still add or remove voters and options, voting is not possible yet. When set to true, voting is possible, but the organizer will not be able to change voter and options any more.
Signatories, Observers and Constraints
The code snippet below shows how to define contract signatories, observers and integrity constraints.
In line 2 we specify that the organizer party is a contract signatory. The signatory is the party that must agree to the creation of the contract. By agreeing to the creation of the contract the signatory also agrees to all the consequences of contract choices being exercised on the contract by itself or entitled third parties.
In line 3, we specify that all the parties in the voters list are observers of the contract. You should keep in mind that all signatories are also observers. Observers are allowed to see the content of the contract.
In line 4, it gets more interesting: we don’t want a voter to be able to register twice, and we also don’t want duplicate options.
To enforce this rule, we use the “ensure” keyword followed by a boolean expression.
We use the “unique” function to check if a list only contains distinct elements. “unique” is a function in the DA.List module. You should think of DAML modules as of java packages or C++ namespaces. After calling “unique” on both the voters and the options array, we combine the result using the “&&” logical operator.
As a side note, you probably have noticed that in DAML, when invoking a function, parameters are not places between braces. They are just added after the function name.
In lines 13 and 14 we’re setting up a contract key. This is a business key, as opposed to the contract’s ID which is a technical primary key.
A key is composed either of a signatory party or a tuple containing at least one signatory party and any number of other template parameters. One or more of the signatories that compose the key must be designated as “maintainer”. Maintainers own the key.
A key value is guaranteed to be unique per template.
In our Doodle template, the key is a tuple where the first element is a party (the organizer, who’s also the maintainer) and the second is a text (the name of the doodle). This will allow entitled parties to look up Doodle contracts if they know the organizer and the Doodle’s name.
You’ll notice that in order to specify the maintainer we compute it from the key using the “fst key” expression. This may seem odd from an object oriented programming point of view where “fst” would be a behavioral feature of the tuple and you’d say something like “key.fst”. DAML however is a functional language, therefore fst is a function and you invoke it by passing the tuple as a parameter.
Choices, Contract Archival and Creation
The organizer needs to be able to manage their doodle by adding and removing voters before issuing voting invites. Let’s have a look at the code below:
Declaring a Choice
Operations such as adding new voters, removing voters and opening the Doodle up for voting are achieved by exercising choices on the contract.
DAML contract choices are the equivalent a of public method or function in a Java, Scala or C++ class. Their declaration however is more verbose than what you’re used to from other programming languages.
To define a choice, you start with the “choice” keyword, followed by the name of the choice, a colon and the return data type.
If the choice requires parameters you’ll need to use the “with” keyword, followed by the parameters list, just like we’ve done for declaring the template’s parameters.
Up until now it’s the same as declaring a method in a class in other languages, except a bit more verbose. But it’s about to get more interesting: we can specify who is allowed to exercise the choice.
To do that, you need to use the “controller” keyword followed by a comma separated list of parties. These parties can be either template parameters or choice parameters.
Exercising the choice is only allowed if the party who initiated the transaction is one of the named controllers.
We mentioned immutable. This means that in order to make changes to the contract, we need to archive the old version and create a new one.
Unless annotated with the “nonconsuming” keyword, choices will archive the current version of the contract. In order to create the new, updated version of the contract, we need to use the “create” keyword, as shown in lines 7 and 15 of the code snippet above.
When it comes to archiving contracts, choices can be “postconsuming”, “preconsuming” and “nonconsuming”.
Post-consuming choices will archive the current version of the contract after the choice execution has completed.
Pre-consuming choices will archive the current version of the contract before the choice execution has completed.
Non-consuming choices will not archive the current version of the contract
The difference between pre-consuming and post-consuming is subtle and consists merely in the set of parties which witness the creation of contracts created by exercising the choice.
Creating a New Contract Version
The semantics of the create instruction is quite clever and compact. Instead of creating the new version of the contract by specifying the template name and repeating all it parameters, we say “create this with my_parameter=new_value”. This literally means “create a new contract, identical to this one, but set “my_parameter” to “new_value”
Working With Lists
Working with lists is going to look very familiar if you’re a Scala person. In DAML, list processing is implemented by functions in the DA.List and DA.Traversable modules.
Adding an Element to a List
To add an element to the list you use the double colon operator as in the code fragment on the left. Remember, in DAML pretty much everything is immutable, and so are lists. So here, just like in Scala, you are not adding an element to the list, you are creating a new list by concatenating “voter” to the original “voters” list.
Removing an Element from a List
To remove an element from a list, you can use the “DA.List.delete” function. Here again, you aren’t actually removing the element from the list, you are creating a new list with all the elements from the original one, in the same order, but without the one you want to remove.
Transforming a List
Remember from the Doodle specifications, in order to open it up for voting we need to issue an invite for each registered voter. This practically means that we need to issue an invite contract for each element in the voters list.
Functions to iterate over a list and process its elements are found in the DA.Traversable module.
Let’s have a look at how this works:
First thing to notice is that IssueInvites is a “preconsuming” choice. Exercising it will archive the current version of the contract before executing the choice code.
To transform each element of the list into a contract, we’re using the DA.Traversable.mapA function which takes two arguments:
- first — lines 6–7 in the code fragment above — a lambda that consumes a list element and creates a DoodleInvite contract out of it.
Creating a contract is quite self explanatory: use the “create” keyword followed by the template name and the list of named parameters.
- second, the list the function will iterate over.
mapA also collects all transformed elements in a list, but we are not interested in that for this use case.
To wrap up this choice we create a new version of the Doodle contract and set the open flag to true to prevent the voters and options lists from being changed again.
To Be Continued
This is it for now, hope it was interesting. In the next iteration of this series we’ll look at how each voter can exercise their right to vote using the voting invite contracts and how votes are counted in the Doodle.
Proofreading credits: Peter Schubert