Writing your first Clarity Smart Contract

I had the opportunity to work on a new smart contract being developed on the Stacks blockchain, and I learned a thing or two.  Below are some interesting things you may want to know before jumping in.

Blazing Saddles of Speed

OK, so remember trying to run unit tests in Truffle?  You type the command in, then you go for a walk, and then you come back to check and see if it has finished compiling??? Since clarity has no compilation step, this bad boy just rips off your unit tests.  

I have 44 unit tests completing in 6 and a half seconds.  Nice.

WTF are these things?

OK, so you will need to send blockchain addresses (referred to as 'principals' in Clarity) through your code.  No one is gonna tell you this, but you need to put a single quote in front of the address.

'ST3J2GVMMM2R07ZFBJDWTYEYAR8FZH5WKDTFJ9AHA

And no one is gonna tell you that your unit tests need to do this too.  Otherwise you will get errors.  In the code below, the mintTo variable does not have the single quote in front of it.  Unlike other libraries you may have used, it will not coerce the string to a "principal" type for you.

const tx = client.createTransaction({
    method: {
      name: 'mint-tokens',
      args: [`u${amount}`, `${mintTo}`],
    },
  });
"Error parsing argument \"ST3J2GVMMM2R07ZFBJDWTYEYAR8FZH5WKDTFJ9AHA\"\nCaused by: Unchecked(UndefinedVariable(\"ST3J2GVMMM2R07ZFBJDWTYEYAR8FZH5WKDTFJ9AHA\"))"

Also, see above the u in front of the amount.  Unsigned integers need that notation.

You've been warned.

Tokens are Flying First Class

Fungible and Non-Fungible Tokens are first class citizens in Clarity.  There are built in functions to create tokens, mint tokens, transfer tokens, and burn tokens.  Your smart contract only needs to wrap these and define how the token behaves.

There is a work in progress standard as of right now... and a reference implementation of how a token should behave.

Which brings me to tx-sender vs contract-sender.

In Ethereum world, you never, ever, ever, ever utilize the tx-sender.  This is the Externally Owned Account that broadcast a transaction.  You never would know if a malicious contract has taken control and is sending a command that you couldn't trust.  In Clarity, the idea is that you can use this variable to determine if tokens can be transferred, since the broadcasting wallet can use postconditions on the transaction to guarantee their assets didn't get moved that they didn't expect.  The transfer function in the reference impl looks like this:

;; Transfers tokens to a recipient
(define-public (transfer (amount uint) (sender principal) (recipient principal))
  (if (is-eq tx-sender sender)
    (ft-transfer? example-token amount sender recipient)
    (err u4)))

It is only checking if the originating account of the transaction call chain is sending tokens, and allowing it.  This means that any contract that gains control of the call stack can move your tokens.  The rationale is that a user's wallet should set limits on what assets should be moved and have the tx revert if those are violated.  And of course this would remove the stupid approve + transferFrom hoops that we jump through on other chains.  I am skeptical, but this could be a good move.

Variables are Flying Coach

OK, so we just want to store a variable, update it in a function, and have a public getter... not much to ask, right?  This may be a simple ask, but for some reason it seemed really hard to me.  The docs don't quite explain the process for dummies like me, so I had to thrash a little to understand it.

You can define a top level variable like:

;; Variable for URI storage
(define-data-var uri (string-utf8 1024) u"")

You can read the variable using var-get... and if you forget this and just reference the variable, you will get use of unresolved variable.

;; Public getter for the URI
(define-read-only (token-uri)
  (ok (var-get uri)))

Finally, you can update the variable with var-set.  This one is pretty well documented with the example apps.

;; Setter for the URI 
(define-public (set-token-uri (updated-uri (string-utf8 1024)))
  (ok (var-set uri updated-uri))

Wrapping It Up

These were just some interesting things I noted as I was hacking on the new smart contract language.  There are some other cool features like maps with tuple keys, response object types, and syntaxes with ! and ? that take a bit to wrap your head around, but overall I am hyped for this new platform to build smart contracts on.

While some platforms are shooting for Turing Complete and ultimate flexibility, Stacks has purposefully chosen a language with explicit limitations (not Turing complete, no re-entrancy, no recursion, etc) in order to make the applications easier to understand and safer.  In many domains, you will hear people discuss having limitations or boundaries as a positive that focuses energy where it matters.

Good luck to the new blockchain and the new language!  I enjoyed dipping my toes in the water.