Plutus

From Organic Design wiki

Plutus is a language for writing smart contracts in Cardano. Plutus contracts are written in a form of Haskell.

Structure

Plutus contracts are easier to understand if you break it into smaller parts. Most of this code is take from the "Starter" example in the Plutus playground.

Imports

The contract should start by importing all the useful libraries you might use in a contract.

Datum and Redeemer Types

The contract should have Datum and redeemer types.

newtype MyDatum = MyDatum Integer deriving newtype PlutusTx.IsData
PlutusTx.makeLift ''MyDatum

newtype MyRedeemer = MyRedeemer Integer deriving newtype PlutusTx.IsData
PlutusTx.makeLift ''MyRedeemer

This looks a little strange at first, but all it is doing is defining two types called MyRedeemer and MyDatum that has a constructor that takes a single argument - an Integer. It derives from the two types newtype and PlutusTx.IsData which is nessisary so it can be converted into Plutus Core so it can be run on the chain - this is exactly what the second line does PlutusTx.makeLift.

Validator Function

A contract should have a function for validating transactions so that only when a certain criteria is met a transaction will be made to withdraw coin from the contract.


The simplest validator function might look like this:

validateSpend :: MyDatum -> MyRedeemer -> ValidatorCtx -> Bool
validateSpend _myDataValue _myRedeemerValue _ = error ()

The first line gives the definition of the types for the function that takes 3 arguments and returns a Bool. The second line is the function itself which takes the first two arguments and ignores the third because the third argument is for the Plutus Core and not of interest to us. This function just throws an error and returns the unit value. This function isn't very useful because it just throws an error and does not validate the transaction, a more useful one might check that the Datum and Redeemer values are the same (since they are both Integer).

validateSpend :: MyDatum -> MyRedeemer -> ValidatorCtx -> Bool
validateSpend (MyDatum datum) (MyRedeemer redeemer) _ = datum == redeemer

This is a very simple example, your validator function could be much more complicated.

Script Address and Instance

To interact with the contract we need a compiled version of the validator script with our data types, to do this first we will create a type for it that uses our datum and redeemer types:

data Starter
instance Scripts.ScriptType Starter where
    type instance RedeemerType Starter = MyRedeemer
    type instance DatumType Starter = MyDatum

This next part gets a little complicated - it basically uses the data type we created above and creates and instance with the compiled version of validateSpend and wraps and compiles our types.

starterInstance :: Scripts.ScriptInstance Starter
starterInstance = Scripts.validator @Starter
    $$(PlutusTx.compile [|| validateSpend ||])
    $$(PlutusTx.compile [|| wrap ||]) where
        wrap = Scripts.wrapValidator @MyDatum @MyRedeemer

The address of a contract is the hash of it's validator script, so to make any transactions from the contract we need to get this address.

contractAddress :: Address
contractAddress = Ledger.scriptAddress (Scripts.validatorScript starterInstance)

Contract Definition

Before creating the contract itself we need to define the possible endpoints for interacting with it.

type Schema =
    BlockchainActions
        .\/ Endpoint "publish" (Integer, Value)
        .\/ Endpoint "redeem" Integer
        .\/ Endpoint "customLog" String

This defines the schema with 3 actions on top of the existing ones in BlockchainActions. Each action should be created using the Endpoint function that takes a String which is the name of the endpoint and the parameter it takes, each action needs to be merged (.\/) with BlockchainActions to add the functionality.


Once the schema is defined we can create the contract.

contract :: AsContractError e => Contract Schema e ()
contract = publish `select` redeem `select` customLog

The second line uses the select function to choose what contract function (which we will define next) to invoke depending on what endpoint got called.

Contract Endpoints

A contract needs endpoints for interacting with it, it should have endpoints for locking and unlocking the funds at a minimum:

"customLog" Endpoint

The customLog endpoint function is simple example of a custom endpoint.

customLog :: AsContractError e => Contract Schema e ()
customLog = do
    logVal <- endpoint @"customLog"
    logInfo @String logVal

The first line is the type which is the same type as the contract we defined earlier. The first line of the function itself just gets the value from the endpoint (which we defined earlier to be of type String) and stores it in logVar. The last line just out puts that value to the log.

"publish" Endpoint

The publish endpoint just creates a transaction which sends coin to the script with an Integer value.

publish :: AsContractError e => Contract Schema e ()
publish = do
    (i, lockedFunds) <- endpoint @"publish"
    let tx = Constraints.mustPayToTheScript (MyDatum i) lockedFunds
    void $ submitTxConstraints starterInstance tx

The second to last like creates the transaction that locks the value in "lockedFunds" into the script with parameter (MyDatum i). The last line simple submits the transaction for the contract to receive.

"redeem" Endpoint

The redeem endpoint just attempts to withdraw all funds from the script.

redeem :: AsContractError e => Contract Schema e ()
redeem = do
    myRedeemerValue <- endpoint @"redeem"
    unspentOutputs <- utxoAt contractAddress
    let redeemer = MyRedeemer myRedeemerValue
        tx       = collectFromScript unspentOutputs redeemer
    void $ submitTxConstraintsSpending starterInstance unspentOutputs tx

The second line of the function gets a list of the unspent transaction outputs from the contract address. The second line generates a transaction that attempts to collect coin from the script from the UTXO's in unspentOutputs and also passes the value passed with the endpoint for validation. The last line submits this transaction to attempt to unlock the funds - the funds will not be send unless the transaction passes the validation script we created earlier.

Playground Specifics

Take this section with a grain of salt because I am still uncertain of what is playground specific and what isn't.


The Plutus playground needs some code to work properly, below is everything that I believe is required for the playground to work properly.

endpoints :: AsContractError e => Contract Schema e ()
endpoints = contract

mkSchemaDefinitions ''Schema

$(mkKnownCurrencies [])

The first two lines define the function that the playground uses to interact with the endpoints. The mkSchemaDefinitions and mkKnownCurrencies exposes the schema definitions and currencies to the playground.

See Also