Archetype is a simple high-level language to develop Smart Contracts on the Tezos blockchain. It enables to formally verify contracts by transcoding them to Why3. For more information on the Archetype language, please follow the link below:

In this article, we develop, specify and formally verify a smart contract; with formal verification, we find and fix bugs, and make sure it complies with its specification.

The video below is the screening of the demo (25 minutes):

Below is the textual version of the demo.

Contract

The objective of this demo is to develop a smart contract for an on-chain loyalty program. This contract records miles and miles owners with two entry points: add and consume.

Contract assets

A mile owner is identified by an address value and possesses several miles (potentially zero). A mile is identified by a unique string id and an expiration date so that a mile cannot be consumed after its expiration date.

The schema below illustrates the basic modeling of data:

An optimization is added to the above model. A mile is augmented with a quantity value to gather several miles with the same expiration date. For example, 3 miles with the same expiration date are merged into one mile with the quantity value set to 3:

3 miles with the same expiration date …

… merged into one asset.

Code

The following snippet below is the archetype implementation of the contract data:

Note that the type of collection of miles (line 11) is partition: this is to specify that one mile is owned by one and only one owner. This prevents for example from adding or removing a mile straightforwardly to the mile collection. It must go through the miles collection of an owner.

When adding a mile to an owner’s miles collection, it is tested whether the mile already exists; if so, the add instruction fails.

This is implemented with a single mile collection and the miles field is implemented as a list of mile identifiers, not as a list of miles.

Actions

The add action has two arguments: an owner address and a new mile to add to this owner:

owner above (line 4) is the collection of all owners. The get method enables to retrieve one owner from its address (which is the owner identifier).

The add method (line 5) is used to add an asset to a collection (here o.miles).

The consume action takes two arguments: an owner's address ow and the number of this owner’s miles to consume qty :

The implementation of the consume action is not that straightforward because of the quantity field carried by each mile:

the collection of the owner’s valid miles is created with the select method (line 4)

miles are removed until the number of removed miles is equal to qty; for that, each valid mile is iterated: if current mile’s quantity is less than the remaining number of miles to remove (line 9), then the mile is removed (line 11) and the remaining value is decreased by its quantity; otherwise current mile’s quantity is decreased (line 14) and remaining number is set to 0 (line 13)

This implementation has 2 bugs: can you spot them? The verification presented in the section below will help to detect and fix them.

Specify

Note that the type of the quantity field of the mile asset is int (ie. integer line 6 in the asset section above), which may be negative. However, it does not make sense for the quantity to be equal or lower than zero. We would like to specify some constraints on the quantity field, specifically that it should remain strictly positive.

It is possible to specify such a constraint with an asset invariant (lines 5 to 7 below):

This will generate the corresponding proof obligations for all actions (add and consume).

We want to operate the contract from one address, that is that each action may only be called by this address. Archetype provides a dedicated “modifier” called by (line 4):

The security predicate only_by_role (line 2 below) may be used in the security section to specify that any action should use the called by modifier on admin:

The no_storage_fail predicate (line 3 above) is useful to state that an action (here the add action) should not fail, either with a “key not found” or “key exists” exception. It is usually best practice not to program with logical exceptions, either explicit (with try … catch mechanisms) or implicit like in smart contracts which come with a rollback mechanism in case of failure. It is better to explicitly open execution branches with if then else controls. Exceptions should be used only for hardware, network … failures, that is to say, anything the program itself does not have control over.

Obviously, this is not the case of the add action which will fail for example if ow does not correspond to any existing recorded owner.

These security predicates generate proof obligations as presented in the verification section below. The list of available security predicates may be found here.

At last, we want to make sure the implementation of the consume action complies with the basic requirement that qty miles are removed. The way to formulate such a requirement is to set up an arithmetic relation between the storage state before the execution of the action and after the execution:

The postcondition p1 (line 5 above) literally reads “the sum of quantity fields after execution of consume is equal to the sum before execution minus the value qty”.

As a recap, here is the full contract code before verification:

Verify

In this section, we verify the 4 predicates defined in the previous section:

s0: any action may be called only by the admin role

s1: the add action does not fail when accessing the storage

p0: the consume action consumes exactly qty miles

i0: the quantity value of any mile is strictly positive

The VS Code archetype extension provides a left panel which lists the predicates and enables to call why3 ide on each one to be verified: