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/exceptblocks 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:
if a > b:
return a # Error: 'return' in non-terminal position.
return bInstead, functions must return from a single block of code, as in this example:
if a > b:
return a
else:
return bLoops
Similarly, you can't use the break command to end a loop early, as in this example:
x = 3
i = 0
while i < 5:
if i == x:
break # SyntaxError: Not a statement: break
i += 1Errors
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:
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
ifandelsestatements, but not theelifstatement. - SmartPy does not support Python exception handling with statements such as
tryandexcept. - SmartPy does not support some built-in Python functions, such as
typeandbool.
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:
@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:
@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:
@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.