Создание смарт-контракта с помощью Solidity

Solidity был создан доктором Гэвином Вудом (соавтором этой книги) как язык, специально предназначенный для написания смарт-контрактов с функциями, непосредственно поддерживающими выполнение в децентрализованной среде мирового компьютера Ethereum. Полученные в результате атрибуты являются достаточно общими, поэтому в итоге он стал использоваться для кодирования смарт-контрактов на нескольких других платформах блокчейн. Он был разработан Кристианом Рейтивесснером, а затем также Алексом Берегзасци, Лианой Хусикян, Йоичи Хираи и несколькими бывшими участниками ядра Ethereum. Сейчас Solidity разрабатывается и поддерживается как независимый проект на GitHub.

Основным "продуктом" проекта Solidity является компилятор Solidity, solc, который преобразует программы, написанные на языке Solidity, в байткод EVM. Проект также управляет важным стандартом бинарного интерфейса приложений (ABI) для смарт-контрактов Ethereum, который мы подробно рассмотрим в этой главе. Каждая версия компилятора Solidity соответствует и компилирует определенную версию языка Solidity.

Для начала работы мы загрузим двоичный исполняемый файл компилятора Solidity. Затем мы разработаем и скомпилируем простой контракт, следуя примеру, с которого мы начали в [intro_chapter].

Выбор версии Solidity

Solidity использует модель версионности, называемую семантической версионностью, которая определяет номера версий в виде трех чисел, разделенных точками: MAJOR.MINOR.PATCH. Номер "major" увеличивается для основных и обратно несовместимых изменений, номер "minor" увеличивается по мере добавления обратно совместимых функций между основными выпусками, а номер "patch" увеличивается для обратно совместимых исправлений.

На момент написания статьи Solidity находится на версии 0.6.4. Правила для основной версии 0, которая предназначена для начальной разработки проекта, другие: все может измениться в любой момент. На практике Solidity рассматривает номер "minor" как основную версию, а номер "patch" - как основную версию. Поэтому в версии 0.6.4 6 считается основной версией, а 4 - минорной.

Выход основной версии Solidity 0.5 ожидается в ближайшее время.

Как вы видели в [intro_chapter], ваши программы Solidity могут содержать директиву pragma, которая определяет минимальную и максимальную версии Solidity, с которыми они совместимы, и может быть использована для компиляции вашего контракта.

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

Скачать и установить

Существует несколько способов загрузки и установки Solidity, либо в виде бинарного релиза, либо путем компиляции из исходного кода. Подробные инструкции можно найти в документации по Solidity.

Вот как установить последний бинарный релиз Solidity на операционную систему Ubuntu/Debian, используя менеджер пакетов apt:

$ sudo add-apt-repository ppa:ethereum/ethereum
$ sudo apt update
$ sudo apt install solc

После установки solc проверьте версию, выполнив команду:

$ solc --version
solc, the solidity compiler commandline interface
Version: 0.6.4+commit.1dca32f3.Linux.g++

Существует несколько других способов установки Solidity, в зависимости от вашей операционной системы и требований, включая компиляцию из исходного кода напрямую. Для получения дополнительной информации см. https://github.com/ethereum/solidity.

Среда развития

Для разработки в Solidity вы можете использовать любой текстовый редактор и solc в командной строке. Однако вы можете обнаружить, что некоторые текстовые редакторы, предназначенные для разработки, такие как Emacs, Vim и Atom, предлагают дополнительные возможности, такие как подсветка синтаксиса и макросы, которые облегчают разработку Solidity.

Существуют также веб-среды разработки, такие как Remix IDE и EthFiddle.

Используйте те инструменты, которые делают вас продуктивными. В конечном счете, программы Solidity - это обычные текстовые файлы. Хотя модные редакторы и среды разработки могут облегчить работу, вам не нужно ничего больше, чем простой текстовый редактор, такой как nano (Linux/Unix), TextEdit (macOS) или даже NotePad (Windows). Просто сохраните исходный код вашей программы с расширением .sol, и он будет распознан компилятором Solidity как программа Solidity.

Написание простой программы Solidity

В [intro_chapter] мы написали нашу первую программу на Solidity. Когда мы впервые создали контракт Faucet, мы использовали IDE Remix для компиляции и развертывания контракта. В этом разделе мы вернемся к Faucet, улучшим и приукрасим его.

Наша первая попытка выглядела как Faucet.sol: Контракт Solidity, реализующий кран.

Пример 1. Faucet.sol: Контракт Solidity, реализующий смеситель link:code/Solidity/Faucet.sol[]

Компиляция с помощью компилятора Solidity (solc)

Теперь мы будем использовать компилятор Solidity в командной строке для непосредственной компиляции нашего контракта. Компилятор Solidity solc предлагает множество опций, которые вы можете увидеть, передав аргумент --help.

Мы используем аргументы --bin и --optimize в solc для создания оптимизированного двоичного файла нашего примера контракта:

$ solc --optimize --bin Faucet.sol
======= Faucet.sol:Faucet =======
Binary:
608060405234801561001057600080fd5b5060cc8061001f6000396000f3fe6080604052600436106
01f5760003560e01c80632e1a7d4d14602a576025565b36602557005b600080fd5b34801560355760
0080fd5b50605060048036036020811015604a57600080fd5b50356052565b005b67016345785d8a0
000811115606657600080fd5b604051339082156108fc029083906000818181858888f19350505050
1580156092573d6000803e3d6000fd5b505056fea26469706673582212205cf23994b22f7ba19eee5
6c77b5fb127bceec1276b6f76ca71b5f95330ce598564736f6c63430006040033

Результат, который производит solc, представляет собой шестнадцатеричный двоичный файл, который может быть отправлен в блокчейн Ethereum.

ABI контракта Ethereum

В компьютерном программном обеспечении двоичный интерфейс приложения - это интерфейс между двумя программными модулями; часто между операционной системой и пользовательскими программами. ABI определяет, как структуры данных и функции доступны в машинном коде; это не следует путать с API, который определяет этот доступ в высокоуровневом, часто человекочитаемом формате в виде исходного кода. Таким образом, ABI является основным способом кодирования и декодирования данных в машинный код и из него.

В Ethereum ABI используется для кодирования вызовов контракта для EVM и для считывания данных из транзакций. Цель ABI - определить функции в контракте, которые могут быть вызваны, и описать, как каждая функция будет принимать аргументы и возвращать результат.

ABI контракта задается в виде JSON-массива описаний функций (см. Функции) и событий (см. События). Описание функции - это объект JSON с полями type, name, inputs, outputs, constant и payable. Объект описания события имеет поля type, name, inputs и anonymous.

Мы используем компилятор Solidity командной строки solc для создания ABI для нашего примера контракта Faucet.sol:

$ solc --abi Faucet.sol
======= Faucet.sol:Faucet =======
Contract JSON ABI
[{"inputs":[{"internalType":"uint256","name":"withdraw_amount","type":"uint256"}], \
"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}, \
{"stateMutability":"payable","type":"receive"}]

Как вы можете видеть, компилятор создает массив JSON, описывающий две функции, которые определены в Faucet.sol. Этот JSON может быть использован любым приложением, которое хочет получить доступ к контракту Faucet после его развертывания. Используя ABI, такое приложение, как кошелек или браузер DApp, может создавать транзакции, вызывающие функции Faucet с правильными аргументами и типами аргументов. Например, кошелек может знать, что для вызова функции withdraw ему необходимо предоставить аргумент uint256 с именем withdraw_amount. Кошелек может попросить пользователя предоставить это значение, затем создать транзакцию, которая закодирует его и выполнит функцию withdraw.

Все, что необходимо приложению для взаимодействия с контрактом, - это ABI и адрес, по которому контракт был развернут.

Выбор компилятора Solidity и версии языка

Как мы видели в предыдущем коде, наш контракт Faucet успешно компилируется с Solidity версии 0.6.4. Но что, если бы мы использовали другую версию компилятора Solidity? Язык все еще находится в постоянном движении, и все может измениться неожиданным образом. Наш контракт довольно прост, но что если бы наша программа использовала функцию, которая была добавлена только в версии Solidity 0.6.1, а мы попытались бы скомпилировать ее в версии 0.6.0?

Для решения таких проблем Solidity предлагает директиву компилятора, известную как прагма версии, которая указывает компилятору, что программа ожидает определенную версию компилятора (и языка). Давайте рассмотрим пример:

pragma solidity ^0.6.0;

Компилятор Solidity считывает праграмму версии и выдает ошибку, если версия компилятора несовместима с праграммой версии. В данном случае наша прагма версии говорит, что эта программа может быть скомпилирована компилятором Solidity с минимальной версией 0.6.0. Однако символ ^ говорит, что мы разрешаем компиляцию с любой минорной ревизией выше 0.6.0; например, 0.6.1, но не 0.7.0 (которая является основной, а не минорной ревизией). Директивы Pragma не компилируются в байткод EVM. Они используются компилятором только для проверки совместимости.

Давайте добавим директиву pragma в наш контракт Faucet. Мы назовем новый файл Faucet2.sol, чтобы отслеживать наши изменения по мере выполнения примеров, начиная с Faucet2.sol: Добавление прагмы version к Faucet.

Пример 2. Faucet2.sol: Добавление прагмы версии в Faucet link:code/Solidity/Faucet2.sol[]

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