How to build a Contract Factory that Creates Contract Clones

Upstate Interactive
Apr 1 · 6 min read
Image for post
Image for post

The following article was written by Upstate Interactive’s Kseniya Lifanova and Doug Crescenzi.

Last year we began working on a DAO for a commercial client comprised of nine smart contracts. We can’t get into the specifics per NDA, but in summary the DAO enables its collective of users and shareholders to operate autonomously via the smart contracts we’ve developed. It’s responsible for processing financial transactions, creating and deploying smart contracts, minting tokens, handling governance, and managing multisignature authentication.

A key feature of the DAO centers upon the need for end users to be able to generate smart contract clones that they themselves own and manage.

We realized that deploying the same contract hundreds if not thousands of times would be expensive, so we decided to implement a contract factory. The contract factory allows users to generate and deploy proxy contracts that point to the same implementation contract. The implementation contract is where all of the logic resides.

Deploying proxy contracts in this manner is more efficient and cost effective than redeploying the same logic contract over and over again. This is because they are only responsible for delegating calls to the implementation contract. In other words, all of the proxy contracts, or clones, have the same functionality as the implementation contract, but the key difference is that each proxy contract has its own state.

Libraries and standards

After implementing this contract factory solution we gave thought to creating a contract factory library or standard. We soon realized our friends at OpenZeppelin had beat us to it and had implemented their own upgradeable, proxy factory base contract. We also came across Peter Murray, Nate Welch, and Joe Messerman’s EIP 1167 standard for Minimal Proxy Contracts. Their standard enables developers to simply and cheaply clone smart contract functionality in an immutable way by specifying a minimal bytecode implementation that delegates all calls to a known, fixed address.

Instead of trying to reinvent the wheel and develop our own contract factory library or propose our own new standard, we thought it would be best to highlight what is already out there and hone in on why contract factories are useful, how they work, and explore a few sample use cases.

The Code

To use the OpenZeppelin ProxyFactory contract, create a new project and install the OpenZeppelin SDK JavaScript Library by running

npm install @openzeppelin/upgrades

This library is typically used by the OpenZeppelin SDK, but you can also use it directly in your project which is the method we’ll be using today.

First, let’s create the implementation contract that you want to clone.

pragma solidity ^0.5.0;import '@openzeppelin/upgrades/contracts/Initializable.sol';import '@openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol';contract Thing is Initializable, Ownable {  string public name;  /**  * @dev Logic contract that proxies point to  * @param _name name  * @param _owner The address of the thing owner  */  function initialize(string memory _name, address _owner) public {    Ownable.initialize(_owner);  name = _name;  }}

You’ll see that instead of using a constructor function, we use an initialize function. This is because cloning a contract with a constructor function will not be 100% precise. A constructor function is part of a contract’s creation code, which is the runtime code (the actual contract code stored on the blockchain, plus the initialization process). When cloning a contract, we only clone the runtime code. We can switch the constructor function with the initialize function which is a regular function that is included in the runtime code. For a super technical breakdown of creation code vs runtime code, check out the article here.

This brings us to another point. Since the initialize function is a regular function that can be called a number of times, we need to add extra functionality to ensure it is only called once (per proxy). Thankfully OpenZeppelin provides an Initializable contract, a base contract we can inherit from which provides the logic to perform that check for us.

We also want to give the user who stands up the contract ownership rights, so we inherit the Ownable contract which contains an initialize function we can use.

Next, let’s create our contract factory:

pragma solidity ^0.5.0;import "./Thing.sol";import '@openzeppelin/upgrades/contracts/upgradeability/ProxyFactory.sol';contract ThingFactory is ProxyFactory {  address public implementationContract;  constructor (address _implementationContract) public {    implementationContract = _implementationContract;  }
function createThing(bytes memory _data) public returns (address){ address proxy = deployMinimal(implementationContract, _data); return proxy; }}

For the Factory contract, we inherit the ProxyFactory contract from OpenZeppelin. The ProxyFactory has several ways you can deploy a proxy: deployMinimal, deploy and deploySigned. deploy includes a salt and deploySigned includes a signature. We are using the deployMinimal function which simply creates a clone of the implementation contract that we deployed previously.

We set the implementation contract address in the constructor function. Then we add the createThing function which calls the deployMinimal function and passes in the implementation address, and the initialize function data in bytes. When a user wants to create a proxy / clone, this function will be called using the arguments that are passed into the initialize function in the implementation code. Let’s test it out:

const ThingFactory = artifacts.require('ThingFactory.sol');const Thing = artifacts.require('Thing.sol');const encodeCall = require('zos-lib/lib/helpers/encodeCall').default;
contract('ThingFactory', function (accounts) { beforeEach(async function () { implementation = await Thing.new(); proxyFactory = await ThingFactory.new(implementation.address); }); it('it should return logicContract', async function () { const address = await proxyFactory.implementationContract.call(); assert.equal(address, implementation.address); });
it('it should createThing ', async function () { initializeData = encodeCall(
'initialize',
['string', 'address'],
['Kseniya', accounts[2]]
);
const transactionReceipt = await proxyFactory.createThing
(
initializeData, {from : accounts[2]}
);
proxyAddress = transactionReceipt.logs[0].args.proxy; const impl = await Thing.at(proxyAddress); const name = await impl.name.call(); const owner = await impl.owner(); assert.equal(name, 'Kseniya'); assert.equal(owner, accounts[2]); });});

Why are Contract Factories Useful

Contract factories are extremely useful because they support “use-cases wherein it is desirable to clone exact contract functionality with a minimum of side effects (e.g. memory slot stomping) and with low gas cost deployment of duplicate proxies.” [1] This is extremely powerful in distributed systems where you want the end user to have access to an implementation contract’s functionality all while still maintaining full control over their own contract’s storage and state.

How do Contract Factories work

Your standard contract factory architecture looks like this:

Image for post
Image for post

The factory contract itself is used to generate identical proxy contract clones that forward transactions to and from the implementation contract which contains the logic. The factory can often also be used to update the implementation contract’s address so that new proxy contracts will have new, upgraded functionality. Furthermore, you can customize factory controls and add admin privileges such as updating the implementation contract, etc.

Use Cases

  • Uniswap, the Ethereum-based protocol designed to facilitate automatic digital asset exchange between ETH and ERC20 tokens, uses a factory contract to create exchange contracts for any ERC20 token that does not already have one. It also functions as a registry of ERC20 tokens that have been added to the system, and the exchange with which they are associated.
  • Low friction wallet solutions like Authereum. Authereum uses a factory to generate user wallets (or proxies) that the end users themselves own and use to access their favorite dapps in any mobile or native app.
  • Deploying multisig wallet clones for end users to receive and store their assets
  • Deploying governance contracts clones for different entities and organizations
  • Deploying registry contract clones that can be used to set and retrieve contract addresses
  • Deploying and managing gambling pools (e.g., NCAA tournament, FIFA World Cup, etc).

Takeaways

Contract factories can be used to empower end users and provide them with ownership and control over their own proxies’ storage and state. Contract factories are also very useful when it comes to generating upgradable proxy contracts. Lastly, contract factories used in conjunction with proxy pattern architectures are quite common. We therefore encourage developers to utilize the EIP 1167 standard and OpenZeppelin’s ProxyFactory base contract.

Resources

[1] EIP 1167: Minimal Proxy Contract

[2] Deep dive into the Minimal Proxy contract

[3] Reason Why You Should Use EIP1167

Interested in learning more about smart contract development?

Upstate Interactive

We're a women-owned business that helps B2B organizations…

Thanks to Kseniya Lifanova

Upstate Interactive

Written by

Advancing Upstate New York through digital — Web. Mobile. Content Marketing. Design.

Upstate Interactive

We're a women-owned business that helps B2B organizations turn great ideas into software.

Upstate Interactive

Written by

Advancing Upstate New York through digital — Web. Mobile. Content Marketing. Design.

Upstate Interactive

We're a women-owned business that helps B2B organizations turn great ideas into software.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store