SmartPy is an intuitive and powerful smart contract development platform for Tezos.

The SmartPy language is available through a Python library for building and analyzing Tezos smart contracts.
It comes with various tools: a compiler that generates Michelson code, a simulation engine, a contract explorer, etc.

Building Blocks

SmartPy is a Python library. SmartPy scripts are regular Python scripts that use SmartPy constructions.
This mechanism is useful because it brings very powerful meta-programming capabilities, as explained later.

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.

Inside a contract, when we write

        y = self.data.x + 2

we declare y as an alias the SmartPy expression self.data.x + 2.
This is not a command for SmartPy.

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. This is performed at run time, i.e., in the blockchain, once translated into Michelson.

Entry Points

An entry point is a method of a contract class that can be called from the outside. Entry points 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 check_big_enough(self, params):
        sp.verify(params > 2)
sp.entry_point

Decorator to introduce an entry point.

See reference Store Value template for simple entry points examples.

Return values

Entry points do not return values in Michelson.
SmartPy respects this constraint but allows other functions to return values.
These functions use sp.result(value) to return value.
See Lambdas for examples with sp.global_lambda and sp.sub_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.
See reference Private Entry Point template.

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

    @sp.private_entry_point
    def set_x(self, params):
        self.data.x = 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 set_x(newX):
        self.data.x = newX

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

Types

Types are usually automatically infered and not explicitly 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 blockchain (although they can be simulated). This is indeed meta-programming: we can write a programm that writes a program, i.e., 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

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.

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 Keys.

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 Scenarios use them.
See Secret Key.

sp.TSaplingState(memo_size = None)
sp.TSaplingTransaction(memo_size = None)

Types for Sapling integration.
See Sapling Integration.

sp.TBls12_381_g1
sp.TBls12_381_g2

Points on the BLS12-381 curves G1 and G2, respectively.
Written as raw bytes, using a big-endian point encoding, as specified here.

sp.TBls12_381_fr

An element of the scalar field Fr, used for scalar multiplication on the BLS12-381 curves G1 and G2.
Written as raw bytes, using a little-endian encoding.
See BLS12-381.

Record types, Variant types and layouts

In SmartPy, we can use custom data types called records and variants.
Records and variants are translated in Michelson into binary trees of pair and or with annotations corresponding to each field.
The geometry of these binary trees is described by a layout.

Layouts

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.

Record types

A record type represents a cartesian product of several types similar to a struct in C with a layout.
Please see the Records section.

sp.TRecord(**fields)

A record type is introduced by enumerating the field names together with types, e.g., sp.TRecord(x = sp.TInt, y = sp.TInt).
A sp.TRecord(**fields) uses the default layout as determined by SmartPy (today a binary tree, it will change in the future).

.layout(layout)

A record type, i.e. something of the form sp.TRecord(…​), can be used to define a record type with a layout by doing:

    t = sp.TRecord(owner    = sp.TAddress,
                   operator = sp.TAddress,
                   token_id = sp.TString)
    t_with_layout = t.layout(("owner", ("operator", "token_id")))
.right_comb()

Like .layout(..) but the geometry used is (f1, (f2, .. (f_{k-1}, f_k))))), the list of fields is determined automatically and sorted alphabetically.

.with_fields(**fields)

Add some fields to the TRecord(..) where fields is a Python (string, type)-dictionary of fields.

.without_fields(fields)

Remove some fields from the TRecord(..) where fields is a Python list of fields.

Variant types

A variant type represents a union of several choices, similar to a clean version of a struct with an enum/union pattern in C.
Please see Variants.

sp.TVariant(**fields)

A variant type is introduced by enumerating the constructor names together with their inner types, e.g. sp.TVariant(default_choice = sp.TInt, alternative_choice = sp.TString).
A sp.TVariant(**fields) uses the default layout as determined by SmartPy (today a binary tree, it will change in the future).

.layout(layout)

Similar to what happens for TRecord.

.right_comb(layout)

Similar to what happens for TRecord.

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 the user to make the storage type explicit 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 explicitly 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_result_type(t)

Wrap a block of commands and constrain its result type to t. This can only be used as a command inside a contract.
There is no equivalent instruction in Michelson.
This is e.g. useful for making types of failures explicit.

    @sp.global_lambda
    def oh_no(params):
        with sp.set_result_type(sp.TInt):
           sp.if params > 0:
              sp.failwith("too big")
           sp.else:
              sp.failwith("too small")
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)

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.

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).

BLS12-381

BLS12-381 is a paring-friendly elliptic curve.
SmartPy supports all associated Michelson types and constructions including in tests in the interpreter.
See reference BLS12 template.

Types

sp.TBls12_381_fr

An element of the scalar field Fr, used for scalar multiplication on the BLS12-381 curves G1 and G2.
Written as raw bytes, using a little-endian encoding.
See Michelson bls12_381_fr.

sp.TBls12_381_g1
sp.TBls12_381_g2

Points on the BLS12-381 curves G1 and G2, respectively.
Written as raw bytes, using a big-endian point encoding, as specified here.
See Michelson bls12_381_g1, bls12_381_g2.

Operations

- e

Negate a curve point or field element.
Supported types: sp.TBls12_381_g1 | sp.TBls12_381_g2 | sp.TBls12_381_fr
See Michelson NEG.

sp.to_int(n)

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

n + m

Add two curve points or field elements.

Adding sp.TBls12_381_g1 with sp.TBls12_381_g1 originates sp.TBls12_381_g1
Adding sp.TBls12_381_g2 with sp.TBls12_381_g2 originates sp.TBls12_381_g2
Adding sp.TBls12_381_fr with sp.TBls12_381_fr originates sp.TBls12_381_fr

See Michelson ADD.

sp.mul(n, m)

Multiply a curve point or field element by a scalar field element.

Multiplying sp.TBls12_381_g1 with sp.TBls12_381_fr originates sp.TBls12_381_g1
Multiplying sp.TBls12_381_g2 with sp.TBls12_381_fr originates sp.TBls12_381_g2
Multiplying sp.TBls12_381_fr with sp.TBls12_381_fr originates sp.TBls12_381_fr
Multiplying sp.TInt with sp.TBls12_381_fr originates sp.TBls12_381_fr
Multiplying sp.TNat with sp.TBls12_381_fr originates sp.TBls12_381_fr
Multiplying sp.TBls12_381_fr with sp.TInt originates sp.TBls12_381_fr
Multiplying sp.TBls12_381_fr with sp.TNat originates sp.TBls12_381_fr

See Michelson MUL.

sp.pairing_check(pairs)

Verify that the product of pairings of the given list of points is equal to 1 in Fq12. Returns true if the list is empty. Can be used to verify if two pairings P1 and P2 are equal by verifying P1 * P2^(-1) = 1.

pairs must be of type sp.TList(sp.TPair(sp.TBls12_381_g1, sp.TBls12_381_g2))

See Michelson PAIRING_CHECK.

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.

sp.keccak(value)
sp.sha3(value)

Introduced in Edo, the functions sp.keccak, sp.sha3 take a sp.TBytes value and return the corresponding hash as a new sp.TBytes value.
See Michelson KECCAK and SHA3.
Not supported yet in the interpreter.

sp.bytes_of_string(s)

Encode a constant string as sp.TBytes.

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.

Example: Chain Id

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)
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)

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

sp.self_address

Alias for sp.to_address(sp.self).
This is the proper way to get a contract’s own address.
In tests, a contract’s address is accessible through the address field.

sp.self_entry_point_address(entry_point = "")

Alias for sp.to_address(sp.self_entry_point(entry_point)).
This is the proper way to get a contract’s own address of an entry point.

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, message = None)

Send the specified amount to the destination contract. Will fail with optional error message 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).
Abbreviation for: sp.transfer(sp.unit, amount, sp.contract(sp.TUnit, destination).open_some(message = message))
See Michelson TRANSFER_TOKENS.

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)
sp.create_contract(contract, storage = None, amount = tez(0), baker = None)

Create a new contract from stored SmartPy contract with optional storage, amount and baker.
See Michelson CREATE_CONTRACT.
See reference Create Contract template.

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, message = None)

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

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.

Other Operations

Voting Power

sp.total_voting_power

Return the total voting power of all contracts.
The total voting power coincides with the sum of the rolls count of every contract in the voting listings.
The voting listings is calculated at the beginning of every voting period.
See Michelson TOTAL_VOTING_POWER.

sp.voting_power(key_hash)

See Key Hash.

Levels

sp.level

Like sp.now but for the level of the transaction. It is of type sp.TNat.

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.
In tests, a contract’s baker is accessible through the baker field.

sp.implicit_account(key_hash)

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

sp.voting_power(key_hash)

Return the voting power of a given key hash.
This voting power coincides with the weight of the key hash in the voting listings (i.e., the rolls count) which is calculated at the beginning of every voting period.
See Michelson VOTING_POWER.
See Other Operations for sp.total_voting_power.

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.
This function is usually useless as it is called automatically by SmartPy in most contexts.
See Michelson LAMBDA.

sp.global_lambda

Decorator to introduce a lambda that is also a global variable.
This is used for pure functions that are expected to be used more than once and that do not change the global state nor create operations.
Values are returned by using sp.result(value).
See reference WorldCalculator template.

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)))
        sp.result(y.value)

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

See Michelson LAMBDA.

sp.sub_entry_point

Like sp.global_lambda but for functions that can change the global state or create operations.
Values are returned by using sp.result(value).
See reference Sub entry point template.

class MyContract(sp.Contract):
    def __init__(self):
        self.init(x = 2, y = "aaa", z = 0)

    @sp.sub_entry_point
    def a(self, params):
        sp.set_delegate(sp.none)
        self.data.x += 1
        sp.result(params  * self.data.x)

    @sp.entry_point
    def f(self, params):
        self.data.z = self.a(5) + self.a(10)

    @sp.entry_point
    def g(self, params):
        self.data.z = self.a(6)

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.
To iterate on sp.TMap(key, value) or sp.TSet(elem), we first convert to an sp.TList(..) with e.items(), e.keys(), e.values() or e.elements().

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

Match a list and expose its head and tail if any.
See reference Lists template.

    with sp.match_cons(params) as x1:
        self.data.head = x1.head
        self.data.tail = x1.tail
    sp.else:
        self.data.head = "abc"

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 and Maps 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.

my_map[key] = value

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

del my_map[key]

Delete an element from a map.
See Michelson UPDATE.

my_map[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.

my_map.get(key, defaultValue = None)

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

my_map.contains(key)

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

sp.len(my_map)

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

my_map.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.

my_map.keys()

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

my_map.values()

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

sp.update_map(my_map, key, value)

Return a new copy of map my_map where key has optional value value (sp.none to delete).

sp.get_and_update(my_map, key, value)

Return two elements: the previous optional value (sp.none if missing`) and a new map as sp.update_map would do.
It is typically used in the following way.

(previous_value, new_map) = sp.get_and_update(self.data.m, 1, sp.some("one"))

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.
See Michelson EMPTY_BIG_MAP.

my_big_map[key] = value

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

del my_big_map[key]

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

my_big_map[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.

my_big_map.get(key, defaultValue = None)

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

my_big_map.contains(key)

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

sp.update_map(my_big_map, key, value)

Return a new copy of big map my_big_map where key has optional value value (sp.none to delete).

sp.get_and_update(my_big_map, key, value)

Return two elements: the previous optional value (sp.none if missing`) and a new map as sp.update_map would do.
It is typically used in the following way.

(previous_value, new_map) = sp.get_and_update(self.data.m, 1, sp.some("one"))

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.
Due to the unintuitive semantics in Michelson, we suggest that developers do not rely on balance too much. See Tezos Agora post.
In tests, a contract’s balance is accessible through the balance field.

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.

Never

An empty type sp.TNever.

sp.never(expression)

Terminate a never branch.
See reference Never template.

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(message = None)

If e is equal to sp.some(x), return x; otherwise fail. message is the optional error raised in case of error.
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(..)
sp.snd(..)

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

Furthermore, pairs can be matched using the following command:

sp.match_pair(x)

If x is a SmartPy pair (i.e. a value of type sp.TPair(t,u)), this returns a Python pair of its components. This is typically used as follows:

x1, x2 = sp.match_pair(p)

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.

Furthermore, records can be matched using the following command:

sp.match_record(x, *fields)

If x is a SmartPy record (i.e. a value of type sp.TRecord(…​)), this returns a Python tuple of selected record fields. The list of fields can be all the record’s fields or just some of them. This is typically used as follows:

fld1, fld2, fld3 = sp.match_record(x, "fld1", "fld2", "fld3" )

A variant of this command allows modifying a record that is held in the contract storage or in a local variable:

with sp.modify_record(self.data.x, "fld1", "fld2", "fld3") as (fld1, fld2, fld3):
   ...
   sp.result(sp.record(fld1 = fld1, fld2 = fld2, fld3 = fld3))

Note that a new record must be returned via sp.result. This command is mostly useful when dealing with tickets.

Sapling Integration

Sapling is a new feature in Michelson. SmartPy.io is 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(memo_size) for states and sp.TSaplingTransaction(memo_size) for transactions where memo_size is a constant integer between 0 and 65535 and corresponds to potentially included data in transaction. The memo_size parameter is optional in types sp.TSaplingState and sp.TSaplingTransaction as it may be computed through type inference.

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_test_transaction(source, target, amount, memo_size)

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.

memo_size

memo_size is a non-negative Python integer between 0 and 65535.

Two operations
sp.sapling_empty_state(memo_size)

Building an empty sp.TSaplingState with a default memo_size (Expected length for message of Sapling transaction)

memo_size

memo_size is a uint16 value and represents the expected message length of the sapling transaction. (must be between 0 and 65535)

sp.sapling_verify_update(transaction)

When state is a sp.TSaplingState(memo_size) and transaction a sp.TSaplingTransaction(memo_size), sp.sapling_verify_update(state, 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(memo_size)).

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 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.

Tickets

Tickets of content type t have type sp.TTicket(t).
The corresponding type in Michelson is ticket cty.
See reference Tickets template.

The following operations are supported:

sp.ticket(content, amount)

Create a ticket with content and amount. If content is of type t, the return type is sp.TTicket(t). The amount has to be of type sp.TNat.
See Michelson TICKET.

High Level Functions

sp.read_ticket(ticket)

Read the data of a ticket and return a Python object with four fields ticketer, content, amount, and copy.

read_ticket = sp.read_ticket(ticket)

The output read_ticket object is not a SmartPy expression and cannot be passed as an expression to regular SmartPy functions. However, it can be passed to regular Python functions thanks to meta-programming.
It is an error to access ticket again after this command, but read_ticket.copy must be used instead. If ticket is of type sp.TTicket(t), then read_ticket.ticketer is of type sp.TAddress, read_ticket.content of type t, and read_ticket.amount of type sp.TNat.
See Michelson READ_TICKET.

sp.split_ticket(ticket, q1, q2)

Split ticket into two tickets of amounts q1 and q2. These two amounts must sum up to ticket's value, otherwise sp.none is returned. If ticket is of type sp.TTicket(t), this returns a value of type sp.TOption(sp.TPair(sp.TTicket(t), sp.TTicket(t))).
See Michelson SPLIT_TICKET.

sp.join_tickets(ticket1, ticket2)

Return a new ticket with the sum of the amounts in ticket1 and ticket2. The two tickets must have the same contents, or an error is raised. Both tickets must have the same type sp.TTicket(t), which is also the return type.
See Michelson JOIN_TICKETS.

Low Level Functions

sp.read_ticket_raw(ticket)

Like sp.read_ticket(ticket) when the output is two elements, similar to Michelson READ_TICKET.

sp.split_ticket_raw(ticket, q1, q2)

Like sp.split_ticket(ticket, q1, q2) when the output is two elements, similar to Michelson SPLIT_TICKET.

sp.join_tickets_raw(tickets)

Like sp.join_tickets(ticket1, ticket2) where tickets is a pair of tickets, similar to Michelson JOIN_TICKETS.

Testing Tickets

sp.test_ticket(ticketer, content, amount)

Create a new ticket issued by the locally defined contract ticketer. This is meant to be used for testing purposes in scenarios.

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.

New features in Florence

Florence flags

self.add_flag("protocol", "florence") - Enables Florence features.

Baking Accounts

New types

sp.TBakerHash

The b58check hash of the baker account (prefixed “SG1”).
baker_hash.

Literals: sp.baker_hash('SG1…​')

A literal baker hash is of the form sp.baker_hash(h) where h is a Python string 'SG1…​'.

Experimental Features

Flags

The compilation of contracts can be controlled by the use of flags.
SmartPy supports two sorts of flags: boolean flags and flags with arguments.

self.add_flag(flag, *args)

Add a flag where flag is a string constant and args are potential arguments.
For example, you can write sp.add_flag("lazy_entry_points", "single") or sp.add_flag("erase_comments") or sp.add_flag("no-erase_comments").

To enable Edo specific compilation, use self.add_flag("protocol", "edo") or self.add_flag("protocol", "delphi") for Delphi.
"edo" is the current default.

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 to store lazy entry points. This is the most efficient of these two methods.
self.add_flag_lazy_entry_points() is an alias for self.add_flag("lazy_entry_points", "single").

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.
self.add_flag_lazy_entry_points_multiple() is an alias for self.add_flag("lazy_entry_points", "multiple").

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:

"full-debug"

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

"debug-message"

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

"verify-or-line"

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

"default-line"

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

"line"

Only puts line numbers everywhere.

"default-unit"

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

"unit"

Always puts unit.

Erasing comments

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("erase_comments") in the contract.

Views

sp.view(t, message = None)

Decorator sp.view to introduce an entry point that happens to be a view.
A view is an entry point that

  1. Doesn’t change the storage;

  2. Calls a callback sp.TContract(t).

    @sp.view(sp.TNat)
    def getBalance(self, params):
        sp.result(self.data.balances[params].balance)

This code is a simpler version of the equivalent:

    @sp.entry_point
    def getBalance(self, params):
        __s3 = sp.local("__s3", self.data.balances[sp.fst(params)].balance)
        sp.set_type(sp.snd(params), sp.TContract(sp.TNat))
        sp.transfer(__s3.value, sp.tez(0), sp.snd(params))

Importing SmartPy and Python code

Importing regular Python code can be done in any template with the regular Python import.

Importing SmartPy code that uses the syntactic sugar is also doable but needs specific functions.

sp.import_template(template_name)

Import a template.
It only works in https://SmartPy.io, not with the CLI.

  FA2 = sp.import_template("FA2.py")
  class my_token(FA2.FA2):
      ...
sp.import_script_from_url(url, name = None)

Same functionality but instead of using a template from within SmartPy.io; import any file. The url is a string of the form http://, https://, file://, file:, etc. The module obtained name is the optional name parameter, with default equal to url.

sp.import_stored_contract(name)

Same functionality but instead of importing a file from a url, import a script saved in the browser local storage.
It only works in https://SmartPy.io, not with the CLI.

sp.import_script_from_script(name, script)

Import some script where both name and script are strings.

Metadata support

Helper function for TZip 16 Standard.
See reference Metadata template.

sp.metadata_of_url(url)

Initialize some metadata field in a storage with a constant url string.
Simple alias for sp.big_map({"" : sp.bytes_of_string(url)}).

self.init_metadata(name, expression)

Generate a JSON metadata document for string name containing an arbitraty constant Python expression converted into JSON.

sp.offchain_view(pure = False, doc = None)

Decorator to introduce an offchain view. Two optional parameters: pure (default is False) to declare purity of view (dependent only on storage and parameters) and doc to set its documentation. If doc is None, the documentation is the docstring of the method.

    @sp.offchain_view(pure = True)
    def get_x(self, params):
        """blah blah ' fdsfds " """
        sp.result(sp.record(a = self.data.x, b = 12 + params))

Debugging contracts

sp.trace(expression)

Compute an expression and print its value to std::out (or the console in a web-browser).

scenario.simulation

Graphical user interface and step by step simulation in a web-browser.

Returning and Binding data

This is an advanced topic and need not be looked at for regular SmartPy developers.

In a block, we can write sp.result(expr) to compute expr and prepare the local result. This data can be bound by using sp.bind_block().
See reference Bind template. In this example, we define a block b, computes some value and return it.

    def f(self, params)
        b = sp.bind_block()
        with b:
            sp.if params > 12:
                sp.result(self.data.x + params)
            else:
                sp.result(self.data.x + 2)
        return b.value

Tests and Scenarios

This has been introduced by the following Medium Post.

General Framework

Scenarios describe a sequence of actions: originating contracts, computing expressions, calling entry points, etc.

They are directly used in SmartPy tests.

SmartPy currently supports two uses for scenarios: tests and compilation targets.

Adding a Test

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

Adding a test.

sp.test_scenario()

Define a scenario.

Tests are added by :

  @sp.add_test(name = "First test")
  def test():
    scenario = sp.test_scenario()
    c1 = MyContract()
    scenario += c1
    scenario += c1.my_entrypoint(...)
    ...

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.

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.my_entrypoint(12)
      scenario += c1.my_entrypoint(13)
      scenario += c1.my_entrypoint(14)
      scenario += c1.my_entrypoint(50)
      scenario += c1.my_entrypoint(50)
      scenario += c1.my_entrypoint(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))

Inside a 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)

Contract Methods

Additionaly to entry points, contracts have two additional methods that can be called once, before origination.

c.set_storage(expression)

set the storage of a contract with no initial storage.

c.set_initial_balance(expression)

set the initial balance of a contract.

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.my_entrypoint(12)
  scenario += c1.my_entrypoint(...).run(sender = ..., source = ..., amount = ..., now = ..., level = ..., valid = ..., show = ..., chain_id = ..., voting_powers = ...)
  # To only execute a call to an entry point but not show it
  scenario.register(c1.my_entrypoint(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 an expression of type sp.TAddress.

source

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

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.

level

the level of the transaction. Example: 1234. It populates sp.level.

show

show or hide the transaction. True by default.

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").

voting_powers

the simulated voting powers for the test. Example: voting_powers = { sp.key_hash("tz1…​") : 10 }.

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.

Accessing Data associated to Contracts

When c is a contract in a scenario, we can access some associated data:

c.data

Retrieve its storage.

c.balance

Retrieve its balance.

c.baker

Retrieve its optional delegated baker.

c.address

Retrieve its testing address.

c.typed

Retrieve its testing typed contract value.

Dynamic Contracts

See reference Create Contract template.

Internally, SmartPy uses two types of contracts: static ones and dynamic ones. Static contracts appear explicitely in the scenarios. Dynamic ones are created in other contracts executed in the scenario (with sp.create_contract).

my_dynamic_contract = scenario.dynamic_contract(contractId, tcontract, tparameter)

Declare that a dynamic contract of dynamic id (an integer) is created with the corresponding storage and full parameter types.
The first dynamically created contractId is 0, then '1', etc.
Return a dynamic contract that contains regular fields data, balance, baker, address and typed and a call method.

scenario += my_dynamic_contract.call(entry_point, parameter)

Send the parameter to the dynamic contract dync’s `entry_point.
We can use .run(…​) on the generated call as described in Registering and Displaying Calls to Entry Points.

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 Explicit Scenarios

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

As a convenience, one can call sp.add_simulation_target(contract, …​) instead of @sp.add_test …​.
sp.add_simulation_target 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.

Compilation Targets

Adding a Compilation Target

We can also add compilation targets by doing

sp.add_compilation_target(name, contract, storage=None)

Define a contract with name with optional storage.
Compilation targets also use scenarios behind the scene.

sp.add_expression_compilation_target(name, expression)

Define an expression called name.
Compilation targets also use scenarios behind the scene.

Compilation Target Examples

# A contract with an empty (unit) storage
sp.add_compilation_target("min_comp", MyContract())

# A contract with a simple int storage
sp.add_compilation_target("min_comp_int", MyContract(x = 1))

# An expression
sp.add_expression_compilation_target("x", 42)

# Another expression
sp.add_expression_compilation_target("y", ("a", [1, 2, 3]))

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.sh test

Perform tests defined in a script.py (see Tests and Scenarios).

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

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

Compiling SmartPy Contracts or Expressions

SmartPy.sh compile

Compute the Compilation Targets defined in a script.py.
Warning: the interface changed.

~/smartpy-cli/SmartPy.sh compile <script.py> <output-directory>

Example:

~/smartpy-cli/SmartPy.sh compile welcome.py /tmp/welcome

CLI optional arguments

SmartPy.sh takes several optional arguments.

--purge

Empty the output directory before writting to it.

--html

Add some .html outputs such as a log.html which mimicks the output panel.

--protocol <delphi|edo|florence>

Select a Tezos protocol.
Default is edo.

--<flag> arguments

Set some <flag> with arguments.

--<flag>

Activate some boolean <flag>.

--no-<flag>

Deactivate some boolean <flag>.