plinth — init

50x50
8 min readJul 17, 2024

--

This is a story of but one small feature built in the whole universe of Open Source Software — why is then the author takes more time to write about it than to be done with it and move on?

Study hard what interests you the most in the most undisciplined, irreverent and original manner possible.

Breaking Free from the 50/50 Rule: Unshackling Dev Potential

For too long, I’ve been held back by the daunting prospect of splitting my time 50/50 between tedious interview prep and online profile management, versus actually building and contributing to meaningful projects. It’s a vicious cycle that’s stifled my growth as a dev.

But recently, some key developments have cracked open my mind to the possibility of investing serious time and energy into the Cardano codebase. It’s like a weight’s been lifted, and I can finally focus on what really matters — writing code that makes a difference.

The 50/50 rule be damned; it’s time to flip the script and prioritize the work that truly fulfills me.

Hacking My Way into the Plutus Working Group

I recently threw my hat into the ring to become the secretary of the Plutus Working Group, but life had other plans. No worries, though — I’ve got a passion for hacking, and I wasn’t about to let a little setback hold me back.

I dove headfirst into the issue tracker, searching for the perfect challenge to sink my teeth into. And then, I stumbled upon it: issue #6210, a testing-related problem that spoke directly to my heart. I mean, who doesn’t love testing, right?

I commented on the issue, expressing my interest in tackling it, and before I knew it, I got a response. It turned out that the awesome folks at MLabs had already made some progress on the issue for Plutarch, and now it was my turn to “upstream” their work.

For the uninitiated, “upstreaming” might sound like a fancy term for “stealing code,” but trust me, it’s all about collaboration and giving credit where credit is due. My mission, should I choose to accept it, is to take the existing code, adapt it to work seamlessly with Plinth, and make it shine like the top of the Chrysler Building.

Uncovering the Secrets of PlutusLedgerApi

As I dive deeper into issue #6210, I’m faced with a crucial question: what types does PlutusLedgerApi export? The issue description is clear: I need to implement QuickCheck Arbitrary instances for each of these types. But, where do I find this information?

My first instinct is to scour the Plutus codebase, searching for clues that will reveal the secrets of PlutusLedgerApi. I fire up my trusty code editor, and start digging through the files.

But first things first! As I prepare to tackle issue #6210, I know that I need to lay the groundwork before diving into the code. It’s time to get my environment set up, and get familiar with the conventions and guidelines that the Plutus maintainers use.

I need to build Plutus from source. I fire up my terminal, and clone the Plutus repository. Next, I navigate into the repository, and follow the instructions in the `README.md` file to build the project.
As the build process chugs along, I take a moment to appreciate the complexity of the Plutus codebase. This is going to be a fun challenge!

Reading the Contributing Guide

With the build complete, I turn my attention to the `CONTRIBUTING.md` file. This is where the maintainers share their wisdom on how to contribute to the project. I take a close read, absorbing the guidelines on coding style, testing, and code review.

As I read through the contributing guide, I start to get a feel for the project’s structure and conventions. I take note of the coding style, the use of Haskell language extensions, and the testing frameworks employed.

I also take a peek at the `plutus-ledger-api` package, where I’ll be working on implementing those QuickCheck `Arbitrary` instances. I get a sense of the existing code, and start to think about how I can contribute to it.

Read of `plutus-ledger-api.cabal`

As I dive deeper into the Plutus codebase, I realize that the plutus-ledger-api.cabal file holds the key to understanding the structure and dependencies of the project. This file is the heart of the Cabal package description, and it’s where I’ll find the answers to my questions.

Which types does PlutusLedgerApi export?

The exposed-modules section of the library stanza in plutus-ledger-api.cabal lists all the modules that are exported by the PlutusLedgerApi library. This is where I’ll find the types that I need to implement QuickCheck Arbitrary instances for.

As I scan the list, I see a plethora of modules, each with its own set of types. I notice that the modules are organized into different versions (V1, V2, V3), each with its own set of types. This is going to be a fun challenge!

By looking at the modules listed here, I can see which types are exported by PlutusLedgerApi. For example, the PlutusLedgerApi.V1 module exports types such as Address, Bytes, Contexts, and more.

Note that these are not individual types, but rather modules that export various types and functions. To find the specific types exported by each module, you would need to look at the source code for each module.

V2 Orphan QuickCheck Instances — what does it mean?!

Thanks to effectfull and kozross who linked the issue #684 of plutarch-plutus, and my eyes land on a cryptic message in the README: “A collection of orphan QuickCheck instances for Plutus ledger API types.” What does it mean?

I decide to embark on a journey to uncover the secrets of this enigmatic phrase. I start by breaking it down into smaller pieces.

In Haskell, an “orphan instance” refers to a typeclass instance that is defined in a different module than the typeclass itself. This means that the instance is not defined in the same module where the typeclass is defined, but rather in a separate module.

QuickCheck is a popular testing library in Haskell that allows us to generate random values for our types. A QuickCheck instance is a way of telling QuickCheck how to generate random values for a particular type.

The Plutus ledger API is a set of types and functions that provide an interface to the Plutus ledger. These types are used to represent various concepts in the Plutus ecosystem, such as addresses, transactions, and scripts.

As I put the pieces together, I realize that the phrase “A collection of orphan QuickCheck instances for Plutus ledger API types” means that someone has created a collection of QuickCheck instances for various Plutus ledger API types, but these instances are defined in a separate module, rather than in the same module as the typeclass.

This is useful because it allows us to use QuickCheck to generate random values for these types, even if the types themselves don’t have built-in support for QuickCheck. It’s like having a special plugin that enables QuickCheck support for these types.

The Quest for Origins: Unraveling the Mystery of FeeValue and SizedByteString

As I dive into the PR, I find myself in the midst of a utility module, QuickCheck/Utils.hs. This module is a treasure trove of helper functions and types, designed to make our lives easier when working with QuickCheck and Plutus ledger API types.

The first thing that catches my eye is the SizedByteString type. This type is a newtype wrapper around ByteString, but with a twist. It’s designed to ensure that the underlying ByteString has a fixed length, specified by the type parameter n. This is achieved through the use of a read-only pattern, SizedByteString, and an accessor function, unSizedByteString. But how do I know if this type exists in the Plutus repo?

I fire up my terminal and reach for the trusty grep command. I want to search for any occurrences of SizedByteString in the Plutus repo. Here's the command I use:

grep -r "SizedByteString" .

The -r flag tells grep to search recursively through the current directory and its subdirectories. The . at the end specifies the current directory as the search target.

But, to my surprise, grep comes up empty. It seems that SizedByteString is a custom type, defined specifically for plutarch-plutus. No worries, I'll just have to dig deeper to understand its purpose and how it fits into the larger ecosystem.

As I venture deeper into the PR, I stumble upon another intriguing module — Orphans.hs. This module is packed with goodies that’ll help us write better property tests for Plutus.

FeeValue is a newtype wrapper around Value, with a single constructor FeeValue. But what's the deal with fees in the Plutus Ledger API? Are they a special case that requires custom handling? The getFeeValue function suggests that we can extract the underlying Value from a FeeValue, but what's the use case for this?

After a fruitful trip to the fruit market, I returned to my terminal with a fresh perspective. The connections between FeeValue and SizedByteString began to crystallize in my mind. Both are wrappers, but around different types — Value and ByteString, respectively. The question remained: where do these types originate from?

About WrapperTypes — these newtypes are all about giving the type system a gentle nudge, saying “hey, I’ve got some extra info to share”. They’re like a whispered secret, a hint that says “treat me differently, bro”. And that’s it. They’re trivial, lightweight, and designed to convey a specific meaning to the type checker. So, when you see a WrapperType, don’t expect fireworks — just a subtle cue to the type system to behave in a certain way.

I dove back into the plutus-ledger-api.cabal file, scouring the exposed modules stanza for clues. And there it was: PlutusLedgerApi.V1.Value. This was my next lead. I had to uncover the definition of Value to understand the context of FeeValue.

From the code, it’s clear that FeeValue doesn’t add any new functionality or complexity to Value. Instead, it seems to be a specialized version of Value that’s specifically designed to represent fees in the Plutus ledger.

The Arbitrary instance for FeeValue provides a hint about its purpose. The arbitrary function generates a FeeValue by creating a Value with a single entry for Ada (the cryptocurrency used in the Plutus ledger). The shrink function, on the other hand, shrinks the FeeValue by reducing the Ada amount.

As I dug deeper into the code, I stumbled upon a revelation that made my eyes widen with excitement. It turns out that the Arbitrary instance for Value already exists in testlib. I mean, why reinvent the wheel, right? The existing implementation generates Value instances with a clever combination of multiSplit0, uniqueNames, and listsToValue. And the shrink function? It’s a beautiful piece of code that coerces the shrink function for lists of tuples into a Value-friendly format. I felt a mix of emotions — relief that I didn’t have to write it myself, and a sense of accomplishment for uncovering this hidden gem.

Ugh, the sweet taste of ignorance. I was so caught up in my own investigation, I forgot to pay attention to the comments made in the Issue:

We have a generator for Value in plutus-ledger-api/testlib/PlutusLedgerApi/Test/V1/Value.hs.

It’s a classic case of “avoid success at all costs”, right? Lesson learned: whatever information you are given, you will miss some of it — facepalm thyself and carry on!

What’s Next?

Stay tuned for the next installment of this hacking adventure!

--

--