Development Environment and Basic Types in LIGO
Last time, we launched a simple smart contract in the online compiler LIGO. You won’t use it for big projects, though: it doesn’t store code, add libraries, or call other smart contracts.
Coders use a development environment to write code, which usually are text editor programs that support plugins and libraries. This time, we install VS Code and cover the most fundamental data types in LIGO.
Installing and setting up the environment
We’ll be using Visual Studio Code (VS Code) with the plug-in pascaligo-vscode.
Here’s how to install it:
- Download the software from the official website of Visual Studio Code.
- Install the app and download the plug-in pascaligo-vscode.
- Install Docker. You will need it to start the LIGO library in the VM. The computer will be able to compile and execute code in PascalLIGO.
- Start Docker and wait for a green stripe to appear in the lower-left corner of the app’s interface.
You might encounter problems like these:
- Docker presents “This computer doesn’t have VT-X/AMD-v enabled” while launching. Open BIOS and activate the virtualisation of VT-X or AMD-V;
- The computer noticeably slowed down once Docker started. Open the software’s settings, go to Resources and assign fewer resources to Docker. To accept the change, click Apply and Restart in the lower right corner.
Install the LIGO container by executing the following commands in the terminal:
- for MacOS and Linux:
docker run --rm -v "$PWD":"$PWD" -w "$PWD" ligolang/ligo:0.19.0
- for Windows:
docker run --rm -v "%CD%":/cd -w /cd ligolang/ligo:0.19.0
Docker will download the container and show the help menu for LIGO.
Make sure everything works. Execute the following command:
ligo --version
If the terminal returns “ligo: command not found”, it means Docker prematurely closes the LIGO container. This is how you fix it:
-
Open VS Code and create a file named “ligo”.
-
Paste the following code therein:
#!/bin/sh docker run --user=`id -u` -v $PWD:$PWD -w $PWD ligolang/ligo:next "$@"
-
Save the document and open the terminal. Go to the folder containing the file ligo:
cd ~/Documents
-
If your machine runs on Linux or Mac OS, activate the superuser mode:
sudo -s
-
Execute the following commands in the terminal:
chmod +x ligo ./ligo
They will link starting LIGO to the command “ligo.”
-
Make the file system-wide executable. In Linux and macOS open Finder and move ligo to /usr/local/bin on the system disc. In Windows, open the terminal and execute the following command:
$env:path += ";c:\[путь к папке с файлом ligo]"
-
Check if LIGO works by executing “ligo --version” again.
Compiling a test contract
Open VS Code and create a new project:
- Click Open...
- Choose the folder to save your text project in.
- Therein, create a folder named LIGO_test.
- Open the new folder.
On the left-hand side, you see files and folders in the current project. In the list, create an empty file named test.ligo. The extension .ligo will let VS Code know it has to activate the plugin to highlight the syntax and to use PascaLIGO commands.
Copy-paste the function from the first lesson into test.ligo.
function main (const number : int; const storage : int) : list (operation) * int is ((nil : list (operation)), number + 1)
Try to compile the contract. Open the VS Code terminal.
Input the following command and press Enter:
ligo compile-contract test.ligo main
If you use Linux or macOS the terminal may run into an error “root/ligo.” In this case, execute sudo -s to grant the terminal the rights to run the script ligo in the bin folder.
VS Code will translate the PascaLIGO code in test.ligo into Michelson and will check whether the function Main is executable.
Now test the smart contract via the dry run. The dry-run command will execute the contract with pre-set params within the Docker VM. To “rehearse” the test contract, input the following command into the terminal:
ligo dry-run test.ligo main 2 0
- main: entry point name;
- 2: the first entry param;
- 0: storage state.
You can use any other numbers to dry-run the contract.
After that, the compiler will display the results of the main function execution with pre-set params.
Constants, variables, and types in LIGO
To store information, LIGO uses the following:
- const (constants). A smart contract cannot change the value of a constant during function execution. Often, this is used to store params coming to the main function’s input.
- var (variables). A smart contract can change the variable’s value at any time. Usually, variables are used in loop functions that the contract calls several times.
Constants and variables have to be assigned with a type so that the compiler could know how to treat them. LIGO uses the following types:
- int: any integer number, including those negative.
- nat: any natural number that cannot be negative.
- string: a string containing characters like Latin letters, numbers, punctuation marks and certain special characters like | and ~.
- bool: a Boolean variable, i.e. true or false.
Here’s an example of declaring variables and constants:
// const [название] : [тип данных] = [значение]
// var [название] : [тип данных] := [значение]
const a : int = 50
const b : nat = 3n
var c : string := “Example”
var d : bool := True
Sometimes, you can’t assign a value to a param until the smart contract is executed. For instance, a contract gets an entry param and uses it for calculations. In this case, you have to declare an empty constant or variable and assign a value to it later.
In the example below, we declare the constant “number” of int type. During the execution, the smart contract will assign the user-sent value to it.
function main (const number : int; const storage : int) : list (operation) * int is ((nil : list (operation)), number + 1)
You can declare constants anywhere in the contract yet variables have to be declared strictly within functions. For instance, we can take the number 1 from the main function in the example above. To do that, we have to declare the constant “add” in the beginning of the contract, assign the value 1 to it, and replace 1 with add.
const add : int = 1
function main (const number : int; const storage : int) :
list (operation) * int is ((nil : list (operation)), number + add)
But if you make add a variable, the compiler will run into the error ILL FORMED CONTRACT.
Advanced types and aliases
Aside from basic types, there are several advanced ones in LIGO:
- data types — tuple, record, and map to send several values within one param;
- special types — addresses, tez balances, variants, timestamp.
Developers create such types with aliases. Aliases also simplify code reading and allow one to sort params according to their use cases.
An example of declaring an alias:
//declaring an alias of int type
type numbers is int
//declaring a constant of numbers type and assigning a value
const a : numbers = -5
//declaring a constant admin of user_id type
type user_id is nat
const admin : user_id = 1n
Frequently used data types
The main function accepts one param, so basic types like int or nat are not enough for complex smart contracts. We can bypass this, however, with data types that return several values within the same param.
tuple stores one or more values of a given type. In the example below, we declare a type cube where one can record the length of cube sides:
//declaring the alias cube to store the length of three sides of the cube.
type storage is int
type cube is int * int * int
//declaring the input param in the function, a constant side of cube type
function main (const side : cube ; const _store : storage) :
(list(operation) * int) is block {
//side declaring the constant result and assigning a value: multiplication of three values stored in the tuple
const result : int = side.0 * side.1 * side.2
} with ((nil : list(operation)), result)
record stores several constants/variables of different types. For example, a user type constant can contain id of int type, is_admin of bool time, and name of string type.
//declaring the type ser
type user is
record [
id : nat;
is_admin : bool;
name : string
]
//declaring the constant alice and assigning it the value of user type
const alice : user =
record [
id = 1n;
is_admin = True;
name = "Alice"
]
map links data in a set of key-value pairs. LIGO also has a big_map data type that optimises uploading a vast array of key-value pairs to save gas. It, however, does not support iteration.
//map declaring alias dims of tuple type, then cube dimensions of map type
type dims is int * int * int
type cube_dimension is map (string, dims)
//declaring map cube containing the cube name and its side length values
const cubes : cube_dimension =
map [
"big cube" -> (123243, 1251, 823);
"small cube" -> (3, 3, 7)
]
//recording big of dims type to constant. For that, we get values out of map by calling “big cube” key
const big : option(dims) = cubes["big cube"]
unit is a value-less data type. You need it when syntax rules require the function to accept a parameter that isn’t important for the code’s operation. We use unit to declare types Labeouf, Nike, and Yoda in the example below.
variant allows one to use variables of different types depending on the situation and the smart contract’s logic. In the example, we declare variant to create pseudo-entry points.
//unit declaring the alias speach and writing several variants. For each variant, we specify the param type it will use. In our case, the main function does nothing with the parameter, so we specify the empty type unit for the variants
type speach is
Labeouf of unit
|Nike of unit
|Yoda of unit
//we want the contract to return the string, so in the return type we say it returns the list of operations and the string
type return is (list(operation) * string)
//we declare that the function main has to receive a user param and record it in the constant word of speach type
function main (const word : speach; const _store : string) : return is
((nil : list (operation)),
//“case word of” compares the param with variants of speach type. If it is the same, the contract returns the specified string
case word of
Labeouf (_n) -> "DO IT!"
| Nike (_n) -> "Just do it"
| Yoda (_n) -> "Do it you can"
end)
Special data types
Aside from complex data types, LIGO uses several blockchain-specific data types and the failwith function.
tez specifies the number of Tezos tokens XTZ (tez). When declaring, assigning, or operating the tez type one has to add tz or tez to the number.
LIGO also uses the suffix “mutez” for one-millionth of tez. For easier reading, big numbers can be divided by orders with underscoring. Thus, “1_000_000tez” will be read as “one million tez.”
//1 000 000 mutez = 1 tez
//assigning t the value of 1.5003 tez with the mutez suffix
const t : tez = 1_500_300mutez
address is a data type to store Tezos addresses.
const my_account : address =
("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address)
failwith is a function for processing errors and exceptions. Thus, we can use failwith to prohibit the calculator to divide numbers by 7.
Division(n1, n2) -> if n2 = 7 then failwith("Error: you can not divide by 7!") else divide(n1, n2)
You will find more examples of operations with different data types in the LIGO documentation
Some conclusions
In an online development environment, you can’t save code, plug in libraries, and call other contracts so programmers use Visual Studio Code and the LIGO container in Docker.
Basic data types in LIGO are int, nat, string, and bool. Special types for data storage include:
- tuple: several values within one constant or variable;
- record: several constants with assigned values within one constant or variable;
- map: several advanced-type constants linked to a key constant;
- tez: XTZ balance;
- address: address on the Tezos blockchain;
- unit: a value-less data type.