How to write SCaml

Programming model of SCaml

Learn OCaml

SCaml is basically OCaml. If you do not write OCaml, you will not write SCaml either. Learn OCaml first at https://ocaml.org/learn .

Examples

There are small tests under src/tests/ in the repository.

One file per one contract

In SCaml, one contract must be written in one .ml file.

Start with open SCaml

The API functions to access Michelson primitives are declared in a library module SCaml. You should always open SCaml first. You can omit it but there is almost no point for it.

Check SCaml.mli to find what available

Module SCaml should be found at directory `opam config var prefix`/lib/scaml:

$ opam config var prefix
/where-you-install-SCaml/_opam
$ ls /where-you-install-SCaml/_opam/lib/scaml
META		SCaml.cmx	SCamlib.cma	opam
SCaml.cmi	SCaml.ml	SCamlib.cmxa
SCaml.cmt	SCaml.mli	SCamlib.cmxs
SCaml.cmti	SCamlib.a	dune-package
$ cat /where-you-install-SCaml/_opam/lib/scaml/SCaml.mli
...
type ocaml_int = int

type nat = Nat of ocaml_int
(** Arbitrary length nat *)

type int = Int of ocaml_int
(** Arbitrary length int *)

type tz = Tz of float
(** Tezzies.  The smallest unit is micro tz, [Tz 0.000001]. *)
...

Check SCaml.mli in this directory or the source code of SCaml or in the repository to learn what functions are available and their comments.

Entrypoint

A Michelson smart contract has one or more entrypoints where its execution starts.

Unless specified explicitly, the last value definition in an .ml file is considered as the entrypoint of the code.

Multiple entrypoint support

To have more than one entrypoints, they must be attributed with [@entry]. For example:

open SCaml

(* defines an entrypoint "init" *)
let [@entry] init () _ = Int 0

(* defines an entrypoint "do" *)
let [@entry name="do"] do_ () x = x + Int 1

Each entrypoint can optionally take a binding name=<name> to specify the name of the entrypoint. If omitted, the name of the defined variable is used for it.

Entrypoint access

Accessing a specific entrypoint of a contract is via address literals like Address "KT1.....%entrypoint_name".

Entrypoint typing

An additional small typing phase to enforce the types of each entrypoint to be

'parameter -> 'storage -> (SCaml.operation list, 'storage)

where 'parameter and 'storage are closed types (without type variables) for the paramter type of the entrypoint and the type of the contract storage respectively.

Arithmetics

Arithmetic types

In SCaml, there are 3 arithmetic types:

Integers int
Arbitrary sized integers. Int 3, Int (-23). This is not the native int type of OCaml but defined in SCaml.
Natural numbers nat
Arbitrary sized natural numbers. Nat 0, Nat 12345.
Tezzies tz
It takes a float but internally it is handled as a natural number of micro tezzies. Tz 0.000001 is for 1 mutez. Note that the size is fixed to 64bits (signed) and Tz 9223372036854.775807 is the maximum value for tz. Any overflow fails the execution of contracts.

There is no overloading of arithmetic constants. Even simple integers must be explicitly wrapped with its constructor Int. This is lousy but required for the simplicitly of the language.

Arithmetic operators

Operations over arithmetics are also monomorphic and not overloaded just as OCaml.

Integers
+, -, *, etc
Natural numbers
+^, -^, *^, etc. (^ depicts “positive”.)
Tezzies
+$, -$, *$, etc. ($ depicts “currency”.)

Primitive containers

SCaml has 4 built-in container types: lists, sets, maps, and big maps. Modules List, Set, Map and BigMap under SCaml provides the APIs for them.

Primitive container literals

Lists, sets, and maps have literals:

Lists
[ Int 1; Int 2; Int 3 ]
Sets
Set [ Nat 1; Nat 2; Nat 3 ]
Maps
Map [ (Nat 1, "1"); (Nat 2, "2"); (Nat 3, "3") ]

Currently, all the elements in Set _ and Map _ must be constants.

Big maps have no way of writing literals in Michelson. Neither in SCaml.

Bytes
Bytes "0123456789abcdef", Even number of [0-9a-f] characters.
Address
Address "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN"
Keys
Key "edpkuSR6ywqsk17myFVRcw2eXhVib2MeLc9D1QkEQb98ctWUBwSJpF"
Key hashes
Key_hash "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx"
Signatures
Signature "edsigu4chLHh7rDAUxHyifHYTJyuS8zybSSFQ5eSXydXD7PWtHXrpeS19ds3hA587p5JNjyJcZbLx8QtemuJBEFkLyzjAhTjjta"
Timestamps
Timestamp "2019-09-11T08:30:23Z", RFC3339 string.

These constructors must take string literals, therefore SCaml has no conversion from string to these types. (It is impossible in Michelson.)

Self

Contract.self returns the contract of the code itself. It has a type 'a contract but actually it must agree with the real type of the contract.

Unlike Michelson's SELF operator, Contract.self can appear inside a function. Even if the function value is sent to another contract, it does not point to the other contract but to the original contract which uses Contract.self.

Contract creation and call

SCaml provides the lowest interface of contract creations and invocations.

Contract creation

SCaml provides the lowest level of APIs to originate contracts within SCaml:

From embeded Michelson codes
Contract.create_from_tz_code <Michelson code string> takes a string literal of Michelson source code.
From Michelson code file
Contract.create_from_tz_file <Michelson code path name> takes a string literal of Michelson source file path. The Michelson code in the source file is included at the compilation time. Be careful of setting proper build dependnecy if the Michelson source file is generated from another language.

Contract call

Operation.transfer_tokens is the only API (so far) to call other contracts within SCaml contracts.


Last modified March 6, 2020: typos (f797c2e)