SmartPy is a Python library for constructing Tezos smart contracts. It comes with a compiler that generates Michelson code.

Building Blocks

Expressions

Like most languages, SmartPy has expressions. For example self.data.x represents the contract storage field x and 2 represents the number 2, whereas self.data.x + 2 represents their sum.

Commands

Commands do something. For example, sp.verify(self.data.x > 2) checks that the field x is larger than 2 and raises an error if it isn’t.

Entry Points

An entry point is a method of a contract class that can be called from the outside. They need to be marked with the @sp.entry_point decorator. For example the following entry point checks that the argument given is larger than 2:

    @sp.entry_point
    def checkLargerThanTwo(p):
        sp.verify(p > 2)
sp.entry_point

Decorator to introduce an entry point.

Public and private entry point

We can restrict an entry point to only appear in SmartPy but not in the generated Michelson contract. This is useful to implement checks or initialization in SmartPy after origination in the test but before real blockchain origination. One can use the full SmartPy machinery such as sp.verify, etc.

This is also useful to build custom UI through simulation by adding:

    @sp.private_entry_point
    def editStorage(self, params):
        self.data.set(params)

Contracts

A SmartPy contract consists of a state together with one or several entry points. It is a class definition that inherits from sp.Contract. The constructor (__init__) makes a call to self.init and initializes fields that make up the contract’s state.

class Hello(sp.Contract):
    def __init__(self):
        self.init(x = 0)

    @sp.entry_point
    def setX(newX):
        self.data.x = newX

    @sp.entry_point
    def checkLargerThanX(p):
        sp.verify(p > self.data.x)

Types

Types are usually automatically infered and not explicitely needed. SmartPy types are all of the form sp.T<TypeName>.

Meta-Programming

The functions described here are used to construct a smart contract. Smart contracts are executed once they are deployed in the Tezos block chain (although they can be simulated). This is indeed meta-programming: we can write a programm that writes a program (a.k.a. constructs a contract).

Note that in the example self.data.x + 2, the actual addition isn’t carried out until the contract has been deployed and the entry point is called.

Typing in SmartPy

Type Inference

Just like in Python, most of the time there is no need to specify the type of an object in SmartPy. For a number of reasons (e.g. because SmartPy’s target language, Michelson, requires types), each SmartPy expression does however need a type. Therefore SmartPy uses type inference in order to determine each expressions type.

In practice this means that information about an expression is gathered according to its usage: for example, when somewhere in your contract you write self.data.x == "abc", SmartPy will automatically determine and remember that self.data.x is a string.

Note that SmartPy types are distinct from Python types: self.data.x == "abc" has the Python type sp.Expr (simply because it is a SmartPy expression), whereas it has the SmartPy type sp.TBool (see below).

While most of the time the user will not write many types explicitly it is beneficial to at least have a basic understanding of what they are. This also helps understanding error messages better.

Primitive Data Types Overview

SmartPy has the following primitive types:

sp.TUnit

A type with a single value, namely sp.unit.
See Unit.

sp.TBool

The type of boolean values, True, False, sp.bool(True) and sp.bool(False).
See Booleans.

sp.TInt

The type of integer values, e.g. -42 or sp.int(-42).
See Integers.

sp.TNat

The type of non-negative integer values, e.g. sp.nat(42).
See Integers.

sp.TIntOrNat

The type of integer values whose type is still undetermined between sp.TInt or sp.TNat, e.g. 42 or sp.intOrNat(42).
See Integers.

sp.TString

The type of strings, e.g. "abc" or sp.string("abc").
See Strings.

sp.TBytes

The type of serialized data, e.g. sp.pack(42).
See Bytes.

sp.TSaplingState
sp.TSaplingTransaction

Experimental types for Sapling integration.
See Sapling.

Container Types

SmartPy has a few built-in data structures. Their types are:

sp.TPair

The type of pairs, e.g. (1, True).
See Pairs.

sp.TList

The type of lists, e.g. [1, 2, 3].
See Lists.

sp.TSet

The type of sets, e.g. {1, 2, 3}.
See Sets.

sp.TMap

The type of maps, e.g. {'A': 65, 'B': 66, 'C'; 67}.
See Maps.

sp.TBigMap

The type of lazy maps.
See Big Maps.

sp.TOption

The type of optional values.
See Options.

There is no array in SmartPy because they are missing in Michelson, we usually use maps instead. There are three helper functions: sp.vector(..), sp.matrix(..) and sp.cube(..) that take respectively a list, a list of lists and a list of lists of lists and return maps.

Convention. Container constructor names are uncapitalized and their types are capitalized. sp.map(…​) of type sp.TMap(…​), sp.big_map(…​) of type sp.TBigMap(…​), sp.set(…​) of type sp.TSet(…​), sp.list(…​) of type sp.TList, sp.pair(…​) of type sp.TPair(…​), etc.

Tezos-specific data types

A few data types are important in the context of smart contracts:

sp.TMutez

The type of Tezos tokens, e.g. sp.mutez(42000) stands for 0.042 Tez, wheras sp.tez(42) stands for 42 Tez.
See Mutez.

sp.TTimestamp

A moment in time, e.g. sp.timestamp(1571761674). The argument to sp.timestamp is in "epoch" format, i.e. seconds since 1970-01-01.
See Timestamps.

sp.TAddress

An address of a contract or account, e.g. sp.address("tz1YtuZ4vhzzn7ssCt93Put8U9UJDdvCXci4").
See Contracts and Addresses.

sp.TContract(t)

A contract whose parameter is of type t.
See Contracts and Addresses.

sp.TKey

A public cryptographic key.
See [Key].

sp.TKeyHash

The hash of a public cryptographic key.
See Key Hash.

sp.TSignature

A cryptographic signature.
See Signatures.

sp.TChainId

The type of chain identifiers, i.e., small identifiers for the different main and test Tezos blockchains.
See Chain Id.

sp.TSecretKey

The type of secret keys. Secret keys cannot appear in smart contracts but Tests and Test Scenarios use them.
See Secret Key.

Records and Variants

In SmartPy, we can use custom data types called records and variants.

Records

A record type is defined simply by enumerating the field names together with types, e.g. sp.TRecord(x = sp.TInt, y = sp.TInt).
It represents a cartesian product of several types similar to a struct in C.
Please see the Records section.

Variants

A variant type, or sum type, is defined simply by enumerating the constructor names together with their inner types, e.g. sp.TVariant(default_choice = sp.TInt, alternative_choice = sp.TString).
It represents a union of several choices, similar to a clean version of a struct with an enum/union pattern in C.
Please see Variants.

Setting a type constraint in SmartPy

This is usually not needed for small contracts or prototypes but gets useful typically when interacting with the outside world, implementing a given interface, etc.

self.init_type(t)

Constrain the contract storage to be of type t (called in the __init__ constructor).
This is useful because it allows to precise the storage type or even to define storage-less contracts.
In a test, if not determined in the __init__ method, the storage can be initialized by calling c.set_storage(expression).
See reference Init Type Only template.
This is not mandatory but is appreciated by many in practice.

    class MyContract(sp.Contract):
    def __init__(self):
        ## Possibly set the storage type
        self.init_type(sp.TRecord(a = sp.TInt, b = sp.TString))
        ## Possibly set the storage
        self.init(...)
sp.set_type(expression, t)

Constrain expression to be of type t. This can only be used as a command inside a contract.
There is no equivalent instruction in Michelson.
A usual pattern for big contracts is to explicitely setting types in the first lines of entry points.
This is usually not needed but is appreciated by many in practice.

    @sp.entry_point
    def my_entry_point(self, x, y, z):
        ## First set types
        sp.set_type(x, sp.TInt)
        sp.set_type(y, sp.TString)
        sp.set_type(z, sp.TList(sp.TInt))
        ## Then do the computations
        ...
sp.set_type_expr(expression, t)

Constrain expression to be of type t. This can only be used as an expression.
There is no equivalent instruction in Michelson.

A few words of caution about the differences between sp.set_type and sp.set_type_expr:

# Inside a contract:

    @sp.entry_point
    def my_entry_point(self, params):
        ...
        ## Warning: this is not taken into account (the expression is simply dropped).
        sp.set_type_expr(params, sp.TInt)
        ...

    @sp.entry_point
    def my_entry_point(self, params):
        ...
        ## This is taken into account (when we call params afterward).
        params = sp.set_type_expr(params, sp.TInt)
        ... params ...

    @sp.entry_point
    def my_entry_point(self, params):
        ...
        ## This is taken into account (sp.set_type is a command).
        sp.set_type(params, sp.TInt) ## This is taken into account.
        ...

# Inside a test scenario:

    scenario += ...
    ## This is illegal (command outside of a contract).
    sp.set_type(..., ...)

    ## This is OK (usually useless nonetheless)
    x = sp.set_type_expr(..., ...)
    scenario += c.f(x)
sp.set_record_layout(expression, layout)
sp.set_variant_layout(expression, layout)

Constrain expression to be of type record or variant with the corresponding layout. This can be used as an expression or a command.
A layout is a Python expression listing all fields or constructors in a binary tree structure such as ("b", ("a", "c")).
There is no equivalent instruction in Michelson.
See reference Data Type Layouts template.

sp.set_type_record_layout(type_expression, layout)
sp.set_type_variant_layout(type_expression, layout)

Same functionality directly applicable to type expressions.

self.init_storage_record_layout(layout)

Constrain the storage, supposed to be of type record (called in the __init__).
Useful for convenience.

self.init_entry_points_layout(layout)

Constrain the generated parameter type for entry points to be a variant of the declared layout (called in the __init__).
There is no equivalent instruction in Michelson.
See reference Data Type Layouts template.

Containers have built-in optional constraint arguments.

sp.map(l = …​, tkey = …​, tvalue = …​)

Define a map of (optional) elements in l with optional key type tkey and optional value type tvalue.

sp.big_map(l = …​, tkey = …​, tvalue = …​)

Define a big_map of (optional) elements in l with optional key type tkey and optional value type tvalue.

sp.set(l = …​, t = …​)

Define a set of (optional) elements in l with optional element type t.

sp.list(l = …​, t = …​)

Define a list of (optional) elements in l with optional element type t.

SmartPy Types and Operators

SmartPy expressions have the Python type sp.Expr. In this class many methods are overloaded so to provide convenient syntax: e.g. we can write self.data.x + 2 for the SmartPy expression that represents the sum of the storage field x and 2.

The on-chain evaluation of an expression does not have any side effects except a possible failure (such as accessing a missing element in a map).

Any Python literal (string or integer) that is used in place of an sp.Expr is automatically converted. Thus we can write self.data.x + 1 instead of self.data.x + sp.int(1).

Booleans

The type of SmartPy booleans is sp.TBool.
The corresponding type in Michelson is bool.

SmartPy has the following logical operators:

~ e

Return the negation of e, where e must be a boolean.
See Michelson NOT.

e1 | e2

Return True if e1 is True, otherwise e2. Both e1 and e2 must be booleans.
SmartPy uses a lazy version of Michelson OR.

e1 & e2

Return False if e1 is False, otherwise e2. Both e1 and e2 must be booleans.
SmartPy uses a lazy version of Michelson AND.

e1 ^ e2

Compute e1 xor e2.
See Michelson XOR.

Note that, unlike in Python, & and | do short-circuiting on SmartPy boolean expressions: for example, the evaluation of (x==x) | (self.data.xs[2] == 0)) will not fail.

Also, please note that not, and, and or cannot be overloaded in Python. Hence, we cannot use them to construct SmartPy expressions and, as is customary for custom Python libraries, we use ~, &, and | instead.

Bytes

The type of byte arrays in SmartPy is sp.TBytes.
The corresponding type in Michelson is bytes.

See reference Strings and Bytes template.

Literals: sp.bytes('0x…​')

Introduce a sp.TBytes in hexadecimal notation.

e1 + e2

Concatenate two bytes.
See Michelson CONCAT.

sp.concat(l)

Concatenate a list l of sp.TBytes.
See Michelson CONCAT.

sp.len(e)

Return the length of e.
See Michelson SIZE.

sp.slice(expression, offset, length)

Slices expression from offset for length characters. sp.slice(expression, offset, length) is of type sp.TOption(sp.TBytes).
See Michelson SLICE.

sp.pack(x)

Serialize a piece of data x to its optimized binary representation. Return an object of type sp.TBytes.
See Michelson PACK.

sp.unpack(x, t = …​)

Parse the serialized data from its optimized binary representation. There is an optional argument t to fix the type. sp.unpack(e, t) is of type sp.TOption(t).
See Michelson UNPACK.

sp.blake2b(value)
sp.sha512(value)
sp.sha256(value)

The functions sp.blake2b, sp.sha512, sp.sha256 take a sp.TBytes value and return the corresponding hash as a new sp.TBytes value.
See Michelson BLAKE2B, SHA256, and SHA512.
See reference Hash Functions template.

Chain Id

The type of chain identifiers in SmartPy is sp.TChainId.
The corresponding type in Michelson is chain_id.

sp.chain_id

The id of the network currently evaluating the transaction.
See Michelson CHAIN_ID.

Literals: sp.chain_id_cst("0x9caecab9")

Introducing a chain id by its hexadecimal representation.

Please note that chain ids are non comparable. Equality can be verified by using sp.verify_equal.

Comparable Types

Comparison operators ==, !=, <, <=, >, >=

The comparison operators ==, !=, <, <=, >, >= behave just like in python. They return a boolean. They are defined on SmartPy comparable types which extend Michelson’s.
See Michelson EQ, NEQ, LT, LE, GE, GT, and COMPARE.

sp.min(x, y) and sp.max(x, y)

sp.min(x, y) and sp.max(x, y) return the minimum and maximum of x and y, respectively.

Contracts and Addresses

Following Michelson, there are two ways to point to other contracts in SmartPy: typed sp.TContract(t) for contracts with an entry point of type t and untyped sp.address.
The corresponding types in Michelson are contract and address.

See reference On Chain Contract Calls - Collatz and FA1.2 templates.

Literals: sp.address("tz…​ or KT…​")

Literals for addresses.

sp.self

The current contract of type sp.TContract(t) for some type t.
See Michelson SELF.

sp.self_entry_point(entry_point = '')

The optional entry point named entry_point of the current contract; of type sp.TContract(t) where t is the type of the entry point’s parameters. If entry_point is empty, use current entry point.

sp.to_address(contract)

Computes the address, of type sp.TAddress, of a contract of type sp.TContract(t) for some type t.
See Michelson ADDRESS.

sp.sender

The address that called the current entry point.
See Michelson SENDER.

sp.source

The address that initiated the current transaction.
It may or may not be equal to sp.sender.
See Michelson SOURCE.

sp.contract(t, address, entry_point = "")

Cast an address of type sp.TAddress to an optional typed contract of type sp.TContract(t).

  • When optional parameter entry_point is empty or unspecified, it returns sp.some(c), where c is a contract handle of type sp.TContract(t), if address, of type sp.TAddress, points to a contract that expects a parameter of type t. Otherwise it returns sp.none.

  • When entry_point is not empty, it returns the specific entry point specified by the string entry_point of the contract. t must match the entry point’s expected parameter type. Otherwise, it returns sp.none.
    Due to restrictions of Michelson, it only works properly for contracts avec multiple entry points.
    See Michelson CONTRACT.

sp.transfer(arg, amount, destination)

Call the destination contract with argument arg while sending the specified amount to it. Note that destination must be of type sp.TContract(t). The type of arg must be t, i.e. the argument sent to the destination must be consistent with what it expects.
See Michelson TRANSFER_TOKENS.

sp.send(destination, amount)

Send the specified amount to the destination contract. Will fail if destination (of type sp.TAddress) does not resolve to a contract that expects a sp.TUnit argument (e.g. an account that does not result in any actions).

sp.implicit_account(key_hash)

See Key Hash for description.
See Michelson IMPLICIT_ACCOUNT.

Example: Suppose we have an address a of a contract with an entry point "foo" that expects an integer. To call it, we first obtain a handle to the entry point:

  c = sp.contract(sp.TInt, a, entry_point = "foo").open_some()

The call to open_some() asserts that the address resolved successfully and that the referenced entry point indee expects an integer. Now that we have our handle c, we can call the contract e.g. with the argument -42 while sending along 0 tokens:

  sp.transfer(-42, sp.mutez(0), c)

Integers

There are two main types of integers in SmartPy: signed integers sp.TInt and non negative ones sp.TNat.
The corresponding types in Michelson are int and nat.

SmartPy also uses a third definition sp.TIntOrNat which stands for integers that are not yet determined as sp.TInt or sp.TNat.

Literals: 1, 2, 0, -5, etc.

Literal of type sp.TIntOrNat when non negative and sp.TInt otherwise. The usual way to input literals in SmartPy, thanks to type inference.

Literals: sp.int(i)

A literal of type sp.TInt when i is a Python integer literal.

Literals: sp.nat(n)

A literal of type sp.TNat when n is a non negative Python integer literal.

e1 + e2, e1 - e2, - e, e1 * e2, e1 % e2, e1 // e2, e1 << e2, e1 >> e2

The usual arithmetic operators +, -, *, %, //, <<, >> behave just like in Python.
See Michelson ADD, SUB, NEG, MUL, EDIV, LSL and LSR.

e1 | e2

Compute bitwise e1 or e2 for e1 and e2 of type sp.TNat. Result is also of type sp.TNat.
See Michelson OR.

e1 & e2

Compute bitwise e1 and e2 for e1 and e2 of type sp.TNat. Result is also of type sp.TNat.
See Michelson AND.

e1 ^ e2

Compute bitwise e1 xor e2 for e1 and e2 of type sp.TNat. Result is also of type sp.TNat.
See Michelson XOR.

In SmartPy, type inference of arithmetic operators imposes that both sides have the same type. This constraint can be relaxed by explicitly using sp.to_int.

Int vs Nat

abs(i)

Return the absolute value of i. abs converts an sp.TInt into a sp.TNat.
See Michelson ABS.

sp.to_int(n)

Convert a sp.TNat into an sp.TInt.
See Michelson INT.

sp.is_nat(i)

Convert a sp.TInt into an sp.TOption(sp.TNat). sp.is_nat(i) == sp.some(n) when i is a non negative sp.TInt and sp.none otherwise.
See Michelson ISNAT.

sp.as_nat(i)

Convert an sp.TInt into a sp.TNat and fails if not possible, i.e., when i is negative. It is implemented as sp.as_nat(i) = sp.is_nat(i).open_some().

Division

e1 / e2

The / operator performs truncated integer division when applied to SmartPy expression, just like // does. This is different to Python 3 (where / doesn’t truncate and yields a float when applied to integers).

sp.ediv(num, den)

Perform Euclidian division.
See Michelson EDIV.

Keys

Public Key

The type of public keys in SmartPy is sp.TKey.
The corresponding type in Michelson is key.

Literals: sp.key('tz…​')

A literal key is of the form sp.key(s) where s is a Python string.

sp.hash_key(key)

See Key Hash.

sp.check_signature(k, s, b)

See Signatures.

Key Hash

The type of key hashes in SmartPy is sp.TKeyHash.
The corresponding type in Michelson is key_hash.

See reference Baking Swap template.

Literals: sp.key_hash('tz…​')

A literal key hash is of the form sp.key_hash(s) where s is a Python string 'tz…​'.

sp.hash_key(key)

Compute the base58check of key (which must be of type sp.TKey).
It returns a sp.TKeyHash value.
See Michelson HASH_KEY.

sp.set_delegate(baker)

Set or unset an optional baker of type sp.TOption(sp.TKeyHash).
See Michelson SET_DELEGATE.

sp.implicit_account(key_hash)

Return the implicit account of type sp.TContract(sp.TUnit) from a sp.TKeyHash.
See Michelson IMPLICIT_ACCOUNT.

Secret Key

The type of secret keys in SmartPy is sp.TSecretKey.
There is no corresponding type in Michelson.

Secret keys are used in tests.
See Cryptography in Test Scenarios and Signatures.

Lambdas

The type of functions in SmartPy is sp.TLambda(t1, t2) where t1 is the parameter type and t2 the result type.
The corresponding type in Michelson is lambda.

See reference Lambdas template.

sp.build_lambda(f)

Define a SmartPy lambda from a Python function or lambda.
For example, sp.build_lambda(lambda x: x + 3) represents a function that takes an argument x and returns x + 3.
See Michelson LAMBDA.

sp.global_lambda

Decorator to introduce a lambda that is also a global variable.

class MyContract(sp.Contract):
    ...

    @sp.global_lambda
    def square_root(x):
        sp.verify(x >= 0)
        y = sp.local('y', x)
        sp.while y.value * y.value > x:
            y.value = (x // y.value + y.value) // 2
        sp.verify((y.value * y.value <= x) & (x < (y.value + 1) * (y.value + 1)))
        return y.value

    @sp.entry_point
    def h(self, params):
        self.data.result = square_root(params)

See Michelson LAMBDA.

f(x)

Call a lambda.
If f is of type sp.TLambda(t1, t2) and x is of type t1 then f(x) is of type t2.
See Michelson EXEC.

f.apply(x)

Partially apply a lambda.
If f is of type sp.TLambda(sp.TPair(tp1, tp2), target) and x is of type tp1 then f.apply(x) is of type sp.TLambda(tp2, target).
See Michelson APPLY.

Lists

The type of lists over type t is sp.TList(t).
All elements need to be of the same type.
The corresponding type in Michelson is list.

See reference Lists template.

Literals: sp.list(l = …​, t = …​) and standard Python lists

Define a list of (optional) elements in l whose optional type is t.
See Michelson NIL.
Standard Python lists are also accepted, e.g., [1, 2, 3], ["aa", "bb", "cc"].

myList.push(element)

Push an element on top of a list.
See Michelson CONS.

sp.len(myList)

Return the length of list myList.
See Michelson SIZE.

sp.concat(myList)

Concatenate a list myList of sp.TString or sp.TBytes.
See Michelson CONCAT.

sp.range(x, y, step = 1)

A list from x (inclusive) to y (exclusive). Useful in conjunction with sp.for loops.

myList.rev()

Reverse a list.

sp.for …​ in …​:

Iterate on a list.
See Michelson ITER and MAP.

    @sp.entry_point
    def sum(self, params):
        self.data.result = 0
        sp.for x in params:
            self.data.result += x

To iterate on sp.TMap(key, value) or sp.TSet(elem), we first convert to an sp.List(..) with e.items(), e.keys(), e.values() or e.elements().

Please note that there is no way to perform random access on a list.

Maps and Big Maps

Maps

Maps in SmartPy are of type sp.TMap(key, value).
The corresponding type in Michelson is map.

See reference Lists template.

Literals: sp.map(l = …​, tkey = …​, tvalue = …​)

Define a map of (optional) elements in l (a Python dictionary) with optional key type tkey and optional value type tvalue.
See Michelson EMPTY_MAP and PUSH.

Literals: standard Python dictionaries

Standard Python dictionaries are also accepted, e.g., {0 : "aa", 12 : "bb" }.
See Michelson EMPTY_MAP and PUSH.

myMap[key] = value

Set or replace an element in a map.
See Michelson UPDATE.

del myMap[key]

Delete an element from a map.
See Michelson UPDATE.

myMap[key]

Look up an entry in a map. It fails if the entry is not found. key must have the type of its keys.
See Michelson GET.

myMap.get(key, defaultValue = None)

Same as e[key]. If defaultValue is specified and there is no entry for key in myMap, returns defaultValue instead of failing.
See Michelson GET.

myMap.contains(key)

Check whether the map myMap contains the key.
See Michelson MEM.

sp.len(myMap)

Return the size of the map myMap.
See Michelson SIZE.

myMap.items()

Return the sorted list of key-value entries in a map (not a big_map). Each entry is rendered as record with the two fields key and value.

myMap.keys()

Return the sorted list of keys of a map (not a big map).

myMap.values()

Return the list of values of a map (not a big_map), sorted by keys.

Big Maps

Big maps, of type sp.TBigMap(key, value), are lazy datastructures that are only serialized and deserialized on demand.
The corresponding type in Michelson is big_map.
We cannot iterate on big maps or compute their sizes.

See reference Lists template.

Literals: sp.big_map(l = …​, tkey = …​, tvalue = …​)

Define a big_map of (optional) elements in l with optional key type tkey and optional value type tvalue.

myBigMap[key] = value

Set or replace an element in a big map.
See Michelson UPDATE.

del myBigMap[key]

Delete an element from a big map.
See Michelson UPDATE.

myBigMap[key]

Look up an entry in a big map. Fails if the entry is not found. key must have the type of its keys.
See Michelson GET.

myBigMap.get(key, defaultValue = None)

Same as myBigMap[key]. If defaultValue is specified and there is no entry for key in myBigMap, returns defaultValue instead of failing.
See Michelson GET.

myBigMap.contains(key)

Check whether the big map myBigMap contains the key.
See Michelson MEM.

Mutez

The type of amounts in SmartPy is sp.TMutez.
The corresponding type in Michelson is mutez.

Literals: sp.tez(…​) and sp.mutez(…​)

sp.tez(10) and sp.mutez(500) represent respectively 10 tez and 500 mutez.

sp.amount

The amount of the current transaction.
See Michelson AMOUNT.

sp.balance

The balance of the current contract.
See Michelson BALANCE.

e1 + e2 and e1 - e2

Usual arithmetic operators on sp.TMutez.
See Michelson ADD and SUB.

sp.split_tokens(amount, quantity, totalQuantity)

Compute amount * quantity / totalQuantity where amount is of type sp.TMutez, and quantity and totalQuantity are of type sp.TNat.

sp.ediv(num, den)

Perform Euclidian division.
See Michelson EDIV.

Options

Optional values in SmartPy are of type sp.TOption(t).
The corresponding type in Michelson is option.
They represent values of type t or nothing.

Optional values are useful for accomodating missing data: e.g. if your contract has an optional expiry date, you can add a field expiryDate = sp.none to the constructor. Then, if you want to set the expiry date, you write expiryDate = sp.some(sp.timestamp(1571761674)). Conversely, if you want to unset it again, you write expiryDate = sp.none. SmartPy automatically infers the type sp.TOption(sp.TTimestamp) for x, so you don’t have to make it explicit.

sp.some(e)

Define an optional value containing an element e.
See Michelson SOME.

sp.none

Define an optional value not containing any element.
See Michelson NONE.

e.is_some()

Check that an optional value contains an element, i.e., checks whether it is of the form sp.some(…​).
See Michelson IF_NONE.

e.open_some()

If e is equal to sp.some(x), return x. Otherwise fail.
See Michelson IF_NONE.

e.open_some()

Access the value contained in an optional value if it exists and fails otherwise.
See Michelson IF_NONE.

Pairs

Pairs in SmartPy are of type sp.TPair(t1, t2).

sp.pair(e1, e2)

Define a pair of two elements.
See Michelson PAIR.

sp.fst(..) and sp.snd(..)

sp.fst(..) and sp.snd(..) are used to access elements in pairs. See Michelson CAR and CDR.

Records

Records in SmartPy are of type sp.TRecord(**kargs) where kargs is a Python dict of SmartPy types indexed by strings.
They generalize the Michelson type pair.

Literals: sp.record(field1 = value1, field2 = value2, .., )

Introduce a record.

Field access

If x is a record and a one of its fields, we can obtain the field’s value by writing x.a.

Sets

Sets in SmartPy are of type sp.TSet(element).
The corresponding type in Michelson is set.

See reference Lists template.

Literals: sp.set(l = …​, t = …​)

Define a set of (optional) elements in l with optional element type t.
See Michelson EMPTY_SET.

Literals: standard Python sets

Sets can also be defined using regular Python syntax {1, 2, 3}. This only works with non-SmartPy specific expressions. For SmartPy expressions, we must use sp.set([e1, e2, …​, en]).

mySet.elements()

Return the sorted list of elements in a set.

mySet.contains(element)

Check whether the set mySet contains the element.
See Michelson MEM.

mySet.add(element)

Add an element to a set.
See Michelson UPDATE.

mySet.remove(element)

Remove an element from a set.
See Michelson UPDATE.

Signatures

The type of signatures in SmartPy is sp.TSignature.
The corresponding type in Michelson is signature.

See reference Signatures and State Channels templates.

sp.check_signature(k, s, b)

Determine whether the signature s (a sp.TSignature value) has been produced by signing b (a sp.TBytes value) with the private key corresponding to k (a sp.TKey public key value).
See Michelson CHECK_SIGNATURE.

sp.make_signature(secret_key, message, message_format = 'Raw')

Forge a signature compatible with sp.check_signature(…​); the message is a TBytes value (usually the result of an sp.pack call), the message_format can also be "Hex" in which case the message will be interpreted as an hexadecimal string.

sp.make_signature is not available for compilation to Michelson (a smart contract cannot manipulate secret keys). It can only be used in Tests and Test Scenarios.

Strings

The type of SmartPy strings is sp.TString.
The corresponding type in Michelson is string.

See reference Strings and Bytes template.

Literal strings "…​" and '…​'

Strings in SmartPy are introduced by simply using regular Python strings of the form "…​" or '…​', or by using sp.string(s) where s is a Python string.

e1 + e2

Concatenates two strings.
See Michelson CONCAT.

sp.concat(l)

Concatenates a list l of strings.
See Michelson CONCAT.

sp.len(e)

Return the length of e.
See Michelson SIZE.

sp.slice(expression, offset, length)

Slices expression from offset for length characters. sp.slice(expression, offset, length) is of type sp.TOption(sp.TString).
See Michelson SLICE.

Timestamps

The type of timestamps in SmartPy is sp.TTimestamp.
The corresponding type in Michelson is timestamp.

See reference Timestamps template.

Literals: sp.timestamp(…​)

A literal timestamp is defined by doing sp.timestamp(i) where i is an integer representing the number of seconds since epoch (January 1st 1970).

sp.now

The minimal injection time on the stack for the current block/priority. For all reasonable purposes, this is a technical detail and sp.now should be understood as the timestamp of the block whose validation triggered the execution.
See Michelson NOW.

sp.timestamp_from_utc(year, month, day, hours, minutes, seconds)

Compute a constant timestamp corresponding to an UTC datetime.

sp.timestamp_from_utc_now()

Compute a constant timestamp corresponding to now. This is fixed at contract or test evaluation time.

e.add_seconds(seconds)

Return a timestamp with seconds added to e, where e must be a sp.TTimestamp and seconds a sp.TInt.
See Michelson ADD.

e.add_minutes(minutes)

Return a timestamp with minutes added to e, where e must be a sp.TTimestamp and minutes a sp.TInt.
See Michelson ADD.

e.add_hours(hours)

Return a timestamp with hours added to e, where e must be a sp.TTimestamp and hours a sp.TInt.
See Michelson ADD.

e.add_days(days)

Return a timestamp with days added to e, where e must be a sp.TTimestamp and days a sp.TInt.
See Michelson ADD.

e1 - e2

Return the difference in seconds between two timestamps.
See Michelson SUB.

Unit

The type of unit values in SmartPy is sp.TUnit.
The corresponding type in Michelson is unit.
It is the return type of commands and the input types of entry points with empty parameters.

sp.unit

There is only a single value of type sp.TUnit which is sp.unit.
See Michelson UNIT.

Variants

Variants in SmartPy are of type sp.TVariant(**kargs) where kargs is a Python dict of SmartPy types indexed by strings.
They generalize the Michelson type or.
They are used to define sum-types, similar to enums in other languages with the extra feature that these enums contain values.

See reference Variant template.

sp.variant('constructor', value)

Introduce a variant.
See Michelson LEFT and RIGHT.

sp.left(value)

Introduce a left/right variant.
See Michelson LEFT.

sp.right(value)

Introduce a left/right variant.
See Michelson RIGHT.

e.is_variant(v)

For a variant, checks whether it is sp.variant(v, …​).
See Michelson IF_LEFT.

e.is_left(v)

For a left/right variant, checks whether it is sp.left(…​).
See Michelson IF_LEFT.

e.is_right(v)

For a left/right variant, checks whether it is sp.right(…​).
See Michelson IF_LEFT.

e.open_variant(v)

If e is equal to sp.variant(v, x), return x. Otherwise fail.
See Michelson IF_LEFT.

Commands

Assignment

lhs = rhs

Evaluate rhs and assign it to lhs. Both lhs and rhs must be SmartPy expressions. Doesn’t work if lhs is a Python variable.

lhs.set(rhs)

Alternative syntax for assignment. Useful when the left-hand-side is a single Python variable, e.g. one referencing a SmartPy local variable (see below).

Local variables

Local SmartPy variables can be defined as follows: x = sp.local("x", 0)

The first argument to sp.local is a string that will be used in error messages. It is advisable to use the same name that is used on the left of =.

Local variable values can be accessed to and updated with the .value field: x.value = 1, x.value = 2 * x.value + 5, etc.

This is mostly useful in loops.

Note that local SmartPy variables are different to Python variables. The latter cannot be updated during contract execution.

As an example, here is how we can commute a square root.

    @sp.entry_point
    def squareRoot(self, x):
        sp.verify(x >= 0)
        y = sp.local('y', x)
        sp.while y.value * y.value > x:
            y.value = (x // y.value + y.value) // 2
        sp.verify((y.value * y.value <= x) & (x < (y.value + 1) * (y.value + 1)))
        self.data.value = y.value
sp.compute(expression)

This is defined as a local variable immediately created and returned with its value.

def compute(expression):
    return local("compute_%i" % (get_line_no()), expression).value

It’s used to evaluate an expression and remember its value.

When we evaluate this code:

    x = sp.compute(self.data.a)
    self.data.a += 1
    y = sp.compute(self.data.a)

Then y contains a value equals to the value of x plus one.

Control and Syntactic Sugar

Since Python doesn’t allow its control statements to be overloaded, certain language constructs are desugared by a pre-processor: sp.if, sp.else, sp.for, sp.while are SmartPy commands. (The desugared version has sp.if_ etc. instead.)

sp.if …​:
sp.else:

A if condition that is evaluated on-chain.

sp.for …​ in …​:

A for loop that is evaluated on-chain.

        sp.for x in params:
            self.data.result += x
sp.while …​:

A while loop that is evaluated on-chain.

        sp.while 1 < y:
            self.data.value += 1
            y.set(y // 2)

See Michelson LOOP.

sp.for / sp.if / sp.else
See Michelson ITER, MAP, and IF.

If we use e.g. sp.if instead of a plain if, the result will be a SmartPy conditional instead of a Python one. SmartPy conditionals are executed once the contract has been constructed and has been deployed or is being simulated. On the other hand, Python conditionals are executed immediately. Therefore the condition after the if cannot depend on the state of the contract. When in doubt, always use the sp. prefix inside a smart contract.

Checking a Condition

sp.verify(condition, message = …​)

Check that the boolean expression condition evaluates to True and raises an error if it doesn’t. This is useful to prevent an entry point from proceding if certain conditions are not met (e.g. in a contract that manages accounts a client cannot withdraw more money than they deposited).

An optional parameter message is raised if condition is not met. When message is not present, an exception of the form WrongCondition: …​ is raised.

See Michelson FAILWITH.

sp.verify_equal(v1, v2, message = …​)

It serves the same purpose by checking equality between v1 and v2. This works on both comparable and non-comparable types.

Raising Exceptions

Once an exception is raised, it cannot be caught.
String error messages may take a lot of space in smart contracts so we try to be cautious there.

sp.failwith(message)

Abort the current transaction and raises a message of arbitrary type. This cannot be caught.
See Michelson FAILWITH.

Besides sp.verify and sp.verify_equal, exceptions can also be raised by other constructions:

Accessing fields my_map[x]

The exception raised is now a pair containing the (x, my_map).

Opening variants

This may fail with sp.unit.

Dividing by zero

A message is shown.

Variable step in a range which is 0

A message is shown.

Experimental Features

Sapling

Sapling is an experimental feature in Michelson. SmartPy.io is already compatible with Sapling in different ways: types, michelson implementation, compiler, and a fake implementation for test scenarios.
See reference Sapling and Sapling2 templates.

There are two types for Sapling: sp.TSaplingState for states and sp.TSaplingTransaction for transactions.

SmartPy doesn’t generate proper literals for Sapling types but an ad-hoc, fake, test implementation is provided for test scenarios. This is not a proper Sapling transaction but it is enough to test contracts (see reference templates).

Literals: sp.sapling_transaction(source, target, amount)

This is a fake / test version.

source

source is a Python string, empty string for minting tokens.

target

target is a Python string, empty string for burning tokens.

amount

amount is a non-negative Python integer.

Two operations
sp.sapling_empty_state()

Building an empty sp.TSaplingState.

my_state.verify_update(transaction)

When my_state is a sp.TSaplingState and transaction a sp.TSaplingTransaction, my_state.verify_update(transaction) applies the transaction to the state, validates it and returns sp.none if it fails and sp.some(x) where x is of type sp.TPair(sp.TInt, sp.TSaplingState).

Lazy Entry Points

The class sp.Contract, inherited by all SmartPy contracts, gets two new methods that control the generation of entry points into lambdas stored in big maps. See reference Send back template.

self.add_flag_lazy_entry_points()

Add one big map for lambda version of the entry points. This is the most efficient of these two methods.

self.add_flag_lazy_entry_points_multiple()`

An alternative method that creates a big map per entry point. This is less efficient because big maps are costly to generate and this method enlarges the storage type too much.

Exception Optimization Levels

Exception reporting is determined for a contract by setting self.exception_optimization_level = a_level.
See reference Exception Optimization template.

Different levels are:

"FullDebug"

This is extremely costly in terms of size and gas. Useful for debugging purposes. Type of failure, line number, some parameters.

"DebugMessage"

This is still very costly in terms of size and gas.

"VerifyOrLine"

This is the default. Puts messages for sp.verify and sp.failwith, and line numbers for other failures.

"DefaultLine"

Puts messages for sp.verify with custom messages and sp.failwith, and line numbers for other failures.

"Line"

Only puts line numbers everywhere.

"DefaultUnit"

Puts messages for sp.verify with custom messages, and sp.failwith, and unit for other failures.

"Unit"

Always puts unit.

No comment flag

A flag to remove comments that do not come from types in the generated Michelson.
See reference Exception Optimization template.

This is done by calling self.add_flag("no_comment") in the contract.

Tests and Test Scenarios

This has been introduced by the following Medium Post.

Tests

Adding a Test

sp.add_test(name, shortname=None, profile=False, is_default=True)

Adding a test.

Tests are added by :

  @sp.add_test(name = "First test")
  def test():
    ...

Besides name, sp.add_test accepts several parameters.

shortname=None

Optional parameter. Short names need to be unique. Used in smartpy-cli outputs.

profile=False

Computes and pretty-prints profiling data.

is_default=True

Determines if the test is performed by default when evaluating all tests. Can be typically used in conjonction with sp.in_browser in templates to improve speed in browser.
See reference FA2 template.

Defining a Scenario

sp.test_scenario()

Introduces a scenario.

Scenarios are defined in a test, by doing:

  @sp.add_test(name = "First test")
  def test():
      # We define a test scenario, called scenario,
      # together with some outputs and checks
      scenario = sp.test_scenario()

Test Example

  @sp.add_test(name = "First test")
  def test():
      # We define a test scenario, called scenario,
      # together with some outputs and checks
      scenario = sp.test_scenario()
      # We first define a contract and add it to the scenario
      c1 = MyContract(12, 123)
      scenario += c1
      # And send messages to some entry points of c1
      scenario += c1.myEntryPoint(12)
      scenario += c1.myEntryPoint(13)
      scenario += c1.myEntryPoint(14)
      scenario += c1.myEntryPoint(50)
      scenario += c1.myEntryPoint(50)
      scenario += c1.myEntryPoint(50).run(valid = False) # this is expected to fail
      # Finally, we check the final storage of c1
      scenario.verify(c1.data.myParameter1 == 151)
      # and its balance
      scenario.verify(c1.balance == sp.tez(0))

In a Test Scenario

Registering and displaying contracts

  scenario += c1
  # This is identical to doing
  scenario.register(c1, show = True)
  # To only register the smart contract but not show it
  scenario.register(c1)

Test Accounts

Test accounts can be defined by calling sp.test_account(seed) where seed is a string. A test account account contains some fields: account.address, account.public_key_hash, account.public_key, and account.secret_key.
See Cryptography in Test Scenarios.

  admin = sp.test_account("Administrator")
  alice = sp.test_account("Alice")
  bob   = sp.test_account("Robert")

They can be used for several purposes: getting addresses with account.address, in sender or source parameters or for checking or creating signatures.

Registering and Displaying Calls to Entry Points

  scenario += c1.myEntryPoint(12)
  scenario += c1.myEntryPoint(...).run(sender = ..., source = ..., amount = ..., now = ..., valid = ..., chain_id = ...)
  # To only execute a call to an entry point but not show it
  scenario.register(c1.myEntryPoint(12))

The run method and its parameters are all optional.

sender

the simulated sender of the transaction. It populates sp.sender. It can be either built by a sp.test_account(…​) or sp.address(…​).

source

the simulated source of the transaction. It populates sp.source. It can be either built by a sp.test_account(…​) or sp.address(…​).

amount

the amount sent. Example: amount = sp.tez(10) or amount = sp.mutez(10000). It populates sp.amount.

now

the timestamp of the transaction. Example: sp.timestamp(1571761674). It populates sp.now.

valid

the expected validity of the transaction. True by default. If the validity of a transaction doesn’t match its expected validity, SmartPy shows an alert.

chain_id

the simulated chain_id for the test. Example: sp.chain_id_cst("0x9caecab9").

Adding Document Informations

  scenario.h1("a title")
  scenario.h2("a subtitle")
  scenario.h3(..)
  scenario.h4(..)
  scenario.p("Some text")

Showing Expressions

To compute expressions, we use scenario.show(expression, html = True, stripStrings = False).

  scenario.show(expression, html = True, stripStrings = False)
  # html: True by default, False to export not in html but like in source code.
  # stripStrings: False by default, True to remove quotes around strings.

  scenario.show(c1.data.myParameter1 * 12)
  scenario.show(c1.data)

Computing Expressions

To compute expressions, we use scenario.compute.

  x = scenario.compute(c1.data.myParameter1 * 12)

The variable x can now be used in the sequel of the scenario and its value is fixed.

Checking Assertions

To verify conditions, we use scenario.verify. To verify an equality condition, we can also use scenario.verify_equal which works on both comparable and non-comparable types.

  scenario.verify(c1.data.myParameter == 51)

  scenario.verify_equal(c1.data.myList, [2, 3, 5, 7])

Interactive Testing

To test interactively a contract, we use scenario.simulation. It also provides a step-by-step mode that is very usefull to understand some computation.

  scenario.simulation(c1)

Cryptography in Test Scenarios

Some constructions are only available in tests, not in smart contracts.

sp.test_account(seed)

The class alice = sp.test_account("Alice")
Create a deterministic key-pair from a “seed” string.

  • alice.address
    Get the public-key-hash as a TAddress.

  • alice.public_key_hash
    Get the public-key-hash as a TKeyHash.

  • alice.public_key
    Get the full public-key as a TKey.

  • alice.secret_key
    Get the secret-key as a TString.

sp.make_signature(secret_key, message, message_format = 'Raw')

See Signatures.

sp.test_account methods and sp.make_signature are not available for compilation to Michelson (a smart contract cannot manipulate secret keys).

Test without Scenarios

sp.show(contract, name="Simulation", shortname=None, profile=False, is_default=True)

As a convenience, one can call sp.show(contract, …​) instead of @sp.add_test …​.
sp.show optional parameters are:

name="Simulation"

Optional parameter with default value ="Simulation".

shortname=None

Optional parameter. Short names need to be unique. Used in smartpy-cli outputs.

profile=False

Computes and pretty-prints profiling data.

is_default=True

Determines if the test is performed by default when evaluating all tests. Can be typically used in conjonction with sp.in_browser in templates to improve speed in browser.
See reference FA2 template.

Command Line Interface

The command line interface is called smartpy-cli and has been introduced by the following Medium Post.

Installation

See installation instructions.

Dependencies

smartpy-cli depends on python3 and node.js.

Execution

Executing a SmartPy Script with its tests

~/smartpy-cli/SmartPy.sh test <myscript.py> <output-directory>

This includes many outputs: types, generated michelson code, pretty-printed scenario, etc.

Compiling a SmartPy Script

~/smartpy-cli/SmartPy.sh compile <contractBuilder.py> <class-call> <output-directory>

Example:

~/smartpy-cli/SmartPy.sh compile welcome.py "Welcome(12,123)" /tmp/welcome

Executing a SmartPy Script

~/smartpy-cli/SmartPy.sh run <myscript.py>

This runs an arbitrary Python script that can happen to contain SmartPy code.