How to create a smart contract¶
A smart contract in pyledger is a function that returns an instance of
pyledger.contract.Builder
. This object is a helper to manage the
attributes of the smart contract and the methods that may or may not modify
those attributes. The simplest smart contract you may think of is one that
just returns the string “Hello”.
from pyledger.handlers import make_tornado
from pyledger.contract import Builder
from pyledger.config import args
import tornado.ioloop
def hello():
def say_hello(attrs):
return attrs, 'Hello'
contract = Builder('Hello')
contract.add_method(say_hello)
return contract
if __name__ == '__main__':
application = make_tornado(hello)
application.listen(args.port)
tornado.ioloop.IOLoop.instance().start()
If you run this snippet as script without options, you will be able to
connect to this server with the command line client provided by pyledger,
called pyledger-shell
:
(env) $> pyledger-shell
PyLedger simple client
(http://localhost:8888)> contracts
Hello
(http://localhost:8888)> api Hello
say_hello ( )
(http://localhost:8888)> call Hello say_hello
Hello
(http://localhost:8888)>
This almost trival example is useful to understand the very basics about how
the contracts are created. The contract is called Hello which is the argument
of the Builder instance. The method say_hello gets no arguments and it
modifies no attributes, but it must get the attributes as an argument and
return them anyways. If an additional argument, like the Hello
string,
is returned by the method, it is given as a second return argument.
Attributes¶
Let’s change the previous example a little by adding an attribute to the contract. For instance, we will make a counter of the amount of times the contract has greeted us.
def hello():
def say_hello(attrs):
attrs.counter += 1
return attrs, 'Hello {}'.format(attrs.counter)
contract = Builder('Hello')
contract.add_attribute('counter', 0)
contract.add_method(say_hello)
return contract
A session with this new smart contract would be as follows:
(http://localhost:8888)> call Hello say_hello
Hello 1
(http://localhost:8888)> call Hello say_hello
Hello 2
(http://localhost:8888)> status Hello
{'counter': 2}
Note that the contract function pretty much looks like an object, it has attributes and methods that change those attributes. It is also quite similar as how Solidity defines the smart contracts, with attributes and methods that modify them. Pyledger is a little more explicit.
We can also define methods with arguments, and here’s one of the important particularities of pyledger: all the arguments but the first one (attrs) must be type annotated. For instance, this is a contract that greets with a name, that is passed as a parameter.
def hello():
def say_hello(attrs, name: str):
attrs.counter += 1
return attrs, 'Hello {} for time #{}'.format(name, attrs.counter)
contract = Builder('Hello')
contract.add_attribute('counter', 0)
contract.add_method(say_hello)
return contract
A smart contract must expose an API, and type annotation is needed to let the client and any user of the contract to know which type the arguments must be:
(env) $> pyledger-shell
PyLedger simple client
(http://localhost:8888)> api Hello
say_hello ( name [str] )
(http://localhost:8888)> call Hello say_hello Guillem
Hello Guillem for time #1
(http://localhost:8888)> call Hello say_hello Guillem
Hello Guillem for time #2
(http://localhost:8888)> status Hello
{'counter': 2}
(http://localhost:8888)>
With these features, the smart contracts can be as complex as needed. One can store information of any kind within the arguments, that are the ones that define the status of the contract.
Important
If you want the contract to be fast and you want to avoid obscure bugs too, keep your attributes as primitive python types.
Exceptions¶
Contracts can raise only a generic exception of type Exception
.
The goal is only to inform the user that the operation has not been
successful. Note that the methods that return no additional value send back
to the client the string SUCCESS. This means that the client is always
waiting for a message to come.
We will introduce some very simple exception that checks the most common mispelling of my name
def hello():
def say_hello(attrs, name: str):
if name == 'Guillen':
raise Exception('You probably mispelled Guillem')
attrs.counter += 1
return attrs, 'Hello {} for time #{}'.format(name, attrs.counter)
contract = Builder('Hello')
contract.add_attribute('counter', 0)
contract.add_method(say_hello)
return contract
And how the exception is handled at the client side:
(env) $> pyledger-shell
PyLedger simple client
(http://localhost:8888)> call Hello say_hello Guillem
Hello Guillem for time #1
(http://localhost:8888)> call Hello say_hello Guillen
You probably mispelled Guillem
(http://localhost:8888)> call Hello say_hello Guillem
Hello Guillem for time #2