Що таке смарт-контракти та як вони працюють

Розповідаємо про смарт-контракти 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. Віртуальна машина виконає контракт так:

  1. Співставить параметр Multiplication з варіантами псевдо-точок під оператором case.
  2. Перейде до функції multiply, яку розробник описав на початку контракту.
  3. Підставить замість констант a і b параметри запиту — 3 і 9.
  4. Виконає функцію multiply і запише її в store.
  5. Завершить виконання головної функції — поверне порожній список і нове значення store.

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

Змініть значення в полях Parameters і Storage і подивіться, як змінюється результат

Змініть значення в полях Parameters і Storage і подивіться, як змінюється результат

Підводимо підсумки

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

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

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

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

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