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 {
}
pragma solidity 0.5.0;
contract C {
constructor() internal {}
}contract D {
function tryDeploying() public {
C c = new C();
}
}
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)
{
// ...
}
}
contract Lion is Animal(4, true) {
}
Вы не можете вызывать внешние функции внутри конструктора, потому что функции контракта ещё не существуют (контракт ещё не развернут). Конструкторы не могут быть объявлены в интерфейсе.
Чего нельзя и не стоит делать с конструкторами!
Если конструктор неплатёжеспособный, код создания содержит 8 низкоуровневых инструкций (в ассемблере EVM), которые проверяют, был ли отправлен эфир на контракт после его развертывания. Если эфир был отправлен, проверка не проходит, и контракт откатывается.
Если при развертывании контракта с неплатёжеспособным конструктором попытаться отправить эфир, возникнет исключение, и операция откатится (revert). Различие между платёжеспособным и неплатёжеспособным конструктором отражается и в коде создания контракта.
Конструкторы могут принимать эфиры. В этом случае они должны быть помечены ключевым словом payable. В среде Remix кнопка Deploy изменит цвет (будет красной), если конструктор принимает эфир.
Платёжеспособный (payable) конструктор
Примечание: указание аргументов одновременно в двух местах (в списке наследования и в конструкторе производного контракта) приведёт к ошибке. Примечание 2: если производный контракт не укажет аргументы для всех конструкторов своих базовых контрактов, он будет считаться абстрактным.
Этот способ используется, если аргументы конструктора базового контракта зависят от аргументов конструктора производного контракта. Для иллюстрации давайте добавим ещё одну переменную состояния в наш контракт Animal.
Через конструктор производного контракта, как «модификатор»
Если базовый контракт имеет аргументы, производные контракты должны указать все эти аргументы. Это можно сделать двумя способами:
Примечание: До версии 0.4.22 в Solidity конструкторы определялись как функции с тем же именем, что и у контракта (похоже на Java?). Этот синтаксис был устаревшим и больше не допускается начиная с версии 0.5.0.
В Solidity конструктор определяется с помощью ключевого слова constructor(), за которым следуют круглые скобки. Обратите внимание, что использовать ключевое слово function не требуется, так как это специальная функция.
Код конструктора является частью кода создания (creation code), а не частью кода выполнения (runtime code).
В случае с Solidity код, определённый внутри конструктора, будет выполнен только один раз — при создании и развертывании контракта в сети.
Важный момент, который стоит упомянуть: Байткод, развернутый в сети, не содержит кода конструктора, поскольку код конструктора выполняется только один раз — при развертывании контракта.
Этот способ удобнее, если аргументы конструктора являются константами и определяют поведение контракта или описывают его. Отличным примером является создание стандартного контракта ERC20.
Это полезно, если значения известны заранее и могут быть захардкожены в коде.
Напрямую в списке наследования
Ранее мы уже упоминали, что конструктор может принимать аргументы, определяясь с параметрами, как и функции. Но что если контракт B наследуется от другого контракта A, у которого есть аргумент конструктора? Давайте рассмотрим это на примере.
Ответ: если контракт не предназначен для непосредственного создания, а должен лишь наследоваться, безопаснее использовать internal конструктор (= определить контракт как абстрактный! 😉). Это предотвратит прямое создание контракта.
- Internal конструктор / абстрактный контракт позволяет дочернему контракту, который от него наследуется, задать некоторую логику по умолчанию при развертывании (специфичную для абстрактного контракта). Эта логика развертывания может быть динамической, если объявить конструктор с параметрами. Именно этой теме будет посвящена следующая часть нашей статьи.
Продолжая тему различий в видимости конструкторов, возникает вопрос: Если контракт предназначен для наследования, должен ли конструктор быть определён как internal или public? (= контракт должен быть абстрактным или нет?)
Заключительное замечание по «internal конструкторам» (= абстрактным контрактам)
Если вы пишете контракты с компилятором Solidity версии выше 0.7.0, вам больше не нужно беспокоиться о конструкторах с internal. На самом деле, вам нужно всего лишь:
- убрать ключевое слово internal из конструктора, и
- определить ваш контракт как абстрактный (abstract).
Параметры конструкторов и наследование
Начиная с Solidity 0.7.0
Аналогично, контракты с конструкторами internal не могут быть созданы напрямую внутри других контрактов. Приведённый ниже фрагмент кода не скомпилируется.
До версии Solidity 0.7.0 конструкторы контрактов могли иметь одну из двух модификаторов видимости: public (по умолчанию) или internal. Основное различие между ними простое: Контракт с конструктором, определённым как internal, не может быть развернут. То есть, если конструктор у контракта имеет видимость internal, вы не сможете развернуть такой контракт вообще:
- Ни напрямую,
- Ни через другой контракт.
До версии Solidity 0.7.0
Аналогично созданию класса в других языках программирования. Если в контракте не указан конструктор, будет использоваться конструктор по умолчанию — пустой, эквивалентный constructor() {}.
Конструкторы являются необязательными
Конструкторы — это распространённое понятие в объектно-ориентированном программировании (ООП). Во многих языках программирования при определении классов можно также определить «магический» метод, который будет выполняться один раз — в момент создания нового экземпляра объекта.
Как определить конструктор в Solidity?
Введение
Эта статья посвящена конструкторам — функциям в Solidity, которые выполняются только один раз, при развертывании контракта в сети Ethereum.
contract Example { constructor() {
// code running when contract deployed...
}}