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
Initial storage
Contract storages are typically determined in the contract constructors but it's possible to do it right before registering them inside scenarios.
c2 = MyContract()
c2.init_storage(sp.record(a = 12, b = True))
scenario += c2
Initial balance
Additionally to entrypoints, 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")
Calls to entrypoints
# Call entrypoint
c1.my_entrypoint(12)
# Call entrypoint with customized block attributes.
c1.my_entrypoint(13).run(
sender = None, # sp.address
source = None, # sp.address
chain_id = None, # sp.chain_id
level = None, # sp.nait
now = None, # sp.timestamp
voting_powers = None, # sp.map[sp.key_hash,sp.nat]
amount = None, # sp.mutez
valid = True, # sp.bool
show = True, # sp.bool
exception = None, # any
)
The .run(...)
method and its parameters are all optional.
Calls optional arguments
Generic context arguments are:
Parameter | Type | Accessor | Description |
---|---|---|---|
sender | sp.address or sp.test_account | sp.sender | The simulated sender of the transaction. Specific to call. |
source | sp.address or sp.test_account | sp.source | The simulated source of the transaction. Specific to call. |
chain_id | sp.chain_id | sp.chain_id | The simulated chain_id. Preserved until changed. |
level | [sp.nat] | sp.level | The simulated block level. Preserved until changed. |
now | [sp.timestamp] | sp.now | The simulated block timestamp. Preserved until changed. |
voting_powers | [sp.map][sp.key_hash, [sp.nat]] | sp.total_voting_power , sp.voting_power | The simulated voting powers for the test. Preserved until changed. |
Specific context arguments are:
Parameter | Type | Description |
---|---|---|
amount | [sp.mutez] | The simulated amount sent. It populates sp.amount . |
valid | [sp.bool] | Tells the interpreter if the transaction is expected to fail or not. True by default. |
show | [sp.bool] | Show or hide the transaction. True by default. |
exception | any type | The expected exception raised by the transaction. If present, valid must be False. |
Exceptions
Content of exceptions
Views
Views, both off-chain or on-chain, can now be called from test scenarios the same way as entrypoints. 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.string)
scenario.verify(e == sp.some("This is false: param > 5"))
Document extra 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.")
Expressions
Showing expressions
Computing expressions
Compute optional arguments
Generic context arguments are:
Parameter | Type | Accessor | Description |
---|---|---|---|
sender | sp.address or sp.test_account | sp.sender | The simulated sender of the computation. Specific to computation. |
source | sp.address or sp.test_account | sp.source | The simulated source of the computation. Specific to computation. |
chain_id | sp.chain_id | sp.chain_id | The simulated chain_id. Preserved until changed. |
level | [sp.nat] | sp.level | The simulated block level. Preserved until changed. |
now | [sp.timestamp] | sp.now | The simulated block timestamp. Preserved until changed. |
voting_powers | [sp.map][sp.key_hash, [sp.nat]] | sp.total_voting_power , sp.voting_power | The simulated voting powers for the test. Preserved until changed. |
Data associated with 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 entrypoints, one can use field notation:
c1.typed.my_entrypoint
: to access typed entrypoint my_entrypoint of contractc1
.
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 entrypoint
.
It is also possible to use .run(...)
on the generated call as described in Registering and Displaying Calls to Entrypoints.
dynamic_contract.call(entrypoint, parameter)
dynamic_contract.call(entrypoint, parameter).run(
sender = ..., # sp.address
source = ..., # sp.address
amount = ..., # sp.mutez
now = ..., # sp.timestamp
level = ..., # sp.nat
chain_id = ..., # sp.chain_id
voting_powers = ..., # sp.map[sp.key_hash, sp.nat]
valid = ..., # sp.bool
show = ..., # sp.bool
exception = ..., # any
)