Skip to content

Differences between SmartPy and Python

Although SmartPy is similar to Python in many ways, it has some significant differences. The differences that apply depend on whether the code is in a SmartPy module or test scenario.

Modules vs. test scenarios

SmartPy files contain two main divisions of code:

  • Modules, which primarily contain smart contracts, which are the source code of programs to be compiled to Michelson and deployed to Tezos
  • Test scenarios, which test the smart contracts and run other tasks related to their compilation

The behavior and limitations of SmartPy code are different in modules and test scenarios. Therefore, you must keep in mind the behavior and limitations of the code that you are working in.

Modules

The code in SmartPy modules has limitations because of how it is intended to be used, how it is compiled, and how it is run:

  • SmartPy modules are intended to define Tezos smart contracts and therefore they are structured around functions called entrypoints. For more information about contracts, see Contracts.

  • SmartPy modules are compiled to the Michelson stack-based language, which creates limitations on how control structures work and how you can access variables via iteration (see Access and iteration). Similarly, Michelson is a strongly-typed language and therefore the SmartPy compiler must be able to determine the type of each variable in a module at compile time. For more information on typing, see Casting.

  • Smart contracts run on the Tezos blockchain, which has specific behaviors and limitations. For example, Tezos cannot call external APIs or import external libraries and therefore SmartPy modules cannot call external APIs or import external libraries. Similarly, SmartPy modules cannot use standard Python try/except blocks because smart contracts are limited to the way that Tezos handles exceptions; see Exceptions.

For more examples of the limitations of SmartPy modules, see Limitations of SmartPy modules below.

Test scenarios

In contrast to modules, the code in SmartPy test scenarios is pure Python. Therefore, within a test scenario, you can do things that you can't do in a smart contract, such as importing and using Python libraries, calling external APIs, and using try/except blocks. The limitations described in Limitations of SmartPy modules do not apply to the code in test scenarios.

However, test scenarios have behavior that is specific to how they work with and test contracts. Test scenarios run a simulated Tezos environment, and you can access your contracts only within that environment.

Therefore, when a test accesses a contract in the simulation, such as to access the value of its storage, it receives an expression that it can evaluate only within that simulation. In short, test scenarios can access contracts only within functions such as scenario.verify, not by accessing the variables that represent those contracts directly.

INFO

You must define test scenarios in Python .py files, not SmartPy .spy files.

For more information, see Test scenarios.

Limitations of SmartPy modules

Code within a SmartPy module has these limitations:

Importing Python modules

You cannot use imported Python modules in SmartPy code. You can import SmartPy code from other .spy files or import SmartPy modules defined within sp.module blocks.

Please see modules.

Functions

Functions (including entrypoints, views, and auxiliary functions) must end at a single block of code. For example, this code is not valid because it could return from more than one place in the code, even though the return statements are close to each other:

smartpy
if a > b:
    return a  # Error: 'return' in non-terminal position.
return b

Instead, functions must return from a single block of code, as in this example:

smartpy
if a > b:
    return a
else:
    return b

Loops

Similarly, you can't use the break command to end a loop early, as in this example:

smartpy
x = 3
i = 0
while i < 5:
    if i == x:
        break  # SyntaxError: Not a statement: break
    i += 1

Errors

SmartPy modules cannot use standard Python try/except blocks to catch errors. See Exceptions.

Pattern matching

The pattern-matching syntax (using the match statement) is valid only for SmartPy options and variants. Therefore, the following example is not syntactically correct, because it attempts to pattern-match on integer values:

smartpy
def ep10(self, params):
    sp.cast(params.other, int)
    match params.other:
        case 0:  # ParseError: unexpected token 0
            ...
        case 1:  # ParseError: unexpected token 1
            ...

Logic

  • SmartPy supports the Python if and else statements, but not the elif statement.
  • SmartPy does not support Python exception handling with statements such as try and except.
  • SmartPy does not support some built-in Python functions, such as type and bool.

Logging

To write to STDOUT from SmartPy, use the sp.trace function.

Variables

SmartPy is limited by the types of variables that Michelson supports and how it uses variables:

Types

SmartPy does not support every data type that Python does. Also, SmartPy data types may not have the same methods that the equivalent Python data types have. See the Data types section for the types that SmartPy supports.

Some SmartPy types behave differently from the equivalent Python types. For example, SmartPy numerical types behave differently when they are divided; see Division.

You must be aware of the types that you use in SmartPy modules versus the types that you use in the Python code of test scenarios. For example, within a SmartPy module, lists that you create have the type sp.list[t], where t is the type of the list elements; see Lists, sets, and maps. Therefore, to add elements to a list, you use the push() method of the sp.list type, as in this example:

smartpy
@sp.entrypoint
def lists(self):
    my_list = [1, 2, 3]
    my_list.push(sp.int(4))

However, lists that you create in Python code, including test scenarios, are ordinary Python lists. Therefore, to add elements to a list, you use the append() method, as in this example:

python
@sp.add_test()
def test():
    # Create a test scenario
    scenario = sp.test_scenario("A Test")

    # ...

    my_list = [1, 2, 3]
    my_list.append(4)

Similarly, to check if an element is in a SmartPy set of type sp.set, use its contains() method, not the standard Python in operator.

Casting

In most cases, you cannot change the type of a variable after you define it. The sp.cast function does not change the type of a variable; it clarifies the type of a variable for the compiler.

The STDLIB modules provide some traditional casting functions, such as converting between different numerical types.

For more information about casting, see Casting.

Enumerations

To set up an enumeration with SmartPy, use a variant type to create a group of cases. Each value has unit as a value, as in this example:

smartpy
@sp.module
def main():
    status: type = sp.variant(Active=sp.unit, Inactive=sp.unit)

    class C(sp.Contract):
        def __init__(self):
            self.data.status = sp.cast(sp.variant.Active(), status)
            self.data.statusMessage = ""
            match self.data.status:
                case Active(_):
                    self.data.statusMessage = "Running"
                case Inactive(_):
                    self.data.statusMessage = "Not running"

Access and iteration

Michelson variables are stored in a stack, which introduces limitations on accessing and iterating over variables. Here are some of those limitations:

  • You cannot retrieve or change an arbitrary element in a list or set with brackets, as in the code myList[2].

  • You can add items to lists but you cannot remove them without iterating over the list.