Skip to main content

Smart Contracts - Language

A Smart Contract is defined as followed:

The actions blocks contain the code to execute when a specific event is triggered.

The condition inherit block is used to check the outgoing transaction (result) of the contract.

The condition transaction block is used to check the incoming transaction that triggered the contract.

The condition oracle block is used to check the incoming oracle transaction when there is an action triggered by oracle.

Version attribute

A version attribute must be specified on the first line of the code. This version is used by the interpreter to have the right behavior in case of breaking changes.

@version 1
# Rest of the code

Basic Types

This language is based on a functional language (elixir), but we added some imperative twist to it, to make it friendlier!

  • Comments are lines that start with a #
  • Strings are double quoted "I am a string"
  • String interpolation "hello #{name}"
  • Integers & Floats can use _ at your convenience 10_000 10_000.0
  • Floats can use the scientific format: 1.0e2 == 100
  • Booleans syntax: true / false
  • The absence of value: nil

Hexadecimals

In the Archethic blockchain, we extensively uses hexadecimals to convert all addresses, public keys or hashes on the user-facing interfaces. A Smart Contract is one of these interfaces.

To reduce the possibility of errors when comparing hexadecimals ("000ABCD" != "000abcd" even if it's actually the same value), we introduced a new syntax: 0x. The goal of this syntax is only to facilitate comparaison: 0x000ABCD == 0x000abcd.

# don't
transaction.address == "000ABCD123"

# do
transaction.address == 0x000ABCD123 # no quotes!

So whenever you write a hexadecimal value by hand, prefix it with 0x.

ASCII character in string

If you need to use some specific character in a string, you can use their hexadecimal representation by prefixing the hexadecimal with \x

"hello" == "\x68\x65\x6C\x6C\x6F"
# true

Comparaison

We compare by value, which means you can pretty much compare anything and it will work as you expect.

  • 1 == 1.0
  • [1,2] == [1,2]
  • [name: "John"] == [name: "John"]
  • !true == false
  • true != false
  • if <expr> do ... end
  • if <expr> do ... else ... end

Arithmetic

  • 1 + 2 == 3
  • 2.0 - 1.1 == 0.9
  • 1 * 2.0 == 2
  • 1 / 1 == 1.0
  • 1 / 0 contract failure
info

The arithmetic is done with the Decimal library to ensure there is no floating point precision issue. The only thing to keep in mind is that we truncate at decimal 8.

Ranges

  • 1..5 is equivalent to [1,2,3,4,5]

Loop

  • for name in names do ... end
  • for i in 1..10 do ... end

Variables & Scopes

Variables are not typed, you can assign any value to them.

Variables are mutable, if you update a variable declared in a parent scope, it will also update the value of this variable for the parent scope.

A new scope is created every time you enter a new block (do .. end). A scope can access (read/write) the variables declared in its parent's scope, but not its child's scope.

names = ["Tom", "Jerry", ""]
text = ""
for name in names do # ENTER SCOPE 1
if name != "" do # ENTER SCOPE 1.1
new_line = "\n"
text = "#{name}#{new_line}"
end # EXIT SCOPE 1.1
# new_line does not exists here
end # EXIT SCOPE 1
# test = "Tom\nJerry\n"
# name and new_line does not exists here

Here's the tree of scopes and variables for the above example:

[SCOPE 0]
├── names
├── text
└── [SCOPE 1]
├── name
└── [SCOPE 1.1]
└── new_line

Lists

The list is the data structure to work with collections. The syntax is: [1, 2]. An empty list is: []. There is a List module in the library to manipulate lists.

info

Lists are actually Linked Lists.

Maps

The map is the key-value data structure in the Smart Contract language. The syntax is: [key1: 1, key2: 2] or if key starts with a number: ["00001abc...": 1]. The keys must be strings. There is 3 ways to retrieve a value:

  • map.property
  • map["property"]
  • Map.get(map, "property")

We suggest to use the 1st syntax as often as possible. Use the 2nd or 3rd when dealing with dynamic properties.

There is a Map module module in the library to manipulate maps.

note

Since the maps and the lists have a very close syntax, it is impossible to differentiate an empty map from an empty list. So, for semantic reasons, to create an empty map, use the Map.new() function call.

tip

The brackets are mostly optional! key1: 1, key2: 2 will work as well. But if you have a nested map, you cannot omit them!

Functions

In the Archethic Smart Contract Language, you can declare 2 types of functions:

  • Internal Functions declared with the fun keyword
  • Exported Functions declared with the export fun keyword

Internal Functions

Internal functions are functions that are only available in the Smart Contract. They are not callable from the outside.

They are declared with the fun keyword and can be called from the Action or Condition blocks.

They can have 0 argument:

fun hello() do
"Hello World!"
end

Or more:

fun sum(a, b) do
a + b
end

You can also have the same function name with different signature:

fun sum(a, b) do
a + b
end

fun sum(list) do
acc = 0
for i in list do
acc = acc + i
end
acc
end

These functions aren't able to call another internal function but can call an Exported one.

info

Internal functions are able to use library module functions tagged as I/O but not the functions tagged as UPDATE_CONTRACT.

Exported Functions

Exported functions are callable from the Smart Contract but also through the outside via the JSON-RPC API. They are declared like internal function but with the export fun syntax.

export fun get_current_count() do
State.get("count")
end

Unlike internal functions, they can be called from any block of code.

info

External functions are not able to use library module functions tagged as I/O or Transaction.

Library

You may use any functions from the library. The syntax is Module.function(arg1, arg2).

tip

The parenthesis are actually optional! Module.function arg1, arg2 will work as well.

To see the list of functions available in the Smart Contract Language, check the Library page.

Errors

We introduced in v1.5.0 the throw keyword. It's useful to provide insightful errors to the end users and helps the dApp developers interacting with contracts.

if Crypto.hash(secret) != State.get("secret_hash") do
throw code: 1, message: "invalid secret", data: secret
end

The throw takes a map with the following keys:

  • code: an integer of your choice that dApp developers may use to react on a specific error. It should be uniquely identifiable within the contract.
  • message: a string describing the error.
  • data (opt): some context relevant to the error (can be any type).

Reserved keywords

  • for
  • do
  • end
  • if
  • else

... to be completed