Quickstart Guide

Now that you’ve installed Pint, it’s time to write your first Pint contract. The first contract we will implement is a counter.

Creating a Project Directory

You'll start by making a directory to store your Pint code. Open a terminal and enter the following commands to make a projects directory:

mkdir ~/projects
cd ~/projects

Now, we will use the pint tool to create a new project in the projects directory:

pint new counter
cd counter

The pint new commands creates a new Pint project with the provided name. In the newly created directory counter, you should find a pint.toml file as well as a src/ directory with a single file in it called contract.pnt:

projects
└── counter
    ├── pint.toml
    └── src
        └── contract.pnt

The auto-generated pint.toml file describes how to build the project. The src/contract.pnt file is the root file of a contract project, i.e., this is where the compilation starts when we build the project.

Open the pint.toml and inspect its content:

[package]
name = "counter"
kind = "contract"

[dependencies]
# Library dependencies go here.

[contract-dependencies]
# Contract dependencies go here.

The one thing to note is that the kind field in this pint.toml is set to contract. This is the default project kind. Alternatively, kind can be a library.

Writing a Pint Program

Next, open the src/contract.pnt file and erase its content. Then, paste the following:

storage {
    counter: int,
}

predicate Increment {
    state counter: int = mut storage::counter;

    constraint (counter == nil && counter' == 1) || counter' == counter + 1;
}

This is a contract with a single predicate and a single storage variable. The storage variable counter, of type int (i.e. integer), is declared in a storage block. The only predicate in this contract is called Increment and contains two statements:

  1. A state declaration which reads the variable counter.
  2. A constraint statement which enforces some logic on the state of the contract.

The above constraint is essentially saying: "if counter hasn't been set yet (i.e. is nil), set the new value of counter to 1 (counter' == 1). Otherwise, increment counter by 1".

Don't worry if this looks a bit overwhelming! We will later dive into each feature in this contract separately.

Building the Project

To build the project above, simply run the following command in the counter directory:

pint build

You should get something like this:

   Compiling counter [contract] (/path/to/counter)
    Finished build [debug] in 4.157208ms
    contract counter            C276E4E5EAF789F82B671E53F0B202AE357C511F2B711E4921310946ACE63B7C
         └── counter::Increment 3D7ABBA5C62DF177EFD61CA9D74ED055B29267404112583FA2E4C0D98B828149

Note the two 256-bit numbers in the output. These represent the content hash (i.e. the sha256 of the bytecode) of the counter contract (as a whole) and the Increment predicate, respectively. These "addresses" will be used when referring to the contract or the predicate (in a proposed solution for example).

In the counter directory, you will also notice a new directory called out. Navigate to out/debug and inspect the two json files that you see.

  • counter.json represents the compiled bytecode in JSON format, which is the most important artifact produced by the compiler. This file is used when validating a solution. That is, when a solution is submitted, this file contains the bytecode that "runs" and decides whether all the relevant constraints are valid.
  • counter-abi.json is the Application Binary Interface (ABI) of the contract. It basically describes how to interact with the contract from an external application or another contract. For example, while crafting a solution, the ABI can be used to figure out where the various storage variables are stored (i.e. their keys) and their types. This information is crucial to form correct solutions.

Note: Appendix C contains the ABI spec.

Now that we have built and inspected the artifacts of our project, we can proceed to build an application that interacts with this contract. We won't cover this topic here, but you can check out this Getting Started with Essential Application book, which covers this exact same "counter" example and how to build a Rust application that interacts with it using the Essential VM.