Starting with SmartPy Part 3: Testing Patterns and Analytics

Verifying Smart Contract Behavior and Security

Teckhua Chiang
Aug 16 · 3 min read

Writing a smart contract is half the battle, but now we have to perform thorough testing to ensure that our work performs correctly and securely. Usually, this would entail deploying our experimental contract to a testnet and repeatedly invoking the contract to test for unexpected behavior, which is a tiring and time-consuming process. SmartPy’s online editor relieves us from the majority of this burden with its built-in simulation suite, which enables local contract invocations and automatic assertion verification.

Just like the basic test we wrote in Part 1, we begin defining a SmartPy test with the annotation @addTest() and the method header def test(). Note that test names cannot have spaces. The testScenario object includes functionality to render HTML to make the test output presentable.

We can start building our HTML output by creating a header element with h1("Event Planner"). Then, we initialize firstOwner and secondOwner to be of type address with the sp.address() constructor for later use. Calling EventPlanner(firstOwner) will create an instance of the EventPlanner contract with firstOwner as the owner. We transform the contract into HTML with scenario += c1.

@addTest(name = "AdvancedTest")
def test():
scenario = sp.testScenario()
# Create HTML output for debugging
scenario.h1("Event Planner")

# Initialize test addresses
firstOwner = sp.address("tz1-firstOwner-address-1234")
secondOwner = sp.address("tz1-secondOwner-address-5678")

# Instantiate EventPlanner contract
c1 = EventPlanner(firstOwner)

# Print contract instance to HTML
scenario += c1

The next step is to invoke the various entry points of the EventPlanner in order to verify that it functions correctly. We can print the name of a specific invocation by adding the result of h2(). An invocation is denoted by an expression of the form <contract>.<entryPoint>(<paramsField1> = <val1>, <paramsField2> = <val2>, <...>).run(sender = <address>), which will invoke the entryPoint of contract with params encapsulating the fields paramsField1, paramsField2, … representing the values val1, val2, … respectively. run(sender = <address>) will set the sp.sender to the given address, and turn the invocation details into HTML.

    # Invoke EventPlanner entry points and print results to HTML
scenario.h2("Set date for Tezos Meetup to 11-28-2017")
scenario += c1.setDate(name = "Tezos Meetup", newDate = "11-28-2017").run(sender = firstOwner)

scenario.h2("Set number of guests for Tezos Meetup to 80")
scenario += c1.setNumGuests(name = "Tezos Meetup", newNumGuests = 80).run(sender = firstOwner)

scenario.h2("Change owner")
scenario += c1.changeOwner(newOwner = secondOwner).run(sender = firstOwner)

scenario.h2("New owner sets date for Tezos Meetup to 03-21-2019")
scenario += c1.setDate(name = "Tezos Meetup", newDate = "03-21-2019").run(sender = secondOwner)

scenario.h2("Old owner attempts to set date for Tezos Meetup")
scenario += c1.setDate(name = "Tezos Meetup", newDate = "10-15-2018").run(sender = firstOwner, valid = False)

Finally, we can perform automatic verification of the expected end state of the EventPlanner after testing with scenario.verify() statements. SmartPy will infer types for numerics and dates. When working with addresses, a special syntax must be followed, as shown by the comparison statement (c1.data.owner) == sp.address('tz1-secondOwner-address-5678').

    # Verify expected results
scenario.verify((c1.data.nameToEvent["Tezos Meetup"].date) == '03-21-2019')
scenario.verify((c1.data.nameToEvent["Tezos Meetup"].numGuests) == 80)
scenario.verify((c1.data.owner) == sp.address('tz1-secondOwner-address-5678'))

We’re completed our exploration of the SmartPy simulation suite! Here is the full EventPlanner test for your reference:

@addTest(name = "AdvancedTest")
def test():
scenario = sp.testScenario()
# Create HTML output for debugging
scenario.h1("Event Planner")

# Initialize test addresses
firstOwner = sp.address("tz1-firstOwner-address-1234")
secondOwner = sp.address("tz1-secondOwner-address-5678")

# Instantiate EventPlanner contract
c1 = EventPlanner(firstOwner)

# Print contract instance to HTML
scenario += c1

# Invoke EventPlanner entry points and print results to HTML
scenario.h2("Set date for Tezos Meetup to 11-28-2017")
scenario += c1.setDate(name = "Tezos Meetup", newDate = "11-28-2017").run(sender = firstOwner)

scenario.h2("Set number of guests for Tezos Meetup to 80")
scenario += c1.setNumGuests(name = "Tezos Meetup", newNumGuests = 80).run(sender = firstOwner)

scenario.h2("Change owner")
scenario += c1.changeOwner(newOwner = secondOwner).run(sender = firstOwner)

scenario.h2("New owner sets date for Tezos Meetup to 03-21-2019")
scenario += c1.setDate(name = "Tezos Meetup", newDate = "03-21-2019").run(sender = secondOwner)

scenario.h2("Old owner attempts to set date for Tezos Meetup")
scenario += c1.setDate(name = "Tezos Meetup", newDate = "10-15-2018").run(sender = firstOwner, valid = False)

# Verify expected results
scenario.verify((c1.data.nameToEvent["Tezos Meetup"].date) == '03-21-2019')
scenario.verify((c1.data.nameToEvent["Tezos Meetup"].numGuests) == 80)
scenario.verify((c1.data.owner) == sp.address('tz1-secondOwner-address-5678'))

Here’s the full contract, with tests pre-loaded in the IDE.

The Cryptonomic Aperiodical

Product and development updates from Cryptonomic

Thanks to SmartPy.io

Teckhua Chiang

Written by

The Cryptonomic Aperiodical

Product and development updates from Cryptonomic

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade