The Supreme Importance of Specifications as Illustrated in Cryptocurrencies
Tags:
May 25th, 2022

Originally Published on June 24th, 2021

One of the core issues of the blockchain is that its immutable -- that code will stay there forever.

That simple fact means you need to spend time on making sure your code works and doesn't

$TITAN & $IRON: The Background

The inspiration for this article come from the now infamous $TITAN equality bug. To make a very long story short, $IRON, a dollar stablecoin, uses a sister-crypto-as-collateral called $TITAN in its minting process. This $TITAN currency went to zero and now a bug in $IRON is preventing people from redeeming their tokens for US dollars.

Image of the bug in question in TITAN

Link to the source code in question. If you're curious, you can read more about it here.

A fix cannot be deployed easily owing to how cryptocurrencies work. Code is immutable on the blockchain and once deployed cannot be changed. This "off-by-one" error -- it should've been a greater-than-or-equal rather than a greater-than -- literally cannot be corrected.

Why did this happen? Can we prevent this again?

The $IRON - $TITAN fiasco is the result of on-the-fly coding that should've been by-the-spec coding.

On-the-fly coding is coding as you think until you get what "looks" right.  It's best exemplified by hackathons. You have an idea that you want to MVP. You build until you get it out the door. This gives you a tangible thing you can show to people.

By-the-spec coding is done according to a thought out, pre-worked technical spec. You type up a coding spec ahead of time, make it real by writing appropriate tests to it, then code until you pass those tests. [0] The resulting code is much more thoroughly checked as you've dictated exactly what you'll build before you've built in via the tests and spec. It's also much slower to build as you need a very thorough idea of what you're building before you begin.

It's appropriate to use on-the-fly coding when you don't have a clear idea of what you're building and want to discover it as you go along. Designers & front-end devs, including for GUI and web, often code on-the-fly for this reason. [1] It's also why hackathons and some startups use it: they're trying to figure out how something is going to look and discover it along the way. People will have some idea before they start but not a fleshed out technical spec yet.

Technical Specs come into play when you have a defined problem and solution. Think of traffic lights or ATMs: people know how they work and what they should do. There's no discovery in programing such a device, their functionality is set in stone. For this reason, its more important to know where things can go wrong (e.g. null cases) and that the software functions exactly as is expected. That means having an exhaustively thought out technical spec before programming. [2]

Cryptocurrencies absolutely-and-without-a-doubt fall into this latter category.

You do not build programs that rely on money without knowing exactly what you are building. Thousands to millions to, sometimes, billions of dollars get locked up inside cryptocurrency projects. Any mistake you make will be locked forever on the blockchain. It is paramountto think through all the bad things that could happen. [3]

Testing also lets you be at peace knowing your code behaves as expected. This means you can refactor to either improve readability or performance. The latter is particularly important because performance in ethereum smart contracts can be tricky and counter-intuitive.

Writing a spec can mean many things to many people. Joel Spoelsky has a great write-up on this. But they all have common features:

  1. Who is your end user?
  2. How will they use the product?
  3. What will your product do? What won't it do?

Testing is a way to make these points tangible. You write an explicit pass-or-fail code test that states exactly what you expect and under what conditions. Then you write the code that makes this happen.

Cryptocurrenies like ethereum have their own way of writing technical specs calls EIPs or Ethereum Improvement Proposals. ERC20 is one of the most popular of these proposals. It's technical spec is quite exhaustive, detailing each function involved and what it should and should not do. That includes what falls on the smart contract versus the end client. You can read about it here. More on this later.

One way to guarantee that your smart contract behaves correctly is through TDD, or Test Driven Development. TDD is controversial in many technical circles. But it is very apt in the world of crypto where iterative development is borderline impossible.

Know that TDD is meant loosely here. Many practicioners have specific rules about how to do TDD. What's meant here is more of a spiritual direction.

  1. Write a technical spec (see above)
  2. Program out tests that many this technical spec concrete and tangible
  3. Code the smart contract that makes these tests pass

How do I write a spec for Cryptocurrencies?

We can use the ERC20 as a "gold standard" for a technical spec in cryptocurrency.

First is think what your tech product or application should do. Why does it need to exist? Who is going to be the user? ERC20 answers these questions in their beginning section:

## Simple Summary
A standard interface for tokens.
<span class="hljs-comment">

## Abstract
The following standard allows for the implementation of a standard API
for tokens within smart contracts.
This standard provides basic functionality to transfer tokens, as well
as allow tokens to be approved so they can be spent by another
on-chain third party.
<span class="hljs-comment">

## Motivation

...

We know what's being proposed (a standard interface for tokens) and
what it will do (transfer tokens from wallet to wallet on
decentralized exchanges).

Next, we need to think through the necesary methods that will accomplish this task. What public and external functions are needed? what will they do? These will be written in exhausting detail but kept in plain english -- no code yet. Write out the assumptions behind each method and what each should and should not do. Always "know thy end user" and how it will be called and used. Think through edge cases and state assumption up front in the spec.

In projects, it helps to write an interface once you have these methods described. Below is an interface for ERC20 written in solidity by me based on the document spec. Some EIPs, Like ERC721, are written as an interface with the method descriptions included as comments.

pragma solidity =0.8.0;
// note that I added separating lines to differentiate no wallet, one
// wallet, and multiple wallet functions -- more on that in part 2
interface IERC20 {
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">name</span><span class="hljs-params">()</span> <span class="hljs-title">external</span> <span class="hljs-title">view</span> <span class="hljs-title">returns</span> <span class="hljs-params">(string memory)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">symbol</span><span class="hljs-params">()</span> <span class="hljs-title">external</span> <span class="hljs-title">view</span> <span class="hljs-title">returns</span> <span class="hljs-params">(string memory)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">decimals</span><span class="hljs-params">()</span> <span class="hljs-title">external</span> <span class="hljs-title">view</span> <span class="hljs-title">returns</span> <span class="hljs-params">(uint8)</span></span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">balanceOf</span><span class="hljs-params">(address _owner)</span> <span class="hljs-title">external</span> <span class="hljs-title">view</span> <span class="hljs-title">returns</span> <span class="hljs-params">(uint256 balance)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">totalSupply</span><span class="hljs-params">()</span> <span class="hljs-title">external</span> <span class="hljs-title">view</span> <span class="hljs-title">returns</span> <span class="hljs-params">(uint256)</span></span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">approve</span><span class="hljs-params">(address _spender, uint256 _value)</span> <span class="hljs-title">external</span> <span class="hljs-title">returns</span> <span class="hljs-params">(bool success)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">allowance</span><span class="hljs-params">(address _owner, address _spender)</span> <span class="hljs-title">external</span> <span class="hljs-title">view</span> <span class="hljs-title">returns</span> <span class="hljs-params">(uint256)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span><span class="hljs-params">(address _to, uint256 _value)</span> <span class="hljs-title">external</span> <span class="hljs-title">returns</span> <span class="hljs-params">(bool success)</span></span>;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transferFrom</span><span class="hljs-params">(address _from, address _to, uint256 _value)</span> <span class="hljs-title">external</span> <span class="hljs-title">returns</span> <span class="hljs-params">(bool success)</span></span>;

Next we want to think about how the user will use these functions. Going back to the technical spec, we have a (mostly) plain english description of each function. These will get transformed into tests.

See more about that in the next part

Footnotes

[0]: When and how to write tests is a whole separate debate that goes back to Test-Driven-Development (TDD) vs Behavior-Driven-Development (BDD). I find a lot of that discussion has been hijacked by consultants and isn't entirely relevant. For instance, strictly speaking TDD involves writing one test at a time and then going back while the technical spec discussed here is implied to have been fully fleshed out by the time coding is started. More on this is discussed in the piece.

[1]: Though many designers will have "soft tests" about whether a page view, GUI interface, or product does what people expect. Asking questions like "Can I send email from this view? How do I do that?" when building an email client and then testing this via release cycles (e.g. lean product dev) or doing product focus groups (e.g. old-school development). My point is that this isn't necesarily cut-and-dry here.

[2]: Somewhat covering my ass here, people have used technical specs and things-that-are-technical-specs-but-called-otherwise for startups and in startup-like environments. Amazon's write-ups would fit this definition for meand I'm sure many startups have applied it to their operations.

[3]: To be sure, you can deploy in such a way that you use a pointer contract that calls functions from other contracts which can shift. This way you could deploy a version 2, 3, etc. down the line. This makes the code not quite as immutable as I stated. But mistakes that trigger because of poorly written smart contracts will have permanent issues and are worth noting.