What is the minimal proxy contract?

The minimal proxy contract is a smart contract design pattern that allows for efficient deployment (low gas consumption) of a contract as a clone of another previously deployed contract, known as the implementation contract. As a trade-off, executing functions in cloned contracts consumes slightly more gas because it involves calls to “delegatecall” in the implementation contract. Another limitation is the inability to upgrade cloned contracts.

It is a useful pattern when it is necessary to deploy the same version of a contract repeatedly on the blockchain, ensuring that the clones maintain the code of the implementation contract immutable, where all the logic is developed. The state, on the other hand, is stored independently in each clone.

It is a stable standard of Ethereum, EIP-1167.

In this in-depth article on the Minimal Proxy contract, all the technique surrounding this design pattern is detailed from the perspective of code execution in the EVM.

How is the minimal proxy contract pattern used in the decentralized application?

The decentralized application manages the relationship between the promoter and the collaborator through a smart contract that defines the milestones of the task and the assigned rewards.

An implementation contract has been developed with all the necessary functions to manage the relationship. Each time a new agreement is reached between a promoter and a collaborator, a cloned contract is deployed on the blockchain to handle the new task.

How has the minimal proxy contract pattern been implemented?

Following the best practices of Solidity smart contract programming, standardized contracts from OpenZeppelin have been used, particularly their Clone.sol library from ‘Minimal Clones‘ and the base contract Initializable.sol from their ‘Utils‘ package.

The following design rules are followed:

  • The implementation contract derives from the ‘Initializable.sol’ contract and incorporates a constructor that calls the ‘_disableInitializers’ function to prevent it from being reinitialized by calling any functions with the ‘initializer’ modifier. In that situation, the ‘InvalidInitialization()’ error would be emitted.
  • The implementation contract develops an ‘initialize’ function to initialize the clones, since these, being ‘proxy’ contracts, do not call any constructor. The function is qualified by the ‘initializer’ modifier to prevent duplicate calls (initialize the clone a second time). In that situation, the ‘InvalidInitialization()’ error would be emitted.
  • A factory contract is developed to create the clones by calling the ‘clone’ method of the ‘Clones’ library. After deploying the clone, its ‘initialize’ method is called. This call will execute a ‘delegatecall’ on the implementation contract.

An important aspect to consider during the initialization of the clones is that immutable state variables in the implementation contract are directly inherited in the clones. The rest of the variables must be initialized independently, ideally in the call to the ‘initialize’ function.

Let’s see an example of implementing the minimal proxy contract in Solidity code.

First, the implementation contract:

import "@openzeppelin/contracts/proxy/utils/Initializable.sol";

contract ImplementationContract is Initializable {

	//Immutable variables are inherited by cloned contracts
	uint256 internal immutable _operationFee;

	//Will be initialized at cloned contract initialization
	address public collaborator;

	constructor(uint256 operationFee)  {
        /* 
		Prevents deployed contract implementation to be reinitialized calling 
		any "initializer" function.       
        See OpenZeppelin proxy contracts documentation of Clones and Initializable 
		classes. 
		*/
        _disableInitializers();		
        _operationFee = operationFee;
    }
	
	function initialize(address collaborator_) public initializer {        
        
        collaborator = collaborator_;     
        
    }    
}

Now the factory contract to create and initialize the clones:

import "@openzeppelin/contracts/proxy/Clones.sol";
import "./ImplementationContract.sol";

contract FactoryContract {

	//Address of deployed implementation contract
	address public implementationContract;
	
	constructor(address implementationContract_) {
		implementationContract = implementationContract_;
	}

	function createClonedContract(address collaborator) public {

		//Creates a cloned contract
		address clonedContract = Clones.clone(implementationContract); 
		
		//Initializes cloned contract
		ImplementationContract(clonedContract).initialize(collaborator);
	}
}

Conclusion: Efficient and Reliable Contract Deployment

The minimal proxy contract pattern is very useful when it comes to deploying multiple instances of a contract efficiently (with reduced gas consumption) and reliably (with immutable code) from a known implementation.

The immutability of the contracts created as clones from the implementation contract is particularly notable (they are not upgradable). This increases transparency and ensures the execution of the developed functions.

It is always possible to deploy a new implementation contract to modify the business logic developed in the new cloned contracts.


Have you used the minimal proxy contract pattern in any DApp? Are you familiar with other similar design patterns in Solidity? I encourage you to share your experiences.