Basic Contracts: Owned & Terminable

I like to keep smart contracts small and simple to help understanding and reviews. A couple contracts I have developed are useful for everyone. I’ll share them in this and later posts.

On the blockchain, nobody knows you’re a dog.

Owned

Probably the most used contract is Owned, also know as Ownable. It’s role is to keep track of a special role called owner. The ownership game is played like this: Whoever created the contract is its first owner. Whoever is the current owner, can decide who becomes the new owner. That’s it. By itself, ownership doesn’t mean anything. It’s up to the smart contract developer to embellish this role with privileges. We will see later, when we allow the owner to terminate the contract. The basic contract looks like this:

contract Owned {
    address public owner;    function Owned() {
        owner = msg.sender;
    }    modifier onlyOwner() {
        assert(msg.sender == owner);
        _;
    }    function transferOwnership(address newOwner) external onlyOwner {
        if (newOwner != address(0)) {
            owner = newOwner;
        }
    }
}

It does the job. But there’s a couple of things we can do to make it better. Let’s start with the newOwner != address(0) check. The goal here is to prevent the contract from being transfered to an incorrect address. But we are only checking for zero, and not the near infinite amount of other invalid addresses. Instead, we can use the approve-accept paradigm:

contract Owned {
    address private ownerCandidate;
        modifier onlyOwnerCandidate() {
        assert(msg.sender == ownerCandidate);
        _;
    }    function transferOwnership(address candidate) external onlyOwner {
        ownerCandidate = candidate;
    }    function acceptOwnership() external onlyOwnerCandidate {
        owner = ownerCandidate;
    }
}

Now the new owner needs to accept the ownership before she receives it. If we accidentally transfered ownership to an invalid address, there is no way this address can accept the ownership. When this happens, we retain ownership and can redo the transfer. Problem solved!

We can do one better: If we accidentally send it to a wrong but existing address, and this address accepts ownership before we realize this and redo our transfer, then our contract still ends up in wrong hands! To solve this we necessarily need some additional authentication mechanism for the new owner. We can use a one time key:

contract Owned {
    address private ownerCandidate;
    bytes32 private ownerCandidateKeyHash;

    modifier onlyOwnerCandidate(bytes32 key) {
        assert(msg.sender == ownerCandidate);
        assert(sha3(key) == ownerCandidateKeyHash);
        _;
    }    function transferOwnership(address candidate, bytes32 keyHash)
        public
        onlyOwner
    {
        ownerCandidate = candidate;
        ownerCandidateKeyHash = keyHash;
    }    function acceptOwnership(bytes32 key)
        external
        onlyOwnerCandidate(key)
    {
        owner = ownerCandidate;
    }
}

Now, to transfer ownership you generate a random 256 bit key and hash it using web3.sha3. Then we call transferOwnership with the new owner’s address and the hash of the key. We contact the new owner (through any available secure channel), and hand her the key. The new owner calls acceptOwnership providing the key. The contract verifies the key, and if correct, accepts the new owner.

Note: It is important to use a unique key that has not been used before anywhere. Once used, the key is permanently and publicly visible on the blockchain. This is why I used 256 bit random keys. If I were to use a password, someone could brute-force it.

Do we really need this security device? If we accidentally make a typo in the new owners address, the chances of this addresses accepting ownership are pretty low. With probability bordering on certainty, the typo-address won’t exist and won’t do anything. But let’s assume we are transferring the ownership of a very valuable contract, and evil people are on the prey. In that scenario, this technique introduces an additional authentication factor.

Which one of the two solutions is best depends on your security/usability trade-off. The later adds an additional authentication factor, but it also requires communicating a key over a secure channel, which is huge penalty in the usability field.

Terminable

Contract Owned often goes hand in with contract Terminable, also known as Mortal. This contract adds a function only owner can trigger, the function to permanently terminate the contract. Any Ether owned by the contract get send to the owner. It looks like this:

contract Terminable is Owned {
    function terminate() external onlyOwner {
        selfdestruct(owner);
    }
}

This terminates the contract and sends any Ether held by the contract back to the owner. Easy-peasy. But! Ether is no longer the only currency in Ethereum. There are many tokens out there, and smart contracts can own tokens too. If you use the above terminate function on a contract that has tokens, those tokens are permanently lost!

Tokens, as a reminder, tend to have the following interface:

contract IERC20Basic {
    function balanceOf(address who) constant public returns (uint);
    function transfer(address to, uint value) public returns (bool ok);
    event Transfer(address from, address to, uint value);
}

We can use these functions to send the tokens to owner on terminate:

contract Terminable is Owned {
    function terminate(IERC20Basic[] tokens) external onlyOwner {
        // Transfer tokens to owner
        for(uint i = 0; i < tokens.length; i++) {
            uint256 balance = tokens[i].balanceOf(this);
            tokens[i].transfer(owner, balance);
        }

        // Transfer Ether to owner and terminate contract
        selfdestruct(owner);
    }
}

Now, we need to be really careful here! The innocent looking tokens[i].balanceOf(…) and tokens[i].transfer(…) are function calls to external contracts. This means they can fail or do nasty things like re-entering your contract!

Let’s consider failure first. Solidity bubbles the failures up the call stack, so when one of the calls fails, it will fail the entire terminate operation. This default behavior is not bad, we can inspect which token contract made us fail, exclude the misbehaving token and try again. You will not recover that token. This is fine; who wants to own a broken/malicious token anyway?

The terminate call can also fail when it runs out of gas. This puts an upper limit to the number of tokens types that can be recovered: at some point we reach the block gas limit. If your contract is going to own many different kinds of tokens, (for example an exchange) you need to come up with something better.

This leaves reentrancy. It is not a problem for our Terminable contract: onlyOwner prevents other contracts from calling the function. But this contract is not meant to be used on its own. When you inherit from Terminable and add external functions, remember that they can be indirectly called from the terminate function!

In general, don’t try to reclaim tokens you don’t trust. (This is a special case of the general lemma: don’t interact with contracts you don’t trust)

Cool! How do I use this?

The Terminable contract is contributed to the Zeppelin project under the name TokenDestructable. For the Ownable there already exists a similar contract called Claimable.

Remco Bloemen
Math & Engineering
https://2π.com