Smart Contracts

Smart contracts are pre-programmed, automatic digital agreements. They are self-executing, unalterable, and incorruptible. They don't necessitate any acts or the presence of others.

Smart contracts on Cardano are designed to be highly programmable, which means they can be customized to suit the specific needs of a particular use case. They are also designed to be highly secure, as they are executed on a decentralized network of nodes that validate each transaction.

Table of Content

Introduction

Smart contracts on Cardano are self-executing contracts that automate the execution of a contract agreement between two or more parties. They are powered by the Cardano blockchain, which provides a secure and decentralized platform for executing these contracts.

One of the unique features of smart contracts on Cardano is the ability to execute them off-chain. This means that the contract can be executed without requiring every node on the network to validate the transaction. This makes the execution of the contract faster and more efficient.

Available Programming Languages

  • Aiken - for on-chain validator scripts only: a language & toolchain favouring developer experience.
  • Marlowe - a domain-specific language, it covers the world of financial contracts.
  • opshin - a programming language for generic Smart Contracts based on Python.
  • Plutus - a platform to write full applications that interact with the Cardano blockchain.
  • plu-ts - Typescript-embedded smart contract programming language and a transaction creation library.

The Plutus programing language, which Haskell heavily inspires, has been the default standard for developing smart contracts on Cardano. Haskell has several advantages but has a steep learning curve and a small universe of developers proficient in it.

Aiken is the up-and-coming programming language that has generated much excitement for its ease of use (relative to Haskell) and improved dev experience.

Advantages of Haskell

Here are some advantages of Haskell for smart contracts:

  • Safety and security: Haskell is a strongly typed language, which means that it provides a high level of type safety. This helps to catch errors at compile-time rather than run-time, which reduces the risk of security vulnerabilities in the smart contract.
  • Functional programming: Haskell is a functional programming language, which means that it uses a declarative style of programming. This makes it easier to reason about the code and reduces the risk of side effects that can cause bugs and vulnerabilities.
  • Modularity: Haskell is designed to be modular, which means that it makes it easy to break down complex code into smaller, more manageable components. This can help to improve the readability, maintainability, and reusability of smart contract code.
  • High performance: Haskell is known for its high performance, which makes it suitable for building smart contracts that require high computational power. It is also designed to work well with parallel processing, which can improve the efficiency of smart contract execution.
  • Formally verified libraries: Haskell has a large and active community that has developed many high-quality libraries for smart contract development. Many of these libraries have been formally verified, which provides additional assurance that they are secure and free from bugs and vulnerabilities.

Disadvantages of Haskell

While Haskell is a powerful language for smart contract development, it also has some disadvantages that can make it less suitable for certain use cases. Here are some disadvantages of Haskell for smart contracts:

  • Steep learning curve: Haskell is a complex language that can be difficult to learn for developers who are not familiar with functional programming. This can make it challenging for new developers to get up to speed and start building smart contracts.
  • Limited community: Although Haskell has a dedicated community, it is relatively small compared to other programming languages. This means that finding support and resources for smart contract development in Haskell can be more difficult.
  • Limited tooling: There are fewer tools and frameworks available for Haskell compared to other programming languages. This can make it more challenging to develop, test, and deploy smart contracts.
  • Limited interoperability: Haskell is not as widely supported as other programming languages, which can make it more difficult to integrate with other systems and platforms.

While Haskell has many benefits for smart contract development, it may not be the best choice for all use cases. Developers should carefully consider their specific needs and requirements before choosing a programming language for smart contract development.

Aiken

Aiken emerges as a promising alternative to the Haskell Plutus platform for developing and deploying smart contracts on Cardano. Designed with usability, security, and performance in mind, Aiken offers a more accessible and familiar syntax to developers, reducing the learning curve associated with Haskell-based Plutus. The language is tailored to address the unique requirements of on-chain smart contract execution while ensuring compatibility with off-chain processes and external tools. Aiken’s open source and collaborative development environment, along with the support from the Cardano Foundation, ensures continuous improvement and feature enhancements.

https://aiken-lang.org/

It is a pure functional programming language that offers developers a modern and efficient environment for building smart contracts on Cardano. With a toolkit for working with Plutus, Aiken streamlines smart contract development and fosters an open source ecosystem. Designed to provide a user-friendly experience, Aiken makes it easier than ever to create decentralized applications (dApps) and implement advanced blockchain solutions.

Aiken addresses the disadvantages of Haskell by offering a more approachable platform, a modern development environment, compatibility with Plutus and seamless off-chain interoperability with any language stack. This allows developers to write and deploy smart contracts on Cardano using a functional programming language that shares similarities with Haskell, ensuring full integration with Cardano’s features and benefits, while also retaining freedom for their off-chain infrastructure.

Smart Contract Basics

When developing smart contracts on Cardano there are 4 concepts that must be always kept in mind: validators, datums, redeemers and script context

Validators

A validator is a piece of code that defines the rules for determining whether a transaction is valid or not. It is written in the Plutus programming language and executed on the Cardano blockchain.

The validator is associated with a particular output of a transaction, and it specifies the conditions that must be met for that output to be spent in a subsequent transaction. For example, a validator might require that a certain condition be met, such as a particular party providing a valid digital signature, before the output can be spent.

The validator can also take into account additional data associated with the output, such as datum and redeemer, to help determine whether a transaction is valid.

Datum

A datum is a piece of data that is associated with a particular output of a transaction. It is used to provide additional context or information to the validator when evaluating whether a transaction is valid.

An example of how datum can be used is in a simple crowdfunding smart contract. The contract might require a certain amount of ADA to be contributed by a certain date in order for the campaign to be successful. Each contributor would create an output with the specified amount of ADA, and a corresponding datum containing their contribution amount and any other relevant information.

When the deadline arrives, the validator for the smart contract would evaluate the outputs and data associated with them to determine whether the campaign was successful. The validator would check the total amount of ADA contributed, the deadline, and any other conditions specified in the contract to determine whether the campaign was successful or not.

In this example, the datum provides additional information to the validator that is used to determine whether the campaign was successful. Without the datum, the validator would not have enough information to determine whether the conditions of the contract had been met.

Redeemer

A a redeemer is a piece of data that is included in a transaction when attempting to spend a particular output of a contract. The redeemer is used to provide additional context or information to the validator when evaluating whether the transaction is valid or not.

For example, let's say you have a smart contract that represents a simple escrow agreement between two parties. The contract requires that one party must deposit a certain amount of ADA, and the other party must provide proof that they have fulfilled their obligations before the funds are released.

In this case, the redeemer could be used to provide the proof of fulfillment. For instance, the party that fulfilled their obligations could include a digital signature in the redeemer to prove that they have completed the required tasks. The validator would then check the redeemer, along with any other relevant data, to determine whether the transaction is valid and the funds can be released.

Another example of a redeemer could be a secret code that is required to unlock a particular output. The redeemer would include the code, and the validator would check it to determine whether the transaction is valid and the output can be spent.

Script Context

The script context refers to the environment in which a validator, datum, and redeemer are evaluated. It provides relevant information about the current state of the blockchain, such as the current block height, the inputs and outputs of the transaction being validated, and other relevant data.

For example. Suppose we want to create a smart contract that allows Alice to send some ADA to Bob, but only if certain conditions are met. Specifically, we want to require that Alice must have a minimum balance of 100 ADA, and she must provide a secret key that matches a predetermined value.

To implement this logic, we would write a validator function in Plutus that checks whether the current transaction meets these conditions. The validator function would take as input the current script context, the datum associated with the output being spent, and the redeemer provided with the transaction.

The script context would provide information about the current block height and the inputs and outputs of the transaction being validated. The datum would contain any additional data associated with the output, such as a hash of the secret key required to spend the output. The redeemer would contain the secret key itself.

Using this information, the validator function would check whether Alice's balance is at least 100 ADA, and whether the provided secret key matches the predetermined value. If both conditions are met, the validator function would return True, indicating that the transaction is valid. Otherwise, it would return False, indicating that the transaction is invalid.

Run a Smart Contract

The purpose of this section is to get  familiar with operating with the Cardano infrastructure, and you will need the node that has been set up in previous steps

Always Succeeds Script

An always succeed script is like the "Hello World" of Cardano smart contracts. It gets the feet dirty with running your first validator. We will work through an example of how to lock some ADA in a validator that always returns "True".

The Haskell source code for the AlwaysSucceed script is very long and contains information that we would not have time to grasp in a day. Therefore here is just the relevant part of it in Haskell

{-# INLINABLE mkValidator #-}
mkValidator :: Data -> Data -> Data -> ()
mkValidator _ _ _ = ()

It accepts:

  • Datum with type Data
  • Redeemer with type Data
  • Script Context with type Data

And always returns True. So what ever assets are locked at that script will always get released by the first person who sends a transaction to the address of the validator requesting them.

A similar script that always returns an error and works like a "black hole" where anything that gets sent is always burned could look something like this

{-# INLINABLE mkValidator #-}
mkValidator :: Data -> Data -> Data -> ()
mkValidator _ _ _ = traceError "BURNT!"

Script CBORHex

The Script CBORHex is a format used to represent Plutus scripts in binary form. CBOR stands for Concise Binary Object Representation, which is a binary serialization format used to encode data structures in a compact and efficient way.

The Script CBORHex format is used to represent the Plutus script as a sequence of bytes, which can then be stored on the blockchain or transmitted over the network. The format is designed to be compact and efficient, while still allowing for easy parsing and decoding of the script.

To create a Script CBORHex representation of a Plutus script, the script is first compiled into a binary format using the Plutus compiler. This binary format is then encoded in CBOR format, which produces a sequence of bytes that can be represented as a hexadecimal string.

The Script CBORHex format is used in several places in Cardano, such as when submitting a transaction with a Plutus script as one of its inputs or outputs. The format allows the script to be stored and transmitted efficiently, while still retaining all of the necessary information for executing the script on the blockchain.

Compiling the Script source file to CBORHex requires to set-up the dev environment, which should be attempted after the workshop and can be done with a Haskell or the Aiken dev environment. Here we just use precompiled script. Save the following code to a file AlwaysSucceeds.plutus in your working directory.

{
    "type": "PlutusScriptV1",
    "description": "",
    "cborHex": "4e4d01000033222220051200120011"
}

Also, note that we are using V1 of the Plutus Script

Script Address

Every smart contract on Cardano has an address that starts with addr... with which any one can interact with, and send assets to the address.

To get the script address run the following command in the terminal in your working directory

cardano-cli address build --payment-script-file AlwaysSucceeds.plutus --testnet-magic 2

This should return the address: addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8

You can query what assets are currently locked at that address, and you are likely to see quite a lot of UTXO as the same example has been used by many students over the years.

Send ADA to the Script

We will lock some ADA at the script address. First find the UTXO from your wallet that should be consumed. You will get the change back in your wallet.

cardano-cli query utxo \
--address $(cat payment.addr) \
--testnet-magic 2

In our case we got the following UTXO to use:

  • TxHash: be07d6c2432a77250f7055925de6a6446be1759621f93f5986fd4033e5fef6cd
  • TxIx: 0
  • Amount: 10000000000

We also need to set the Datum hash to something. The necessary conditions of Plutus scripts is that each one must have a datum - otherwise the assets can't be redeemed from  that scripts. We set a random number of 8763176

DATUM_HASH=$(cardano-cli transaction hash-script-data --script-data-value "8763176")

Now lets build and submit the transaction to send a 35 ADA, which is 35 000 000 lovelaces

cardano-cli transaction build \
    --tx-in be07d6c2432a77250f7055925de6a6446be1759621f93f5986fd4033e5fef6cd#0 \
    --tx-out addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8+35000000 \
    --tx-out-datum-hash ${DATUM_HASH} \
    --change-address=addr_test1qr93nus5fartwuxdt4qqmk8d3ddnq2rjl4wmwwrntwqxry7qf70znkl0agtyha2lqrqmahjj06wc9srfyxj9ydzx440qyw5s7r \
    --testnet-magic 2 \
    --out-file tx.build \
    --alonzo-era

cardano-cli transaction sign \
    --tx-body-file tx.build \
    --signing-key-file payment.skey \
    --testnet-magic 2 \
    --out-file tx.signed

cardano-cli transaction submit --tx-file tx.signed --testnet-magic 2

If all went well you should receive a message Transaction successfully submitted.

You can find the UTXO of the new transaction by running

cardano-cli transaction txid --tx-file tx.signed

Which, in our case, gave this transaction id 582e2cf3fd1486ef0c47071f5394da0f1fad9de82d606667e375ed312399a358 and check this on one of the explorers e.g. https://preview.cardanoscan.io/

Transaction confirmation on https://preview.cardanoscan.io/

Collateral

When trying to consume UTxOs locked in a script, you need to provide collateral that will cover the costs if validation fails. So we will send a 5 ADA to ourselves first that we can use as collateral when redeeming from the script

This is also a good practice for building and sending transactions on Cardano. Find an available UTXO in your wallet

cardano-cli query utxo \
--address $(cat payment.addr) \
--testnet-magic 2

Then send yourself 5 ADA

cardano-cli transaction build \
--tx-in 582e2cf3fd1486ef0c47071f5394da0f1fad9de82d606667e375ed312399a358#1 \
--tx-out addr_test1qr93nus5fartwuxdt4qqmk8d3ddnq2rjl4wmwwrntwqxry7qf70znkl0agtyha2lqrqmahjj06wc9srfyxj9ydzx440qyw5s7r+5000000 \
--change-address=addr_test1qr93nus5fartwuxdt4qqmk8d3ddnq2rjl4wmwwrntwqxry7qf70znkl0agtyha2lqrqmahjj06wc9srfyxj9ydzx440qyw5s7r \
--testnet-magic 2 \
--out-file tx.build \
--alonzo-era

cardano-cli transaction sign \
--tx-body-file tx.build \
--signing-key-file payment.skey \
--out-file tx.signed

cardano-cli transaction submit --tx-file tx.signed --testnet-magic 2

After successfully submitting the transaction, wait a few seconds and check if the UTXOs at your wallet have been updated. You should now see 2 UTXOs

The UTXO with 5 ADA we will use as collateral when redeeming from Plutus scripts.

Redeem ADA from Script

Now is the time to do the reverse - to redeem the ADA that we have locked at a script address. First lets check if the transaction that we sent in the previous step is actually sitting at the script address. We will search for 35 000 000 lovelaces

cardano-cli query utxo --address addr_test1wpnlxv2xv9a9ucvnvzqakwepzl9ltx7jzgm53av2e9ncv4sysemm8 --testnet-magic 2 | grep 35000000

The returned UTXO should be the same as your transaction Id from the previous step.

Get the latest protocol parameters. This dictates how much it costs to interact with scripts

cardano-cli query protocol-parameters --testnet-magic 2 > params.json

Build and submit the transaction

cardano-cli transaction build \
    --tx-in 3b40e22fbf8a533abc6815ab4cda4d0142702cd4eaaa90d51f345932959f0408#0 \
    --tx-in 582e2cf3fd1486ef0c47071f5394da0f1fad9de82d606667e375ed312399a358#0 \
    --tx-in-datum-value "8763176" \
    --tx-in-redeemer-value "123" \
    --tx-in-script-file AlwaysSucceeds.plutus \
    --tx-in-collateral=3b40e22fbf8a533abc6815ab4cda4d0142702cd4eaaa90d51f345932959f0408#0 \
    --change-address=addr_test1qr93nus5fartwuxdt4qqmk8d3ddnq2rjl4wmwwrntwqxry7qf70znkl0agtyha2lqrqmahjj06wc9srfyxj9ydzx440qyw5s7r \
    --tx-out addr_test1qr93nus5fartwuxdt4qqmk8d3ddnq2rjl4wmwwrntwqxry7qf70znkl0agtyha2lqrqmahjj06wc9srfyxj9ydzx440qyw5s7r+35000000 \
    --tx-out-datum-hash 1b83bc2af36aa9d9c6e212caf57b11b40f865c67029dcec1b91035577177571a \
    --out-file tx.build \
    --testnet-magic 2 \
    --protocol-params-file "params.json" \
    --alonzo-era

cardano-cli transaction sign \
    --tx-body-file tx.build \
    --signing-key-file payment.skey \
    --testnet-magic 2 \
    --out-file tx.signed

cardano-cli transaction submit --tx-file tx.signed --testnet-magic 2

Find the transaction Id which you can then check the status in an explorer

cardano-cli transaction txid --tx-file tx.signed

If everything went through correctly then you should see 35 ADA in your wallet in a few seconds.  Note that the transaction fee has been deducted from the collateral value as we included its UTXO as one of the inputs. And the 35 ADA still has the datum attached to it as we included its hash in the tx-out-datum-hash when building the transaction.  

Conclusion

This section provided an introduction to operating with the Cardano infrastructure by walking through an example of an Always Succeed Script. We covered the concept of the Haskell source code for the script, the Script CBORHex format, the script address, and how to send and redeem ADA from the script. We also learned about the concept of providing collateral when consuming UTXOs locked in a script and the importance of checking the latest protocol parameters. By following the steps provided in this section, the reader should have gained a basic understanding of how to work with Plutus scripts and interact with the Cardano blockchain.

References

Smart Contracts - Cardano Developer Docs

Aiken - AdaPulse

UTXO handbook

Plutus Pioneer Program - Github

Always Succeed script - Plutus Pioneer Program

Always Succeed script - readthedocs

Helper Shell Scripts - Plutus Pioneer Program

License

This work is distributed under a Creative Commons Attribution 4.0 International (CC BY 4.0) The license allows you to copy and redistribute the material in any medium or format, as well as remix, transform, and build upon the material for any purpose, including commercial, as long as you give appropriate credit to the creator.