Introduction to SmartPy Paris Francois Maurel – Roland Zumkeller 24 October 2019 1 / 52
SmartPy Python library for writing Tezos smart contracts Compiles to Michelson Simulate & analyze Deploy & interact 2 / 52
SmartPy Python is the most popular language in the world, thanks to an intuitive syntax and amazing meta-programming capabilities. SmartPy has a backend called SmartML. SmartML is written in OCaml, the same language as Tezos. We use it for everything behind the scene : typing, analysis, simulation, compilation, etc. 3 / 52
Outline The SmartPy.io website 1 The SmartPy library 2 How SmartPy Compiles Contracts 3 Conclusion 4 4 / 52
Outline The SmartPy.io website 1 The SmartPy library 2 How SmartPy Compiles Contracts 3 Conclusion 4 5 / 52
SmartPy.io 6 / 52
Hello, World! import smartpy as sp 1 2 class HelloWorld (sp.Contract): 3 def __init__(self): self.init(mem = "") 4 5 @sp.entryPoint 6 def remember(self, param): 7 self.data.mem += param 8 9 @addTest(name = "Test") 10 def test(): 11 c = HelloWorld() 12 s = sp.testScenario() 13 s += c 14 s += c.remember("Hello, ") 15 s += c.remember("World!") 16 7 / 52
Hello, World : Michelson 8 / 52
Library vs. Language Python is used to construct SmartPy contracts, not to execute them. Internal representation : “SmartML ” (OCaml) Could also be embedded in JavaScript etc. 9 / 52
What happens inside the browser? 1 The Python code (executed once , via Brython) constructs a syntax tree : @sp.entryPoint def remember(self, params): print (self.data.mem + param + 42) self.data.mem += params On the browser console : (add (add (attr (data) "mem") (params)) (literal (intOrNat 42))) 2 The SmartPy backend (run via js_of_ocaml) performs type checking, evaluation, compilation etc. 3 The output is rendered as HTML. 10 / 52
Non-Hello-World Examples on SmartPy.io : Calculator Fungible and non-fungible assets Multisig contracts Escrow contract State channels (under development) Games : tic-tac-toe, nim, chess 11 / 52
Out-of-browser SmartPy : Interpreter $ SmartPy.sh local-test calculator.py calc $ cat calc/test.output Creating contract -> 0 Executing add(Record(x = 2, y = 5))... -> 7 Executing square(12)... -> 144 Verifying contractData(0).value == 144... OK Executing squareRoot(1234)... -> 35 Executing factorial(100)... -> 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 Executing log2(contractData(0).value)... -> 524 12 / 52
Out-of-browser SmartPy : Compiler $ SmartPy.sh local-compile calculator.py 'Calculator()' calc $ cat calc/contractCode.tz parameter (or (or (or (or (or (pair %add (nat %x) (nat %y)) storage nat; code { DUP; # pair(params, storage).pair(params, storage) CDR; # storage.pair(params, storage) SWAP; # pair(params, storage).storage CAR; # params.storage IF_LEFT ... Soon : automated testing with compiled code (in sandboxed tezos-client) 13 / 52
Example : Fungible Assets class Fungible (sp.Contract): 1 def __init__(self, admin): 2 self.init(balances = sp.bigMap(), admin = admin) 3 4 def increaseBalance(self, x, amount): 5 sp. if self.data.balances.contains(x): 6 self.data.balances[x] += amount 7 sp. else : 8 self.data.balances[x] = amount 9 10 def decreaseBalance(self, x, amount): 11 b = self.data.balances[x] - amount 12 sp.verify(b >= 0) 13 sp. if b == 0: 14 del self.data.balances[x] 15 sp. else : 16 self.data.balances[x] = b 17 14 / 52
Example : Fungible Assets @sp.entryPoint 1 def transfer(self, params): 2 self.decreaseBalance(sp.sender, params.amount) 3 self.increaseBalance(params.dest, params.amount) 4 5 @sp.entryPoint 6 def mint(self, params): 7 sp.verify(sp.sender == self.data.admin) 8 self.increaseBalance(params.dest, params.amount) 9 15 / 52
Example : Fungible Assets @sp.entryPoint 1 def transfer(self, params): 2 self.decreaseBalance(sp.sender, params.amount) 3 self.increaseBalance(params.dest, params.amount) 4 5 @sp.entryPoint 6 def mint(self, params): 7 sp.verify(sp.sender == self.data.admin) 8 self.increaseBalance(params.dest, params.amount) 9 We are going to extend this example together after this talk. 15 / 52
How To Write a Test @addTest(name = "First test") 1 def test(): 2 admin = sp.address("Admin") 3 alice = sp.address("Alice") 4 bob = sp.address("Bob") 5 6 s = sp.testScenario() 7 8 s = Fungible(admin = admin) 9 s += c 10 s += c.mint(dest = alice, amount = 5).run(sender = admin) 11 s += c.mint(dest = bob, amount = 5).run(sender = admin) 12 s += c.transfer(dest = alice, amount = 3).run(sender = bob) 13 s += c.transfer(dest = alice, amount = 3).run(sender = bob) 14 s += c.mint(dest = bob, amount = 100).run(sender = bob) 15 16 / 52
How To Run a Test 17 / 52
How To Run a Test 18 / 52
How To Run a Test c.mint(dest = alice, amount = 5).run(sender = admin) c.mint(dest = bob, amount = 5).run(sender = admin) c.transfer(dest = alice, amount = 3).run(sender = bob) c.transfer(dest = alice, amount = 3).run(sender = bob) c.mint(dest = bob, amount = 100).run(sender = bob) 19 / 52
How To Originate 20 / 52
How To Interact With a Contract 21 / 52
Fungible Assets : better-call.dev c.mint(dest = alice, amount = 5).run(sender = admin) 22 / 52
Fungible Assets : better-call.dev c.mint(dest = bob, amount = 5).run(sender = admin) 23 / 52
Fungible Assets : better-call.dev c.transfer(dest = alice, amount = 3).run(sender = bob) 24 / 52
Fungible Assets : better-call.dev c.transfer(dest = alice, amount = 3).run(sender = bob) 25 / 52
Fungible Assets : better-call.dev c.mint(dest = bob, amount = 100).run(sender = bob) 26 / 52
Outline The SmartPy.io website 1 The SmartPy library 2 How SmartPy Compiles Contracts 3 Conclusion 4 27 / 52
SmartPy language elements Entry points Data types : TUnit , TBool , TInt / TNat , TString , TBytes TRecord Containers : lists, TSet , TMap , TBigMap TMutez , TTimestamp , TAddress , THash , TKey Control flow : sp.if , sp.for , sp.while Local variables Runtime errors and checks 28 / 52
Data : Integers In Michelson there are two integer types : int for integers nat for non-negative integers In SmartPy there are : TInt , TNat , and TIntOrNat . When we write y = abs(x) , SmartPy infers that x : TInt and y : TNat . When we write x + y , SmartPy checks that x and y have the same type. 29 / 52
Data : Records sp.record(row = 2, col = 3) is a value. No need to declare a type, SmartPy infers it : {row: TIntOrNat; col: TIntOrNat} . Can be nested, passed to entry points, used inside complex data structures etc. Compiled to nested Michelson pairs (with annotations). 30 / 52
Data : Lists and maps self.init(l = ['a','b'], m = {'a': 65, 'b': 66}) Inferred types : l: [TString] m: TMap(TString, TIntOrNat) More explicitly : sp.map({'a': 65, 'b': 66} , tkey=TString, tvalue=TInt) Operations : self.data.l.append('c') self.data.m['c'] = 67 31 / 52
Local Variables and Assignment Python variables contain SmartPy expressions (effectively inlined) : s = self.data.x + self.data.y self.data.z = s*s SmartPy “local” variables are preserved : s = sp.newLocal('s', self.data.x + self.data.y) self.data.z = s*s 32 / 52
Control flow : sp. while loops Binary logarithm : @sp.entryPoint def log2(self, x): self.data.result = 0 y = sp.newLocal('y', x) sp. while 1 < y: self.data.result += 1 y.set(y // 2) sp. while ... is syntactic sugar for with sp.whileBlock(...) . No recursion. 33 / 52
Runtime Errors and Checks Division and map lookups can fail. sp.verify checks a condition at runtime : def log2(self, x): sp.verify(x > 0) # <<< self.data.result = 0 y = sp.newLocal('y', x) sp. while 1 < y: self.data.result += 1 y.set(y // 2) Compiled to Michelson- FAIL . 34 / 52
Assignment In Nested Maps What does this Python program output? x = {'a': {'b': 0}} y = x['a'] y['b'] = 42 print (x['a']['b']) 35 / 52
Assignment In Nested Maps What does this Python program output? x = {'a': {'b': 0}} y = x['a'] y['b'] = 42 print (x['a']['b']) Output : 42 35 / 52
Assignment In Nested Maps What does this Python program output? x = {'a': {'b': 0}} y = x['a'] y['b'] = 42 print (x['a']['b']) Output : 42 What does this SmartPy contract output? def __init__(self): self.init(x = {'a': {'b': 0}}, out = -1) @sp.entryPoint def ep(self, params): y = sp.newLocal('y', self.data.x['a']) y['b'] = 42 self.data.out = self.data.x['a']['b'] Output : 0 35 / 52
Outline The SmartPy.io website 1 The SmartPy library 2 How SmartPy Compiles Contracts 3 Conclusion 4 36 / 52
Compiler : Inner Workings Input : SmartML representation. "stack tags" keep track of location of data : parameter, storage, local variable, iteration variable “Operations” (calls to other contracts) are added as late as possible. Param-storage pair always remains at bottom of the stack. 37 / 52
Recommend
More recommend