My thoughts after 6 months working with DAML: A Smart Contract Language
A smart contract is a fancy term for a “program that runs on the blockchain”. They were first introduced in the Ethereum blockchain and the idea is simple: The blockchain acts as an ubiquitous computer to run the programs (or “smart contracts”) and stores its results and current state. We can all agree on the results and current state of the program, because of the independent consesus provided by the blockchain.
Having programs that we can run and that everybody can be sure that we have not tampered with the result has opened many applications as the creation of blockchain based “currencies” (e.g. ERC-20 tokens, etc) and NFTs. Of course, we can also tell the program to encode an agreement between two parties and thus it becomes an actual “smart contract”.
Warming-up: Very Short Introduction to DAML
DAML is a smart contract language in the sense that it actually models an agreement between parties, not just because it runs on a blockchain. An example I usually give to people is the renting of a car. It is the agreement between two parties, a company and the renter. We can explain the process of renting a car as a sequence of agreements between the parties, for example:
- The company agrees to propose a car to the renter who has the choice to either accept or reject the offer. The company also keeps the option to withdraw the offer at any moment.
- The renter agrees to accept the proposal and then, after inspection of the actual vehicle it has the choice to either accept or reject the offer. The company keeps the option not to offer a new vehicle if the proposed vehicle is rejected.
- Upon accepting a vehicle, the renter agrees to return the return the vehicle after a certain time and the company agrees to provide the vehicle until the date.
- Finally, after returning the vehicle the user agrees to be charged by the company if the vehicle has any damage not covered by the insurance or if the fuel tank is not full.
These kind of workflows are naturally expressed in the DAML. DAML also as a very particular semantics. The only way to persist information in your DAML program is to instanciate a template, which DAML will persist in its backend. This means that the current state of your program is the set active contracts that are in the DAML node. Do you need to store something for later reference? You need to store it as a contract. Do you need some configuration data? The natural way is to store it like a contract. To perform actions or logic you need to exercise choices on the different contracts. The choices can implement as complex logic as you need.
From a user perspective, the current state of the program is the set of active contracts. However, the main storage backend is not a relational database but a list of events or commits. In short, a ledger (in blockchain terminology) or a journal (in accounting terminology). Contrary to Ethereum, the DAML platform is not aimed at being a public universal computer, so each node stores its ledger locally. However, several local nodes can interoperate and allow contracts and transactions to flow between ledgers.
Scratching the Surface: My First Steps With DAML
When I started working with DAML I just followed the tutorials on their website. Their documentation is excellent and very comprehensive. They also have forum and are very responsive. The main challenge I found is the mental model. DAML is Turing complete, but it does not work in the same way I am used in other programming languages. I wanted to do an integration to another blockchain and was completely lost on the right way to do it. After some research I found an example code that explained how to use DAML to integrate with Ethereum. Even though my Ethereum knowledge at the time was minimal I got the gist of building an actual library with DAML. In retrospect, my issue was the integration part, and not the actual implementation. DAML documentation focuses a lot on how to program an application but how to integrate to other platforms is left as an exercise to the reader.
My first prototypes focused on implementing the logic of the application using the only tools that DAML provides you: contracts and choices. After a while I was able to translate the Ethereum integration logic into my own logic. My logic was complex at times and it required me to get better at expressing my contracts' logic using the DAML language. Here is a gotcha for programmers that are not familiar with the functional programming. The DAML language uses Haskell as its base language. This requires to be familiar with functional programming because when the logic becomes complex you will need to use functional programming techniques. I had a lot of experience with functional programming in Scala, but Haskell’s syntax stood constantly in the way. The good news is that Haskell’s syntax is really not that hard. Most of the time, when I did not know something I googled for a Haskell solution and then it worked almost without any changes.
After some time coding, my outcome was just a set of DAML files and a set of tests. DAML provides a very neat SDK that includes a navigator component. This component lets you test your contracts and choices using a generic web interface. DAML also offers a nice facility to create unit tests for your contracts. After a while, I was sure that my set of contracts was doing what I wanted, but where do you go from there?
The main takeaway here is that you can safely focus on the logic and actually test it very fast. This gives a fast feedback loop and improves the development speed. However, be prepared to update your mental model to program in the way of DAML.
Digging Deeper: The Java Integration
I had a set of DAML contracts and I needed something to integrate. I started to research how to map what happens in DAML to my actual target and also to go beyond the navigator. DAML actually offers a way to have a nice UI for your contracts using React, but at the time my Typescript knowledge was limited and the official SDK for my target integration is in Java. Luckily, DAML supports the generation of Java bindings out of the box and there were even some examples on Github (at the time of this writing the examples are not maintained anymore for the latest versions of DAML).
I created a Spring Boot Java application that read the streams of contract creations and did modifications on the target system. The responses of the target systems where translated to exercising of choices of contracts on the DAML node and that way we had an actual integration between the two systems. The app also provided a simple web interface. Also, the app was in charge of translating the actions of the user on the UI to the creation of contracts and the exercising of choices on the DAML node side. To simplify UI, I used just standard HTML forms and avoided Javascript altogether.
In short, the DAML node was providing the core application logic, and the Java application was providing both a UI and integration with the our target system. This was a good proof of concept that we could actually use our DAML contracts with something facing the user and also integrate with other systems.
Broadening the Perspective: Integrating with the Browser
The next step in my DAML journey was integrating DAML with a Chrome extension. As I mentioned, we were integrating to a blockchain, and the blockchain provides a wallet browser plugin. We needed the wallet browser plugin to talk to the app (and viceversa), connect to the ledger to perform the logic of the application and finally reflect that logic in our target system.
The integration with the Chrome extension from the webpage could not avoid Javascript (as I did for the previous application), so I decided to go full in with DAML react bindings. A new architecture was born for the application:
- The integration to the target system was to be implemented in Java as a generic component.
- The DAML contracts that refer to the target system’s logic become a separate module (let’s call them
integration-contracts.dar
). - The application logic DAML contracts become a module that reuses the
integration-contracts.dar
. - The UI uses the react bindings provided by DAML.
- The glue between application code and integration is provided by triggers.
This new architecture provided a lot of benefits:
- All of the integration logic could be reused, both as the generic Java component and the integration contracts.
- The application logic is completely written using DAML thanks to the triggers.
- The UI is completely written with Typescript and React, well known and supported platforms.
- The whole can easily be deployed to DAML Hub, Digital Assets platform for d-apps.
I have not given a description of triggers yet. Triggers are programs that constantly monitor the active contracts and can exercise choices on them when required. This is necessary because of DAML’s way of working. For example, the integration contracts do not know about the logic that will be using them.
This new version was felt more idiomatic. The generic Java component for integration is almost completely transparent for the developer. Building the UI using Typescript is also a step forward. After some time everything worked perfectly locally.
Large Projects: Module Dependencies and Other Issues
However, in this project I also encountered some of the still rough edges on the DAML ecosystem. In my application I use three different DAML modules: one for the common operations (the ones needed for the integration and handled by my broker), one for the application specific contracts and one for the triggers. Working with three DAML modules is still a cumbersome process.
DAML supports two ways of dealing with dependencies. A project can depend on actual DAR files or it can also depend on DAR files that are already in the node. In both cases it is difficult to work with other modules as dependencies. For the former case, you need to constantly copy your build artifacts to a new directory, for the latter, you need to deploy you artifact, delete the local cache (.daml/
directory) and rebuild your artifact each time. Both processes are cumbersome. In my case, in one instance I am depending on a module because I want to modularize my application, so I accept the burden. However, in the other instance it is just the trigger project that depends on the application specific contracts. This is not the way I would do it, but sadly as I am writing this post, DAMLHub requires to have the triggers in a separate dar file (or at least I have not found a way to have the triggers working on the same dar file as the main contracts).
While this is not a show stopper, it greatly complicates the development workflow and slows down development. I hope that the DAML team addresses this issue to improve the overall language usability.
Going public: Deploying to DAML Hub
DAML Hub is a platform as a service provided by the DAML team. It allows to:
- Deploy a set of contracts.
- Deploy the UI assets.
- Deploy the triggers.
- Integrate through gRPC services.
They offer a nice graphical interface to have everything deployed. The deployment was not that hard, except for the integration. To make my integration work I needed to configure certificates and security in the app. However, in the end it worked correctly as expected.
One of the main gotchas was the DAML Hub only supports an “Initiate and Accept” architecture using the react bindings. In this kind of architecture the user needs to start by creating a contract and then the triggers on the app side exercise a choice. In this way the interaction is started. My assumption was that I could start directly by exercising a contract on the app side. This is not possible using the react bindings.
My experience was fairly positive, the documentation is complete, but my feedback to the team is to have a checklist to make you d-app ready for DAML hub, so that the different gotchas of the platform are documented in one specific place and not scattered through the documentation.
The DAML Hub platform is really impressive. It lets you transform a set of contracts and some Typescript code into a real world deployed application in an almost seamless manner. My only wish for improvement is that they provide a way to automate deployments (through CLI tools for example) so that this can be automated in a CI/CD pipeline.
Conclusion
Working with DAML is an interesting experience. The language is very expressive and it shines where privacy and authorization are needed. It is also well supported with not only tooling but a whole platform to deploy applications to. If the logic of the program actually reflects “contracts”, the code is very elegant and readable. However, when the logic is more workflow oriented the language becomes very cumbersome. For example, when integrating with other systems, the interaction between DAML and the other system is very complex when done in DAML. A solution to this might be to implement the workflow logic in the other system and avoid it altogether in DAML. Nevertheless, when you are on DAML Hub the only persistent state available for complex workflows is the one that DAML provides.
Some criticism is that sometimes the team move fast and break some things with new versions. For example, I tried to upgrade from 2.3 to the latest 2.6.0 and it broke some of my Java generated code. Nevertheless, their framework is flexible enough to let you stay with a particular version and supports having several versions installed at the same time. Also, the fact that modularization needs to work better if DAML is to be used for large multi-module projects.