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.
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 functions 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
type, any mutations on the blockchain state from executing the
function (and any function that it called during execution) will be
In addition to functions defined via
define-public, contracts may expose
read-only functions. These functions, defined via
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
This function returns a response type result -- the return value of the called smart contract function.
We distinguish 2 different types of
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)(contract-call? .registrar register-name name-to-register)
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))))(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)))(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)(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 explicitly declare
conformance using the
(impl-trait .contract-defining-trait.can-transfer-tokens)(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.