📑Schemas & Tables

When your smart contract needs to persist some data across multiple calls to functions in the contract, it should use a table. Tables in Pact are relational databases and have a key-row structure. Keys are always strings. You can define a table with deftable.

Our smart contract needs to persist four pieces of data. First, we need to record how much NCH in total each account has requested and returned so that we know when a request would exceed the per-account limit. We also need to record the per-request and per-account limits, as they can be adjusted by the faucet account at any time.

Before we define any tables, however, we should define schemas for them. The schema for a table specifies the table columns and their data types. #defschema

The schema will be used to verify we are using the right types when reading or writing the table. For example, Pact can typecheck our module and ensure we never try to provide a string for an integer column, or try to insert a row that’s missing a column.

By convention, we use the same name for a table and its schema, except we give the schema a -schema suffix.

// pact

(defschema accounts-schema   @model   [ (invariant (<= (- funds-requested funds-returned)        account-limit))     (invariant (>= (- funds-requested funds-returned) 0.0))   ]   funds-requested:decimal   funds-returned:decimal   request-limit:decimal   account-limit:decimal)

We’ve seen @model used to define some reusable properties at the module level. Now, let’s see how to leverage invariants (ie. formal verification for table schemas) to guarantee it is never possible for an address to exceed their account limit or return more funds than they have requested. To specify an invariant, use (invariant) and provide a predicate; the Z3 theorem prover will check that the variables used in your predicate can never have values that would fail the predicate. Not all Pact functions can be used in the predicate; you can see a list of available ones here

The first invariant ensures that you can never receive more funds than your account limit. The second ensures you can never return more funds than you have received. Then, we define our four columns and their types.

Now that we have our schema we can define a table which uses it with the (deftable) function.

We’ll refer to the table by name when we need to insert, read, or update data. When our module is deployed, we’ll also need to create the table using the (create-table) function (this must be called outside the module).

Pact supplies several data-access functions for working with tables.

Note that these functions can only be called by functions within the module that defined the table, or in a transaction that satisfies the module governance function. Beyond these points of access, no one can read or write to tables directly.

// pact
(deftable accounts:{accounts-schema})

Last updated