Смарт-контракт, EVM и первая строка кода
Прежде чем писать на Solidity, разберёмся, куда попадёт этот код и почему к нему такие строгие требования. Спойлер: поправить его вечером, как сайт, не получится. А в конце модуля развернём первый контракт - заготовку нашего краудфандинга.
- что такое смарт-контракт и чем он отличается от обычной программы;
- как устроена виртуальная машина Ethereum (EVM), аккаунты, транзакции и газ;
- из чего состоит минимальный файл на Solidity и как запустить его в Remix.
1.1Что такое смарт-контракт
Смарт-контракт - это программа, которая живёт по адресу в блокчейне. У неё есть своё хранилище (память, которая переживает любые перезапуски), свой баланс в криптовалюте и набор функций, которые может вызвать кто угодно - достаточно отправить транзакцию. А после развёртывания код обычно неизменен. Нашли баг в пятницу вечером? Хотфикса не будет.
Отсюда три свойства, которые определяют всё дальнейшее:
- Публичность. Байт-код и всё хранилище контракта видны всем. «Приватных» переменных в смысле секретности не существует - есть только ограничения на запись.
- Необратимость. Выполненную транзакцию нельзя отменить. Ошибка в логике - это не баг в бэклоге, а, возможно, потерянные деньги пользователей.
- Работа с деньгами напрямую. Контракт сам хранит и переводит средства. Поэтому почти каждая ошибка в нём - это ошибка безопасности.
- Подождите, то есть если я ошибся, это навсегда?
- Почти всегда да. Есть приёмы, которые позволяют обновлять логику (о них - в продвинутом курсе), но по умолчанию развёрнутый код живёт вечно. Поэтому поговорку «семь раз отмерь» здесь приходится соблюдать буквально.
Смарт-контракт похож на торговый автомат. Правила написаны заранее и одинаковы для всех: бросил монеты, нажал кнопку - получил банку. Продавец-посредник не нужен, исключений «по дружбе» автомат не делает и исполняет ровно то, что в нём зашито. А если в механизме есть изъян - скажем, он выдаёт две банки за одну монету, - об этом очень быстро узнают все. В блокчейне такие «две банки» обычно стоят миллионы.
1.2EVM: кто исполняет код
Solidity компилируется не в машинный код процессора, а в байт-код виртуальной машины Ethereum (EVM - Ethereum Virtual Machine). EVM - это стековая машина, одинаковая на всех узлах сети. Каждый узел, проверяя блок, прогоняет те же инструкции и обязан получить тот же результат, иначе сеть не договорится о состоянии.
Из этого следует важное ограничение: в EVM нет случайности, нет «настоящих» часов и нет интернета. Представьте программу, которой запретили узнать время и бросить кубик, - это она. Всё должно быть детерминированным: то, что в обычном языке берётся из системы (текущее время, случайное число, запрос к API), в контракте либо недоступно, либо имеет особый смысл (урок 4.4).
| Понятие | Что это |
|---|---|
| Аккаунт-пользователь (EOA) | Управляется приватным ключом. Только он может инициировать транзакцию и подписать её. |
| Аккаунт-контракт | Управляется своим кодом. Сам транзакцию не начинает - лишь отвечает на вызовы. |
| Транзакция | Подписанное сообщение от EOA: кому, сколько эфира, с какими данными (какую функцию вызвать). |
| Газ (gas) | Единица стоимости вычислений. Каждая инструкция EVM стоит газа; пользователь платит за газ эфиром. |
| Хранилище (storage) | Постоянная память контракта. Запись в неё - самая дорогая по газу операция. |
У каждой транзакции есть лимит газа. Если выполнение его превысит, транзакция откатывается целиком, как будто её не было, - только заплаченный газ не возвращается. Обидно, но честно. Зато бесконечный цикл в контракте не «повесит» сеть: он просто упрётся в лимит и отменится. Экономия газа - вечная забота разработчика; ей посвящён целый модуль продвинутого курса.
1.3Анатомия минимального контракта
Вот самый маленький осмысленный контракт на Solidity. Разберём его построчно.
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; contract Counter { uint256 public count; // переменная состояния, лежит в storage function increment() public { // функция, меняющая состояние count += 1; } }
| Строка | Зачем |
|---|---|
// SPDX-License-Identifier |
Указание лицензии в комментарии. Компилятор предупреждает, если его нет. MIT или UNLICENSED - частый выбор. |
pragma solidity ^0.8.24; |
Какой версией компилятора собирать. ^0.8.24 значит «0.8.24 и выше, но не 0.9». Фиксируйте версию - поведение между мажорными версиями меняется. |
contract Counter { } |
Объявление контракта. Похоже на класс в обычных языках: есть поля и методы. |
uint256 public count; |
Переменная состояния: хранится в блокчейне между вызовами. Модификатор public автоматически создаёт функцию для её чтения. |
function increment() public |
Функция, доступная для вызова извне. Меняет состояние, поэтому её вызов - это транзакция, которая стоит газа. |
Слово public у переменной означает «сгенерируй функцию чтения». А вот private вводит в заблуждение почти всех: значение такой переменной всё равно лежит в открытом хранилище, и прочитать его напрямую из блокчейна может любой желающий. private запрещает читать переменную другим контрактам в коде, людям оно не мешает. Пароль в контракте - это пароль, написанный мелом на заборе. Никогда не храните там секреты.
1.4Remix: где всё это запустить
Самый быстрый способ начать - Remix IDE (remix.ethereum.org): редактор прямо в браузере, которому ничего не нужно устанавливать. Внутри есть учебный блокчейн «Remix VM» с готовыми аккаунтами, на каждом - тестовый эфир. Разворачивать и вызывать контракты можно мгновенно и бесплатно.
- Откройте Remix, создайте файл
Counter.solи вставьте код из урока 1.3. - Вкладка Solidity Compiler → выберите версию 0.8.24+ → Compile.
- Вкладка Deploy & Run → Environment: Remix VM → Deploy.
- Внизу появится развёрнутый контракт. Нажмите
increment(это транзакция), затемcount(это бесплатное чтение) и убедитесь, что значение выросло.
Почувствовали разницу? increment меняет состояние, поэтому это транзакция с газом. count только читает - мгновенно и бесплатно. Этот водораздел «меняю состояние» против «только читаю» будет сопровождать нас весь курс, так что запомните его прямо сейчас.
- А почему чтение бесплатное, а запись - нет?
- Запись должны повторить все узлы сети и навсегда сохранить у себя, и за эту работу кто-то платит. Чтение ваш узел выполняет сам, у себя, никого больше не беспокоя.
1.5Что мы построим: краудфандинг
Учить синтаксис в вакууме - всё равно что учить правила дорожного движения, ни разу не сев за руль. Поэтому весь курс мы собираем один проект - контракт краудфандинга.
Идея знакома по Kickstarter, только без посредника, который берёт комиссию и решает, кому вернуть деньги: правила сбора средств исполняет сам контракт.
- у кампании есть цель (сколько эфира нужно собрать) и дедлайн (до какого момента);
- любой может сделать вклад, отправив эфир в контракт;
- если к дедлайну цель достигнута - создатель кампании забирает собранное;
- если не достигнута - каждый вкладчик может вернуть свои деньги;
- контракт честно ведёт учёт: кто сколько внёс и сколько собрано всего.
Каждый следующий модуль добавляет к этому контракту ровно те возможности, которые в нём изучаются.
Вот первый шаг - пустой каркас, куда мы будем дописывать поля и функции:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.24; contract Crowdfunding { // адрес создателя кампании (заполним в модуле 3) // цель и дедлайн (модуль 2) // учёт вкладов (модуль 4) // приём и возврат денег (модуль 5) }
Смарт-контракт - публичная программа, которую нельзя тихо поправить: она сама хранит деньги и исполняется виртуальной машиной EVM на каждом узле сети. За вычисления платят газом, а любое изменение состояния - это транзакция. Каркас краудфандинга на месте, пора наполнять его смыслом.
Каркас контракта готов, но о кампании он пока ничего не знает. Как записать цель в 10 эфиров, если дробных чисел в Solidity нет вовсе? В следующем модуле разберём типы данных и три места, где живут переменные, - от этого зависит цена каждой транзакции.