Что такое смарт-контракты и как они работают
Рассказываем о смарт-контрактах 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. Она может выполнить только те функции, которые вызывает точка входа.
Разработчики увеличивают количество операций, создавая псевдо-точки входа внутри главной функции. Для этого нужно:
- Объявить псевдо-точки входа и тип параметра, с которым они будут работать.
- Описать функции, которые будет вызывать каждая псевдо-точка.
- Использовать оператор 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.
Подводим итоги
Смарт-контракт — это код в блокчейне. Виртуальная машина исполняет его, когда получает транзакцию с нужными параметрами.
Разработчики пишут смарт-контракты на высокоуровневых языках программирования с синтаксисом Python, Pascal, JS или Haskel. Опытные разработчики часто пишут байт-код на Michelson.
Смарт-контракты на языке PascalLIGO состоят из переменных и функций. Виртуальная машина исполняет контракт начиная с точки входа — главной функции main. В нее можно вставить псевдо-точки входа — дополнительные функции.
Смарт-контракт всегда возвращает результат исполнения: список операций и значение хранилища storage.