5

Right now i am transforming a basic smart contract into a upgradeable smart contract using @openzeppelin/truffle-upgrades

So i followed all required steps from the docs but one issue remains:

Truffle-Upgrades requires me to replace the constructor with an initializer which is fine for me, but not for the smart contracts imported into my own smart contract, sample:

pragma solidity 0.6.6;

import "@chainlink/contracts/src/v0.6/VRFConsumerBase.sol";

contract Sample is VRFConsumerBase {
    
    address private owner;
    
    bytes32 internal keyHash;
    uint256 internal fee;
    
    constructor(address _owner)
        VRFConsumerBase(
            0xa555fC018435bef5A13C6c6870a9d4C11DEC329C, // VRF Coordinator
            0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06  // LINK Token
        ) public
    {
        keyHash = 0xcaf3c3727e033261d383b315559476f48034c13b18f8cafed4d871abe5049186;
        fee = 0.1 * 10 ** 18; // 0.1 LINK (Varies by network)
        
        owner = _owner;
    }

    ...

And therefore truffle complains:

../@chainlink/contracts/src/v0.6/VRFConsumerBase.sol:182: Contract `VRFConsumerBase` has a constructor
Define an initializer instead

As it is a third party package i can not replace it :)

Are there any architectural tricks/configurations?

I went through pretty much all docs on chainlink/truffle but did not find a solution for this issue.

Thanks!

UPDATE 1:

First of all, i modified the VRFConsumerBase contract to: (i also removed the comments to keep it short..)

// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import "@chainlink/contracts/src/v0.6/vendor/SafeMathChainlink.sol";

import "@chainlink/contracts/src/v0.6/interfaces/LinkTokenInterface.sol";

import "@chainlink/contracts/src/v0.6/VRFRequestIDBase.sol";

abstract contract VRFConsumerBaseUpgradable is VRFRequestIDBase {

  using SafeMathChainlink for uint256;

  function fulfillRandomness(bytes32 requestId, uint256 randomness)
    internal virtual;

  function requestRandomness(bytes32 _keyHash, uint256 _fee, uint256 _seed)
    internal returns (bytes32 requestId)
  {
    LINK.transferAndCall(vrfCoordinator, _fee, abi.encode(_keyHash, _seed));
    
    uint256 vRFSeed  = makeVRFInputSeed(_keyHash, _seed, address(this), nonces[_keyHash]);
    
    nonces[_keyHash] = nonces[_keyHash].add(1);
    return makeRequestId(_keyHash, vRFSeed);
  }

  // removed immutable keyword <--
  LinkTokenInterface internal LINK;
  // removed immutable keyword <--
  address private vrfCoordinator;

  mapping(bytes32 /* keyHash */ => uint256 /* nonce */) private nonces;

  // replaced constructor with initializer <--
  function initialize(address _vrfCoordinator, address _link) public {
    vrfCoordinator = _vrfCoordinator;
    LINK = LinkTokenInterface(_link);
  }

  function rawFulfillRandomness(bytes32 requestId, uint256 randomness) external {
    require(msg.sender == vrfCoordinator, "Only VRFCoordinator can fulfill");
    fulfillRandomness(requestId, randomness);
  }

}

What did i do:

  • I replaced the constructor with an initializer
  • I removed the immutable keyword from the state variables

Next, i used the Initializable contract from @openzeppelin/contracts-upgradeable in my file system to prevent the smart contract executing the initializer more than once:

// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

abstract contract Initializable {

    bool private _initialized;

    bool private _initializing;

    modifier initializer() {
        require(_initializing || !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }
}

Important:

I did not import the Initializable contract via the import statement in solidity.

Instead i copied the source code manually and set the compiler to 0.6.12 because @openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol is running on 0.8.x

Finally, i updated my contract to implement the Initializable and the new VRFConsumerBaseUpgradable contract:

// SPDX-License-Identifier: MIT
pragma solidity 0.6.12;

import "./Initializable.sol";
import "./VRFConsumerBaseUpgradable.sol";

contract Sample is Initializable, VRFConsumerBaseUpgradable {

    bytes32 internal keyHash;
    uint256 internal fee;
    
    address private owner;
    
    function initialize(address _owner)
        public
        initializer
    {
        VRFConsumerBaseUpgradable.initialize(
            0xa555fC018435bef5A13C6c6870a9d4C11DEC329C, // VRF Coordinator
            0x84b9B910527Ad5C03A9Ca831909E21e236EA7b06  // LINK Token
        );

        keyHash = 0xcaf3c3727e033261d383b315559476f48034c13b18f8cafed4d871abe5049186;
        fee = 0.1 * 10 ** 18; 
        
        owner = _owner;
    }

    function getRandomNumber(uint256 userProvidedSeed) public returns (bytes32 requestId) {
        require(LINK.balanceOf(address(this)) >= fee, "Not enough LINK - fill contract with faucet");
        return requestRandomness(keyHash, fee, userProvidedSeed);
    }

    function fulfillRandomness(bytes32 requestId, uint256 randomness) internal override {
        // logic
    }

    ...
    
}

I tested an initial migration as well as an upgrade via truffle and it both worked so i think this is fine and i am leaving this for future researchers..

What do you think? Should i create a merge request for VRFConsumerBaseUpgradable?

  • 1
    Great question. The answer might be a little technical. You could have your constructor be called and just have blanks for those two variables, and then have your initializer actually set them. Is there a way to override truffle looking at the constructor? If not, we could add that to the plugin. – Patrick Collins Jun 05 '21 at 22:29
  • Thank you for for your answer @PatrickCollins. Unfortunately truffle requires me to remove the constructor completely... I also can not override truffle core logic as there is some more logic depending on it. What do you mean by saying *you could add that to the plugin* ? – therealbrudi Jun 07 '21 at 09:37
  • I was saying you could upgrade the upgrades plug-in to allow modifiers. The other thing you could do (this might be easier) is use your own custom vrfconsumerbase. With this, you just have an initializer function instead of a constructor. Do you know how to do that? Just copy the code of the vrfconsumerbase into your code and modify it – Patrick Collins Jun 08 '21 at 12:45
  • Alright, thanks for the update. I will work on the codebase and update the question with the new version of my smart contract / VRFConsumerBase / Initializable – therealbrudi Jun 08 '21 at 13:01
  • @PatrickCollins i updated the source code and created a new VRFConsumerBaseUpgradable.. what do you think about this approach? Basically developers can now upgrade their smart contracts when using chainlink. – therealbrudi Jun 10 '21 at 12:08
  • I think this is the correct approach to take. Sorry on not getting back here. Is this something that you'd want to introduce to the chainlink repo? Or answer here below? – Patrick Collins Sep 21 '21 at 11:14
  • I have added this file here - https://gist.github.com/abhi3700/a3030c64b432c793c3899aee4ffd8ad0 – abhi3700 Nov 18 '21 at 19:27
  • How to use the subscription id and all the different parameters that are required by the VRFCoordinator interface? Could you provide an example of a working solution for fulfillRandomness and getRandomNumber? I really don't get it since I can't see how to link a consumer to the new VRFConsumerBaseUpgradable contract. Thanks – bgcode Aug 02 '22 at 03:29

0 Answers0