Смарт — контракты- Chainlink VRF / @openzeppelin/трюфель-совместимость с обновлениями

#random #numbers #solidity #truffle #chainlink

Вопрос:

Прямо сейчас я превращаю базовый смарт-контракт в обновляемый смарт-контракт, используя @openzeppelin/truffle-обновления

Поэтому я выполнил все необходимые шаги из документов, но остается одна проблема:

Обновление трюфеля требует, чтобы я заменил конструктор инициализатором, что меня устраивает, но не для смарт-контрактов, импортированных в мой собственный смарт-контракт, пример:

 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;
    }

    ...
 

И поэтому трюфель жалуется:

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

Поскольку это сторонний пакет, я не могу его заменить 🙂

Существуют ли какие-либо архитектурные хитрости/конфигурации?

Я просмотрел почти все документы по chainlink/трюфелю, но не нашел решения этой проблемы.

Спасибо!

ОБНОВЛЕНИЕ 1:

Прежде всего, я изменил контракт VRFConsumerBase следующим образом: (я также удалил комментарии, чтобы сделать его коротким..)

 // 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

Наконец, я обновил свой контракт, чтобы реализовать инициализируемый и новый контракт VRFConsumerBaseUpgradable:

 // 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
    }

    ...
    
}
 

Я протестировал начальную миграцию, а также обновление с помощью трюфеля, и оба они сработали, так что я думаю, что это нормально, и я оставляю это для будущих исследователей..

А ты как думаешь? Должен ли я создать запрос на слияние для VRFConsumerBaseUpgradable?

Комментарии:

1. Отличный вопрос. Ответ может быть немного техническим. Вы могли бы вызвать свой конструктор и просто иметь пробелы для этих двух переменных, а затем ваш инициализатор фактически установил их. Есть ли способ переопределить трюфель, глядя на конструктор? Если нет, мы могли бы добавить это в плагин.

2. Спасибо вам за ваш ответ @PatrickCollins. К сожалению, трюфель требует, чтобы я полностью удалил конструктор… Я также не могу переопределить логику трюфельного ядра, так как от нее зависит еще немного логики. Что вы имеете в виду, говоря, что могли бы добавить это в плагин ?

3. Я говорил, что вы можете обновить плагин обновления, чтобы разрешить модификаторы. Еще одна вещь, которую вы могли бы сделать (это может быть проще), — это использовать свою собственную пользовательскую базу vrfconsumerbase. При этом у вас просто есть функция инициализатора вместо конструктора. Ты знаешь, как это сделать? Просто скопируйте код базы vrfconsumerbase в свой код и измените его

4. Хорошо, спасибо за обновление. Я буду работать над базой кода и обновлю вопрос новой версией моего смарт-контракта / VRFConsumerBase / Initializable

5. @PatrickCollins я обновил исходный код и создал новый VRFConsumerBaseUpgradable.. что вы думаете об этом подходе? В основном разработчики теперь могут обновлять свои смарт-контракты при использовании chainlink.