Середовище розробки та базові типи даних LIGO
На минулому уроці ми запустили простий смарт-контракт в онлайн-компіляторі LIGO. У ньому не вийде створити великі проекти: там не можна зберегти код, додати сторонню бібліотеку або викликати інший смарт-контракт.
Програмісти пишуть код в середовищах розробки — текстових редакторах з підтримкою плагінів і бібліотек. На цьому уроці ми встановимо середу розробки VS Code і розповімо про базові типи даних LIGO.
Установка і настройка середовища розробки
Ми будемо використовувати редактор Visual Studio Code (VS Code) з плагіном pascaligo-vscode.
Порядок установки середовища розробки:
- Завантажте редактор з офіційного сайту Visual Studio Code.
- Встановіть програму та завантажте плагін pascaligo-vscode.
- Встановіть Docker. Він потрібен для запуску бібліотеки LIGO у віртуальній машині. Так комп'ютер зможе компілювати і виконувати код на PascalLIGO.
- Запустіть Docker і дочекайтеся, поки в лівому нижньому кутку додатка з'явиться зелена смужка.
Під час установки можуть виникнути такі проблеми:
- при запуску Docker видає помилку «This computer does not have VT-X/AMD-v enabled». Завантажте BIOS. Увімкніть віртуалізацію VT-X або AMD-V;
- після запуску Docker комп'ютер повільно працює. Відкрийте настройки програми, перейдіть у вкладку Resources і зменшіть ресурси комп'ютера, виділені для Docker. Натисніть кнопку Apply and restart в нижньому правому куті, щоб застосувати зміни.
Встановіть контейнер LIGO. Для цього відкрийте термінал і виконайте команду:
- для MacOS і Linux:
docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.19.0
- для Windows:
docker run --rm -v "%CD%":/cd -w /cd ligolang/ligo:0.19.0
Docker завантажить контейнер і видасть вбудовану довідку LIGO.
Перевірте, чи все працює. Виконайте в терміналі команду
ligo --version
Якщо термінал видає помилку «ligo: command not found», значить Docker передчасно закриває контейнер LIGO. Рішення проблеми:
-
Відкрийте VS Code і створіть файл з назвою ligo.
-
Вставте в нього наступний код:
#!/bin/sh docker run --user=`id -u` -v $PWD:$PWD -w $PWD ligolang/ligo:next "$@"
-
Збережіть документ і відкрийте термінал. За допомогою команди cd перейдіть в папку із файлом ligo:
cd ~/Documents
-
Якщо у вас Linux або Mac OS, включіть режим суперкористувача:
sudo -s
-
У терміналі виконайте команди:
chmod +x ligo ./ligo
Вони прив'яжуть запуск контейнера LIGO до команди ligo.
-
Зробіть файл виконуваним на рівні системи. В Linux і Mac OS відкрийте провідник, і перенесіть файл ligo в папку /usr/local/bin на системному диску. У Windows відкрийте термінал і виконайте команду:
$env:path += ";c:\[путь к папке с файлом ligo]"
-
Перевірте роботу LIGO: знову введіть в терміналі команду ligo --version.
Компіляція пробного контракту
Відкрийте VS Code і створіть новий проект:
- Натисніть на кнопку Open…
- Виберіть папку, в якій хочете зберегти тестовий проект LIGO.
- Створіть в ній папку LIGO_test.
- Відкрийте цю папку.
Зліва відображаються папки і файли поточного проекту. Створіть в цьому списку порожній файл з назвою test.ligo. За розширенням .ligo VS Code зрозуміє, що потрібно задіяти плагін для підсвічування синтаксису і використовувати команди PascaLIGO.
Вставте в файл test.ligo функцію з першого уроку.
function main (const number : int; const storage : int) : list (operation) * int is ((nil : list (operation)), number + 1)
Спробуйте скомпілювати цей контракт. Відкрийте термінал VS Code.
Введіть в ньому таку команду та натисніть Enter:
ligo compile-contract test.ligo main
Якщо у вас Linux або Mac OS, термінал може видати помилку «root/ligo». У такому випадку спочатку виконайте команду sudo -s, щоб дати терміналу право виконати скрипт ligo в папці bin.
VS Code перекладе код PascaLIGO з test.ligo в код Michelson і перевірить, чи можна виконати функцію main.
Тепер перевірте роботу смарт-контракту за допомогою команди dry-run (репетиції). Вона виконає контракт із заданими параметрами всередині віртуальної машини Docker.
Для «репетиції» тестового контракту введіть в терміналі команду:
ligo dry-run test.ligo main 2 0
- main — назва точки входу;
- 2 — перший входить параметр;
- 0 — значення сховища.
Ви можете використовувати інші числа для перевірки цього контракту.
При виконанні dry-run компілятор видасть результат виконання функції main з заданими параметрами.
Константи, змінні та базові типи даних LIGO
Для зберігання інформації LIGO використовує:
- const (константи). Смарт-контракт не може змінити значення константи під час виконання функцій. Найчастіше в них записують параметри, які надходять на вхід головної функції;
- var (змінні). Смарт-контракт може змінити значення змінної в будь-який момент. Зазвичай змінні використовують в loop-функціях, які контракт викликає кілька разів.
Константам і змінним потрібно присвоїти тип даних, щоб компілятор розумів, як з ними працювати. LIGO використовує такі базові типи даних:
- int — необмежене за розміром ціле число, може бути негативним;
- nat — необмежене за розміром натуральне число, не може бути негативним;
- string — рядок, містить друковані символи: латинські букви, цифри, розділові знаки і деякі спецсимволи на кшталт | чи ~;
- bool — логічна змінна true або false.
Приклад оголошення констант і змінних:
//const [назва] : [тип даних] = [значення]
//var [назва] : [тип даних] := [значення]
const a : int = 50
const b : nat = 3n
var c : string := “Example”
var d : bool := True
Іноді константі або змінної не можна привласнити значення до виконання смарт-контракту. Приклад: контракт отримує вхідний параметр, а потім використовує його для розрахунків. У такому випадку потрібно оголосити порожню константу або змінну і привласнити їй значення пізніше.
У прикладі нижче ми оголосили константу number типу int. Під час виконання коду смарт-контракт присвоїть їй значення, яке відправить користувач.
function main (const number : int; const storage : int) : list (operation) * int is ((nil : list (operation)), number + 1)
Оголошувати константи можна в будь-якому місці смарт-контракту, а змінні — тільки всередині функцій. Наприклад, ми можемо винести число 1 з головної функції в прикладі вище. Для цього потрібно оголосити константу add на початку контракту, присвоїти їй значення 1 і замінити одиницю на add.
const add : int = 1
function main (const number : int; const storage : int) :
list (operation) * int is ((nil : list (operation)), number + add)
Але якщо зробити add змінною, при спробі виконати код компілятор видасть помилку «ILL FORMED CONTRACT» — неправильно сформований контракт.
Просунуті типи і псевдоніми
Крім базових типів даних LIGO підтримує кілька просунутих типів:
- структури даних — tuple, record і map для передачі декількох значень в одному параметрі;
- спеціальні типи — адреси, баланси tez, варіанти, timestamp.
Розробники створюють такі типи за допомогою псевдонімів (alias). Псевдоніми також спрощують читання коду і дозволяють сортувати параметри по способам застосування.
Приклад оголошення псевдонімів:
//оголошуємо псевдонім типу int
type numbers is int
//оголошуємо константу типу numbers і присвоюємо їй значення
const a : numbers = -5
//оголошуємо константу admin типу user_id
type user_id is nat
const admin : user_id = 1n
Часто використовувані структури даних
Головна функція приймає один параметр, тому базових типів на кшталт int і nat недостатньо для роботи складних смарт-контрактів. Це обмеження можна обійти за допомогою структур даних, які повертають декілька значень в одному параметрі.
tuple або «кортеж» зберігає два і більше значень заданого типу. У прикладі нижче ми задаємо тип cube, в який можна записати довжину сторін куба.
//оголошуємо псевдонім cube, щоб зберігати в ньому довжину трьох граней куба.
type storage is int
type cube is int * int * int
//оголошуємо в функції входить параметр — константу side типу cube
function main (const side: cube; const _store: storage):
(List (operation) * int) is block {
// оголошуємо константу result і присвоюємо їй значення: множення трьох значень, які зберігаються в кортежі side
const result: int = side.0 * side.1 * side.2
} With ((nil: list (operation)), result)
record зберігає кілька констант або змінних різних типів. Наприклад, в константу типу user можна записати аргументи: id типу int, is_admin типу bool і name типу string:
//оголошуємо тип user
type user is
record [
id: nat;
is_admin: bool;
name: string
]
//оголошуємо константу alice і присвоюємо їй значення типу user
const alice: user =
record [
id = 1n;
is_admin = True;
name = "Alice"
]
map пов'язує дані в набір пар «ключ-значення». Також в LIGO є тип даних big_map, який оптимізує завантаження великої кількості «ключів-значень» і економить газ, але не підтримує ітерацію.
//оголошення псевдоніму dims типу tuple, потім — cube_dimensions типу map
type dims is int * int * int
type cube_dimension is map (string, dims)
//оголошення map cubes, яка містить два значення: назва куба і значення довжини його граней
const cubes: cube_dimension =
map [
"Big cube" -> (123243, 1251, 823);
"Small cube" -> (3, 3, 7)
]
// записуємо в константу big типу dims. Для цього отримуємо значення з map за зверненням до ключу "big cube"
const big: option (dims) = cubes [ "big cube"]
unit — тип даних без значення. Він потрібен, коли за правилами синтаксису функція повинна прийняти параметр, але цей параметр не важливий для роботи коду. Ми використовуємо unit для оголошення типів Labeouf, Nike і Yoda в прикладі нижче.
variant дозволяє використовувати змінні декількох типів в залежності від ситуації і логіки смарт-контракту. У прикладі оголошуємо variant для створення псевдо-точок входу.
// оголошуємо псевдонім speach і записуємо кілька варіантів. Для кожного варіанта вказуємо тип параметра, який він буде приймати. У нашому випадку головна функція нічого не робить з прийнятим параметром, тому для варіантів вказуємо порожній тип unit
type speach is
Labeouf of unit
| Nike of unit
| Yoda of unit
// ми хочемо, щоб контракт повертав рядок (string), тому в типі return вказуємо, що він повертає список операцій і рядок
type return is (list (operation) * string)
// оголошуємо, що функція main повинна прийняти від користувача параметр і записати його в константу word типу speach
function main (const word: speach; const _store: string): return is
((Nil: list (operation)),
// "case word of" порівнює прийнятий параметр з варіантами типу speach. Якщо варіант співпаде, контракт поверне зазначений рядок
case word of
Labeouf (_n) -> "DO IT!"
| Nike (_n) -> "Just do it"
| Yoda (_n) -> "Do it you can"
end)
Спеціальні типи даних
Крім просунутих структур даних, LIGO використовує кілька «блокчейнових» типів даних і функцію failwith.
tez — позначає кількість токенов XTZ (tez). При оголошенні, присвоєння або оперуванні типом tez потрібно додавати до числа суфікс «tz» або «tez».
У LIGO також використовується суфікс «mutez» для позначення однієї мільйонної частини tez. Для зручності читання можна розділяти великі числа на порядки за допомогою нижнього підкреслення. Наприклад, «1_000_000tez» віртуальна машина Tezos сприйме як 1 млн tez.
// 1 000 000 mutez = 1 tez
// присвоюємо t значення 1,5003 tez за допомогою суфікса mutez
const t: tez = 1_500_300mutez
address — тип даних для зберігання адрес Tezos.
const my_account : address =
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address)
failwith — функція обробки помилок і виключень. Наприклад, ми можемо застосувати failwith, щоб калькулятор не ділив числа на 7.
Division(n1, n2) -> if n2 = 7 then failwith("Error: you can not divide by 7!") else divide(n1, n2)
Більше прикладів операцій з різними типами даних — в документації LIGO.
Підводимо підсумки
В онлайн-середовищі розробки не можна зберігати код, довантажувати бібліотеки і викликати інші контракти, тому програмісти використовують Visual Studio Code і контейнер LIGO в Docker.
Базові типи LIGO — int, nat, string і bool. Спеціальні структури для зберігання даних:
- tuple — кілька значень в одній константі або змінної;
- record — кілька констант з присвоєними значеннями в одній константі або змінної;
- map — кілька констант просунутих типів з прив'язкою до константи-ключу;
- tez — баланс XTZ;
- address — адреса в блокчейне Tezos;
- unit — тип даних без значення.