Clarity Smart Contract Language

Content

Clarity of Mind Foreword Introduction

asserts!

The asserts! function takes two parameters, the first being a boolean expression and the second a so-called throw value. If the boolean expression evaluates to true, then asserts! returns true and execution continues as expected, but if the expression evaluates to false then asserts! will return the throw value and exit the current control flow.

That sounds complicated, so let us take a look at some examples. Keep in mind that the basic form for asserts! as described looks like this:

(asserts! boolean-expression throw-value)

The following assertion is said to pass, as the boolean expression evaluates to true.

(asserts! true (err "failed"))

The next one is said to fail, as the boolean expression evaluates to false.

(asserts! false (err "failed"))

Notice how somewhere in that error message we find the (err "failed")? Let us make it more clear with a test function. The test function takes a boolean input value and asserts its truthiness. For a throw value we will use an err and the final expression will return an ok.

(define-public (asserts-example (input bool))
    (begin
        (asserts! input (err "the assertion failed"))
        (ok "end of the function")
    )
)

(print (asserts-example true))
(print (asserts-example false))

The first print gives us the ok as seen at the end of the begin expression. Nothing too strange there. But the second call gives us the err throw value!

Even though the begin function gives us the result of the final expression under normal circumstances, the asserts! control function has the ability to override that behaviour and exit the current flow. When asserts! fails, it short-circuits and returns the throw value from the function immediately. It makes asserts! really useful for creating guards by _asserting_—hence the name—that certain values are what you expect them to be.

Remember that is-valid-caller function in the chapter on private functions? The example used an if function to only allow the action if the tx-sender was equal to the principal that deployed the contract. Let us now rewrite that contract to use asserts! instead:

(define-constant contract-owner tx-sender)

;; Try removing the contract-owner constant above and using a different
;; one to see the example calls error out:
;; (define-constant contract-owner 'ST20ATRN26N9P05V2F1RHFRV24X8C8M3W54E427B2)

(define-constant err-invalid-caller (err u1))

(define-map recipients principal uint)

(define-private (is-valid-caller)
    (is-eq contract-owner tx-sender)
)

(define-public (add-recipient (recipient principal) (amount uint))
    (begin
        ;; Assert the tx-sender is valid.
        (asserts! (is-valid-caller) err-invalid-caller)
        (ok (map-set recipients recipient amount))
    )
)

(define-public (delete-recipient (recipient principal))
    (begin
        ;; Assert the tx-sender is valid.
        (asserts! (is-valid-caller) err-invalid-caller)
        (ok (map-delete recipients recipient))
    )
)

;; Two example calls to the public functions:
(print (add-recipient 'ST1J4G6RR643BCG8G8SR6M2D9Z9KXT2NJDRK3FBTK u500))
(print (delete-recipient 'ST1J4G6RR643BCG8G8SR6M2D9Z9KXT2NJDRK3FBTK))

That looks a lot more readable. If you disagree, wait until you get to the chapter on best practices.