Clarity Smart Contract Language


Clarity of Mind Foreword Introduction

Implementing traits

Trait conformance is just a fancy way of saying that a specific smart contract implements the functions defined in the trait. Take the following example trait:

(define-trait multiplier
        (multiply (uint uint) (response uint uint))

If we want to implement the trait, all we have to do is make sure that our contract contains a function multiply that takes two uint parameters and returns a response that is either a (ok uint) or (err uint). The following contract will do just that:

(define-read-only (multiply (a uint) (b uint))
    (ok (* a b))

(define-read-only (divide (a uint) (b uint))
    (ok (/ a b))

Notice how the contract has another function divide that is not present in the multiplier trait. That is completely fine because any system that is looking for contracts that implement multiplier do not care what other functions those contracts might implement. Reliance on the trait ensures that conforming contracts have a compatible multiply function implementation, nothing more.

Asserting trait implementations

In the opening paragraph of this chapter we talked about implicit and explicit conformity. Under normal circumstances you always want to explicitly assert that your contract implements a trait.

Imagine that the multiplier trait is deployed in a contract called multiplier-trait by the same principal. To assert that the example contract implements the trait, the impl-trait function is used.

(impl-trait .multiplier-trait.multiplier)

By adding this expression, the analyser will check if the contract implements the trait specified when the contract is deployed. It will reject the transaction if the contract is not a full implementation. It is therefore recommended to always use impl-trait because it prevent accidental non-conformity.

The impl-trait function takes a single trait reference parameter. A trait reference is a contract principal plus the name of the trait. Trait references can be written in short form as seen above, or as a fully qualified reference.

(impl-trait 'ST1HTBVD3JG9C05J7HBJTHGR0GGW7KXW28M5JS8QE.multiplier-trait.multiplier)

The introduction mentioned Clarity favours composition over inheritance. Smart contracts can implement multiple traits, leading to more complex composite behaviour when required. For example, say that there also exists a divider trait that describes the divide function, deployed in another contract called divider-trait.

(define-trait divider
        (divide (uint uint) (response uint uint))

A contract that implements both traits simply contains multiple impl-trait expressions.

(impl-trait .multiplier-trait.multiplier)
(impl-trait .divider-trait.divider)