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

Docstrings of the classes cited in this section