Skip to content

Latest commit

 

History

History
205 lines (131 loc) · 10.4 KB

README.md

File metadata and controls

205 lines (131 loc) · 10.4 KB

Table of Contents

Plutarch Library for Common Design Patterns in Cardano Smart Contracts

To help facilitate faster development of Cardano smart contracts, we present a collection of tried and tested modules and functions for implementing common design patterns.

How to Use

Prerequisites

Before you begin, ensure you have Nix installed on your system. Nix is used for package management and to provide a consistent development environment. To install run the following command:

sh <(curl -L https://nixos.org/nix/install) --daemon

and follow the instructions.

$ nix --version
nix (Nix) 2.18.1

Make sure to enable Nix Flakes by editing either ~/.config/nix/nix.conf or /etc/nix/nix.conf on your machine and add the following configuration entries:

experimental-features = nix-command flakes ca-derivations
allow-import-from-derivation = true

Optionally, to improve build speed, it is possible to set up binary caches maintained by IOHK and Plutonomicon by setting additional configuration entries:

substituters = https://cache.nixos.org https://iohk.cachix.org https://cache.iog.io
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ= iohk.cachix.org-1:DpRUyj7h7V830dp/i6Nti+NEO2/nhblbov/8MW7Rqoo=

To facilitate seamlessly moving between directories and associated Nix development shells we use direnv and nix-direnv:

Your shell and editors should pick up on the .envrc files in different directories and prepare the environment accordingly. Use direnv allow to enable the direnv environment and direnv reload to reload it when necessary. Otherwise, the .envrc file contains a proper Nix target you can use with the nix develop command.

To install both using nixpkgs:

nix profile install nixpkgs#direnv
nix profile install nixpkgs#nix-direnv

Building and developing

Once Nix is installed, you should be able to seamlessly use the repository to develop, build and run packages.

Download the Git repository:

git clone https://github.com/Anastasia-Labs/plutarch-linked-list.git

Navigate to the repository directory:

cd plutarch-linked-list
direnv allow

Activate the development environment with Nix:

nix develop .

Additionally, when you run nix run .#help you'll get a list of scripts you can run, the Github CI (nix flake check) is setup in a way where it checks the project builds successfully, haskell format is done correctly, and commit message follows conventional commits. Before pushing you should run cabal run , nix run .#haskellFormat (automatically formats all haskell files, including cabal), if you want to commit a correct format message you can run cz commit

Build:

cabal build all

plutarch-design-patterns.gif

Provided Patterns

Stake Validator

This module offers two functions meant to be used within a multi-validator for implementing a "coupled" stake validator logic.

The primary application for this is the so-called "withdraw zero trick," which is most effective for validators that need to go over multiple inputs.

With a minimal spending logic (which is executed for each UTxO), and an arbitrary withdrawal logic (which is executed only once), a much more optimized script can be implemented.

Endpoints

spend merely looks for the presence of a withdrawal (with arbitrary amount) from its own reward address.

withdraw takes a custom logic that requires 3 arguments:

  1. Redeemer (arbitrary PData)
  2. Script's validator hash (PStakingCredential)
  3. Transaction info (PTxInfo)

UTxO Indexers

The primary purpose of this pattern is to offer a more optimized solution for a unique mapping between one input UTxO to one or many output UTxOs.

Singular UTxO Indexer

One-to-One

By specifying the redeemer type to be a pair of integers ((Int, Int)), the validator can efficiently pick the input UTxO, match its output reference to make sure it's the one that's getting spent, and similarly pick the corresponding output UTxO in order to perform an arbitrary validation between the two.

The provided example validates that the two are identical, and each carries a single state token apart from Ada.

One-to-Many

Here the validator looks for a set of outputs for the given input, through a redeemer of type (Int, List<Int>) (output indices are required to be in ascending order to disallow duplicates). To make the abstraction as efficient as possible, the provided higher-order function takes 3 validation logics:

  1. A function that validates the spending Input (single invocation).
  2. A function that validates the input UTxO against a corresponding output UTxO. Note that this is executed for each associated output.
  3. A function that validates the collective outputs. This also runs only once. The number of outputs is also available for this function (its second argument).

Multi UTxO Indexer

While the singular variant of this pattern is primarily meant for the spending endpoint of a contract, a multi UTxO indexer utilizes the stake validator provided by this package. And therefore the spending endpoint can be taken directly from stake_validator.

Subsequently, spend redeemers are irrelevant here. The redeemer of the withdrawal endpoint is expected to be a properly sorted list of pairs of indices (for the one-to-one case), or a list of one-to-many mappings of indices.

It's worth emphasizing that it is necessary for this design to be a multi-validator as the staking logic filters inputs that are coming from a script address which its validator hash is identical to its own.

The distinction between one-to-one and one-to-many variants here is very similar to the singular case, so please refer to its section above for more details.

The primary difference is that here, input indices should be provided for the filtered list of inputs, i.e. only script inputs, unlike the singular variant where the index applies to all the inputs of the transaction. This slight inconvenience is for preventing extra overhead on-chain.

Transaction Level Validator Minting Policy

Very similar to the stake validator, this design pattern utilizes a multi-validator comprising of a spend and a minting endpoint.

The role of the spendig input is to ensure the minting endpoint executes. It does so by looking at the mint field and making sure a non-zero amount of its asset (where its policy is the same as the multi-validator's hash, and its name is specified as a parameter) are getting minted/burnt.

The arbitrary logic is passed to the minting policy so that it can be executed a single time for a given transaction.

Validity Range Normalization

The datatype that models validity range in Cardano currently allows for values that are either meaningless, or can have more than one representations. For example, since the values are integers, the inclusive flag for each end is redundant and can be omitted in favor of a predefined convention (e.g. a value should always be considered inclusive).

In this module we present a custom datatype that essentially reduces the value domain of the original validity range to a smaller one that eliminates meaningless instances and redundancies.

The datatype is defined as following:

data NormalizedTimeRange
  = ClosedRange Integer Integer
  | FromNegInf Integer
  | ToPosInf Integer
  | Always

The exposed function of the module (normalize_time_range), takes a ValidityRange and returns this custom datatype.

Merkelized Validator

Since transaction size is limited in Cardano, some validators benefit from a solution which allows them to delegate parts of their logics. This becomes more prominent in cases where such logics can greatly benefit from optimization solutions that trade computation resources for script sizes (e.g. table lookups can take up more space so that costly computations can be averted).

This design pattern offers an interface for off-loading such logics into an external withdrawal script, so that the size of the validator itself can stay within the limits of Cardano.

Note

While currently the sizes of reference scripts are essentially irrelevant, they'll soon impose additional fees. See here for more info.

The exposed spend function from merkelized_validator expects 3 arguments:

  1. The hash of the withdrawal validator that performs the computation.
  2. The list of arguments expected by the underlying logic.
  3. The Dict of all redeemers within the current script context.

This function expects to find the given stake validator in the redeemers list, such that its redeemer is of type WithdrawRedeemer (which carries the list of input arguments and the list of expected outputs), makes sure provided inputs match the ones given to the validator through its redeemer, and returns the outputs (which are carried inside the withdrawal redeemer) so that you can safely use them.

For defining a withdrawal logic that carries out the computation, use the exposed withdraw function. It expects 3 arguments:

  1. The computation itself. It has to take a list of generic inputs, and return a list of generic outputs.
  2. A redeemer of type WithdrawRedeemer<a, b>. Note that a is the type of input arguments, and b is the type of output arguments.
  3. The script context.

It validates that the puropse is withdrawal, and that given the list of inputs, the provided function yields identical outputs as the ones provided via the redeemer.