LIGO - Writing Smart Contracts
Smart contract development with CameLIGO
LIGO
LIGO is a statically-typed, high-level language that compiles down to Michelson. The syntaxes currently supported are PascaLIGO (Pascal-like syntax), CameLIGO (Caml-like syntax), and ReasonLIGO (Reason-like syntax).
Similar to SmartPy, it is still in development. The idea is to offer a secure and simple tool. In the long term, the plan is to support multiple syntaxes.
The installation is easy. Since we have previously worked with Docker, we also want to use it for LIGO:
$ curl https://gitlab.com/ligolang/ligo/raw/dev/scripts/installer.sh | bash -s "next"Although other syntaxes will be supported in the future, here we want to introduce two which are already quite advanced in their development.
CameLIGO
Along the way, we have gained experience with smart contracts and you can have a look at our OCaml introduction (at the beginning of the section) to become familiar with the OCaml syntax. This will make it easier for us to engage with CameLIGO. Before diving into details, let us again write our prominent repeater contract.
Create repeater.mligo:
type storage = int
let main (arg,storageIn  : storage*storage) = (([] : operation list), arg)It looks very similar to OCaml with the difference being that, momentarily, we have to explicitly pass all types. The rest should look familiar from OCaml and Michelson:
type storage = intis a type definition,maintakes two arguments, the parameterargand the storagestorageIn,- Even though we named our input storage instance 
storageIn, we do nothing with it, so it could have been left as_, and - The value is a tuple 
([], arg), whereas[]is of the typeoperation list. 
Let us do a very quick test with the online editor:
    At the bottom you will see the Michelson output:
{ parameter int ; storage int ; code { UNPAIR ; SWAP ; DROP ; NIL operation ; PAIR } }Well, we have worked with Michelson before and know that this code can be written in a bit more efficient way by replacing
UNPAIR ; SWAP ; DROPwith
CARwhich will do the same, take the left of the input pair parameter*storage.
Storage
Smart contracts have access to and can modify their storage, the structure that has to be defined as part of the contract definition. The code and the storage type go together. The storage is just another type, and by convention, we name it storage. The type is flexible, but, like the code, cannot be changed once deployed.
For instance, if you have a contract that does not need to save anything to the storage, you would declare:
type storage = unitIf your contract only needs to save a positive number, you would declare:
type storage = natIf your contract only needs to save either a positive number or nothing, you would declare:
type storage = nat optionWhere option is one of the native parameterised variants: type 'a option = None | Some 'a.
If your contract only needs to save a single person, you would declare it with a record:
type storage = {
    name: string;
    age: nat;
    streetAddress: string;
    tezAddress: address;
}If your contract wants to act as a Tezos token ATM, you may declare its storage with:
type storage = {
    balances: (address, tez) map;
    totalSupply: tez;
}This will create a namespace mapping addresses to balances in tez.
The smart contract to certify students
We want to start with an example that we already know from SmartPy and Michelson, the student certification contract.
Let us begin by defining a student:
type student = {
    name : string ;
    certificate : bool;
  }Since we don't want everyone to be able to issue certificates, we also want to save an address that represents the authorized person:
type storage = {
  students : student list;
  certifier : address;
}Now, we can add the following function:
type student = {
    name : string ;
    certificate : bool;
  }
type storage = {
  students : student list;
  certifier : address;
}
let certifyStudent (name,oldState : string*storage) =
    let reqSender = sender in
    if reqSender = oldState.certifier 
    then 
      let newStudents:(student list) = 
      { name = name; certificate = true } :: oldState.students in
      let newState:storage = { students = newStudents; certifier = oldState.certifier } in
      ( ([]:operation list) , newState )
    else 
      ( ([]:operation list) , oldState ) It should be clear now what we mean with caml-like syntax.
We now can compile the code and get the Michelson output:
{ parameter string ; storage (pair (address %certifier) (list %students (pair (bool %certificate) (string %name)))) ; code { UNPAIR ; SENDER ; DIG 2 ; DUP ; DUG 3 ; CAR ; SWAP ; COMPARE ; EQ ; IF { SWAP ; DUP ; DUG 2 ; CDR ; SWAP ; PUSH bool True ; PAIR ; CONS ; SWAP ; CAR ; PAIR ; NIL operation ; PAIR } { DROP ; NIL operation ; PAIR } } }You can store this Michelson code and deploy it with the help of the tezos-client, as we have done before; e.g. using --init 'Pair {} "tz1W4W2yFAHz7iGyQvFys4K7Df9mZL6cSKCp"'.
Let us take a look at the example at https://ligolang.org/:
type storage = int
(* variant defining pseudo multi-entrypoint actions *)
type action =
| Increment of int
| Decrement of int
let add (a,b: int * int) : int = a + b
let sub (a,b: int * int) : int = a - b
(* real entrypoint that re-routes the flow based on the action provided *)
let main (p,s: action * storage) =
 let storage =
   match p with
   | Increment n -> add (s, n)
   | Decrement n -> sub (s, n)
 in ([] : operation list), storage
This shows that we can use pattern matching as we know it from OCaml, and also add comments the same way. This is especially useful when used as shown in this example. You have been using multiple entrypoints already. When using LIGO, this alternative pattern is currently recommended.
PascaLIGO
Pascal is a relatively beginner-friendly language and has many keywords. For this reason, a Pascal-like syntax should help with writing smart contracts that are easier to read and understand.
LIGO offers a VSCode extension.
Let us start with the repeater contract again:
function main (const arg : int;  const storageIn : int) : (list(operation) * int) is
  block {skip} with ((nil : list(operation)), arg)After a quick test via online IDE, save this as repeater.ligo and run:
ligo dry-run ./repeater.ligo main 5 0Hopefully, you will see the expected value.
For definitions, similar to Pascal we use:
function mainto define a function that can be used as an entrypoint.(const arg : int; const storageIn : int)for our input parameters, as we have done with other languages.(list(operation) * int)to set the return type, as seen before it is a tuple consisting of a(list(operation)and anint.block {skip}when we don't want to calculate anythingwith ((nil : list(operation)), arg)to return a the tuple together with the new input.
We define variables with const(immutable) and var(mutable), where var can only be used inside functions or blocks.
So, how would our certification contract for the students look like?
type student is record
    name : string ;
    certificate : bool;
end
type certStorage is record
  students : list(student);
  certifier : address;
end
function certifyStudent (const studentName : string; const oldState:certStorage) : (list(operation) * certStorage) is
  begin
  if sender =/= oldState.certifier then failwith("Only certifier can call this function");
  else skip;
  const newStudent : student = record 
            name = studentName; 
            certificate= True;
            end;
  const oldList:list(student) = oldState.students;
  const newList:list(student) = cons(newStudent, oldList);
  const newState : certStorage = record
            students= newList;
            certifier= oldState.certifier;
            end;
  end with ((nil : list(operation)), newState)In this sample, we define our storage similar to our implementation in CameLIGO:
type student is record
    name : string ;
    certificate : bool;
end
type certStorage is record
  students : list(student);
  certifier : address;
endTake a look at the types section in the LIGO documentation.
Afterwards, we define our entrypoint with the right input and output parameter types:
function certifyStudent (const studentName : string; const oldState:certStorage) : (list(operation) * certStorage)We add our usual check:
if sender =/= oldState.certifier then fail("Only certifier can call this function");
  else skip;After that, we define a new student:
const newStudent : student = record 
            name = studentName; 
            certificate= True;
            end;And extend our list with this new student:
const oldList:list(student) = oldState.students;
const newList:list(student) = cons(newStudent, oldList);Next, we change the state with the new list:
const newState : certStorage = record
          students= newList;
          certifier= oldState.certifier;
          end;to finally store this new state:
end with ((nil : list(operation)), newState)