Estimated time: 4 minutes read
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 has the minimal proxy contract pattern been implemented?
Following the best practices of Solidity smart contract programming, it is recommended to use the standardized contracts from OpenZeppelin, particularly their Clone.sol library from ‘Minimal Clones‘ and the base contract Initializable.sol from their ‘Utils‘ package.
The following design rules are proposed:
- 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.
Do you find this Solidity design pattern useful? I encourage you to share your comments.
Do you need to develop smart contracts for your blockchain project? Contact me, I can collaborate with you. Thank you very much!