Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Direct CLI plutus testing #2900

Merged
merged 3 commits into from
Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ package cardano-node
package cardano-node-chairman
tests: True

package cardano-testnet
tests: True

-- The following is needed because Nix is doing something crazy.
package byron-spec-ledger
tests: False
Expand Down Expand Up @@ -107,8 +110,8 @@ package cardano-ledger-alonzo-test
source-repository-package
type: git
location: https://github.com/input-output-hk/hedgehog-extras
tag: 2f28e62f1508f07bb628963ee9bb23dc19ec0e03
--sha256: 0sjlk19v0dg63v8dawg844y3wzm1nmq0qxpvhip81x5yi091jjmm
tag: f7adf144bbd6da1aae9a668f40a1f8fdf69c9135
--sha256: 0f411zwlraqrp3f68vmif80kqdw18ddr0lqc9bfq83mv277gcb4k

source-repository-package
type: git
Expand Down
8 changes: 6 additions & 2 deletions cardano-testnet/cardano-testnet.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,19 @@ test-suite cardano-testnet-tests

type: exitcode-stdio-1.0

build-depends: cardano-testnet
build-depends: aeson
, cardano-testnet
, directory
, hedgehog
, hedgehog-extras
, filepath
, tasty
, tasty-hedgehog
, text
, unordered-containers

other-modules: Spec.Plutus
other-modules: Spec.Plutus.Direct.TxInLockingPlutus
Spec.Plutus.Script.TxInLockingPlutus

ghc-options: -threaded -rtsopts -with-rtsopts=-N -with-rtsopts=-T

Expand Down
10 changes: 10 additions & 0 deletions cardano-testnet/src/Test/Process.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Test.Process
( execCli
, execCli'
, procCli
, procNode
, procChairman
Expand All @@ -11,6 +12,7 @@ import Data.Function
import Data.String
import GHC.Stack (HasCallStack)
import Hedgehog (MonadTest)
import Hedgehog.Extras.Test.Process (ExecConfig)
import System.Process (CreateProcess)

import qualified GHC.Stack as GHC
Expand All @@ -23,6 +25,14 @@ execCli
-> m String
execCli = GHC.withFrozenCallStack $ H.execFlex "cardano-cli" "CARDANO_CLI"

-- | Run cardano-cli, returning the stdout
execCli'
:: (MonadTest m, MonadCatch m, MonadIO m, HasCallStack)
=> ExecConfig
-> [String]
-> m String
execCli' execConfig = GHC.withFrozenCallStack $ H.execFlex' execConfig "cardano-cli" "CARDANO_CLI"

-- | Create a 'CreateProcess' describing how to start the cardano-cli process
-- and an argument list.
procCli
Expand Down
2 changes: 1 addition & 1 deletion cardano-testnet/src/Testnet/Conf.hs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ mkConf tempAbsPath maybeMagic = do
tempRelPath <- H.noteShow $ FP.makeRelative tempBaseAbsPath tempAbsPath
base <- H.noteShowM H.getProjectBase
socketDir <- H.noteShow $ tempRelPath </> "socket"
logDir <- H.noteTempFile tempAbsPath "/logs"
logDir <- H.noteTempFile tempAbsPath "logs"

return $ Conf {..}
6 changes: 4 additions & 2 deletions cardano-testnet/test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import qualified Test.Tasty as T
import qualified Test.Tasty.Ingredients as T
import qualified Test.Tasty.Hedgehog as H

import qualified Spec.Plutus
import qualified Spec.Plutus.Direct.TxInLockingPlutus
import qualified Spec.Plutus.Script.TxInLockingPlutus

tests :: IO T.TestTree
tests = do
pure $ T.testGroup "test/Spec.hs"
[ T.testGroup "Spec"
[ H.testProperty "Plutus" Spec.Plutus.hprop_plutus
[ H.testProperty "Spec.Plutus.Direct.TxInLockingPlutus" Spec.Plutus.Direct.TxInLockingPlutus.hprop_plutus
, H.testProperty "Spec.Plutus.Script.TxInLockingPlutus" Spec.Plutus.Script.TxInLockingPlutus.hprop_plutus
]
]

Expand Down
234 changes: 234 additions & 0 deletions cardano-testnet/test/Spec/Plutus/Direct/TxInLockingPlutus.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Spec.Plutus.Direct.TxInLockingPlutus
( hprop_plutus
) where

import Control.Applicative
import Control.Monad
import Data.Aeson (FromJSON(..), Value, (.:))
import Data.Bool (not)
import Data.Eq
import Data.Function
import Data.Functor ((<&>))
import Data.HashMap.Lazy (HashMap)
import Data.Int
import Data.List ((!!))
import Data.Maybe
import Data.Monoid (Last(..), (<>))
import Data.Text (Text)
import GHC.Real
import GHC.Num
import Hedgehog (Property, (===))
import Prelude (head)
import System.FilePath ((</>))
import Text.Show (Show(..))

import qualified Data.Aeson as J
import qualified Data.List as L
import qualified Data.HashMap.Lazy as HM
import qualified Data.Text as T
import qualified Hedgehog.Extras.Stock.IO.Network.Sprocket as IO
import qualified Hedgehog.Extras.Test.Base as H
import qualified Hedgehog.Extras.Test.Concurrent as H
import qualified Hedgehog.Extras.Test.File as H
import qualified Hedgehog.Extras.Test.Process as H
import qualified System.Directory as IO
import qualified Test.Base as H
import qualified Test.Process as H
import qualified Testnet.Cardano as H
import qualified Testnet.Conf as H

{- HLINT ignore "Redundant <&>" -}
{- HLINT ignore "Redundant return" -}
{- HLINT ignore "Use let" -}

data Utxo = Utxo
{ address :: Text
, value :: HashMap Text Integer
} deriving (Eq, Show)

instance FromJSON Utxo where
parseJSON = J.withObject "Utxo" $ \v -> Utxo
<$> v .: "address"
<*> v .: "value"

hprop_plutus :: Property
hprop_plutus = H.integration . H.runFinallies . H.workspace "chairman" $ \tempAbsBasePath' -> do
projectBase <- H.note =<< H.noteIO . IO.canonicalizePath =<< H.getProjectBase
conf@H.Conf { H.tempBaseAbsPath, H.tempAbsPath } <- H.noteShowM $ H.mkConf tempAbsBasePath' Nothing

H.TestnetRuntime { H.bftSprockets, H.testnetMagic } <- H.testnet H.defaultTestnetOptions conf

execConfig <- H.noteShow H.ExecConfig
{ H.execConfigEnv = Last $ Just
[ ("CARDANO_NODE_SOCKET_PATH", IO.sprocketArgumentName (head bftSprockets))
]
, H.execConfigCwd = Last $ Just tempBaseAbsPath
}

base <- H.note projectBase
work <- H.note tempAbsPath
utxoVKeyFile <- H.note $ tempAbsPath </> "shelley/utxo-keys/utxo1.vkey"
utxoSKeyFile <- H.note $ tempAbsPath </> "shelley/utxo-keys/utxo1.skey"

plutusScriptFileInUse <- H.note $ base </> "scripts/plutus/scripts/always-succeeds-spending.plutus"

-- This datum hash is the hash of the untyped 42
let scriptDatumHash = "9e1199a988ba72ffd6e9c269cadb3b53b5f360ff99f112d9b2ee30c4d74ad88b"
let plutusRequiredSpace = id @Integer 70000000
let plutusRequiredTime = id @Integer 70000000
Comment on lines +83 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not (700000 :: Integer ) ?


datumFile <- H.note $ base </> "scripts/plutus/data/42.datum"
redeemerFile <- H.note $ base </> "scripts/plutus/data/42.redeemer"

-- Always succeeds Plutus script in use. Any datum and redeemer combination will succeed.
-- Script at: $plutusscriptinuse

-- Step 1: Create a tx ouput with a datum hash at the script address. In order for a tx ouput to be locked
-- by a plutus script, it must have a datahash. We also need collateral tx inputs so we split the utxo
-- in order to accomodate this.

plutusScriptAddr <- H.execCli
[ "address", "build"
, "--payment-script-file", plutusScriptFileInUse
, "--testnet-magic", show @Int testnetMagic
]

utxoAddr <- H.execCli
[ "address", "build"
, "--testnet-magic", show @Int testnetMagic
, "--payment-verification-key-file", utxoVKeyFile
]

void $ H.execCli' execConfig
[ "query", "utxo"
, "--address", utxoAddr
, "--cardano-mode"
, "--testnet-magic", show @Int testnetMagic
, "--out-file", work </> "utxo-1.json"
]

H.cat $ work </> "utxo-1.json"

utxo1Json <- H.leftFailM . H.readJsonFile $ work </> "utxo-1.json"
utxo1 <- H.noteShowM $ H.jsonErrorFail $ J.fromJSON @(HashMap Text Utxo) utxo1Json
txin <- H.noteShow $ head $ HM.keys utxo1
lovelaceAtTxin <- H.nothingFailM . H.noteShow $ utxo1 & HM.lookup txin <&> value >>= HM.lookup "lovelace"
lovelaceAtTxinDiv2 <- H.noteShow $ lovelaceAtTxin `div` 2

void $ H.execCli
[ "transaction", "build-raw"
, "--alonzo-era"
, "--fee", "0"
, "--tx-in", T.unpack txin
, "--tx-out", plutusScriptAddr <> "+" <> show @Integer lovelaceAtTxinDiv2
, "--tx-out-datum-hash", scriptDatumHash
, "--tx-out", utxoAddr <> "+" <> show @Integer lovelaceAtTxinDiv2
, "--out-file", work </> "create-datum-output.body"
]

void $ H.execCli
[ "transaction", "sign"
, "--tx-body-file", work </> "create-datum-output.body"
, "--testnet-magic", show @Int testnetMagic
, "--signing-key-file", utxoSKeyFile
, "--out-file", work </> "create-datum-output.tx"
]

void $ H.execCli' execConfig
[ "transaction", "submit"
, "--tx-file", work </> "create-datum-output.tx"
, "--testnet-magic", show @Int testnetMagic
]

H.threadDelay 5000000

-- With the tx ouput at the script address we can now attempt to spend it.

void $ H.execCli' execConfig
[ "query", "utxo"
, "--address", plutusScriptAddr
, "--testnet-magic", show @Int testnetMagic
, "--out-file", work </> "plutusutxo.json"
]

H.cat $ work </> "plutusutxo.json"

plutusUtxoJson <- H.leftFailM . H.readJsonFile $ work </> "plutusutxo.json"
plutusUtxo <- H.noteShowM $ H.jsonErrorFail $ J.fromJSON @(HashMap Text Utxo) plutusUtxoJson
plutusUtxoTxIn <- H.noteShow $ head $ HM.keys plutusUtxo

void $ H.execCli' execConfig
[ "query", "utxo"
, "--address", utxoAddr
, "--cardano-mode"
, "--testnet-magic", show @Int testnetMagic
, "--out-file", work </> "utxo-2.json"
]

H.cat $ work </> "utxo-2.json"

utxo2Json :: Value <- H.leftFailM $ H.readJsonFile $ work </> "utxo-2.json"
utxo2 <- H.noteShowM $ H.jsonErrorFail $ J.fromJSON @(HashMap Text Utxo) utxo2Json
txinCollateral <- H.noteShow $ head $ HM.keys utxo2

void $ H.execCli' execConfig
[ "query", "protocol-parameters"
, "--testnet-magic", show @Int testnetMagic
, "--out-file", work </> "pparams.json"
]

let dummyaddress = "addr_test1vpqgspvmh6m2m5pwangvdg499srfzre2dd96qq57nlnw6yctpasy4"

lovelaceAtplutusScriptAddr <- H.nothingFailM . H.noteShow $ plutusUtxo & HM.lookup plutusUtxoTxIn <&> value >>= HM.lookup "lovelace"

txFee <- H.noteShow $ plutusRequiredTime + plutusRequiredSpace
spendable <- H.noteShow $ lovelaceAtplutusScriptAddr - plutusRequiredTime - plutusRequiredSpace

void $ H.execCli
[ "transaction", "build-raw"
, "--alonzo-era"
, "--fee", show @Integer txFee
, "--tx-in", T.unpack plutusUtxoTxIn
, "--tx-in-collateral", T.unpack txinCollateral
, "--tx-out", dummyaddress <> "+" <> show @Integer spendable
, "--tx-in-script-file", plutusScriptFileInUse
, "--tx-in-datum-file", datumFile
, "--protocol-params-file", work </> "pparams.json"
, "--tx-in-redeemer-file", redeemerFile
, "--tx-in-execution-units", show @(Integer, Integer) (plutusRequiredTime, plutusRequiredSpace)
, "--out-file", work </> "test-alonzo.body"
]

void $ H.execCli
[ "transaction", "sign"
, "--tx-body-file", work </> "test-alonzo.body"
, "--testnet-magic", show @Int testnetMagic
, "--signing-key-file", utxoSKeyFile
, "--out-file", work </> "alonzo.tx"
]

void $ H.execCli' execConfig
[ "transaction", "submit"
, "--tx-file", work </> "alonzo.tx"
, "--testnet-magic", show @Int testnetMagic
]

H.threadDelay 5000000

-- Querying UTxO at $dummyaddress. If there is ADA at the address the Plutus script successfully executed!

result <- T.pack <$> H.execCli' execConfig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

[ "query", "utxo"
, "--address", dummyaddress
, "--testnet-magic", show @Int testnetMagic
]

H.noteShow_ result

L.filter (not . T.null) (T.splitOn " " (T.lines result !! 2)) !! 2 === "360000000"
Loading