What Are Smart Contracts and How They Work

This article covers smart contracts on Tezos, their programming language, and the work of the virtual machine.

The lesson omits info on syntax as it focuses on the inner workings of blockchain’s VM.

What are smart contracts?

A smart contract is a set of functions and data stored on a blockchain. Smart contracts have various purposes, from selling tokens to governing decentralised organisations. Here are some examples:

  • Vesting contracts send tokens to users at a specified time. Early adopters of Tezos get their funds from KT1KCYaULopm8i2CBbGF5EHXeXLUr4R6r2dA;
  • Oracles question data sources and return averaged prices of assets or other info. This is how Harbinger gets cryptocurrency prices from trading platforms;
  • Algorithmic stablecoins issue tokens and alter the cost of issuance depending on the native token’s exchange rate. The Kolibri smart contract amends the cost of issuing kUSD depending on the tez price.

Smart contracts are executed by a virtual machine (VM). It uses the computational power of blockchain: smart contracts are executed by all nodes in the network yet only the fastest one gets to record the result in the block.

If smart contracts are called too often, the blockchain may get paralysed. To avoid it, protocol devs limit the maximum size of smart contracts in terms of code size and fee amounts.

How do blockchain VMs work?

Virtual machines understand low-level programming languages like byte code. They interpret such code faster compared to interpreting commands in JavaScript or Python. Here’s an example of the same thing written differently that shows the difference:

  • low-level language: 2 + 2 = 4;
  • high-level language #1: two plus two equals four;
  • high-level language #2: If you add two to two You get four;
  • high-level language #3: as a result of adding two to two we get a four;

When reading, we automatically interpret the latter lines as the simplest understandable variant: 2 + 2 = 4. The same happens in programming: the compiler translates a high-level language into byte code.

Tezos’s VM works with byte code Michelson. It’s a low-level programming language with direct access to the stack (a data structure with fast access to data). Code in Michelson looks like this:

parameter unit ;
storage unit ;
code { CAR ;
      PUSH int 3 ;
      PUSH int 3 ;
      IFCMPEQ { DROP } { DROP } ;
      UNIT;
      NIL operation ;
      PAIR }
//Operators PUSH add the number 3 to the stack two times. Operator IFCMPEQ compares the first two elements in the stack and executes the commands { DROP } and displays the operation results.

Seasoned blockchain devs usually write their smart contracts in Michelson. Newbies, however, should better stick to high-level programming languages with libraries and human-readable syntax.

Programming Languages for Tezos Smart Contracts: SmartPy, LIGO and Lorentz

Tezos ecosystem members have designed several high-level programming languages. The most popular of them are listed below.

  • SmartPy is an object-oriented Python offshoot supporting the online compiler SmartPy.io where one can develop, test, and publish smart contracts.
  • LIGO is an imperative programming language with a simple type system and an online compiler available at ide.ligolang.org. There are several dialects using syntaxes from other popular languages: PascaLIGO (Pascal), CameLIGO (OCaml), ReasonLIGO (ReasonML), and jsLIGO (JavaScript).
  • Lorentz is an inline object-oriented Haskell offshoot. Using Lorentz, devs can directly access the Michelson stack.

In this course, we’ll be using the simplest language of them all, PascaLIGO.

A Simple Smart Contract With a Single Entry Point

A smart contract on LIGO is underpinned by the entry point. It is the main function of the smart contract that accepts the incoming transaction and calls other functions.

function function_name (const incoming_transaction_parameter : type; const storage : type) : result_type is result

Input parameters of the main function:

  • incoming transaction parameter. The function uses it to execute the code;
  • storage. Information to be stored in the blockchain until the next execution of the smart contract, like the number of tokens or user profiles. The developer sets up the storage initial state when deploying the smart contract. After that, only the smart contract can change the contents of storage.

Output parameters of the main function:

  • the list of executed operations, such as transaction details sent by the smart contract;
  • results of developer-made functions’ execution.

An example of a smart contract with an incrementing function that accepts a number and adds 1 to it:

function main (const number : int; const storage : int) : list (operation) * int is ((nil : list (operation)), number + 1)

Function code:

  • function main (const number : int; const storage : int) — calling the main function with input parameters of number and storage. When the user sends a number to the contract, the main function sees it as number and executes the code;
  • : list (operation) * int defining the type of the result returned by the function. In this case, it’s a pair from the list of executed operations and the resulting number;
  • is ((nil : list (operation)), number + 1) defining the result of function executing:
    • nil : list (operation) returns an empty list;
    • , divides values of tuple type;
    • number + 1 a single-string function increasing the number by 1.

This smart contract can be modified: implement a decrement, squaring, or other mathematical operation. It has a single entry point that activates a single string of functions. This smart contract cannot execute several tasks like accepting a deposit, returning the balance, and sending a transaction.

Smart Contracts With Multiple Entry Points

A virtual machine starts executing a smart contract with its main function. It can only execute the functions called by the entry point. Developers increase the number of operations by creating pseudo-entry points within the main function. Here’s what it requires:

  1. Call the pseudo-entry points and the parameter type they will work with.
  2. Describe the functions to be called by each pseudo-point.
  3. Use the operator case in the main function. It will point the VM to the function to call if the pseudo-entry point is called.

An example of a smart contract with pseudo-points is a calculator that accepts the name of a mathematical operation and two numbers and returns calculation results. It doesn’t record the values in storage because we don’t need to put data onto a blockchain to execute mathematical operations.

//declaring type of numbers containing a tuple
type numbers is (int * int)

//declaring types of action that contain tuples
type action is
| Addition of numbers
| Subtraction of numbers
| Multiplication of numbers
| Division of numbers

//declaring data type in the smart contract’s storage
type storage is int

//declaring mathematical functions

//(const a : int ; const b : int) — function parameters
//: int — function result type
//is a + b — result of function execution

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

//declaring the main function
//assign the type action to the first parameter, storage type to the parameter storе
//the function returns data of list(operation) and int types, a tuple of a list and a number
//function execution result follows “is”:
//1) empty list nil : list(operation).
//2) const result : int = recording the function execution result in the constant “result”
//2) case parameter of — it’s the result of action-type parameter’s execution,
//whose name is the same as the incoming transaction’s name.
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;

  //displaying the result of the main function’s execution: empty list of operations      and the value of result
  } with ((nil : list(operation)), result)

If you send a request to the smart contract with the parameter Multiplication(3, 9) it will return the number 27. The virtual machine will execute the contract as follows:

  1. Compare the parameter Multiplication with variants of pseudo-points under the operator case.
  2. Go to the function multiply described by the dev earlier in the contract.
  3. Put the request parameters (3 and 9) as a and b.
  4. Execute the function multiply and record it in store.
  5. Finalise the execution of the main function: return the empty list and the new value of store.

To inspect the smart contract, copy the calculator’s code to the editor form at ide.ligolang.org. Select Dry Run in the drop-down list Configure. In the field Parameters, input the name of the mathematical operation, specify two parameters, and press Run.

Change the values of Parameters and Storage and check out the way the result changes in their wake

Change the values of Parameters and Storage and check out the way the result changes in their wake

Some conclusions

A smart contract is a code on the blockchain. The virtual machine executes it after receiving a transaction with required parameters.

Developers write smart contracts on high-level programming languages based on Python, Pascal, JS, or Haskel. Seasoned devs often opt to write byte code on Michelson.

Smart contracts written in PascalLIGO consist of variables and functions. VM executes the contract starting with the entry point, the main function main. You can insert pseudo-entry points there which are additional functions.

A smart contract always returns the result of execution: a list of operations and the value of storage.

  • Written by Pavel Skoroplyas
  • Produced by Svetlana Koval
  • Stylistic framework by Dmitri Boyko
  • Visuals by Krzystof Szpak
  • Layouts by Zara Arakelian
  • Development by Oleksandr Pupko
  • Directed by Vlad Likhuta