Passing traits as arguments
The contract-call?
function allows contracts to call each other. Since the REPL and Clarinet start
an interactive Clarity session, we have also been using it to manually interact
with our contracts. The first two arguments are the contract identifier and
the function name, followed by zero or more parameters. We learned that the
contract identifier can either be a short form or a fully qualified contract
principal. However, there is a third kind of contract identifier; namely, a
trait reference.
Static dispatch
Up until now we have always been hardcoding the contract identifier; that is
to say, we directly enter a contract principal as the first argument to
contract-call?
. Remember our smart-claimant? It
called into the time-locked wallet contract to claim the balance. Here is the
relevant snippet:
(begin
(try! (as-contract (contract-call? .timelocked-wallet claim)))
(let ;; ...
The contract identifier .timelocked-wallet
is invariant—it is hardcoded into
the contract. When the contract is deployed, the analyser will check if the
specified contract exists on-chain and whether it contains a public or read-only
function called claim
. Contract calls with an invariant contract identifier
are said to dispatch statically.
Dynamic dispatch
Traits enable us to pass contract identifiers as function arguments. It enables
dynamic dispatch of contract calls, meaning that the actual contract called by
contract-call?
depends on the initial call.
You can either define a trait in the same contract or import it using
use-trait
. The latter allows you to bring a trait defined in another contract
to the current contract.
(use-trait trait-alias trait-identifier)
The trait identifier is the same as the one used in
impl-trait
.
The trait alias defines the local name to use in the context of the current
contract.
If the locked-wallet trait we created in the
previous section were to
be deployed in a contract called locked-wallet-trait
, then importing it with
an alias of the same name would look like this:
(use-trait locked-wallet-trait 'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.locked-wallet-trait.locked-wallet-trait)
(locked-wallet-trait
is repeated because both the contract and the trait bear
the same name. Remember that a trait reference follows the pattern
address.contract-name.trait-name
.)
Trait references in function arguments use their own notion. The trait alias as
a type is enclosed in angle brackets (<
and >
).
(define-public (claim-wallet (wallet-contract <locked-wallet-trait>))
(ok (try! (as-contract (contract-call? wallet-contract claim))))
)
The example claim-wallet
function can then be called with a contract principal
that implements the locked-wallet-trait
like so:
(contract-call? .example-contract claim-wallet .timelocked-wallet)
The upcoming marketplace practice project contains practical examples of dynamically dispatching contract calls.