A First Encounter with SmartPy

A first regular Python script in SmartPy.io

def f(x):
return 2 * x - 3
alert(sum(f(x) for x in range(0, 12)))

A first example: building a TicTacToe game

import smartpy as spclass TicTacToe(sp.Contract):
def __init__(self):
self.init(nbMoves = 0,
winner = 0,
draw = False,
deck = [[0, 0, 0], [0, 0, 0], [0, 0, 0]])
@sp.entryPoint
def play(self, params):
# contract code is coming here
pass

Using a test

import smartpy as sp
class TicTacToe(sp.Contract):
def __init__(self):
self.init(nbMoves = 0,
winner = 0,
draw = False,
deck = [[0, 0, 0], [0, 0, 0], [0, 0, 0]])
@sp.entryPoint
def play(self, params):
# contract code is coming here
pass
if "templates" not in __name__:
@addTest(name = "Test TicTacToe")
def test():
html = ""
# define a contract
c1 = TicTacToe()
# show its representation
html += c1.fullHtml()
setOutput(html)
  • Stripped: shows the state of the smart contract and its entry points, here play which happens to do nothing.
  • SmartPy: like “Stripped” but with a bit more information.
  • Data Only: data only, no entry points.
  • Types: type information about storage and entry points.
  • All: gathers information from other tabs.
  • Michelson: a Michelson script derived from the SmartPy smart contract.
  • X: simply to hide the tabs.
  • The html output is generic, nothing was done for this exact example and it is nonetheless quite readable.
  • Types have been inferred from both script and storage. In Michelson, integers have two different types: int and nat. Here, SmartPy doesn’t know how the user wants to use them so infers intOrNat. Depending on some constructions, it infers int, nat or intOrNat. Similar inference is done for lists, maps, big maps, records, sum types, i.e., or-patterns, etc.
  • The Michelson compiler uses int for both int and intOrNat in SmartPy as this is the most general type and one can always require nat by explicitly using sp.nat(..) in SmartPy.

Going further with play

    @sp.entryPoint
def play(self, params):
self.data.deck[params.i][params.j] = params.move
self.data.nbMoves += 1
import smartpy as spclass TicTacToe(sp.Contract):
def __init__(self):
self.init(nbMoves = 0,
winner = 0,
draw = False,
deck = [[0, 0, 0], [0, 0, 0], [0, 0, 0]])
@sp.entryPoint
def play(self, params):
self.data.deck[params.i][params.j] = params.move
self.data.nbMoves += 1
# Tests
if "templates" not in __name__:
@addTest(name = "Test TicTacToe")
def test():
html = ""
# define a contract
c1 = TicTacToe()
# show its representation
html += c1.fullHtml()
html += h2("Message execution")
html += h3("A first move in the center")
html += c1.play(i = 1, j = 1, move = 1).html()
setOutput(html)
  • Types are inferred for params as well.
  • Some Michelson code is also generated.
  • The contract pretty-printing looks really similar to the Python script.
  • A new box appears as the result of c1.play(i = 1, j = 1, move = 1).html(). It describes the environment used to evaluate play, its arguments and outputs.

Checks

        html   += c1.play(i = 1, j = 1, move = 2).html()
        sp.verify(self.data.deck[params.i][params.j] == 0)
    @sp.entryPoint
def play(self, params):
sp.verify(self.data.deck[params.i][params.j] == 0)
self.data.deck[params.i][params.j] = params.move
self.data.nbMoves += 1
    @sp.entryPoint
def play(self, params):
sp.verify((self.data.winner == 0) & ~self.data.draw)
sp.verify((params.i >= 0) & (params.i < 3))
sp.verify((params.j >= 0) & (params.j < 3))
sp.verify((params.move == 1) | (params.move == 2))
sp.verify(self.data.deck[params.i][params.j] == 0)
self.data.deck[params.i][params.j] = params.move
self.data.nbMoves += 1

Computing Winner or Draw

if "templates" not in __name__:
@addTest(name = "Test TicTacToe")
def test():
html = ""
# define a contract
c1 = TicTacToe()
# show its representation
html += h2("A sequence of interactions with a winner")
html += c1.fullHtml()
html += h2("Message execution")
html += h3("A first move in the center")
html += c1.play(i = 1, j = 1, move = 1).html()
html += h3("A forbidden move")
html += c1.play(i = 1, j = 1, move = 2).html()
html += h3("A second move")
html += c1.play(i = 1, j = 2, move = 2).html()
html += h3("Other moves")
html += c1.play(i = 2, j = 1, move = 1).html()
html += c1.play(i = 2, j = 2, move = 2).html()
# assert int(c1.data.winner) == 0
html += c1.play(i = 0, j = 1, move = 1).html()
# assert int(c1.data.winner) == 1
setOutput(html)
    @sp.entryPoint
def play(self, params):
sp.verify((self.data.winner == 0) & ~self.data.draw)
sp.verify((params.i >= 0) & (params.i < 3))
sp.verify((params.j >= 0) & (params.j < 3))
sp.verify((params.move == 1) | (params.move == 2))
sp.verify(self.data.deck[params.i][params.j] == 0)
self.data.deck[params.i][params.j] = params.move
self.data.nbMoves += 1
r = range(0, 3)
self.checkLine([self.data.deck[params.i][j] for j in r])
self.checkLine([self.data.deck[i][params.j] for i in r])
self.checkLine([self.data.deck[i][i ] for i in r])
self.checkLine([self.data.deck[i][2 - i ] for i in r])
sp.if (self.data.nbMoves == 9) & (self.data.winner == 0):
self.data.draw = True
def checkLine(self, lin):
sp.if ((lin[0]!=0) & (lin[0]==lin[1]) & (lin[0]==lin[2])):
self.data.winner = lin[0]
  • checkLine is a helper method of the contract that serves to check if three aligned cells contain identical and non-zero values.
  • It is called on both line and column of the move currently being played and on both diagonals.
  • Meta-programming is nicely at play here, checkLine is called at creation time of the smart contract, generating the corresponding code. The SmartPy code output in the “Stripped” or “SmartPy” tabs is no longer quasi identical to what was inputted; it is expanded.

Wrapping everything up

import smartpy as spclass TicTacToe(sp.Contract):
def __init__(self):
self.init(nbMoves = 0,
winner = 0,
draw = False,
deck = [[0, 0, 0], [0, 0, 0], [0, 0, 0]])
@sp.entryPoint
def play(self, params):
sp.verify((self.data.winner == 0) & ~self.data.draw)
sp.verify((params.i >= 0) & (params.i < 3))
sp.verify((params.j >= 0) & (params.j < 3))
sp.verify((params.move == 1) | (params.move == 2))
sp.verify(self.data.deck[params.i][params.j] == 0)
self.data.deck[params.i][params.j] = params.move
self.data.nbMoves += 1
r = range(0, 3)
self.checkLine([self.data.deck[params.i][j] for j in r])
self.checkLine([self.data.deck[i][params.j] for i in r])
self.checkLine([self.data.deck[i][i ] for i in r])
self.checkLine([self.data.deck[i][2 - i ] for i in r])
sp.if (self.data.nbMoves == 9) & (self.data.winner == 0):
self.data.draw = True
def checkLine(self, lin):
sp.if ((lin[0]!=0) & (lin[0]==lin[1]) & (lin[0]==lin[2])):
self.data.winner = lin[0]
# Tests
if "templates" not in __name__:
@addTest(name = "Test TicTacToe")
def test():
html = ""
# define a contract
c1 = TicTacToe()
# show its representation
html += h2("A sequence of interactions with a winner")
html += c1.fullHtml()
html += h2("Message execution")
html += h3("A first move in the center")
html += c1.play(i = 1, j = 1, move = 1).html()
html += h3("A forbidden move")
html += c1.play(i = 1, j = 1, move = 2).html()
html += h3("A second move")
html += c1.play(i = 1, j = 2, move = 2).html()
html += h3("Other moves")
html += c1.play(i = 2, j = 1, move = 1).html()
html += c1.play(i = 2, j = 2, move = 2).html()
assert int(c1.data.winner) == 0
html += c1.play(i = 0, j = 1, move = 1).html()
assert int(c1.data.winner) == 1
html += p("Player %i has won" % int(c1.data.winner))
setOutput(html)

To go further

  • Add a new element to the storage called nextPlayer declaring a next player and checking, in play, that this next player plays.
  • Further expanding the design to verify that the sender of the transaction is the correct one by something similar to sp.verify(sp.sender.identity == self.data.nextPlayerAddress). This will be explained in a following tutorial.
  • Players could put some XTZ bound before playing and the winner gets everything. This will be explained and expanded with State Channels in a following post.
  • We could also play another game. Chess and Nim have templates in SmartPy.io. They are not finished in two directions: SmartPy scripts that need to be completed and Michelson compilation that still lacks some constructions. Completing the SmartPy scripts is a wonderful exercice for developers wishing to understand better how it works and get ready for more challenging SmartPy programming. For a more complete Michelson compilation, this is being addressed and will be available soon. Even when incomplete, SmartPy’s Michelson compiler tries its best to show some skeleton of a compiled script and this is already useful.

--

--

--

An intuitive and effective smart contracts language and development platform for Tezos. In Python.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

How Does Blockchain Amplifies AdTech Industry

A Vision for a Web3 Social Media Network

On decentralization

WHY ALGORAND? WHAT SHOULD I KNOW BEFORE I CHOOSE ALGORAND FOR MY BUSINESS OR INVESTMENT?

EdenChain, — The Messiah of the Blockchain Technology.

“Decentralisation” is a misleading term

Governance of Blockchain Networks & Other DAOs (Part 2)

Is Blockchain the Future of Social Media?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
SmartPy.io

SmartPy.io

An intuitive and effective smart contracts language and development platform for Tezos. In Python.

More from Medium

Vulnerabilities in Smart Contracts

Why does X’s music sound so good?

An Article a Day: Daniel (2022)

The Most Relaxing Waterfall Video with Sound