Skip to main content

Testing Contracts

Registering and displaying contracts

@sp.add_test(name = "A Test")
def test():
# Create a scenario
scenario = sp.test_scenario()
# Instantiate a contract
c1 = MyContract()

# Add the contract to the scenario
‚Äčscenario += c1 # which is equivalent to `scenario.register(c1, show = True)`

‚Äč# To only register the smart contract but not show it
‚Äčscenario.register(c1)

Contract origination options

Setting the initial balance of a contract

Additionally to entry points, contracts have an additional method that can be called once, before origination.

c1.set_initial_balance(expression)

Test accounts

Test accounts can be defined by calling sp.test_account(seed) where seed is a string.

A test account contains some fields:

  • <account>.address
  • <account>.public_key_hash
  • <account>.public_key
  • <account>.secret_key

See Cryptography

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

Registering and displaying calls to entry points

# Call entry_point
‚Äčc1.my_entrypoint(12)
# Call entry_point with customized block attributes.
‚Äčc1.my_entrypoint(13).run(
sender = ..., # TAddress
source = ..., # TAddress
‚Äčamount = ..., # TMutez
now = ..., # TTimestamp
level = ..., # TNat
chain_id = ..., # TChainId
voting_powers = ..., # Dict(TKeyHash, TNat)
valid = ..., # True | False
show = ..., # True | False
exception = ..., # any
)

The .run(...) method and its parameters are all optional.

ParameterTypeDescription
sendersp.TAddressThe simulated sender of the transaction. It populates sp.sender and it can be either built by a sp.test_account(‚Ķ‚Äč) or an expression of type TAddress.
sourcesp.TAddressThe simulated source of the transaction. It populates sp.source and it can be either built by a sp.test_account(‚Ķ‚Äč) or an expression of type TAddress.
amountsp.TMutezThe simulated amount sent. It populates sp.amount.
nowsp.TTimestampThe simulated timestamp of the transaction. It populates sp.now.
levelsp.TNatThe simulated level of the transaction. It populates sp.level.
chain_idsp.TChainIdThe simulated chain_id for the test. It populates sp.chain_id
voting_powersDict(sp.TKeyHash, sp.TNat)The simulated voting powers for the test. It populates sp.total_voting_power and sp.voting_power
validTrue or FalseShow or hide the transaction. True by default.
showTrue or FalseShow or hide the transaction. True by default.
exceptionany typeThe expected exception raised by the transaction. If present, valid must be False.

Testing if an expression fails

sp.is_failing(<expression>)

Returns True when an expression (views, lambdas, ...) results in failure, and False when the expression succeeds.

Testing the content of exceptions

sp.catch_exception(<expression>, t = <type of the exception>)

t is optional, just using sp.catch_exception(<expression>) will be valid in most situations.

This method is used to test failing conditions of expressions (views, lambdas, ...). It returns an sp.TOption of type t that will contain sp.some(<exception>) when the expression fails or sp.none if it succeeds.

Testing off-chain views

Off-chain views can now be called from test scenarios the same way as entry points. The example below shows how to do it.

Example

import smartpy as sp

class MyContract(sp.Contract):
def __init__(self, param):
self.init(param)

@sp.offchain_view()
def state(self, param):
sp.verify(param < 5, "This is false: param > 5")
sp.result(self.data * param)

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

""" Test views """

# Display the offchain call result
scenario.show(c1.state(1))

# Assert the view result
scenario.verify(c1.state(2) == 2)
# Assert call failures
scenario.verify(sp.is_failing(c1.state(6))); # Expected to fail
scenario.verify(~ sp.is_failing(c1.state(1))); # Not expected to fail

# Assert exception result
# catch_exception returns an option:
# sp.none if the call succeeds
# sp.some(<exception>) if the call fails
e = sp.catch_exception(c1.state(7), t = sp.TString)
scenario.verify(e == sp.some("This is false: param > 5"))

Adding document information

The following elements represent six levels of section headings.

<h1> is the highest section level and <p> is the lowest.

‚Äčscenario.h1("a title")
‚Äčscenario.h2("a subtitle")
‚Äčscenario.h3('Equivalent to <h3> HTML tag.')
scenario.h4("Equivalent to <h4> HTML tag.")
scenario.p("Equivalent to <p> HTML tag.")

Showing expressions

To compute expressions, we use 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(expression, html = True, stripStrings = False)

scenario.show(c1.data.myParameter1 * 12)
‚Äčscenario.show(c1.data)

Computing expressions

To compute expressions, we use scenario.compute(<expression>).

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 a variable c1 represents a contract in a scenario, we can access some associated data:

  • c1.data

    Retrieve contract storage.

  • c1.balance

    Retrieve contract balance.

  • c1.baker

    Retrieve its optional delegated baker.

  • c1.address

    Retrieve its address within the scenario.

    In storage or similar circumstances, deployed contracts get addresses of the form:

    • KT1TezoooozzSmartPyzzSTATiCzzzwwBFA1
    • KT1Tezooo1zzSmartPyzzSTATiCzzzyfC8eF
    • KT1Tezooo2zzSmartPyzzSTATiCzzzwqqQ4H
    • KT1Tezooo3zzSmartPyzzSTATiCzzzseJjWC
    • KT1Tezooo4zzSmartPyzzSTATiCzzzyPVdv3
    • KT1Tezooo5zzSmartPyzzSTATiCzzzz48Z4p
    • KT1Tezooo6zzSmartPyzzSTATiCzzztY1196
    • KT1Tezooo7zzSmartPyzzSTATiCzzzvTbG1z
    • KT1Tezooo8zzSmartPyzzSTATiCzzzzp29d1
    • KT1Tezooo9zzSmartPyzzSTATiCzzztdBMLX
    • KT1Tezoo1ozzSmartPyzzSTATiCzzzw8CmuY
    • ...
  • c1.typed

    Retrieve its testing typed contract value.

    To access entry points, one can use field notation:

    • c1.typed.my_entry_point: to access typed entry point my_entry_point of contract c1.

Dynamic contracts

See reference Create Contract template.

Internally, SmartPy uses two types of contracts:

  • Static contracts which appear explicitly in the scenarios.
  • Dynamic contacts which are created in other contracts executed in the scenario (with sp.create_contract).

Declaring a dynamic contract of dynamic id (an integer) with the corresponding storage and full parameter types. The first dynamically created contractId is 0, then 1, etc.

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

Return a dynamic contract that contains regular fields data, balance, baker, address, typed and a call(...) method.

Dynamic contracts addresses are of the form:

  • KT1TezoooozzSmartPyzzDYNAMiCzzpLu4LU
  • KT1Tezooo1zzSmartPyzzDYNAMiCzztcr8AZ
  • KT1Tezooo2zzSmartPyzzDYNAMiCzzxyHfG9
  • KT1Tezooo3zzSmartPyzzDYNAMiCzzvqsJQk
  • KT1Tezooo4zzSmartPyzzDYNAMiCzzywTMhC
  • KT1Tezooo5zzSmartPyzzDYNAMiCzzvwBH3X
  • KT1Tezooo6zzSmartPyzzDYNAMiCzzvyu5w3
  • KT1Tezooo7zzSmartPyzzDYNAMiCzztDqbVQ
  • KT1Tezooo8zzSmartPyzzDYNAMiCzzq2URWu
  • KT1Tezooo9zzSmartPyzzDYNAMiCzzwMosaF
  • KT1Tezoo1ozzSmartPyzzDYNAMiCzzzknqsi

Call method

Send the parameter to the dynamic contract entry_point.

It is also possible to use .run(‚Ķ‚Äč) on the generated call as described in Registering and Displaying Calls to Entry Points.

dynamic_contract.call(entry_point, parameter)
dynamic_contract.call(entry_point, parameter).run(
sender = ..., # TAddress
source = ..., # TAddress
‚Äčamount = ..., # TMutez
now = ..., # TTimestamp
level = ..., # TNat
chain_id = ..., # TChainId
voting_powers = ..., # Dict(TKeyHash, TNat)
valid = ..., # True | False
show = ..., # True | False
exception = ..., # any
)

Checking assertions

To verify conditions, we use scenario.verify(<condition>).

To verify an equality condition, we can also use scenario.verify_equal(<left_expression>, <right_expression>) 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(<contract>).

It also provides a step-by-step mode that is very useful to understand some computation.

scenario.simulation(c1)