Syntax
Literals
Strings
String literals are created with double-ticks:
pact> "a string""a string"
pact> "a string""a string"
Strings also support multiline by putting a backslash before and after whitespace (not interactively).
(defun id (a) "Identity function. \ \Argument is returned." a)
(defun id (a) "Identity function. \ \Argument is returned." a)
Symbols
Symbols are string literals representing some unique item in the runtime, like a function or a table name. Their representation internally is simply a string literal so their usage is idiomatic.
Symbols are created with a preceding tick, thus they do not support whitespace nor multiline syntax.
pact> 'a-symbol"a-symbol"
pact> 'a-symbol"a-symbol"
Integers
Integer literals are unbounded, and can be positive or negative.
pact> 1234512345pact> -922337203685477580712387461234-922337203685477580712387461234
pact> 1234512345pact> -922337203685477580712387461234-922337203685477580712387461234
Decimals
Decimal literals have potentially unlimited precision.
pact> 100.25100.25pact> -356452.234518728287461023856582382983746-356452.234518728287461023856582382983746
pact> 100.25100.25pact> -356452.234518728287461023856582382983746-356452.234518728287461023856582382983746
Booleans
Booleans are represented by true
and false
literals.
pact> (and true false)false
pact> (and true false)false
Lists
List literals are created with brackets, and optionally separated with commas. Uniform literal lists are given a type in parsing.
pact> [1 2 3][1 2 3]pact> [1,2,3][1 2 3]pact> (typeof [1 2 3])"[integer]"pact> (typeof [1 2 true])"list"
pact> [1 2 3][1 2 3]pact> [1,2,3][1 2 3]pact> (typeof [1 2 3])"[integer]"pact> (typeof [1 2 true])"list"
Objects
Objects are dictionaries, created with curly-braces specifying key-value pairs using a colon :
. For certain applications (database updates), keys must be strings.
pact> { "foo": (+ 1 2), "bar": "baz" }{ "foo": 3, "bar": "baz" }
pact> { "foo": (+ 1 2), "bar": "baz" }{ "foo": 3, "bar": "baz" }
Bindings
Bindings are dictionary-like forms, also created with curly braces, to bind database results to variables using the :=
operator. They are used in with-read, with-default-read, bind and resume to assign variables to named columns in a row, or values in an object.
(defun check-balance (id) (with-read accounts id { "balance" := bal } (enforce (> bal 0) (format "Account in overdraft: {}" [bal]))))
(defun check-balance (id) (with-read accounts id { "balance" := bal } (enforce (> bal 0) (format "Account in overdraft: {}" [bal]))))
Lambdas
Lambdas, or "anonymous functions", allow defining functions to be applied in local scope, as opposed to defining functions at top-level with defun.
Lambdas are supported in let
, let*
, and as inline arguments to built-in function applications.
; identity function (let ((f (lambda (x) x))) (f a)) ; native example (let ((f (lambda (x) x))) (map (f) [1 2 3])) ; Inline native example: (map (lambda (x) x) [1 2 3])
; identity function (let ((f (lambda (x) x))) (f a)) ; native example (let ((f (lambda (x) x))) (map (f) [1 2 3])) ; Inline native example: (map (lambda (x) x) [1 2 3])
Type specifiers
Types can be specified in syntax with the colon :
operator followed by a type literal or user type specification.
Type literals
string
integer
decimal
bool
time
keyset
list
, or[type]
to specify the list typeobject
, which can be further typed with a schematable
, which can be further typed with a schemamodule
, which must be further typed with required interfaces.
Schema type literals
A schema defined with defschema is referenced by name enclosed in curly braces.
table:{accounts}object:{person}
table:{accounts}object:{person}
Module type literals
Module references are specified by the interfaces they demand as a comma-delimited list.
module:{fungible-v2,user.votable}
module:{fungible-v2,user.votable}
Dereference operator
The dereference operator ::
allows a member of an interface specified in the type of a module reference to be invoked at run-time.
(interface baz (defun quux:bool (a:integer b:string)) (defconst ONE 1) )...(defun foo (bar:module{baz}) (bar::quux 1 "hi") ;; invokes 'quux' on whatever module is passed in bar::ONE ;; directly references interface const)
(interface baz (defun quux:bool (a:integer b:string)) (defconst ONE 1) )...(defun foo (bar:module{baz}) (bar::quux 1 "hi") ;; invokes 'quux' on whatever module is passed in bar::ONE ;; directly references interface const)
What can be typed
Function arguments and return types
(defun prefix:string (pfx:string str:string) (+ pfx str))
(defun prefix:string (pfx:string str:string) (+ pfx str))
Let variables
(let ((a:integer 1) (b:integer 2)) (+ a b))
(let ((a:integer 1) (b:integer 2)) (+ a b))
Tables and objects
Tables and objects can only take a schema type literal.
(deftable accounts:{account}) (defun get-order:{order} (id) (read orders id))
(deftable accounts:{account}) (defun get-order:{order} (id) (read orders id))
Consts
(defconst PENNY:decimal 0.1)
(defconst PENNY:decimal 0.1)
Special forms
Docs and Metadata
Many special forms like defun accept optional documentation strings, in the following form:
(defun average (a b) "take the average of a and b" (/ (+ a b) 2))
(defun average (a b) "take the average of a and b" (/ (+ a b) 2))
Alternately, users can specify metadata using a special @
-prefix syntax. Supported metadata fields are @doc
to provide a documentation string, and @model
that can be used by Pact tooling to verify the correctness of the implementation:
(defun average (a b) @doc "take the average of a and b" @model (property (= (+ a b) (* 2 result))) (/ (+ a b) 2))
(defun average (a b) @doc "take the average of a and b" @model (property (= (+ a b) (* 2 result))) (/ (+ a b) 2))
Indeed, a bare docstring like "foo"
is actually just a short form for @doc "foo"
.
Specific information on Properties can be found in The Pact Property Checking System.
bless
(bless HASH)
(bless HASH)
Within a module declaration, bless a previous version of that module as identified by HASH. See Dependency management for a discussion of the blessing mechanism.
(module provider 'keyset (bless "ZHD9IZg-ro1wbx7dXi3Fr-CVmA-Pt71Ov9M1UNhzAkY") (bless "bctSHEz4N5Y1XQaic6eOoBmjty88HMMGfAdQLPuIGMw") ...)
(module provider 'keyset (bless "ZHD9IZg-ro1wbx7dXi3Fr-CVmA-Pt71Ov9M1UNhzAkY") (bless "bctSHEz4N5Y1XQaic6eOoBmjty88HMMGfAdQLPuIGMw") ...)
defun
(defun NAME ARGLIST [DOC-OR-META] BODY...)
(defun NAME ARGLIST [DOC-OR-META] BODY...)
Define NAME as a function, accepting ARGLIST arguments, with optional DOC-OR-META. Arguments are in scope for BODY, one or more expressions.
(defun add3 (a b c) (+ a (+ b c))) (defun scale3 (a b c s) "multiply sum of A B C times s" (* s (add3 a b c)))
(defun add3 (a b c) (+ a (+ b c))) (defun scale3 (a b c s) "multiply sum of A B C times s" (* s (add3 a b c)))
defcap
(defcap NAME ARGLIST [DOC] BODY...)
(defcap NAME ARGLIST [DOC] BODY...)
Define NAME as a capability, specified using ARGLIST arguments, with optional DOC. A defcap
models a capability token which will be stored in the environment to represent some ability or right. Code in BODY is only called within special capability-related functions with-capability
and compose-capability
when the token as parameterized by the arguments supplied is not found in the environment. When executed, arguments are in scope for BODY, one or more expressions.
(defcap USER_GUARD (user) "Enforce user account guard (with-read accounts user { "guard": guard } (enforce-guard guard)))
(defcap USER_GUARD (user) "Enforce user account guard (with-read accounts user { "guard": guard } (enforce-guard guard)))
defconst
(defconst NAME VALUE [DOC-OR-META])
(defconst NAME VALUE [DOC-OR-META])
Define NAME as VALUE, with option DOC-OR-META. Value is evaluated upon module load and "memoized".
(defconst COLOR_RED="#FF0000" "Red in hex")(defconst COLOR_GRN="#00FF00" "Green in hex")(defconst PI 3.14159265 "Pi to 8 decimals")
(defconst COLOR_RED="#FF0000" "Red in hex")(defconst COLOR_GRN="#00FF00" "Green in hex")(defconst PI 3.14159265 "Pi to 8 decimals")
defpact
(defpact NAME ARGLIST [DOC-OR-META] STEPS...)
Define NAME as a pact, a computation comprised of multiple steps that occur in distinct transactions. Identical to defun except body must be comprised of steps to be executed in strict sequential order.
(defpact payment (payer payer-entity payee payee-entity amount) (step-with-rollback payer-entity (debit payer amount) (credit payer amount)) (step payee-entity (credit payee amount)))
(defpact payment (payer payer-entity payee payee-entity amount) (step-with-rollback payer-entity (debit payer amount) (credit payer amount)) (step payee-entity (credit payee amount)))
Defpacts may be nested (though the recursion restrictions apply, so it must be a different defpact). They may be executed like a regular function call within a defpact, but are continued after the first step by calling continue
with the same arguments.
As such, they have the following restrictions:
- The number of steps of the child must match the number of steps of the parent.
- If a parent defpact step has the rollback field, so must the child. If parent steps roll back, so do child steps.
continue
must be called with the same continuation arguments as the defpact originally dispatched, to support multiple nested defpacts of the same function but with different arguments.
The following example shows well-formed defpacts with equal number of steps, nested rollbacks and continue:
(defpact payment (payer payee amount) (step-with-rollback (debit payer amount) (credit payer amount)) (step payee-entity (credit payee amount))) ...(defpact split-payment (payer payee1 payee2 amount ratio) (step-with-rollback (let ((payment1 (payment payer payee1 (* amount ratio))) (payment2 (payment payer payee2 (* amount (- 1 ratio)))) ) "step 0 complete" ) (let ((payment1 (continue (payment payer payee1 (* amount ratio)))) (payment2 (continue (payment payer payee2 (* amount (- 1 ratio))))) ) "step 0 rolled back" ) ) (step (let ((payment1 (continue (payment payer payee1 (* amount ratio)))) (payment2 (continue (payment payer payee2 (* amount (- 1 ratio))))) ) "step 1 complete" ) ))
(defpact payment (payer payee amount) (step-with-rollback (debit payer amount) (credit payer amount)) (step payee-entity (credit payee amount))) ...(defpact split-payment (payer payee1 payee2 amount ratio) (step-with-rollback (let ((payment1 (payment payer payee1 (* amount ratio))) (payment2 (payment payer payee2 (* amount (- 1 ratio)))) ) "step 0 complete" ) (let ((payment1 (continue (payment payer payee1 (* amount ratio)))) (payment2 (continue (payment payer payee2 (* amount (- 1 ratio))))) ) "step 0 rolled back" ) ) (step (let ((payment1 (continue (payment payer payee1 (* amount ratio)))) (payment2 (continue (payment payer payee2 (* amount (- 1 ratio))))) ) "step 1 complete" ) ))
defschema
(defschema NAME [DOC-OR-META] FIELDS...)
(defschema NAME [DOC-OR-META] FIELDS...)
Define NAME as a schema, which specifies a list of FIELDS. Each field is in the form FIELDNAME[:FIELDTYPE]
.
(defschema accounts "Schema for accounts table". balance:decimal amount:decimal ccy:string data)
(defschema accounts "Schema for accounts table". balance:decimal amount:decimal ccy:string data)
deftable
(deftable NAME[:SCHEMA] [DOC-OR-META])
(deftable NAME[:SCHEMA] [DOC-OR-META])
Define NAME as a table, used in database functions. Note the table must still be created with create-table.
let
(let (BINDPAIR [BINDPAIR [...]]) BODY)
(let (BINDPAIR [BINDPAIR [...]]) BODY)
Bind variables in BINDPAIRs to be in scope over BODY. Variables within BINDPAIRs cannot refer to previously-declared variables in the same let binding; for this use let*.
(let ((x 2) (y 5)) (* x y))> 10
(let ((x 2) (y 5)) (* x y))> 10
let*
(let* (BINDPAIR [BINDPAIR [...]]) BODY)
(let* (BINDPAIR [BINDPAIR [...]]) BODY)
Bind variables in BINDPAIRs to be in scope over BODY. Variables can reference previously declared BINDPAIRS in the same let. let*
is expanded at compile-time to nested let
calls for each BINDPAIR; thus let
is preferred where possible.
(let* ((x 2) (y (* x 10))) (+ x y))> 22
(let* ((x 2) (y (* x 10))) (+ x y))> 22
cond;
(cond (TEST BRANCH) [(TEST2 BRANCH2) [...]] ELSE-BRANCH)
(cond (TEST BRANCH) [(TEST2 BRANCH2) [...]] ELSE-BRANCH)
Special form/sugar to produce a series of "if-elseif-else" expressions, such that if TEST1 passes, BRANCH1 is evaluated, otherwise followed by evaluating TEST2 -> BRANCH2 etc. ELSE-BRANCH is evaluated if all tests fail.
cond
is syntactically expanded such that
(cond (a b) (c d) (e f) g)
(cond (a b) (c d) (e f) g)
is expanded to:
(if a b (if c d (if e f g)))
(if a b (if c d (if e f g)))
step
(step EXPR)(step ENTITY EXPR)
(step EXPR)(step ENTITY EXPR)
Define a step within a defpact, such that any prior steps will be executed in prior transactions, and later steps in later transactions. Including an ENTITY argument indicates that this step is intended for confidential transactions. Therefore, only the ENTITY would execute the step, and other participants would "skip" it.
step-with-rollback
(step-with-rollback EXPR ROLLBACK-EXPR)(step-with-rollback ENTITY EXPR ROLLBACK-EXPR)
(step-with-rollback EXPR ROLLBACK-EXPR)(step-with-rollback ENTITY EXPR ROLLBACK-EXPR)
Define a step within a defpact similarly to step but specifying ROLLBACK-EXPR. With ENTITY, ROLLBACK-EXPR will only be executed upon failure of a subsequent step, as part of a reverse-sequence "rollback cascade" going back from the step that failed to the first step. Without ENTITY, ROLLBACK-EXPR functions as a "cancel function" to be explicitly executed by a participant.
use
(use MODULE)(use MODULE HASH)(use MODULE IMPORTS)(use MODULE HASH IMPORTS)
(use MODULE)(use MODULE HASH)(use MODULE IMPORTS)(use MODULE HASH IMPORTS)
Import an existing MODULE into a namespace. Can only be issued at the top-level, or within a module declaration. MODULE can be a string, symbol or bare atom. With HASH, validate that the imported module's hash matches HASH, failing if not. Use describe-module to query for the hash of a loaded module on the chain.
An optional list of IMPORTS consisting of function, constant, and schema names may be supplied. When this explicit import list is present, only those names will be made available for use in the module body. If no list is supplied, then every name in the imported module will be brought into scope. When two modules are defined in the same transaction, all names will be in scope for all modules, and import behavior will be defaulted to the entire module. IMPORTS may only be empty when a module hash is also supplied. If a module hash is not supplied, IMPORTS are required to be either a non-empty list, or left undeclared.
(use accounts)(transfer "123" "456" 5 (time "2016-07-22T11:26:35Z"))"Write succeeded"
(use accounts)(transfer "123" "456" 5 (time "2016-07-22T11:26:35Z"))"Write succeeded"
(use accounts "ToV3sYFMghd7AN1TFKdWk_w00HjUepVlqKL79ckHG_s")(transfer "123" "456" 5 (time "2016-07-22T11:26:35Z"))"Write succeeded"
(use accounts "ToV3sYFMghd7AN1TFKdWk_w00HjUepVlqKL79ckHG_s")(transfer "123" "456" 5 (time "2016-07-22T11:26:35Z"))"Write succeeded"
(use accounts [ transfer example-fun ])(transfer "123" "456" 5 (time "2016-07-22T11:26:35Z"))"Write succeeded"
(use accounts [ transfer example-fun ])(transfer "123" "456" 5 (time "2016-07-22T11:26:35Z"))"Write succeeded"
(use accounts "ToV3sYFMghd7AN1TFKdWk_w00HjUepVlqKL79ckHG_s" [ transfer example-fun ])(transfer "123" "456" 5 (time "2016-07-22T11:26:35Z"))"Write succeeded"
(use accounts "ToV3sYFMghd7AN1TFKdWk_w00HjUepVlqKL79ckHG_s" [ transfer example-fun ])(transfer "123" "456" 5 (time "2016-07-22T11:26:35Z"))"Write succeeded"
interface
(interface NAME [DOR-OR-META] BODY...)
(interface NAME [DOR-OR-META] BODY...)
Define and install interface NAME, with optional DOC-OR-META.
BODY is composed of definitions that will be scoped in the module. Valid expressions in a module include:
(interface coin-sig "'coin-sig' represents the Kadena Coin Contract interface. This contract \ \provides both the general interface for a Kadena's token, supplying a \ \transfer function, coinbase, account creation and balance query." (defun create-account:string (account:string guard:guard) @doc "Create an account for ACCOUNT, with GUARD controlling access to the \ \account." @model [ (property (not (= account ""))) ] ) (defun transfer:string (sender:string receiver:string amount:decimal) @doc "Transfer AMOUNT between accounts SENDER and RECEIVER on the same \ \chain. This fails if either SENDER or RECEIVER does not exist. \ \Create-on-transfer can be done using the 'transfer-and-create' function." @model [ (property (> amount 0.0)) (property (not (= sender receiver))) ] ) (defun account-balance:decimal (account:string) @doc "Check an account's balance" @model [ (property (not (= account ""))) ] ))
(interface coin-sig "'coin-sig' represents the Kadena Coin Contract interface. This contract \ \provides both the general interface for a Kadena's token, supplying a \ \transfer function, coinbase, account creation and balance query." (defun create-account:string (account:string guard:guard) @doc "Create an account for ACCOUNT, with GUARD controlling access to the \ \account." @model [ (property (not (= account ""))) ] ) (defun transfer:string (sender:string receiver:string amount:decimal) @doc "Transfer AMOUNT between accounts SENDER and RECEIVER on the same \ \chain. This fails if either SENDER or RECEIVER does not exist. \ \Create-on-transfer can be done using the 'transfer-and-create' function." @model [ (property (> amount 0.0)) (property (not (= sender receiver))) ] ) (defun account-balance:decimal (account:string) @doc "Check an account's balance" @model [ (property (not (= account ""))) ] ))
module
(module NAME KEYSET-OR-GOVERNANCE [DOC-OR-META] BODY...)
(module NAME KEYSET-OR-GOVERNANCE [DOC-OR-META] BODY...)
Define and install module NAME, with module admin governed by KEYSET-OR-GOVERNANCE, with optional DOC-OR-META.
If KEYSET-OR-GOVERNANCE is a string, it references a keyset that has been installed with define-keyset
that will be tested whenever module admin is required. If KEYSET-OR-GOVERNANCE is an unqualified atom, it references a defcap
capability which will be acquired if module admin is requested.
BODY is composed of definitions that will be scoped in the module. Valid productions in a module include:
(module accounts 'accounts-admin "Module for interacting with accounts" (defun create-account (id bal) "Create account ID with initial balance BAL" (insert accounts id { "balance": bal })) (defun transfer (from to amount) "Transfer AMOUNT from FROM to TO" (with-read accounts from { "balance": fbal } (enforce (<= amount fbal) "Insufficient funds") (with-read accounts to { "balance": tbal } (update accounts from { "balance": (- fbal amount) }) (update accounts to { "balance": (+ tbal amount) })))))
(module accounts 'accounts-admin "Module for interacting with accounts" (defun create-account (id bal) "Create account ID with initial balance BAL" (insert accounts id { "balance": bal })) (defun transfer (from to amount) "Transfer AMOUNT from FROM to TO" (with-read accounts from { "balance": fbal } (enforce (<= amount fbal) "Insufficient funds") (with-read accounts to { "balance": tbal } (update accounts from { "balance": (- fbal amount) }) (update accounts to { "balance": (+ tbal amount) })))))
implements
(implements INTERFACE)
(implements INTERFACE)
Specify that containing module implements interface INTERFACE. This requires the module to implement all functions, pacts, and capabilities specified in INTERFACE with identical signatures (same argument names and declared types).
Note that models declared for the implemented interface and its members will be appended to whatever models are declared within the implementing module.
A module thus specified can be used as a module reference for the specified interface(s).
Expressions
Expressions may be literals, atoms, s-expressions, or references.
Atoms
Atoms are non-reserved barewords starting with a letter or allowed symbol, and containing letters, digits and allowed symbols. Allowed symbols are %#+-_&$@<>=?*!|/
. Atoms must resolve to a variable bound by a defun, defpact, binding form, lambda form, or to symbols imported into the namespace with use.
S-expressions
S-expressions are formed with parentheses, with the first atom determining if the expression is a special form or a function application, in which case the first atom must refer to a definition.
Partial application
An application with less than the required arguments is in some contexts a valid partial application of the function. However, this is only supported in Pact's functional-style functions; anywhere else this will result in a runtime error.
References
References are multiple atoms joined by a dot .
that directly resolve to definitions found in other modules.
pact> accounts.transfer"(defun accounts.transfer (src,dest,amount,date) \"transfer AMOUNT fromSRC to DEST\")"pact> transferEval failure:transfer<EOF>: Cannot resolve transferpact> (use 'accounts)"Using \"accounts\""pact> transfer"(defun accounts.transfer (src,dest,amount,date) \"transfer AMOUNT fromSRC to DEST\")"
pact> accounts.transfer"(defun accounts.transfer (src,dest,amount,date) \"transfer AMOUNT fromSRC to DEST\")"pact> transferEval failure:transfer<EOF>: Cannot resolve transferpact> (use 'accounts)"Using \"accounts\""pact> transfer"(defun accounts.transfer (src,dest,amount,date) \"transfer AMOUNT fromSRC to DEST\")"
References are preferred over use
for transactions, as references resolve faster. However, when defining a module, use
is preferred for legibility.