A simple framework for deploying Ownable contracts

John Forrest
CodexProtocol
Published in
5 min readApr 25, 2018

Originally this was going to be a post that talked specifically about what our deployment story looks like for the Codex Protocol BiddableEscrow smart contract, but upon writing it I realized two things: 1) that post was going to be too long and no one was going to want to read it, 2) parts of our deployment story can be used as a general-purpose way to deploy Ownable contracts.

Building upon the second realization, I decided to break this up into two pieces. The first (this post) will discuss deploying contracts with truffle and a simple framework for deploying Ownable contracts in a secure way, and the second post will describe how we are using this framework at Codex.

Definitions

Before diving in, let me define a few terms that will be used frequently in this post. If you’re an Ethereum developer, you’ve likely already heard of them so feel free to skip ahead.

  • Ownable: Ownable is a smart contract pattern that defines the concept of an owner for the smart contract and modifier called onlyOwner for the purposes of authorization control. There are a lot of different implementations of it floating around, but I’ve found the one by Open Zeppelin to be the most common. Even Augur uses a variation of it.
  • Truffle: From the website, “Truffle is the most popular development framework for Ethereum”. Basically a Swiss-army knife for Ethereum development. It provides tools for local development, compilation, and deployment. Highly recommended.
  • Migrations: Migrations are a Truffle-specific construct designed to make deploying smart contracts easier and more scriptable. They record the completion of each step in your truffle deployment script on the blockchain, so that if there’s an error in step 5 out of 10, you don’t have to run the first 4 steps again, saving you time and money (i.e., no repeat gas spend).

The framework

My goal with this framework was to automate our smart contract deployment as much as possible. For the pieces that aren’t automated, they needed to be low-risk so that if someone makes a mistake (which from experience whenever there’s a human element someone will always make a mistake), it doesn’t effect the rest of the system.

Roles

The framework we propose for deploying Ownable contracts has two roles:

  1. Deployer: The deployer is the Ethereum address that is responsible for actually deploying the smart contract. Out of the two roles, it is the only one that needs to be “hot” (meaning accessible) during deployment. It does not need to hold a balance of Ether outside of deployment, and can even be different addresses for each deployment.
  2. Owner: The owner is the address that will eventually become the owner in the Ownable smart contract. This can be a multi-sig wallet or a DAO, but in most cases it will be a single owner. For single owner accounts, the private key should be stored securely and can even be in cold storage during deployment.

Deployment

From the role definitions you may have already figured out where this is going. At a high level, the deployment story looks like this:

  1. Deploy the smart contract using the deployer address
  2. Perform any post-deployment steps that require the onlyOwner modifier
  3. Call transferOwnership to transfer ownership to the owner address that is in cold storage

That’s it! Like I said, it’s a simple framework. Here’s two questions that immediately came to mind as I was reflecting on these steps.

Q: Why create 2 roles for deployment instead of just having a single address that does everything?

A: For security. By creating and funding new deployer accounts when they’re needed, you can deploy new smart contracts without ever having to take the owner wallet out of cold storage. It’s not catastrophic if someone compromises the deployer address, but if the owner address is ever compromised then it’s game over.

Q: Why can’t the Ownable constructor be updated to accept an owner address at deployment time instead of having to call transferOwnership?

A: This can actually be done for any case that doesn’t require post-deployment steps BUT it does mean modifying the Ownable contract which has its own risk. For any case that does require post-deployment steps (e.g., minting tokens after the token contract is deployed), this can’t be done. If the owner is not the same as msg.sender, then it would be impossible for the deployer to do anything other than deploying the contract itself.

Sample code

The example here will be using a Mintable ERC-20 token to demonstrate the framework. All of the migration code is available below, or directly on the GitHub repo for reference.

These next two blocks of code are both map to the first step in the framework: deploying the smart contracts. Any deployment done through truffle has to deploy the Migrations contract first. This may seem like a waste of gas, but in the long run this may end up saving you gas if there are failures further along in your deployment script. The initial migrations file won’t change, but I’m including it here for completeness.

// 1_initial_migrations.jsconst Migrations = artifacts.require('./Migrations.sol');
module.exports = function (deployer) {
deployer.deploy(Migrations);
};

// 2_deploy_contracts.js
const ExampleToken = artifacts.require('./ExampleToken.sol');
module.exports = function (deployer, network, accounts) {
deployer.deploy(ExampleToken);
};

Now that our smart contract is deployed, we can perform any post-deployment steps that require the onlyOwner modifier. In our case, we call the mint function several times to distribute tokens to a few pre-defined addresses.

// 3_mint_tokens.jsconst SimpleToken = artifacts.require('./SimpleToken.sol');
module.exports = function (deployer, network, accounts) {
switch (network) {
case 'ganache':
mintTokensForGanache(accounts);
break;
default:
throw new Error('No minting function defined for this network');
}
};
async function mintTokensForGanache (accounts) {
const simpleToken = await SimpleToken.deployed();
simpleToken.mint(accounts[5], 1000);
simpleToken.mint(accounts[6], 1000);
simpleToken.mint(accounts[7], 1000);
simpleToken.mint(accounts[8], 1000);
simpleToken.mint(accounts[9], 1000);
console.log('Minting tokens to Ganache accounts 6-10');
}

Finally, after all of our post-deployment steps have completed we can safely transfer ownership to the owner address.

// 4_transfer_ownership.jsconst SimpleToken = artifacts.require('./SimpleToken.sol');
module.exports = async function (deployer, network, accounts) {
let newOwner;
// The owner key should be stored securely in cold storage.
// For demo purposes, we just use another Ganache account
switch (network) {
case 'ganache':
newOwner = accounts[1];
break;
default:
throw new Error('newOwner not defined for this network');
}
console.log('Transferring ownership to: ', newOwner); const simpleToken = await SimpleToken.deployed();
simpleToken.transferOwnership(newOwner);
};

And voila! Your smart contract is deployed, tokens have been minted, and now ownership resides with a secure wallet that wasn’t exposed during deployment.

Closing

Deploying smart contracts isn’t easy. In this article we cover some of the basics, as well as a general purpose framework for deploying any contracts that use the Ownable pattern. There’s tons more to learn so make sure to do your research before you deploy to Mainnet.

Check back later for the next post in the series that will dive into how we use this pattern for our BiddableEscrow smart contract.

Contact Codex Protocol via Telegram or Twitter, and be sure to stay in touch by signing up for our newsletter https://www.codexprotocol.com/.

Written by John Forrest
CTO & Co-founder at Codex Protocol. Previously at MSFT.

--

--

John Forrest
CodexProtocol

CTO & Co-founder at Codex Protocol. Previously at MSFT.