The key principles to effectively develop Plutus smart contracts
In case you’ve been living under a rock or just haven’t heard of smart contract platforms, let me say these are just distributed computing platforms built on top of a blockchain, Ethereum is the most famous, or should we say infamous? as it has been clearly demonstrated that no matter how “secure” you think your smart contract is, the reality is that, if the tools you use to build your contracts have design flaws, then, your end product will also inherently have some flaws, it is just how it is. In this regard I liked the approach of the Cardano blockchain, focus on building smart contracts by using programming languages for high assurance software, languages from which you can reason what the hell are you doing and don’t allow us to leave room for naive failures.
Is not a secret I’m sold in functional programming thanks to Haskell and perhaps this was the reason to why I strongly believe Cardano is a project that is doing the things right from the ground up. We already know any product must choose among doing it good, cheap or fast, sadly most of the projects in the blockchain space are just focusing on building cheap and fast, with no second thought about it. As the good Dr. Ian Malcolm said:
In this posts we will introduce important concepts to take into account when developing smart contracts for the Cardano blockchain, we will get used to the lingo you’ll see around the project and superficially talk about the structure of Plutus smart contracts. Let’s go.
The extended UTxO Model
In the original UTxO model we can find mainly two types of addresses, a pay-to-pub-key address and a pay-to-script address, nowadays the first one is implemented in terms of the latter as a pay-to-pub-key-hash, being just a script that when validated it allows the value to be spent by the owner of such public-key, please refer to the Bitcoin developer guide for details on how this is done. The interesting thing here is that the address to which a user sends money to, is just a hash of a script, literally a small program that when evaluated the spender will need to provide the correct data (mainly a signature) in order to effectively unlock the money and spend it further down. This is clever because now someone that pays do not need to care about the conditions in which the money will be “unlocked” by the receiver, now this conditions are unknown to the sender, he only knows he assigned the coins to some “random” hash and that’s it. Me, the receiver, can build the script with whatever conditions I want, hash that, and give it to the sender, then in order for me to spent those coins I literally need to provide the script I used and any data the script needs to be evaluated (more on this here).
Now, on the Cardano blockchain this model is further extended, now the pay-to-script output carries an additional data script, a field where the sender can put any random data that will be tied to the coins, then the spender of those coins will only need to provide some other data (known as the redeemer script) so that when the script locking the coins is evaluated the script can effectively use the data script tied to the output and the redeemer script data provided by the spender to see if he is indeed allowed to claim ownership of those outputs. The magic here is that data script field allows the sender to “put something” in the transaction, in other words, we can have “conditions” on the money we send, this is the basis for how smart contracts work in the Cardano blockchain.
Furthermore, transactions are extended so that they carry a slot interval of validity, let’s remember that in Cardano, blocks are generated in batches called slots, every slot has a slot leader which is in charge of validation of the blocks generated during that period. Adding this interval of validity to a transaction makes it possible to determine the Gas costs associated with a transaction before this is deployed to the blockchain, this is because the validator script, that is, the script hash, will know in advance all the data is needed to be evaluated.
The validator script, is the program whose successful evaluation determines if coins can be claimed thus allowing the receiver to further spend them. Whenever this program ends in the
error state then the transaction being validated will be flagged as invalid. Now from the extended UTxO model documentation we see that the validator script will have access to the following information:
- The validity interval of the currently validated transaction.
- The hash of the currently validated transaction.
- For every input of the validated transaction, its value and the hashes of its validator, data, and redeemer scripts.
- For every output of the validated transaction, its value and the hash of its validator and data script.
- The sum of the values of all unspent outputs (of the current blockchain without the currently validated transaction) locked by the currently executed validator script.
All of these is provided by the slot leader, and any other validator nodes that wish to verify the validity of the transaction.
Types of scripts
Now, when developing smart contracts for Cardano we will need to understand the data script, the redeemer script and the validator script. Let’s see each of these in detail.
The data script as mentioned briefly earlier is a piece of data that is attached to the coins we wish to pay someone. This piece of data is generated by the Producer of a transaction, it can be what ever this party wants, for example, a string representing the current date and time or perhaps some message that we want to live on the blockchain forever.
When this is used as part of a smart contract, then the data script value type will be determined by the smart contract developer, but the value is always set by the producer of a transaction. It might be possible for someone to specify an invalid data script value when creating a transaction (by hand) and if this is the case the outputs will not be redeemable since the smart contract will not be able to validate this outputs as their data script value is invalid, this will become clearer when we see it on code.
This is always provided by the producer and signed by the producer.
The redeemer script is a piece of data that is attached to a transaction attempting to spend some outputs currently assigned to a smart contract address. This piece of data is generated by the Consumer of a transaction, it can be what ever this party wants, for example, a string representing a password or anything that he may believe will grant him the right to spend those coins. The smart contract developer has previously encoded some logic in the contract that will allow to claim the coins to anyone providing the correct value for the redeemer script.
This is always provided by the consumer and signed by the consumer.
This is the smart contract itself, in Cardano this is the correct term to use to refer to the “smart contract”. The validator script is just a program, a Plutus expression, a function that receives as parameters both the data script and the redeemer script along with the current blockchain state (mentioned above). Then, the validator node in order to validate the transaction (the one that attempts to spend the outputs assigned to the smart contract address) will run this program and if its execution doesn’t terminate in an
error state then the transaction is valid, if it doesn’t then it is flagged as invalid.
Now, I could create my own program that will only pay to the person providing a signature made with my private key, in this particular case the program will not need to care about the data script provided by the producer of the transaction, only the redeemer script must be a valid signature. Someone paying to this smart contract will only need to hash the program and send the coins to that hash, this is the pay-to-script hash. Then whenever I wish to claim the coins (the outputs assigned to the contract address) I’ll need to provide said program, so that when hashed it is the same hash as the pay-to-script address, and not only that, the validator will evaluate the program with the redeemer script I give to it and the data script of each output assigned to the contract, if the evaluation terminates successfully (which in this case will only happen if I provide a valid signature) then I get the coins.
There’s a small detail here, the validator script is run once for each output assigned in the contract, and the evaluation must be successful for all the outputs and not just some of them for the transaction to be flagged as valid.
The validator script is provided by the consumer but signed by the producer.
On-chain code vs Off-chain code
One key aspect one should know when developing smart contracts for the Cardano platform is how on-chain code and off-chain code is organized. In Ethereum, all of the on-chain code it’s written using Solidity in a
.sol file, then the application developer must write some off-chain code in an external application (owned by him) that handles the interaction with the smart contract by using the web3 library or alike. By using this library the developer can manipulate both the smart contract state and the user’s wallet (supposedly with their permission). This introduces several problems, first, developers need to track their application and new features they wish to add among multiple code bases (the solidity file and the web application that interacts with the smart contract), second, the programming language used in both of them will be different and most often than not these will not be languages graded for software correctness.
In Cardano the idea is different, both the on-chain code and off-chain code is written in the same file and in the same language (almost), and with Haskell! a very safe programming language to use. It might be weird to imagine how this is possible, but it is. Once we start to write Plutus code you’ll see.
While in Ethereum users could interact with a contract via public endpoints (either payable or not), in Plutus we can only interact with the smart contract through wallet actions, these are Haskell expressions that have a specific type signature (
MonadWallet m => m ()) these actions then need to be “registered” as endpoints at the end of the contract using the
mkFunctions function, this will make the Plutus Playground to include them in the mock wallet UI so we can test our contract behaviour.
However there’s an interesting note, wallet actions as their name suggest are executed by the user’s wallet, so these actions contain only off-chain code, I imagine that user’s could browse a “smart contract marketplace” directly from their wallets and be able to interact with a specific contract using a UI auto-generated by the user’s wallet.
The validator expression
The validator expression is the one expression in a smart contract that contains on-chain code, this is written in Plutus a small subset of Haskell, and in fact is just a lambda expression that takes three arguments we already introduced, the data script, redeemer script and the pending transaction being validated that contains the current blockchain state.
However, one may wonder, how is it possible to use Plutus code in a Haskell source file? Aside from the fact that Plutus code is a subset of Haskell (so it seems all Plutus code is valid Haskell code) the validator expression must be compiled down to Plutus IR (Intermediate Representation) by the Plutus compiler, whereas off-chain code will be compiled by GHC, the Glasgow Haskell Compiler. The way this seems to work is by using meta programming enabled by Template Haskell, all Plutus code is spliced in with the
$$(...) operator, I assume this performs kind of the same thing as the Haskell’s
$(...), that is, convert from an abstract code representation to Haskell code, in this case from Plutus IR to Haskell. Then the validator expression as is on-chain code it must be enclosed within
||], I find this somewhat analogous to what
|] in template Haskell does, parse an expression to an abstract syntax tree, however instead of parsing the enclosed expression to the AST used by Haskell it must compile it down to Plutus IR. The technicalities of this I believe are not important, the key thing to remember is that on-chain code is enclosed within
||], and on-chain code can only exists within the validator expression and is ultimately “injected” by enclosing it within
In contrast to wallet actions where a smart contract source file can have many of this declared, we should only have one validator expression, at least this is the “normal” way to do it, I could imagine that one could use some control flows in the program to use different validator expressions but I don’t see how this could be useful.
Event Triggers & Event Handlers
One more thing that a smart contract can define are both event triggers and event handlers.
Event triggers define a boolean expression that under certain conditions will evaluate to true, for example, we might want to perform some action when the current state of the blockchain has reached some desired height, that is, some block X in slot Y has been mined. Or perhaps fire an action to be performed when the balance in a certain wallet has exceeded some amount.
In code an event trigger is an expression with the type
Event handlers, on the other hand, define an expression representing a wallet action that is to be performed when an event trigger fires off. While the event trigger specifies the condition of when an action should take place, the event handler specifies what action will be carried out, usually, issue a transaction.
In code an event handler is an expression with the type
MonadWallet m => EventHandler m.
Organization of modules
Any sufficiently meaningful Haskell program requires some modules to be imported, for writing smart contracts we need to import various modules that contain Haskell functions and Plutus expressions. These functions are basically organized across modules as follows:
In this module we can find functions related to interacting with the user’s wallet, for example to issue a transaction, perform a signature, query wallet funds, subscribe event triggers to event handlers, etc.
From this module we can use the
makeLift function to bridge our custom data types to their on-chain representation. Within this module we also have access to the Plutus Prelude which is a set of on-chain functions that are available for us to use inside the validator script lambda expression, most of these will need to be spliced in via wrapping them within
Here we find most of the type definitions required in a smart contract as well as various functions to manipulate blockchain transactions, compile Plutus expressions and the sub-module
Validation which contains validation functions we can use inside the validator expression to for example check a signature or equality of keys.
The Plutus Playground
To end this post, let’s talk about the Plutus Playground. This is a tool developed by IOHK (the developer force of Cardano) with the focus of making smart contract development easy to test, using this tool we can simulate some state in a mockchain and see how the contract will behave under different circumstances. The tool is available online and can be accessed through any web browser here, there we can find a Haskell code editor and some pre-loaded smart contract examples, we can perform simulations and see the transactions generated by running them.
This is all we need to know in order to start developing smart contracts for the Cardano platform, by understanding the way the extended UTxO model works and what are the different expressions that a smart contract contains we will not have any problem understanding Plutus, for the foreign eyes the only difficulty you’ll face as a former Solidity developer is to understand how to do the Haskell way of doing things, if you’ve never done functional programming before it certainly could be challenging but worth it.
I’ll be watching close the direction of Plutus as a smart contract programming language, the principles are sound so I hope see it go mainstream in the blockchain space.