Skip to content

Commit

Permalink
CIP-0057 Plutus Blueprints support: generate blueprint for a contract. (
Browse files Browse the repository at this point in the history
#5772)

* chore: refine imports, exports, formatting.

* refactor: makeIsDataIndexed

* Plutus Blueprint data model

* JSON Schema attributes

* Extra blueprint schema attributes

* Rework blueprint generation logic: use safe references

* chore: rename test fixture types, add a comment

* Fix GHC 8.10 build by adding UndecidableInstances

* fixup! Rework blueprint generation logic: use safe references

* better haddock comments

* fix: blueprint schema for constructors

* fix: blueprint schema encoding for #pair

* rework CIP-57 Blueprints to address some concerns

* allow aeson >= 2.2

* refactor blueprint schemas: use records with labels

* refactor: extract Data.Aeson.Extra (optionalField, requiredField)

* fix: compilation warnings with GHC 8.10

* Contract blueprint contains a set of validators

* refactor: improve wording, add comments

* Haddocks

* Parametherise Blueprint.Schema by the types that it can refer to.

* refactor: remove unnecessary ghc option pragmas

* serialise test script with a proper CBOR header

* Simplify compiled code hash calculation

* s/unique/uniqueItems/

* "fields" is a required field

* chore: user OverloadedStrings

* refactor: extract and use a helper function `oneOfASet`

* refactor: s/HasDataSchema/HasSchema/

* Improve TH that generates HasSchema instance.

* Derive JSON instance using `stripPrefix` field label modifier.

* Validate contract blueprint with CIP-57 meta schema using AJV

* add a comment explaining why NodeJS is gitignored.

* refactor: make JSON object construction cleaner.

* Detailed haddock for the AsDefinitionsEntries
  • Loading branch information
Unisay committed Mar 7, 2024
1 parent b4f045f commit 441b76d
Show file tree
Hide file tree
Showing 40 changed files with 2,234 additions and 69 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ stack.yaml.lock
# Python
__pycache__

# NodeJS (used by scripts/blueprints/check-json-schemas.js as it depends on the AJV nodejs lib)
node_modules

# Nix
result*
pkgs/.cabal
Expand Down Expand Up @@ -98,7 +101,9 @@ secrets/*/.gpg-id
ghcid.txt
plutus-pab/test-node/testnet/db
plutus-pab/test-node/alonzo-purple/db
*.actual.json
# profiling output files
*.timelog
*.stacks
.nvimrc

21 changes: 10 additions & 11 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,13 @@ source-repository-package
location: https://github.com/jaccokrijnen/plutus-cert
tag: e814b9171398cbdfecdc6823067156a7e9fc76a3

if impl(ghc >= 9.8)
allow-newer:
-- https://github.com/tweag/HaskellR/pull/420
, inline-r:singletons-th
, inline-r:aeson
, inline-r:text
, inline-r:template-haskell
, inline-r:deepseq
, inline-r:bytestring
, inline-r:containers
, inline-r:primitive
allow-newer:
-- https://github.com/tweag/HaskellR/pull/420
, inline-r:singletons-th
, inline-r:aeson
, inline-r:text
, inline-r:template-haskell
, inline-r:deepseq
, inline-r:bytestring
, inline-r:containers
, inline-r:primitive
3 changes: 3 additions & 0 deletions nix/shell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ in

# Needed for the cabal CLI to download under https
pkgs.curl

# Node JS
pkgs.nodejs_20
];

# Current HLS doesn't build on 9.8, see https://github.com/input-output-hk/iogx/issues/25
Expand Down
61 changes: 61 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"ajv": "^8.12.0"
}
}
2 changes: 1 addition & 1 deletion plutus-core/testlib/PlutusIR/Pass/Test.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module PlutusIR.Pass.Test where
import Control.Exception (throw)
import Control.Monad.Except
import Data.Bifunctor (first)
import Data.Functor
import Data.Functor (void)
import Data.Typeable
import PlutusCore qualified as PLC
import PlutusCore.Builtin
Expand Down
5 changes: 5 additions & 0 deletions plutus-tx-plugin/plutus-tx-plugin.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ test-suite plutus-tx-plugin-tests
other-modules:
AsData.Budget.Spec
AsData.Budget.Types
Blueprint.Tests
Blueprint.Tests.Lib
Budget.Spec
Budget.WithGHCOptimisations
Budget.WithoutGHCOptimisations
Expand Down Expand Up @@ -163,6 +165,7 @@ test-suite plutus-tx-plugin-tests

build-depends:
, base >=4.9 && <5
, bytestring
, containers
, deepseq
, filepath
Expand All @@ -173,7 +176,9 @@ test-suite plutus-tx-plugin-tests
, plutus-core:{plutus-core, plutus-core-testlib} ^>=1.22
, plutus-tx-plugin ^>=1.22
, plutus-tx:{plutus-tx, plutus-tx-testlib} ^>=1.22
, serialise
, tasty
, tasty-golden
, tasty-hedgehog
, tasty-hunit
, template-haskell
Expand Down
132 changes: 132 additions & 0 deletions plutus-tx-plugin/test/Blueprint/Acme.golden.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{
"$schema": "https://cips.cardano.org/cips/cip57/schemas/plutus-blueprint.json",
"$vocabulary": {
"https://cips.cardano.org/cips/cip57": true,
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://json-schema.org/draft/2020-12/vocab/validation": true
},
"preamble": {
"title": "Acme Contract",
"description": "A contract that does something awesome",
"version": "1.1.0",
"plutusVersion": "v3",
"license": "MIT"
},
"validators": [
{
"title": "Acme Validator",
"description": "A validator that does something awesome",
"redeemer": {
"title": "Acme Redeemer",
"description": "A redeemer that does something awesome",
"purpose": {
"oneOf": [
"spend",
"mint"
]
},
"schema": {
"$ref": "#/definitions/String"
}
},
"datum": {
"title": "Acme Datum",
"description": "A datum that contains something awesome",
"purpose": "spend",
"schema": {
"$ref": "#/definitions/Datum"
}
},
"parameters": [
{
"title": "Acme Parameter",
"description": "A parameter that does something awesome",
"purpose": "spend",
"schema": {
"$ref": "#/definitions/Params"
}
}
],
"compiledCode": "58ec01010032222323232300349103505435003232325333573466e1d200000218000a999ab9a3370e90010010c00cc8c8c94ccd5cd19b874800000860026eb4d5d0800cdd71aba13574400213008491035054310035573c0046aae74004dd51aba100109802a481035054310035573c0046aae74004dd50029919192999ab9a3370e90000010c0004c0112401035054310035573c0046aae74004dd5001119319ab9c001800199999a8911199a891199a89100111111400401600900380140044252005001001400084a400a0020038004008848a400e0050012410101010101010101000498101030048810001",
"hash": "21a5bbebc42a3d916719c975f622508a2c940ced5cd867cd3d87a019"
}
],
"definitions": {
"Bool": {
"dataType": "#boolean"
},
"ByteString": {
"dataType": "bytes"
},
"Bytes_Void": {
"title": "SchemaBytes",
"dataType": "bytes"
},
"Data": {},
"Datum": {
"oneOf": [
{
"$comment": "DatumLeft",
"dataType": "constructor",
"fields": [],
"index": 0
},
{
"$comment": "DatumRight",
"dataType": "constructor",
"fields": [
{
"$ref": "#/definitions/DatumPayload"
}
],
"index": 1
}
]
},
"DatumPayload": {
"$comment": "MkDatumPayload",
"dataType": "constructor",
"fields": [
{
"$ref": "#/definitions/Integer"
},
{
"$ref": "#/definitions/Bytes_Void"
}
],
"index": 0
},
"Integer": {
"dataType": "integer"
},
"Params": {
"$comment": "MkParams",
"dataType": "constructor",
"fields": [
{
"$ref": "#/definitions/Unit"
},
{
"$ref": "#/definitions/Bool"
},
{
"$ref": "#/definitions/Integer"
},
{
"$ref": "#/definitions/Data"
},
{
"$ref": "#/definitions/ByteString"
}
],
"index": 0
},
"String": {
"dataType": "#string"
},
"Unit": {
"dataType": "#unit"
}
}
}
101 changes: 101 additions & 0 deletions plutus-tx-plugin/test/Blueprint/Tests.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}

module Blueprint.Tests where

import PlutusTx.Blueprint
import Prelude

import Blueprint.Tests.Lib qualified as Fixture
import Control.Monad.Reader (asks)
import Data.Set qualified as Set
import Data.Void (Void)
import PlutusTx.Blueprint.Purpose qualified as Purpose
import PlutusTx.Builtins (BuiltinByteString, BuiltinData)
import System.FilePath ((</>))
import Test.Tasty (TestName)
import Test.Tasty.Extras (TestNested, testNested)
import Test.Tasty.Golden (goldenVsFile)

goldenTests :: TestNested
goldenTests = testNested "Blueprint" [goldenBlueprint "Acme" contractBlueprint]

goldenBlueprint :: TestName -> ContractBlueprint types -> TestNested
goldenBlueprint name blueprint = do
goldenPath <- asks $ foldr (</>) name
let actual = goldenPath ++ ".actual.json"
let golden = goldenPath ++ ".golden.json"
pure $ goldenVsFile name golden actual (writeBlueprint actual blueprint)

{- | All the data types exposed (directly or indirectly) by the type signature of the validator
This type level list is used to:
1. derive the schema definitions for the contract.
2. make "safe" references to the [derived] schema definitions.
-}
type ValidatorTypes =
[ Fixture.Datum
, Fixture.DatumPayload
, Fixture.Params
, Fixture.Redeemer
, Fixture.Bytes Void
, ()
, Bool
, Integer
, BuiltinData
, BuiltinByteString
]

contractBlueprint :: ContractBlueprint ValidatorTypes
contractBlueprint =
MkContractBlueprint
{ contractId = Nothing
, contractPreamble =
MkPreamble
{ preambleTitle = "Acme Contract"
, preambleDescription = Just "A contract that does something awesome"
, preambleVersion = "1.1.0"
, preamblePlutusVersion = PlutusV3
, preambleLicense = Just "MIT"
}
, contractValidators = Set.singleton validatorBlueprint
, contractDefinitions = deriveSchemaDefinitions
}

validatorBlueprint :: ValidatorBlueprint ValidatorTypes
validatorBlueprint =
MkValidatorBlueprint
{ validatorTitle = "Acme Validator"
, validatorDescription = Just "A validator that does something awesome"
, validatorParameters =
Just
$ pure
MkParameterBlueprint
{ parameterTitle = Just "Acme Parameter"
, parameterDescription = Just "A parameter that does something awesome"
, parameterPurpose = Set.singleton Purpose.Spend
, parameterSchema = definitionRef @Fixture.Params
}
, validatorRedeemer =
MkArgumentBlueprint
{ argumentTitle = Just "Acme Redeemer"
, argumentDescription = Just "A redeemer that does something awesome"
, argumentPurpose = Set.fromList [Purpose.Spend, Purpose.Mint]
, argumentSchema = definitionRef @Fixture.Redeemer
}
, validatorDatum =
Just
MkArgumentBlueprint
{ argumentTitle = Just "Acme Datum"
, argumentDescription = Just "A datum that contains something awesome"
, argumentPurpose = Set.singleton Purpose.Spend
, argumentSchema = definitionRef @Fixture.Datum
}
, validatorCompiledCode = Just Fixture.serialisedScript
}

0 comments on commit 441b76d

Please sign in to comment.