Что такое смарт-контракты и как они работают

Рассказываем о смарт-контрактах Tezos: на каком языке программирования их пишут и как их исполняет виртуальная машина.

В уроке почти нет информации о синтаксисе: на этом этапе важнее понять, как работает виртуальная машина блокчейна.

Что такое смарт-контракты

Смарт-контракт (Smart contract) — набор функций и данных, который хранится в блокчейне. Смарт-контракты выполняют разные задачи — от продажи токенов до управления децентрализованными организациями. Например:

  • вестинг-контракты отправляют пользователям токены в указанное время. Ранние инвесторы Tezos получают средства из контракта KT1KCYaULopm8i2CBbGF5EHXeXLUr4R6r2dA;
  • оракулы опрашивают источники данных и возвращают усредненные цены активов или другую информацию. Так работает оракул Harbringer, который получает цены криптовалют с торговых площадок;
  • алгоритмические стейблкоины выпускают токены и меняют стоимость их выпуска в зависимости от курса нативного токена. Смарт-контракт Kolibri корректирует цену выпуска kUSD в зависимости от цены tez.

Смарт-контракты исполняет виртуальная машина (Virtual Machine или VM). Она использует вычислительную мощность блокчейна: смарт-контракты исполняют все узлы сети, но только самый быстрый записывает результат в блок.

Частые вызовы смарт-контрактов могут парализовать блокчейн. Чтобы этого избежать, разработчики протоколов ограничивают максимальный размер смарт-контрактов по объему кода и размеру комиссий.

Как работают виртуальные машины блокчейнов

Виртуальные машины «понимают» низкоуровневые языки программирования — байт-код. Они интерпретируют такой код быстрее, чем команды на языках вроде JavaScript или Python. Это можно показать на примере одной фразы, записанной разными словами:

  • низкоуровневый язык — 2 + 2 = 4;
  • высокоуровневый язык №1 — два плюс два равно четыре;
  • высокоуровневый язык №3 — Если Сложить два И два, Получим четыре
  • высокоуровневый язык №2 — в результате суммирования двойки и двойки получается четыре;

При чтении мы автоматически переводим последние строчки в самый удобный для понимания вариант — 2 + 2 = 4. В программировании так же: компилятор переводит высокоуровневый язык в байт-код.

Виртуальная машина Tezos работает с байт-кодом Michelson. Это низкоуровневый язык программирования с прямым доступом к стеку — структуре данных с быстрым доступом к информации. Код на Michelson выглядит так:

parameter unit ;
storage unit ;
code { CAR ;
      PUSH int 3 ;
      PUSH int 3 ;
      IFCMPEQ { DROP } { DROP } ;
      UNIT;
      NIL operation ;
      PAIR }
//Функция if. Операторы PUSH дважды добавляют число 3 в стек. Оператор IFCMPEQ сравнивает два первых элемента в стеке, а затем исполняет команды { DROP } и выводит результат операции.

Опытные блокчейн-разработчики чаще всего пишут смарт-контракты на Michelson. Новичкам лучше использовать высокоуровневые языки программирования с набором библиотек и человекочитаемым синтаксисом.

Языки програмирования смарт-контрактов на Tezos: SmartPy, LIGO и Lorentz

Участники экосистемы Tezos разработали несколько высокоуровневых языков программирования. Самые популярные из них:

  • SmartPy — объектно-ориентированный язык на основе Python. Поддерживает онлайн-компилятор SmartPy.io, в котором можно разрабатывать, тестировать и публиковать смарт-контракты.
  • LIGO — императивный язык программирования с простой системой типов и онлайн-компилятором ide.ligolang.org. Существует несколько диалектов c синтаксисом распространенных языков: PascaLIGO (Pascal), CameLIGO (OCaml), ReasonLIGO (ReasonML) и jsLIGO (JavaScript).
  • Lorentz — встраиваемый предметно-ориентированный язык на базе Haskell. С помощью Lorentz разработчики могут напрямую работать со стеком Michelson.

В этом курсе мы будем использовать язык с самым простым синтаксисом — PasaLIGO.

Простой смарт-контракт с одной точкой входа

Основа смарт-контракта на LIGO — точка входа (entry point). Это главная функция смарт-контракта (main function), которая принимает входящую транзакцию и вызывает другие функции.

function имя_функции (const параметр_входящей_транзакции : тип; const хранилище : тип) : тип_результата is результат

Входные параметры main function:

  • параметр входящей транзакции. Функция использует его при исполнении кода;
  • хранилище [storage]. Информация, которую нужно хранить в блокчейне до следующего исполнения смарт-контракта, например количество токенов или записи о пользователях. Разработчик задает значение storage при развертывании смарт-контракта. В дальнейшем содержимое хранилища может менять только смарт-контракт.

Выходные параметры main function:

  • список выполненных операций, например детали транзакций, которые отправил смарт-контракт;
  • результаты выполнения функций, которые прописал разработчик.

Пример смарт-контракта с функцией-инкрементом, которая принимает число и увеличивает его на 1:

function main (const number : int; const storage : int) : list (operation) * int is ((nil : list (operation)), number + 1)

Код функции:

  • function main (const number : int; const storage : int) — объявляем main function с входными параметрами number и storage. Когда пользователь отправляет контракту число, главная функция воспримет его как number и исполняет код;
  • : list (operation) * int — определяем тип результата, который возвращает функция. В этом случае — пара из списка выполненных операций и число-результат;
  • is ((nil : list (operation)), number + 1) — определяем результат выполнения функции:
    • nil : list (operation) — возвращает пустой список;
    • , — служит разделителем для значений типа tuple;
    • number + 1 — однострочная функция, увеличивает число на 1.

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

Смарт-контракт с несколькими точками входа

Виртуальная машина начинает исполнение смарт-контракта с main function. Она может выполнить только те функции, которые вызывает точка входа.

Разработчики увеличивают количество операций, создавая псевдо-точки входа внутри главной функции. Для этого нужно:

  1. Объявить псевдо-точки входа и тип параметра, с которым они будут работать.
  2. Описать функции, которые будет вызывать каждая псевдо-точка.
  3. Использовать оператор case в главной функции. Он укажет виртуальной машине, какую функцию вызывать в случае обращения к псевдо-точке входа.

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

//объявление типа numbers, который содержит пару из двух чисел (tuple)
type numbers is (int * int)

//объявление типов action, которые содержат пары чисел
type action is
| Addition of numbers
| Subtraction of numbers
| Multiplication of numbers
| Division of numbers

//объявление типа данных в хранилище смарт-контракта
type storage is int

//объявление математических функций.

//(const a : int ; const b : int) — параметры функции
//: int — тип результата функции
//is a + b — результат исполнения функции

function add (const a : int ; const b : int) : int is a + b

function subtract (const a : int ; const b : int) : int is a - b

function multiply (const a : int ; const b : int) : int is a * b

function divide (const a : int ; const b : int) : int is a / b

//объявление главной функции
//назначаем первому параметру тип action, параметру storе — тип storage
//функция возвращает данные типов list(operation) и int — пару из списка и числа
//после is идет результат исполнения функции:
//1) пустой список nil : list(operation).
//2) const result : int = — запись результата исполнения функции в константу result.
//2) case parameter of — результат исполнения объекта типа action,
//чье название совпадает с параметром входящей транзакции.
function main (const parameter : action ; const store : storage) :
  (list(operation) * int) is block {
    const result : int =
    case parameter of
  | Addition(n1, n2) -> add(n1, n2)
  | Subtraction(n1, n2) -> subtract(n1, n2)
  | Multiplication(n1, n2) -> multiply(n1, n2)
  | Division(n1, n2) -> divide(n1, n2)
  end;

  //вывод результата исполнения главной функции: пустой список операций и значение result
  } with ((nil : list(operation)), result)

Если отправить смарт-контракту запрос с параметром Multiplication(3, 9), он вернет число 27. Виртуальная машина исполнит контракт так:

  • Сопоставит параметр Multiplication с вариантами псевдо-точек под оператором case.
  • Перейдет к функции multiply, которую разработчик описал в начале контракта.
  • Подставит вместо констант a и b параметры запроса — 3 и 9.
  • Исполнит функцию multiply и запишет ее в store.
  • Завершит исполнение главной функции — вернет пустой список и новое значение store.

Для проверки смарт-контракта скопируйте код калькулятора в редактор на ide.ligolang.org. Выберите значение Dry Run из выпадающего списка на панели Configure. В поле Parameters введите название математической операции и два параметра, а затем нажмите кнопку Run.

Измените значение Parameters и Storage и посмотрите, как меняется результат

Измените значение Parameters и Storage и посмотрите, как меняется результат

Подводим итоги

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

Разработчики пишут смарт-контракты на высокоуровневых языках программирования с синтаксисом Python, Pascal, JS или Haskel. Опытные разработчики часто пишут байт-код на Michelson.

Смарт-контракты на языке PascalLIGO состоят из переменных и функций. Виртуальная машина исполняет контракт начиная с точки входа — главной функции main. В нее можно вставить псевдо-точки входа — дополнительные функции.

Смарт-контракт всегда возвращает результат исполнения: список операций и значение хранилища storage.

  • Автор — Павел Скоропляс
  • Продюсер — Светлана Коваль
  • Стили — Дмитрий Бойко
  • Иллюстрации — Кшиштоф Шпак
  • Верстка — Зара Аракелян
  • Разработка — Александр Пупко
  • Руководитель — Влад Лихута