How to use Chainlink VRF and Chainlink Upkeep while making clones (EIP-1167)?

:1234: Code to reproduce


:computer: Environment

Versions Used: Hardhat: 2.10.1, Solidity 0.8.8

Hey guys,

I am building a dapp which requires users to make clones of the implementation contract (I'm using the OpenZeppelin Clones Library). The implementation contracts makes use of Chainlink VRF to generate random numbers, and also makes use of Chainlink Upkeep to automate parts of the contract.

I would like to know how to set up my initializer function in the implementation contract so that I can use ChainLink VRF with the cloned contracts.

In a regular contract, I would simply set up the VRF Address using the constructor, as shown here:

contract Whoopy is Initializable, VRFConsumerBaseV2, KeeperCompatible {

constructor(
  address vrfCoordinatorV2, 
  bytes32 gasLane, 
  uint64 subscriptionId, 
  uint32 callbackGasLimit 
) VRFConsumerBaseV2(vrfCoordinatorV2) { 
  i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2); 
  i_gasLane = gasLane; 
  i_subscriptionId = subscriptionId; 
  i_callbackGasLimit = callbackGasLimit; 
}

However, since I am going to be making clones, the CloneFactory cannot call the constructor, hence I will have to use an initialize function instead. I have set up the initialize function as recommended by OpenZeppelin here, however I am not able to figure out how to set up the VRFConsumerBaseV2 contract in this case.

I am currently doing it this way and it doesn't seem to be working:

function initialize(
address _creator ) public override initializer VRFConsumerBaseV2(vrfCoordinatorV2) { //VRFConsumerBaseV2.initialize(vrfCoordinatorV2);***** 
i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2); }

NOTE: I have edited the VRFConsumerBaseV2 contract by removing the 'constructor' and replacing it with an initialiser function instead as shown here (as advised in the OpenZeppelin docs):

function initialize(address _vrfCoordinator) public virtual onlyInitializing {
vrfCoordinator = _vrfCoordinator;

}

TL;DR: How would I go about setting up the initializer in my implementation contract so that I can use ChainLink VRF in the cloned contracts?

VRFConsumerBaseV2 has a constructor so if you want to use it as-is you can't change it to an initializer function.

You can still use this contract because the constructor only initializes an immutable variable and immutable variables are compatible with proxies, but your own contract may need an initializer. So in summary you may have a contract that has both a constructor and an initializer function. The constructor would initialize the state for VRFConsumerBase, and the initializer would initialize the state for each clone.

Hey Frangio,
Thanks for the reply!

Just to clear things up, you are suggesting my Implementation Contract should have both a constructor and an initialization function?

Won't the constructor be completely ignored everytime a clone is deployed? I was under the impression that clone contracts only clone the runtime code and not the creation code.

Looking forward to hearing from you.
Thanks again for the response!

Yes indeed. The constructor will be ignored! As long as the only thing you do in the constructor is assign immutable variables it will be ok. Because immutable variable values will be hardcoded in the smart contract code of the implementation contract. So even though the constructor will only run once the value will be shared across all clones.

Awesome! Thanks so much!