В этой статье рассматриваются конструкторы - функция в Solidity, которая запускается только один раз, когда контракт развертывается в сети Ethereum.

Оглавление

Введение

Как определить конструктор

Общедоступные и внутренние конструкторы

Параметры конструкторов и наследование

Платный конструктор

Введение

Конструкторы - это обычное понятие в ООП. Во многих языках программирования при определении классов вы также можете определить волшебный метод, который будет запускаться один раз при создании нового экземпляра объекта.

В случае Solidity код, определенный внутри конструктора, будет запускаться только один раз во время создания и развертывания контракта в сети.

Следует отметить следующее:

байт-код, развернутый в сети, не содержит кода конструктора, поскольку код конструктора выполняется только один раз при развертывании.

OpenZeppelin более подробно объясняет эту идею во второй части своей серии статей« Деконструкция контракта на твердость »:

Код конструктора является частью кода создания, а не частью кода среды выполнения.

Как определить конструктор в Solidity?

Вы определяете конструктор в Solidity с ключевым словом constructor(), за которым следуют круглые скобки. Обратите внимание, что вам не нужно добавлять ключевое словоfunction, поскольку это специальная функция.

contract Example {
    constructor() {
        // code running when contract deployed...
    }
}

NB: До версии Solidity 0.4.22 конструкторы определялись как функции с тем же именем, что и контракт (как в JAVA?). Этот синтаксис устарел и больше не поддерживается с версии 0.5.0.

Конструкторы необязательны

Как при создании класса на другом языке программирования.

Если в контракте не указан конструктор, в контракте будет использоваться пустой конструктор по умолчанию, эквивалентный constructor() {}.

Публичные и внутренние конструкторы

До появления Solidity 0.7.0

До Solidity 0.7.0 конструкторы контрактов в Solidity могли быть определены со следующими двумя видимостью: public (по умолчанию) или internal.

Основное различие между ними простое.

Контракт, определенный с помощью внутреннего конструктора, не может быть развернут.

Поэтому, если в контракте есть internal конструктор, вы вообще не можете развернуть контракт.

  • Ни прямо
  • Ни по другому контракту.

Если вы попытаетесь сделать это, выбрав контракт в Remix и нажав кнопку «Развернуть», IDE вернет вам следующее сообщение об ошибке.

Точно так же контракты с внутренними конструкторами не могут быть созданы непосредственно в других контрактах. Приведенный ниже фрагмент кода не компилируется.

pragma solidity 0.5.0;
 
contract C {
    constructor() internal {}
}
contract D {
    
    function tryDeploying() public { 
        C c = new C();
    }
 
}

Но с выпуском Solidity 0.7.0 и его новыми критическими изменениями internal конструкторы устарели и представляют собой устаревшую историю, а сообщение с картинки выше было удалено.

Начиная с версии Solidity 0.7.0

Если вы пишете контракты с компилятором Solidity выше 0.7.0, вам больше не нужно беспокоиться о конструкторах internal.

Фактически, вам просто нужно 1) удалить ключевое слово internal в конструкторе и 2) определить свой контракт как abstract.

Заключительное примечание о «внутренних конструкторах» (= абстрактных контрактах)

Следуя разнице между видимостью обоих конструкторов, последний вопрос:

если контракт намеревается наследовать, должен ли конструктор быть определен как внутренний или открытый? (= договор определяется как абстрактный или нет?)

Ответ: если контракт не предназначен для непосредственного создания, а скорее унаследован, может быть безопаснее использовать внутренний конструктор (= определить контракт как абстрактный! 😉 ). Это предотвратит создание контракта напрямую.

Внутренний конструктор / абстрактный контракт позволяет дочернему контракту, наследующему от него, либо настроить некоторую логику по умолчанию при развертывании (специфичную для абстрактного контракта). Эта первоначальная логика развертывания может быть динамической путем объявления конструктора с параметрами. Это будет следующая тема нашей статьи.

Параметры конструкторов и наследование

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

Но как насчет того, чтобы контракт B был получен из другого контракта A, у которого есть аргумент конструктора?

Давайте посмотрим на примере.

pragma solidity 0.8.0;
contract Animal {
    
    uint feet;
    bool canSwim;
    
    constructor(uint _feet, bool _canSwim) {
        feet = _feet;
        canSwim = _canSwim;
    }
}
// how to derive from Animal while passing constructors arguments?
contract Lion {
    
}

Если базовый контракт имеет аргументы, производные контракты должны указывать их все. Сделать это можно двумя способами:

Непосредственно в списке наследования

Это полезно, если вы уже знаете значения заранее, и их можно жестко запрограммировать в коде.

contract Lion is Animal(4, true) {
}

Способ более удобен, если аргументы конструктора являются константами и они определяют поведение контракта или описывают его.

Отличный пример - создание стандартного контракта ERC20.

Через производный конструктор, например «модификатор»

Этот способ можно использовать, если аргументы конструктора базового контракта зависят от аргументов производного контракта.

Для иллюстрации давайте добавим еще одну переменную состояния в наш Animal контракт.

contract Animal {
    
    string name;
    uint feet;
    bool canSwim;
    
    constructor(string memory _name, uint _feet, bool _canSwim) {
        name = _name;
        feet = _feet;
        canSwim = _canSwim;
    }
}
contract Lion is Animal {
    
    constructor(string memory _name)
        Animal(_name, 4, true)
    {
        // ...
    }
}

NB: указание аргументов в обоих местах (список наследования + модификатор в производном конструкторе) вызовет ошибку.

NB2: если производный контракт не определяет аргументы для всех конструкторов своих базовых контрактов, он будет абстрактным.

Платный конструктор

Конструкторы могут принимать эфиры. В этом случае их следует указать с ключевым словом «к оплате». В Remix IDE кнопка «развернуть» изменит цвет (показанный красным), если конструктор принимает эфир.

Если вы попытаетесь добавить эфир при развертывании контракта, определенного с помощью конструктора, не подлежащего оплате, он выдаст вам исключение и вернется.

Разница между оплачиваемым и неоплачиваемым конструктором также отражается в коде создания.

Если конструктор не подлежит оплате, код создания содержит 8 низкоуровневых инструкций (в сборке EVM), которые проверяют, были ли отправлены некоторые эфиры в контракт после его развертывания. Если это так, проверка не выполняется и контракт отменяется.

Вы можете увидеть пример здесь: https://blog.openzeppelin.com/deconstructing-a-solidity-contract-part-ii-creation-vs-runtime-6b9d60ecb44c/

Если конструктор определен как подлежащий оплате, эти 8 инструкций EVM удаляются и отсутствуют.

Чего нельзя и не следует делать с конструкторами!

  • Вы не можете получить доступ к функциям извне в конструкторе, потому что функции контракта еще не существуют (поскольку контракт еще не развернут).
  • Конструкторы нельзя объявлять в интерфейсе.