Среда разработки и базовые типы данных LIGO
На прошлом уроке мы запустили простой смарт-контракт в онлайн-компиляторе LIGO. В нем не получится создать большие проекты: там нельзя сохранить код, добавить стороннюю библиотеку или вызвать другой смарт-контракт.
Программисты пишут код в средах разработки — текстовых редакторах с поддержкой плагинов и библиотек. На этом уроке мы установим среду разработки VS Code и расскажем о базовых типах данных LIGO.
Установка и настройка среды разработки
Мы будем использовать редактор Visual Studio Code (VS Code) с плагином pascaligo-vscode.
Порядок установки среды разработки:
- Скачайте редактор с официального сайта Visual Studio Code.
- Установите приложение и загрузите плагин pascaligo-vscode.
- Установите Docker. Он нужен для запуска библиотеки LIGO в виртуальной машине. Так компьютер сможет компилировать и исполнять код на PascalLIGO.
- Запустите Docker и дождитесь, пока в левом нижнем углу приложения появится зеленая полоска.
Во время установки могут возникнуть такие проблемы:
- при запуске Docker выдает ошибку «This computer doesn’t 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 cube, которая содержит два значения: название куба и значения длины его граней
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
//мы хотим, чтобы контракт возвращал строку, поэтому в типе 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 — тип данных без значения.