-
Notifications
You must be signed in to change notification settings - Fork 481
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Howto: exporting a Plutus contract blueprint
- Loading branch information
Showing
2 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
243 changes: 243 additions & 0 deletions
243
doc/read-the-docs-site/howtos/exporting-a-blueprint.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
.. highlight:: haskell | ||
.. _exporting_a_blueprint: | ||
|
||
How to produce a Plutus Contract Blueprint (CIP-57) | ||
=================================================== | ||
|
||
`CIP-0057`_ Plutus Contract Blueprints are used to document the binary interface of a | ||
Plutus contract in a machine-readable format (JSON schema). | ||
|
||
A contract Blueprint can be produced by using the | ||
`writeBlueprint` function exported by the `PlutusTx.Blueprint` module:: | ||
|
||
writeBlueprint | ||
:: FilePath | ||
-- ^ The file path where the blueprint will be written to, | ||
-- e.g. '/tmp/plutus.json' | ||
-> Blueprint '[MyParams, MyRedeemer, MyDatum] | ||
-- ^ Contains all the necessary information to generate | ||
-- a blueprint for a Plutus contract. | ||
-> IO () | ||
|
||
In order to demonstrate the usage of the `writeBlueprint` function, | ||
Let's consider the following example validator function and its interface:: | ||
|
||
data MyParams = MyParams | ||
{ myBool :: Bool | ||
, myInteger :: Integer | ||
} | ||
deriving anyclass (AsDefinitionId) | ||
|
||
$(PlutusTx.makeLift ''Params) | ||
$(makeIsDataSchemaIndexed ''Params [('MkParams, 0)]) | ||
type MyDatum = Integer | ||
|
||
type MyRedeemer = () | ||
|
||
typedValidator :: MyParams -> MyDatum -> MyRedeemer -> ScriptContext -> Bool | ||
typedValidator datum redeemer scriptContext = ... | ||
|
||
untypedValidator :: MyParams -> BuiltinData -> BuiltinData -> BuiltinData -> () | ||
untypedValidator params datum redeemer _scriptContext = | ||
check $ typedValidator | ||
params | ||
(unsafeFromBuiltinData datum) | ||
(unsafeFromBuiltinData redeemer) | ||
|
||
First of all we need to import required functionality:: | ||
|
||
import PlutusTx.Blueprint | ||
( ContractBlueprint (..) | ||
, Preamble (..) | ||
, Purpose (..) | ||
, ValidatorBlueprint (..) | ||
, ArgumentBlueprint (..) | ||
, ParameterBlueprint (..) | ||
, AsDefinitionId (..) | ||
, definitionRef | ||
, deriveSchemaDefinitions | ||
) | ||
import PlutusTx.Blueprint.TH (makeIsDataSchemaIndexed) | ||
|
||
Then we define a list of data types used at the top level of the typed interface | ||
(excluding `ScriptContext`):: | ||
|
||
type ContractTypes = '[MyParams, MyDatum, MyRedeemer] | ||
|
||
Next we define a contract blueprint value of the following type:: | ||
|
||
data ContractBlueprint (referencedTypes :: [Type]) = MkContractBlueprint | ||
{ contractId :: Maybe Text | ||
-- ^ An optional identifier for the contract. | ||
, contractPreamble :: Preamble | ||
-- ^ An object with meta-information about the contract. | ||
, contractValidators :: Set (ValidatorBlueprint referencedTypes) | ||
-- ^ A set of validator blueprints that are part of the contract. | ||
, contractDefinitions :: Map DefinitionId (Schema referencedTypes) | ||
-- ^ A registry of schema definitions used across the blueprint. | ||
} | ||
|
||
.. note:: | ||
The 'referencedTypes' phantom type parameter is used to track the types used in the contract | ||
making sure their schemas are included in the blueprint and that they are referenced | ||
in a type-safe way. | ||
|
||
The blueprint will contain JSON schema definitions for all the types used in the contract, | ||
including the types **nested** within the top-level types (`MyParams`, `MyDatum`, `MyRedeemer`): | ||
* `Integer` - nested within `MyDatum` and `MyParams`. | ||
* `Bool` - nested within `MyParams`. | ||
* `()` - nested within `MyRedeemer`. | ||
|
||
This way, the `referencedTypes` type variable should be specialized to:: | ||
|
||
'[ MyParams -- top-level type | ||
, MyDatum -- top-level type | ||
, MyRedeemer -- top-level type | ||
, Integer -- nested type | ||
, Bool -- nested type | ||
, () -- nested type | ||
] | ||
|
||
A blueprint author can always write a full list of types manually but it is tedious and error-prone. | ||
Instead, it is recommended to use the ``Blueprint topLevelTypes`` type alias instead, which when given | ||
a list of top-level types, automatically extends this list to also include all the nested types:: | ||
|
||
Blueprint '[MyParams, MyDatum, MyRedeemer] | ||
|
||
is equivalent to:: | ||
|
||
ContractBlueprint '[MyParams, MyDatum, MyRedeemer, Integer, Bool, ()]' | ||
|
||
|
||
We can construct a value of this type like in this:: | ||
myContractBlueprint :: Blueprint ContractTypes | ||
myContractBlueprint = MkContractBlueprint | ||
{ contractId = Just "my-contract" | ||
, contractPreamble = myPreamble -- defined below | ||
, contractValidators = Set.singleton myValidator -- defined below | ||
, contractDefinitions = deriveSchemaDefinitions | ||
} | ||
|
||
The `contractId` field is optional and can be used to give a unique identifier to the contract. | ||
|
||
The `contractPreamble` field is a value of type `PlutusTx.Blueprint.Preamble` | ||
contains a meta-information about the contract:: | ||
|
||
data Preamble = MkPreamble | ||
{ preambleTitle :: Text | ||
-- ^ A short and descriptive title of the contract application | ||
, preambleDescription :: Maybe Text | ||
-- ^ A more elaborate description | ||
, preambleVersion :: Text | ||
-- ^ A version number for the project. | ||
, preamblePlutusVersion :: PlutusVersion | ||
-- ^ The Plutus version assumed for all validators | ||
, preambleLicense :: Maybe Text | ||
-- ^ A license under which the specification | ||
-- and contract code is distributed | ||
} | ||
|
||
Here is an example construction:: | ||
|
||
myPreamble :: Preamble | ||
myPreamble = MkPreamble | ||
{ preambleTitle = "My Contract" | ||
, preambleDescription = Just "A simple contract" | ||
, preambleVersion = "1.0.0" | ||
, preamblePlutusVersion = PlutusV2 | ||
, preambleLicense = Just "MIT" | ||
} | ||
|
||
The `contractDefinitions` field is a map of schema definitions used across the blueprint. | ||
It can be constructed using the `deriveSchemaDefinitions` function which automatically | ||
constructs all the schema definitions used in the contract (it does so by using the `ContractTypes` | ||
list we defined earlier, discovering it via the type-inference mechanism). | ||
|
||
Since every type in the `ContractTypes` list is going to have its derived JSON-schema in the | ||
`contractDefinitions` map under a certain unique `DefinitionId` key, we need to make sure that it | ||
has: | ||
* an instance of the `AsDefinitionId` type class. Most of the times it could be derived using | ||
the the `anyclass` strategy, for example:: | ||
{-# LANGUAGE DerivingStrategies, StandaloneDeriving #-} | ||
deriving instance anyclass AsDefinitionId MyParams | ||
* an instance of the `HasSchema` type class. If your validator exposes standard supported types | ||
like `Integer` or `Bool` you don't need to define this instance. If your validator uses custom types | ||
then you should be deriving it using the `makeIsDataSchemaIndexed` Template Haskell function, | ||
which derives it alongside with the corresponding `ToBuiltinData`/`FromBuiltinData` instances, | ||
for example:: | ||
{-# LANGUAGE TemplateHaskell #-} | ||
$(makeIsDataSchemaIndexed ''MyDatum [('MkMyDatum, 0)]) | ||
|
||
.. note:: | ||
|
||
If we forget to include a type in the `ContractTypes` list, the `deriveSchemaDefinitions` function | ||
will fail to compile with a meaningful message, thus ensuring that JSON schema definitions of | ||
all the types used in the contract are included in the blueprint. | ||
|
||
Finally, we need to define a validator blueprint for each validator used in the contract. | ||
|
||
Our contract can contain one or more validators and for each one we need to provide | ||
a description as a value of the following type:: | ||
|
||
data ValidatorBlueprint (referencedTypes :: [Type]) = MkValidatorBlueprint | ||
{ validatorTitle :: Text | ||
-- ^ A short and descriptive name for the validator. | ||
, validatorDescription :: Maybe Text | ||
-- ^ An informative description of the validator. | ||
, validatorRedeemer :: ArgumentBlueprint referencedTypes | ||
-- ^ A description of the redeemer format expected by this validator. | ||
, validatorDatum :: Maybe (ArgumentBlueprint referencedTypes) | ||
-- ^ A description of the datum format expected by this validator. | ||
, validatorParameters :: Maybe (NonEmpty (ParameterBlueprint referencedTypes)) | ||
-- ^ A list of parameters required by the script. | ||
, validatorCompiledCode :: Maybe ByteString | ||
-- ^ A full compiled and CBOR-encoded serialized flat script. | ||
} | ||
|
||
In our example this would be:: | ||
|
||
myValidator :: ValidatorBlueprint ContractTypes | ||
myValidator = MkValidatorBlueprint | ||
{ validatorTitle = "My Validator" | ||
, validatorDescription = Just "An example validator" | ||
, validatorRedeemer = definitionRef @MyRedeemer | ||
, validatorDatum = Just (definitionRef @MyDatum) | ||
, validatorParameters = Just $ pure $ | ||
MkParameterBlueprint | ||
{ parameterTitle = Just "My Validator Parameters" | ||
, parameterDescription = Just "Parameters configure the validator in compile-time" | ||
, parameterPurpose = Set.singleton Spend | ||
, parameterSchema = definitionRef @MyParams | ||
} | ||
, validatorRedeemer = | ||
MkArgumentBlueprint | ||
{ argumentTitle = Just "My Redeemer" | ||
, argumentDescription = Just "A redeemer that does something awesome" | ||
, argumentPurpose = Set.fromList [Spend, Mint] | ||
, argumentSchema = definitionRef @MyRedeemer | ||
} | ||
, validatorDatum = Just | ||
MkArgumentBlueprint | ||
{ argumentTitle = Just "My Datum" | ||
, argumentDescription = Just "A datum that contains something awesome" | ||
, argumentPurpose = Set.singleton Spend | ||
, argumentSchema = definitionRef @MyDatum | ||
} | ||
, validatorCompiledCode = Nothing -- you can optionally provide the compiled code here | ||
} | ||
|
||
The `definitionRef` function is used to reference a schema definition of a given type. It is | ||
smart enough to discover the schema definition of the given type from the `ContractTypes` list and | ||
fails to compile if the referenced type is not included in the list. | ||
|
||
With all the pieces in place, we can now write the blueprint to a file:: | ||
|
||
writeBlueprint "/tmp/plutus.json" myContractBlueprint | ||
|
||
.. note:: | ||
You can find a more elaborate example of a contract blueprint in the `Blueprint.Tests` | ||
module of the plutus repository. | ||
|
||
.. _CIP-0057: https://cips.cardano.org/cip/CIP-0057 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,4 +8,5 @@ How-to guides | |
:titlesonly: | ||
|
||
exporting-a-script | ||
exporting-a-blueprint | ||
profiling-scripts |