Introducing Chanterelle

a Purely Functional Smart Contract Manager

Previously we wrote about our purescript-web3 library and the advantages it offers over the traditional web3 stack. Up until recently we were still using truffle to deploy and manage smart contract migrations while running tests in purescript, a hybrid approach that worked for a while but ultimately needed to be rethought. This is why we decided to develop our own purely functional alternative, a project we call chanterelle. For those of you who prefer to skip to conclusions of this article, checkout out our example project.

Why Chanterelle?

We’ve already highlighted the need for stronger types in your Ethereum stack in our previous article, and in many ways this project is another iteration on that theme. If you care about security and types, you want to use the most powerful tools available. While web3-js and truffle are certainly expressive enough, we feel like they leave a lot to be desired based on many conversations with developers in the Ethereum space. Here are some of the things we wanted to improve:

When something fails during a deployment or test, it should always be clear to the developer why it fails. If it was because of malformed compiler input, a particular transaction failure, a missing file, or an unresolved promise chain, it should always be clear. Often when truffle fails you find yourself with a cryptic error message and a stack trace from a library you didn’t write, leaving you with little idea as to what happened. With purescript’s superior error-handling, we can easily manage the control flow of a build or deployment to give extremely precise insight into what went wrong. If you’re using purescript-web3, you should use a build tool which is custom made for your stack. purescript-web3 is a production ready library — it is currently used to power the FOAM spatial index beta and other projects in and outside of FOAM. It’s a pain to split time between two different languages for the same project. We end up duplicating functionality in purescript that we wrote in javascript. If we just write code once in a single language, there is a much lower risk of making a mistake. Your testing environment should mimic your deploy environment as much as possible. The typical web-js + truffle stack recommends using terstrpc as an Ethereum client substitute. This can quickly lead to problems with mismatching between the version of solc-js that you’re using vs what testrpc can handle. Why not just run the same geth or parity vm that powers the main net, rinkeby, or another test net? We can offer you just that with a docker image we call cliquebait running a fast geth node configured with proof of authority.

What Does Chanterelle Do?

Chanterelle is a tool used to manage solidity smart contract prototyping, testing, deployments, and migrations. If you are familiar with truffle, the work flow should feel familiar, but we have incorporated our own designs for the project configuration influenced from the haskell ecosystem. The tool can be roughly broken down into three phases:

Compilation: This is done via foreign calls to solcjs using the chanterelle.json configuration file to specify all compiler options and project layout. This includes things like library linking, the shape of the build artifact, optimization flags, and external dependencies like open-zeppelin or other projects distributed via npm or github. The output of this phase is a directory of build artifacts that contain information such as the abi, bytecode, etc., compatible with truffle. purescript code generation: We leverage the purescript-web3-generator to build the purescript library generated by your smart contracts. Unlike web3-js, purescript is not able to generate a contract object on the fly from dynamic abi input. To overcome this, we statically generate all the functionality that the contracts expose and put rigorous types to them. The advantages of doing this were laid out in our initial article. Deployment: All deployment scripts should be written in our deployment DSL in purescript. From the developers perspective this just means that your deployments are allowed to use all the effects you would normally want, such as make web3 calls, read from the filesystem, make network calls, etc. As each contract potentially has its own constructor requiring its own arguments, we standardized a way to represent this configuration so that each argument must be validated before deployment according to whatever validation mechanism you want. At the very least, this means that all the types are known to be correct, but it’s also easy to add your own logic about the values themselves — e.g. integer bounds, string format, etc. Testing: We also provide a utils library for testing which contains some common patterns. Aside from that there isn’t different than writing normal purescript tests. You are free to use whatever testing framework you want, though you may get some insight from looking at the parking-dao example project linked above.

Design Decisions and Motivation

Chanterelle introduces the concept of modules to Solidity projects, a somewhat opinionated approach to Solidity project structure. Derived directly from the world of ML-like languages such as Haskell, OCaml, and PureScript, this battle-tested approach to code management offers a cornucopia of advantages to users of Chanterelle. Here’s what you can expect:

Consistency: Modules enforce a clean, consistent system for organizing Solidity code. While Solidity does allow one to write multiple contracts, interfaces, and libraries in a single source file, such permissive code arrangement tends not to scale as projects grow in scope and complexity. For decades, developers who’ve used contemporary compiled languages such as Java, C#, and Haskell wrote code confidently knowing that it’d be straightforward to find whatever code units they were working against in a consistent place, with their in-language identifier closely resembling where the code is stored on the file system. We believe that Solidity developers deserve to enjoy these benefits as well. Compiler output is organized along the module system as well, allowing one to find their build artifacts exactly where they expect them.

Constructiveness: As the Ethereum ecosystem continues to figure out for itself how best to enable code reuse, we hope that some of the ideas presented here may osmose into the general conversation. Going forward, we can see the concept of modules being used in package management systems to forgo needlessly redeploying contracts and libraries, and making the “hey copy-paste our most recent addresses from this github link!” approach a thing of the past. Indeed, we believe that our approach can actually enable more sophisticated Ethereum package management systems to be developed and permeate the ecosystem.