Smart Contract Design Patterns

A Case Study with Real Solidity Code

HeartBank®
HeartBank Studio
5 min readOct 13, 2018

--

Introduction

Smart contracts are powerful constructs, sometimes even more so than their legal counterparts, because they have the capacity to hold immense value, and funds dispersals can occur autonomously according to immutable instructions. Security of these instructions, therefore, is of paramount importance. Design patterns can help immensely in this regard because they have been vetted by the community and battle-tested in production. This article is an addendum to How to Decentralize the Film Industry to explain the design pattern decisions we made in our Solidity code.

For how we took security measures to prevent common vectors of attack, take a look at this related article:

Contract Source Code on Remix:

https://gist.github.com/thonly/dba764a0bc28544f479d81ba174e1511

BoxOffice.sol is our main contract, a factory that creates instances of BoxOfficeMovie.sol per film project. BoxOfficeMovie inherits the ERC20 standard contract from OpenZeppelin and uses its SafeMath library to securely perform arithmetic operations. The HeartBankCoin contract and the Oracle contract and the OracleLibrary and OracleStorage contracts it depends are ancillary while OracleRegistry is for demonstrating the Upgradable Registry pattern.

Restricting Access

In both BoxOffice and BoxOfficeMovie contracts, there are certain functions that we want to restrict to only the admin or filmmakers. The restricting access pattern helps us to achieve this with the following state variables and function modifiers:

Then, we decorate the necessary functions with these modifiers to protect access.

Fail Early and Fail Loud

We take advantage of the require keyword to throw as early as possible whenever certain conditions we want are not met. This pattern makes it glaringly obvious and stops further execution. It is used everywhere in our code, especially inside modifiers, e.g.:

Notice we also use transfer() instead of send() because the latter fails silently while the former throws if unsuccessful.

Circuit Breaker

BoxOffice implements the circuit breaker pattern to give the admin the ability to stop all state-changing functionalities during emergencies and discoveries of critical bugs. We achieve this using the boolean variable emergency, and two modifiers that check for its value:

Then, we decorate all state-changing functions with stopInEmergency except admin functions, while some admin functions are decorated with onlyInEmergency. The admin can toggle the emergency state by calling toggleEmergency():

While in alpha, we could have implemented the auto deprecation design pattern, which expires our contract after a certain amount of time. However, we want more control and flexibility over said time, which the circuit breaker pattern provides.

In a similar vein, we could have implemented the speed bump pattern instead to slow down important actions so that there is time to recover should an attack occur. However, we feel that while in alpha, making the users wait is superfluous when the circuit breaker pattern can suffice. After alpha, however, this pattern is worth considering for actions like our withdrawFund() function!

Mortal

In the likely event that we want to upgrade to a new contract during our alpha, we want to give our admin the ability to destroy the old one to help clear the blockchain of stale code. We implement this pattern using the selfdestruct keyword:

After destruction, the remaining balance is sent to the admin’s address.

Pull over Push Payments

Instead of letting the contract push payments to recipients, a best practice is to make the recipients pull payments from the contract themselves. This pattern protects against Reentrancy and Denial of Service attacks. In our contract, filmmakers can call withdrawFund() to send payments to themselves and others.

We also use this withdrawal pattern to let our admin donate the collected fees to any charity:

State Machine

A film project typically has four main stages: Development, Pre-production, Production, and Post-production. Initially, we want to include these stages and three more using the state machine pattern:

  1. Development
  2. Pre-production
  3. Production
  4. Post-production
  5. Premier
  6. Cinema release
  7. General release

For our alpha, however, we decide to keep things simple with just a minimum viable product. With a MVP, we could launch quickly to get immediate feedback from the users and measure their behaviors. We believe a data-driven product design process trumps opinions and enhances intuitions.

Upgradable Registry

Coming from a traditional web development background, a culture of moving fast and breaking things, we want our contracts to be upgradable from the start. We modularize our contracts as much as possible, separating the logic from the data as a library contract and a storage contract, respectively. However, we learn the hard way that development on the blockchain is something else! Even after many attempts and iterations on Remix, we fail to overcome the block gas limit. For your reference and study, here are two of those attempts:

  1. https://gist.github.com/thonly/8eabacb3970c1b24478d1add994ce060
  2. https://gist.github.com/thonly/1763488d31b9658d2a76afc6533abbe8

Though quite frustrating, the learning is invaluable. In the end, we decide not to separate concerns by logic and storage, but by purpose instead. Our BoxOffice factory contract focuses on creating BoxOfficeMovie instances and accepting and dispersing ether, while the logic and the storage are distributed among the BoxOfficeMovie instances. Though not as upgradable as we would like, on the bright side, they do adhere more to the spirit of immutability that makes blockchain so special.

Because we were unsuccessful with BoxOffice, we made the Oracle contract upgradable instead for the learning by creating a OracleRegistry contract.

Did we miss anything? Let us know!

Reference: https://solidity.readthedocs.io/en/v0.4.24/common-patterns.html

--

--