Clarity Smart Contract Language


Clarity of Mind Foreword Introduction

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:

    (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.