Що таке смарт-контракти та як вони працюють
Розповідаємо про смарт-контракти 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.