Skip to content

Commit

Permalink
Pinned aiken version and test to check plutus.json (#1777)
Browse files Browse the repository at this point in the history
I noticed the `aiken` version used to generate the `plutus.json` is
different than the one provided through our flake. Consequently, the
re-built scripts would result in different hashes.

This PR pins to a specific aiken version in the flake and also adds a
test that ensures the serialized scripts in `plutus.json` are
deterministically created using this version.

Following this, I realized that the validators in the `plutus.json` were
even the result of manually invoking `aiken blueprint apply` and
interactively filling the commit script hash parameter on the initial
validator.

Instead of this error prone manual step, the `Hydra.Plutus` module now
applies parameters when providing the `initialValidatorScript`.

---

<!-- Consider each and tick it off one way or the other -->
* [x] CHANGELOG update not needed
* [x] Documentation update not needed
* [x] Haddocks update not needed
* [x] No new TODOs introduced
  • Loading branch information
ch1bo authored Jan 8, 2025
2 parents 2039d7c + 14f92f4 commit dd7634b
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 68 deletions.
7 changes: 4 additions & 3 deletions flake.lock

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

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
url = "github:IntersectMBO/cardano-haskell-packages?ref=repo";
flake = false;
};
aiken.url = "github:aiken-lang/aiken";
aiken.url = "github:aiken-lang/aiken/v1.1.9";
hls = {
url = "github:haskell/haskell-language-server";
flake = false;
Expand Down
2 changes: 1 addition & 1 deletion hydra-plutus/hydra-plutus.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ test-suite tests
, lens
, lens-aeson
, plutus-ledger-api
, process
, QuickCheck
, typed-process

build-tool-depends: hspec-discover:hspec-discover

Expand Down
42 changes: 29 additions & 13 deletions hydra-plutus/plutus.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions hydra-plutus/scripts/mHead.plutus

Large diffs are not rendered by default.

5 changes: 0 additions & 5 deletions hydra-plutus/scripts/vCommit.plutus

This file was deleted.

5 changes: 0 additions & 5 deletions hydra-plutus/scripts/vDeposit.plutus

This file was deleted.

5 changes: 0 additions & 5 deletions hydra-plutus/scripts/vInitial.plutus

This file was deleted.

68 changes: 42 additions & 26 deletions hydra-plutus/src/Hydra/Plutus.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{-# LANGUAGE TemplateHaskell #-}

-- | Module to load and provide the Hydra scripts.
--
-- The plutus blueprint in 'plutus.json' is embedded in the binary and serves as
-- the ground truth for validator scripts and hashes.
--
-- XXX: We are using a hardcoded indices to access validators in plutus.json.
-- This is fragile and depends on the validator names not changing.
module Hydra.Plutus where

import Hydra.Prelude
Expand All @@ -10,44 +16,54 @@ import Data.Aeson qualified as Aeson
import Data.Aeson.Lens (key, nth, _String)
import Data.ByteString.Base16 qualified as Base16
import Data.FileEmbed (embedFile, makeRelativeToProject)
import PlutusLedgerApi.Common (SerialisedScript)
import PlutusCore.Core (plcVersion110)
import PlutusCore.MkPlc qualified as UPLC
import PlutusLedgerApi.Common (SerialisedScript, serialiseUPLC, toBuiltin, toData, uncheckedDeserialiseUPLC)
import PlutusLedgerApi.V3 (ScriptHash (..))
import UntypedPlutusCore qualified as UPLC

-- | Loads the "plutus.json" blueprint and provides the decoded JSON.
-- | Loads the embedded "plutus.json" blueprint and provides the decoded JSON.
blueprintJSON :: Aeson.Value
blueprintJSON =
case Aeson.decodeStrict $(makeRelativeToProject "./plutus.json" >>= embedFile) of
Nothing -> error "Invalid blueprint: plutus.json"
Just value -> value

-- | Access the commit validator script from the 'blueprintJSON'.
-- | Get the commit validator by decoding it from 'blueprintJSON'.
commitValidatorScript :: SerialisedScript
commitValidatorScript =
case Base16.decode commitBase16Bytes of
Left e -> error $ "Failed to decode commit validator: " <> show e
Right bytes -> toShort bytes
where
commitBase16Bytes = encodeUtf8 base16Text
-- NOTE: we are using a hardcoded index to access the commit validator.
-- This is fragile and will raise problems when we move another plutus validator
-- to Aiken.
-- Reference: https://github.com/cardano-foundation/CIPs/tree/master/CIP-0057
base16Text = blueprintJSON ^. key "validators" . nth 0 . key "compiledCode" . _String

-- | Access the initial validator script from the 'blueprintJSON'.
toShort . Base16.decodeLenient . encodeUtf8 $
blueprintJSON ^. key "validators" . nth 0 . key "compiledCode" . _String

-- | Get the commit validator hash from 'blueprintJSON'.
commitValidatorScriptHash :: ScriptHash
commitValidatorScriptHash =
ScriptHash . toBuiltin . Base16.decodeLenient . encodeUtf8 $
blueprintJSON ^. key "validators" . nth 0 . key "hash" . _String

-- | Get the initial validator by decoding the parameterized initial validator
-- from the 'blueprintJSON' and applying the 'commitValidatorScriptHash' to it.
initialValidatorScript :: SerialisedScript
initialValidatorScript =
case Base16.decode base16Bytes of
Left e -> error $ "Failed to decode initial validator: " <> show e
Right bytes -> toShort bytes
serialiseUPLC appliedProgram
where
base16Bytes = encodeUtf8 initialBase16Text
initialBase16Text = blueprintJSON ^. key "validators" . nth 4 . key "compiledCode" . _String
appliedProgram = case unappliedProgram `UPLC.applyProgram` argumentProgram of
Left e -> error $ "Failed to applyProgram: " <> show e
Right x -> x

unappliedProgram = uncheckedDeserialiseUPLC unappliedScript

argumentProgram =
UPLC.Program () plcVersion110 $
UPLC.mkConstant () $
toData commitValidatorScriptHash

unappliedScript =
toShort . Base16.decodeLenient . encodeUtf8 $
blueprintJSON ^. key "validators" . nth 4 . key "compiledCode" . _String

-- | Get the deposit validator by decoding it from 'blueprintJSON'.
depositValidatorScript :: SerialisedScript
depositValidatorScript =
case Base16.decode depositBase16Bytes of
Left e -> error $ "Failed to decode commit validator: " <> show e
Right bytes -> toShort bytes
where
depositBase16Bytes = encodeUtf8 depositBase16Text
depositBase16Text = blueprintJSON ^. key "validators" . nth 2 . key "compiledCode" . _String
toShort . Base16.decodeLenient . encodeUtf8 $
blueprintJSON ^. key "validators" . nth 2 . key "compiledCode" . _String
22 changes: 15 additions & 7 deletions hydra-plutus/test/Hydra/Plutus/GoldenSpec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,32 @@ import Hydra.Cardano.Api (
)
import Hydra.Contract.Head qualified as Head
import Hydra.Contract.HeadTokens qualified as HeadTokens
import Hydra.Plutus (commitValidatorScript, depositValidatorScript, initialValidatorScript)
import Hydra.Version (gitDescribe)
import PlutusLedgerApi.V3 (serialiseCompiledCode)
import PlutusLedgerApi.V3 qualified as Plutus
import System.Process.Typed (runProcess_, shell)
import Test.Hspec.Golden (Golden (..))

aikenBuildCommand :: String
aikenBuildCommand = "aiken build -t compact"

spec :: Spec
spec = do
it "Plutus blueprint is up-to-date" $ do
-- Running aiken -t compact should not change plutus.json
existing <- readFileBS "plutus.json"
runProcess_ $ shell aikenBuildCommand
actual <- readFileBS "plutus.json"
-- Undo any changes made by aiken
writeFileBS "plutus.json" existing
when (actual /= existing) $ do
putTextLn $ "Plutus blueprint in plutus.json is not up-to-date. Run " <> show aikenBuildCommand <> " to update it."
actual `shouldBe` existing

it "Head validator script" $
goldenScript "vHead" Head.validatorScript
it "Head minting policy script" $
goldenScript "mHead" (serialiseCompiledCode HeadTokens.unappliedMintingPolicy)
it "Deposit validator script" $
goldenScript "vDeposit" depositValidatorScript
it "Initial validator script" $
goldenScript "vInitial" initialValidatorScript
it "Commit validator script" $
goldenScript "vCommit" commitValidatorScript

-- | Write a golden script on first run and ensure it stays the same on
-- subsequent runs.
Expand Down

0 comments on commit dd7634b

Please sign in to comment.