$ sudo teach IT
Модуль 1 из 7

Смарт-контракт, 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. Разберём его построчно.

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 не значит «секретно наоборот»

Слово public у переменной означает «сгенерируй функцию чтения». А вот private вводит в заблуждение почти всех: значение такой переменной всё равно лежит в открытом хранилище, и прочитать его напрямую из блокчейна может любой желающий. private запрещает читать переменную другим контрактам в коде, людям оно не мешает. Пароль в контракте - это пароль, написанный мелом на заборе. Никогда не храните там секреты.

1.4Remix: где всё это запустить

Самый быстрый способ начать - Remix IDE (remix.ethereum.org): редактор прямо в браузере, которому ничего не нужно устанавливать. Внутри есть учебный блокчейн «Remix VM» с готовыми аккаунтами, на каждом - тестовый эфир. Разворачивать и вызывать контракты можно мгновенно и бесплатно.

  1. Откройте Remix, создайте файл Counter.sol и вставьте код из урока 1.3.
  2. Вкладка Solidity Compiler → выберите версию 0.8.24+ → Compile.
  3. Вкладка Deploy & Run → Environment: Remix VM → Deploy.
  4. Внизу появится развёрнутый контракт. Нажмите increment (это транзакция), затем count (это бесплатное чтение) и убедитесь, что значение выросло.

Почувствовали разницу? increment меняет состояние, поэтому это транзакция с газом. count только читает - мгновенно и бесплатно. Этот водораздел «меняю состояние» против «только читаю» будет сопровождать нас весь курс, так что запомните его прямо сейчас.

- А почему чтение бесплатное, а запись - нет?

- Запись должны повторить все узлы сети и навсегда сохранить у себя, и за эту работу кто-то платит. Чтение ваш узел выполняет сам, у себя, никого больше не беспокоя.

1.5Что мы построим: краудфандинг

Учить синтаксис в вакууме - всё равно что учить правила дорожного движения, ни разу не сев за руль. Поэтому весь курс мы собираем один проект - контракт краудфандинга.

Идея знакома по Kickstarter, только без посредника, который берёт комиссию и решает, кому вернуть деньги: правила сбора средств исполняет сам контракт.

Правила нашей кампании
  • у кампании есть цель (сколько эфира нужно собрать) и дедлайн (до какого момента);
  • любой может сделать вклад, отправив эфир в контракт;
  • если к дедлайну цель достигнута - создатель кампании забирает собранное;
  • если не достигнута - каждый вкладчик может вернуть свои деньги;
  • контракт честно ведёт учёт: кто сколько внёс и сколько собрано всего.

Каждый следующий модуль добавляет к этому контракту ровно те возможности, которые в нём изучаются.

Вот первый шаг - пустой каркас, куда мы будем дописывать поля и функции:

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

contract Crowdfunding {
    // адрес создателя кампании (заполним в модуле 3)
    // цель и дедлайн (модуль 2)
    // учёт вкладов (модуль 4)
    // приём и возврат денег (модуль 5)
}
Вывод модуля

Смарт-контракт - публичная программа, которую нельзя тихо поправить: она сама хранит деньги и исполняется виртуальной машиной EVM на каждом узле сети. За вычисления платят газом, а любое изменение состояния - это транзакция. Каркас краудфандинга на месте, пора наполнять его смыслом.

Дальше · модуль 2

Каркас контракта готов, но о кампании он пока ничего не знает. Как записать цель в 10 эфиров, если дробных чисел в Solidity нет вовсе? В следующем модуле разберём типы данных и три места, где живут переменные, - от этого зависит цена каждой транзакции.