Redundancy is Needed when Building Safe Smart Contracts

As Ohalo further matures the features of the blockchain-based Data Protection Router, we thought it would be useful to start a blog post series about technical engineering aspects we’ve come across in building distributed systems. Please let us know if you enjoyed this post and feel free to check out our open source contract repository.

Distributiveness is a key principle in Blockchain protocols like Ethereum, and is often touted as the solution for building strong and resilient networks. Most people who works with Ethereum DApps would firmly agree, and yet if we look at the reasons as to why some DApps has failed, or are at risk, or why a failure incurred major losses, it is often because the developers in some way or another put all their eggs in one basket. Some errors that you will regularly see are:

  • Code-centralization - many contracts relying on the same few contracts and libraries.

  • Contracts where key functionality hinges on the capacity of a single account to carry out key tasks.

  • Contracts written in such a way that millions of dollars worth of crypto-currency end up being stored in a single account.

In this article we will be talking about how we can avoid a lack of distributiveness in a certain case, while at the same time keeping code complexity at a minimum. We will be focusing on a specific design-pattern called “owned”, which used to manage a contract through a designated master account, or “owner”. The owner account is normally allowed to perform special managerial tasks, such as on-boarding new users, managing user permissions, or deleting the entire contract itself. Below is an example of how a minimalistic “owned” contract could be (and often is) implemented:

contract Owned {

    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    constructor() public {
        owner = msg.sender;
    }

    // Other code
    // ...

}

When this contract is deployed, the constructor will set the owner to the account that deployed the contract. To limit other accounts from calling certain functions you would just add the onlyOwner modifier to those functions. Here is an example of how this could actually be used:

contract SelfDestructer {

    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    constructor() public {
        owner = msg.sender;
    }

    function destroy() public onlyOwner {
        selfdestruct(owner);
    }

}

This contract can only do one thing and that is to self-destruct, and the onlyOwner guard ensures that no account other then the owner can call the destroy function successfully, meaning that in this case, the account that created the contract is the only account that is able to destroy it.

This example is of course not very useful, but we can easily think of others, like a Token contract where its possible for users to buy and sell tokens, but only the owner can do important things like adding users, banishing them, or issuing new Tokens.

Transfer of ownership

In an owned contract you will sometimes want to allow the ownership to be transferred. One (very common) way of implementing that is as such:

contract OwnedTransferrable {

  address public owner;

  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }

  constructor() public {
    owner = msg.sender;
  }

  function transferOwnership(address newOwner) public onlyOwner {
    require(newOwner != 0);
    owner = newOwner;
  }

}

This allows the owner to transfer ownership over to another account. Note how this is different from the constructor assignment though; the address to the new owner is not assigned from msg.sender but is instead passed in as an argument to the function. This method is a lot more risky, because the input address could potentially be bad, for example if the owner made an error when typing in the address, or if there is some kind of bug in the contract-calling API. There is a simple check to ensure that the new owner address is not 0, but that’s it. Now imagine what would happen if a bad address did sneak in: all functions with the onlyOwner modifier would be impossible to call, which in this case also includes the transfer function itself, resulting in a contract that is locked down forever.

To help avoid things like this, we recently added a contract named SafeOwned to our new open-source contract repository (we will aim to update this repository regularly with updates that we feel are valuable to the Ethereum community). This contract helps warding against the problem of bad input by dividing ownership transfer up into two parts:

  1. The current owner must offer the ownership to a new account.

  2. The new account must claim it.

The way this is implemented is by storing the address of a candidate in a separate field, instead of directly replacing the owner. That first step of the process happens when the owner calls a function called offerOwnership. The transfer itself does not happen until the candidate claims ownership, which he does by calling the claimOwnership function, at which time the owner address is replaced by the candidate address, and the candidate address is cleared. This means that the owner can not pass ownership to a dead account, because that account would be unable to claim it, and the contract will continue to operate as normal because the owner will remain owner until the claim is actually made.

Note that while this protects against some mistakes on the account of the owner it is still not entirely safe; for example, the computer run by the owner could be afflicted by a virus or something else that would hijack their calls and perhaps edit the candidate address, which in turn means they would pass on ownership to a malicious agent. They could also type in the address to a (potentially) malicious account that could claim ownership before the current owner has time to notice their mistake and rescind the offer. One solution to that type of issue would be to add an additional step:

  1. The current owner offers the ownership to another account.

  2. The candidate accepts.

  3. The current owner must accept the accept.

Additional account security

A safe transfer of ownership function will reduce the risk of a contract being locked down, because it helps avoid a situation where nobody is allowed to transact to it; especially when a contract - or system of contracts - is governed by a single account. Additionally, it is of course also important for that account holder to observe normal account security too, like making sure that their keys are backed up, and that nobody else can get access to them.

About Ohalo

Ohalo builds tools to automate data governance. The Data X-Ray scans for sensitive data on a regular basis so that you are always up to date with where your sensitive data is. Simply sign up here. Once you have established a baseline of where your most sensitive data is, you can track where that data is going with the blockchain-based Data Protection Router in order to assist your clients in Data Subject requests like access, rectification, erasure, and breach notification.