This file contains the reference for the Clarity language.
A smart contract is composed of two parts:
- A data-space, which is a set of tables of data which only the smart contract may modify
- A set of functions which operate within the data-space of the smart contract, though they may call public functions from other smart contracts.
Users call smart contracts' public functions by broadcasting a transaction on the blockchain which invokes the public function.
This smart contracting language differs from most other smart contracting languages in two important ways:
- The language is not intended to be compiled. The LISP language described in this document is the specification for correctness.
- The language is not Turing complete. This allows us to guarantee that static analysis of programs to determine properties like runtime cost and data usage can complete successfully.
A smart contract definition is specified in a LISP language with the following limitations:
- Recursion is illegal and there is no
lambda
function. - Looping may only be performed via
map
,filter
, orfold
- The only atomic types are booleans, integers, fixed length buffers, and principals
- There is additional support for lists of these types (and lists of lists), however the only variable length lists in the language appear as function inputs.
- Variables may only be created via
let
binding and there is no support for mutating functions likeset
. - Defining of constants and functions are allowed for simplifying
code using
define-private
statement. However, these are purely syntactic. If a definition cannot be inlined, the contract will be rejected as illegal. These definitions are also private, in that functions defined this way may only be called by other functions defined in the given smart contract. - Functions specified via
define-public
statements are public functions. - Functions specified via
define-read-only
statements are public functions and perform no state mutations. Any attempts to modify contract state by these functions or functions called by these functions will result in an error.
Public functions return a Response type result. If the function returns
an ok
type, then the function call is considered valid, and any changes
made to the blockchain state will be materialized. If the function
returns an err
type, it will be considered invalid, and will have no
effect on the smart contract's state. So if function foo.A
calls
bar.B
, and bar.B
returns an ok
, but foo.A
returns an err
, no
effects from calling foo.A
materialize--- including effects from
bar.B
. If, however, bar.B
returns an err
and foo.A
returns an ok
,
there may be some database effects which are materialized from
foo.A
, but no effects from calling bar.B
will materialize.
Unlike functions created by define-public
, which may only return
Response types, functions created with define-read-only
may return
any type.
- Lists may be multi-dimensional (i.e., lists may contain other lists), however each entry of this list must be of the same type.
filter
map
andfold
functions may only be called with user-defined functions (i.e., functions defined with(define-private ...)
,(define-read-only ...)
, or(define-public ...)
) or simple native functions (e.g.,+
,-
,not
).- Functions that return lists of a different size than the input size
(e.g.,
(append-item ...)
) take a required constant parameter that indicates the maximum output size of the function. This is enforced with a runtime check.
Data within a smart contract's data-space is stored within
maps
. These stores relate a typed-tuple to another typed-tuple
(almost like a typed key-value store). As opposed to a table data
structure, a map will only associate a given key with exactly one
value. Values in a given mapping are set or fetched using:
(map-get map-name key-tuple)
- This fetches the value associated with a given key in the map, or returnsnone
if there is no such value.(map-set! map-name key-tuple value-tuple)
- This will set the value ofkey-tuple
in the data map(map-insert! map-name key-tuple value-tuple)
- This will set the value ofkey-tuple
in the data map if and only if an entry does not already exist.(map-delete! map-name key-tuple)
- This will deletekey-tuple
from the data map
We chose to use data maps as opposed to other data structures for two reasons:
- The simplicity of data maps allows for both a simple implementation within the VM, and easier reasoning about functions. By inspecting a given function definition, it is clear which maps will be modified and even within those maps, which keys are affected by a given invocation.
- The interface of data maps ensures that the return types of map operations are fixed length, which is a requirement for static analysis of smart contracts' runtime, costs, and other properties.
A smart contract defines the data schema of a data map with the
define-map
call. The define-map
function may only be called in the
top-level of the smart-contract (similar to define-private
). This
function accepts a name for the map, and a definition of the structure
of the key and value types. Each of these is a list of (name, type)
pairs, and they specify the input and output type of map-get
.
Types are either the values 'principal
, 'integer
, 'bool
or
the output of a call to (buffer n)
, which defines an n-byte
fixed-length buffer.
This interface, as described, disallows range-queries and queries-by-prefix on data maps. Within a smart contract function, you cannot iterate over an entire map.
The Clarity language uses a strong static type system. Function arguments and database schemas require specified types, and use of types is checked during contract launch. The type system does not have a universal super type. The type system contains the following types:
(tuple (key-name-0 key-type-0) (key-name-1 key-type-1) ...)
- a typed tuple with named fields.(list max-len entry-type)
- a list of maximum lengthmax-len
, with entries of typeentry-type
(response ok-type err-type)
- object used by public functions to commit their changes or abort. May be returned or used by other functions as well, however, only public functions have the commit/abort behavior.(optional some-type)
- an option type for objects that can either be(some value)
ornone
(buff max-len)
:= byte buffer or maximum lengthmax-len
.principal
:= object representing a principal (whether a contract principal or standard principal).bool
:= boolean value (true
orfalse
)int
:= signed 128-bit integeruint
:= unsigned 128-bit integer
UnknownType. The Clarity type system does not allow for specifying
an "unknown" type, however, in type analysis, unknown types may be
constructed and used by the analyzer. Such unknown types are used
only in the admission rules for response
and optional
types
(i.e., the variant types).
Type admission in Clarity follows the following rules:
- Types will only admit objects of the same type, i.e., lists will only admit lists, tuples only admit tuples, bools only admit bools.
- A tuple type
A
admits another tuple typeB
iff they have the exact same key names, and every key type ofA
admits the corresponding key type ofB
. - A list type
A
admits another list typeB
iffA.max-len >= B.max-len
andA.entry-type
admitsB.entry-type
. - A buffer type
A
admits another buffer typeB
iffA.max-len >= B.max-len
. - An optional type
A
admits another optional typeB
iff:A.some-type
admitsB.some-type
ORB.some-type
is an unknown type: this is the case ifB
only ever corresponds tonone
- A response type
A
admits another response typeB
if one of the following is true:A.ok-type
admitsB.ok-type
ANDA.err-type
admitsB.err-type
B.ok-type
is unknown ANDA.err-type
admitsB.err-type
B.err-type
is unknown ANDA.ok-type
admitsB.ok-type
- Principals, bools, ints, and uints only admit types of the exact same type.
Type admission is used for determining whether an object is a legal argument for a function, or for insertion into the database. Type admission is also used during type analysis to determine the return types of functions. In particular, a function's return type is the least common supertype of each type returned from any control path in the function. For example:
(define-private (if-types (input bool))
(if input
(ok 1)
(err false)))
The return type of if-types
is the least common supertype of (ok 1)
and (err false)
(i.e., the most restrictive type that contains
all returns). In this case, that type (response int bool)
. Because
Clarity does not have a universal supertype, it may be impossible to
determine such a type. In these cases, the functions are illegal, and
will be rejected during type analysis.
Functions specified via define-public
statements are public
functions and these are the only types of functions which may
be called directly through signed blockchain transactions. In addition
to being callable directly from a transaction (see the Stacks wire formats
for more details on Stacks transactions), public function may be called
by other smart contracts.
Public functions must return a (response ...)
type. This is used
by Clarity to determine whether or not to materialize any changes from
the execution of the function. If a function returns an (err ...)
type, and mutations on the blockchain state from executing the
function (and any function that it called during execution) will be
aborted.
In addition to function defined via define-public
, contracts may expose
read-only functions. These functions, defined via define-read-only
, are
callable by other smart contracts, and may be queryable via public blockchain
explorers. These functions may not mutate any blockchain state. Unlike normal
public functions, read-only functions may return any type.
A smart contract may call functions from other smart contracts using a
(contract-call?)
function.
This function returns a response type result-- the return value of the called smart contract function.
We distinguish 2 different types of contract-call?
:
- Static dispatch: the callee is a known, invariant contract available on-chain when the caller contract is deployed. In this case, the callee's principal is provided as the first argument, followed by the name of the method and its arguments:
(contract-call?
.registrar
register-name
name-to-register)
- Dynamic dispatch: the callee is passed as an argument, and typed as a trait reference ().
(define-public (swap (token-a <can-transfer-tokens>)
(amount-a uint)
(owner-a principal)
(token-b <can-transfer-tokens>)
(amount-b uint)
(owner-b principal)))
(begin
(unwrap! (contract-call? token-a transfer-from? owner-a owner-b amount-a))
(unwrap! (contract-call? token-b transfer-from? owner-b owner-a amount-b))))
Traits can either be locally defined:
(define-trait can-transfer-tokens (
(transfer-from? (principal principal uint) (response uint)))
Or imported from an existing contract:
(use-trait can-transfer-tokens
.contract-defining-trait.can-transfer-tokens)
Looking at trait conformance, callee contracts have two different paths.
They can either be "compatible" with a trait by defining methods
matching some of the methods defined in a trait, or explicitely declare
conformance using the impl-trait
statement:
(impl-trait .contract-defining-trait.can-transfer-tokens)
Explicit conformance should be prefered when adequate. It acts as a safeguard by helping the static analysis system to detect deviations in method signatures before contract deployment.
The following limitations are imposed on contract calls:
- On static dispatches, callee smart contracts must exist at the time of creation.
- No cycles may exist in the call graph of a smart contract. This prevents recursion (and re-entrancy bugs). Such structures can be detected with static analysis of the call graph, and will be rejected by the network.
contract-call?
are for inter-contract calls only. Attempts to execute when the caller is also the callee will abort the transaction.
Assets in the smart contracting language and blockchain are "owned" by objects of the principal type, meaning that any object of the principal type may own an asset. For the case of public-key hash and multi-signature Stacks addresses, a given principal can operate on their assets by issuing a signed transaction on the blockchain. Smart contracts may also be principals (reprepresented by the smart contract's identifier), however, there is no private key associated with the smart contract, and it cannot broadcast a signed transaction on the blockchain.
A Clarity contract can use a globally defined tx-sender
variable to
obtain the current principal. The following example defines a transaction
type that transfers amount
microSTX from the sender to a recipient if amount
is a multiple of 10, otherwise returning a 400 error code.
(define-public (transfer-to-recipient! (recipient principal) (amount uint))
(if (is-eq (mod amount 10) 0)
(stx-transfer? amount tx-sender recipient)
(err u400)))
Clarity provides an additional variable to help smart contracts
authenticate a transaction sender. The keyword contract-caller
returns the principal that called the current contract. If an
inter-contract call occurred, contract-caller
returns the last
contract in the stack of callers. For example, suppose there are three
contracts A, B, and C, each with an invoke
function such that
A::invoke
calls B::invoke
and B::invoke
calls C::invoke
.
When a user Bob issues a transaction that calls A::invoke
, the value
of contract-caller
in each successive invoke function's body would change:
in A::invoke, contract-caller = Bob
in B::invoke, contract-caller = A
in C::invoke, contract-caller = B
This allows contracts to make assertions and perform authorization
checks using not only the tx-sender
(which in this example, would
always be "Bob"), but also using the contract-caller
. This could be
used to ensure that a particular function is only ever called directly
and never called via an inter-contract call (by asserting that
tx-sender
and contract-caller
are equal). We provide an example of
a two different types of authorization checks in the rocket ship
example below.
Smart contracts themselves are principals and are represented by the smart contract's identifier -- which is the publishing address of the contract and the contract's name, e.g.:
'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-name
For convenience, smart contracts may write a contract's identifier in the
form .contract-name
. This will be expanded by the Clarity interpreter into
a fully-qualified contract identifier that corresponds to the same
publishing address as the contract it appears in. For example, if the
same publisher key, SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR
, is
publishing two contracts, contract-A
and contract-B
, the fully
qualified identifier for the contracts would be:
'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-A
'SZ2J6ZY48GV1EZ5V2V5RB9MP66SW86PYKKQ9H6DPR.contract-B
But, in the contract source code, if the developer wishes
to call a function from contract-A
in contract-B
, they can
write
(contract-call? .contract-A public-function-foo)
This allows the smart contract developer to modularize their applications across multiple smart contracts without knowing the publishing key a priori.
In order for a smart contract to operate on assets it owns, smart contracts
may use the special (as-contract ...)
function. This function
executes the expression (passed as an argument) with the tx-sender
set to the contract's principal, rather than the current sender. The
as-contract
function returns the value of the provided expression.