A few months ago, we announced the first release of our pure-Ligo version of the Checker library. This library can be used to create synthetic tokens, such as stablecoins, which can be minted in a permissionless manner by depositing collateral. Checker offers a number of features: it supports various types of collaterals, automatic and decentralized rate adaptation, a built-in CFMM with rewards for liquidity providers, protection against oracle manipulation, and a batched auction mechanism for liquidations.
However, perhaps because of this complexity, it has not received the attention and usage it deserves. Marigold hopes to help new financial projects on Tezos by offering a more developer-friendly version of the library.
What are stablecoins?
In decentralized finance (DeFi), stablecoins are tokens used to represent low-volatility assets pegged (indexed) on real-world ones, such as currencies. By far, the most represented of these assets is the USD, but other currencies, such as the euro, or non-currency assets, such as gold, can be defined as well. In DeFi, stablecoins are, for instance, used by traders to close a trade (e.g., by taking their gains) without having to leave the blockchain and go back to a centralized exchange. They are also useful to represent and transfer value on-chain, but as of now they have not found real adoption for day-to-day usage.
There are many famous and infamous examples of stablecoins, which use various mechanisms to maintain the peg — or fail to do so. To keep things simple, let’s distinguish between fiat-backed stablecoins and crypto-backed stablecoins. The first category is deployed by companies that receive real, off-chain assets (such as USD) which they keep as collateral, and mint (or emit) a corresponding amount of tokens on-chain (such as USDT or USDC). In contrast, crypto-backed stablecoins are operated by on-chain protocols, which let users deposit cryptocurrencies as collateral and mint a quantity of coins.
What matters in both cases is how the stablecoins keep their peg to the asset they’re tracking. As their market value fluctuates, it is necessary to either mint new stablecoins and sell them to lower their value or repurchase them to restore their value and prevent a crash. When a fiat-backed stablecoin is cheaper than it should be, it’s easy to buy it at a discount (say $0.99), redeem it back to the company that minted it against collateral (say $1), and pocket the difference. Things are more challenging with crypto-backed stablecoins, as the value of cryptocurrencies is itself fluctuating and even tends to go down as well when large stablecoins lose their peg: even if you redeemed $1 worth of crypto, who is to say that it is still going to be worth as much when you sell it on an exchange? For this reason, crypto-backed stablecoins are over-collateralized: to mint $1 worth of stablecoin, a user needs to provide much more collateral, usually twice as much. That way, even if the collateral and the stablecoin both lose value, there is still an incentive to buy the latter to redeem it against the former, which could still be worth significantly more than $1.
To wrap things up, let’s say that you wanted to implement a USD stablecoin on Tezos, backed by XTZ coins. The bare minimum your protocol would need is the following:
- a way of letting users deposit collateral (XTZ coins) in individual vaults;
- a system to know the worth of the collateral contained in these vaults relative to USD, called an oracle;
- an operation to mint new stablecoins from overcollateralized vaults;
- an operation to redeem stablecoins against collateral for vaults that became undercollateralized, which is called liquidation.
There are many oracle solutions on Tezos which can be plugged into other smart contracts. Checker provides the rest of these operations and much more.
Checker, a generic “robocoin” implementation
Checker is not a new project: it was started in 2020, involving several Tezos organizations. It favours genericity and abstraction, allowing for tracking any off-chain asset as long as you can provide an oracle. For this reason, the official documentation keeps a rather abstract vocabulary, speaking, for instance, of kit for the minted asset and tok for the collateral.
Once the oracle is set up, any user can create a vault (Checker calls them burrows) and start minting kits. But what happens if these users massively sell their tokens, and the kit depegs? This is where Checker key differentiator kicks in: the system comes with its own CFMM, which allows it to track the current market value of kits on-chain. If kits are oversold, Checker will progressively adapt and start increasing a parameter called drift, which is akin to raising borrowing interest, and making it progressively harder to mint and sell kits. If nothing changes, burrows can become undercollateralized and liquidatable, motivating users to buy back kits in order to participate to the liquidation — thus restoring the price.
Being able to adapt its interest rate automatically is what makes Checker a “robocoin”: instead of doing it through arbitrary and centralized decisions, or through the votes of a DAO, Checker follows the current market. This is especially important for crypto-assets, where a surge in volatility can destabilize the market. In a bull run, for instance, most users will feel confident that they can mint a USD coin and buy the collateral. To mitigate this, it is not uncommon for DAOs controlling the stablecoins to vote for an increase of the rates, several times in a row. Conversely, when the stablecoin is overbought, it is necessary to lower these rates to encourage users to mint new coins. Here again, Checker automatically adapts to the market and can even reach negative rates!
The rate evolves in a fully predictable manner, which means that users can know in advance when they will become under-collateralized. Despite this, liquidations can occur when users fail to redeem kits or provide more collateral. Here again, Checker provides a sophisticated mechanism of batched liquidation auctions to buy back this collateral in kits.
Cleaning up the project
The original Checker was written in the OCaml language and transpiled in Cameligo through a series of scripts. This allowed leveraging various OCaml testing tools such as Alcotest or Qcheck, which in turn helped in trusting the Checker codebase. However, the compilation process was almost as complicated as the code itself: some Ligo files were generated from others, several scripting languages were involved (among which Python, Ruby and even sed, responsible for transpiling the code), and all of this was relying on several heavy Docker images. This process was very opaque and far from ideal for external developers who wanted to use Checker!
Unfortunately, the public version of Checker was not maintained anymore. We thus had to port it to the latest version of Ligo. We also took this opportunity to add new features, such as on-chain views. Most importantly, we decided to simplify the build process: by using the generated Cameligo code as a new foundation for the project, we can remove the dependency on OCaml.
However, Checker uses scripts for more than just building. Being a very large contract, its code does not fit in a single origination call! It thus uses the so-called lambda pattern to store the code of its various entrypoints in a bigmap, and load them when they are called. This is not something that the Ligo compiler supports out of the box, so keeping a few scripts for this can still be handy.
To make it as simple as possible, we opted for a single Python library which allows both to compile and originate the smart contracts. This library is based on the excellent PyTezos library, maintained by Baking Bad. We also use PyTezos to inspect the contracts after origination, taking advantage of its awesome introspection tools.
The choice of Python presents another huge advantage: we can propose tutorials in the form of Jupyter notebooks, which are interactive, web-based notebooks where the user can write Python code blocks and directly observe their result. We think it is a very good way of learning more about Tezos smart contracts interactively, and plan to continue writing tutorials using Jupyter and PyTezos. Try them online!
How it impacts Ligo
Being one of the largest open-source codebases in Ligo, Checker is a good opportunity of testing some of the compiler’s limitations. For instance, when migrating the code to the named module style (using #import in place of #include), we found that the compilation time increased dramatically. This observation revealed an aliasing bug in the module system. With the fix, Checker’s total build time has been reduced by a factor of 5. Sending feedback to the Ligo developers is always really important, and they were always really quick to help us and answer our requests!
While migrating the code base to a recent version of Ligo has been a challenge, the real difficulty comes with migrating the tests. Security is particularly important for complex DeFi protocols such as Checker, especially considering that the standard version is supposed to be immutable. Being a statically-typed language, Ligo takes security very seriously and makes various checks at compilation time (e.g., that the resulting Michelson code can be deployed, or that the metadata has the right type signature for on-chain usage). However, those static checks are not enough, and while Ligo testing framework is one of the most important features of the language, we found it challenging to port some of the original tests. Again, this feedback helps the Ligo team deciding what features are important, and what they should improve.
This is also a good opportunity for us to use our Breathalyzer framework. Breathalyzer (whose name is inspired by its OCaml cousin, Alcotest) helps writing test suites, and makes it easier to adopt a test-driven development methodology in Ligo. Using it on a big project such as Checker inspired us to add several features over the months. We just released version 1.4 of Breathalyzer, which adds functions to deploy and test contracts with views and to control time in tests, as well as a few quality-of-life functions.
While we already encourage DeFi amateurs to use the notebooks to discover Checker, there are still lots of things we want to improve in the library. First, we plan to extend the PyTezos objects to add Checker-specific information and make inspecting a live robocoin system easier. This would help writing scripts either for maintenance or for advanced users of the Tezos blockchain. Of course, the notebooks will be the perfect place to showcase such features.
Moreover, our primary goal is to make Checker as easy to trust as possible, and the best way to help build this trust is to test it vigorously. Unit tests should, of course, cover the entire code base. But, as Checker is a complex, time-dependent system, we also aim to add realistic integration tests to the project.
Checker is a versatile library that can be integrated into many projects. For instance, you could use it to build a Forex-like DEX, a crypto derivatives exchange, or synthetic commodities assets. If you want to learn more about Checker and use it for your next project on Tezos, contact us!