Merge pull request #94 from Liqwid-Labs/seungheonoh/genericSpecInterface

Specs for benchmarking
This commit is contained in:
SeungheonOh 2022-05-26 10:25:02 -05:00 committed by GitHub
commit 4a3882d90f
17 changed files with 505 additions and 330 deletions

View file

@ -1,12 +1,29 @@
module Bench (Benchmark (..), benchmarkSize) where {-# LANGUAGE RecordWildCards #-}
module Bench (Benchmark (..), benchmarkScript, specificationTreeToBenchmarks) where
import Codec.Serialise (serialise) import Codec.Serialise (serialise)
import Data.ByteString.Lazy qualified as LBS import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Short qualified as SBS import Data.ByteString.Short qualified as SBS
import Data.Set (Set) import Data.Csv (DefaultOrdered, ToNamedRecord, header, headerOrder, namedRecord, toNamedRecord, (.=))
import Data.Set qualified as Set import Data.List (intercalate)
import Data.Text (Text) import Data.Maybe (fromJust)
import Plutus.V1.Ledger.Scripts qualified as Plutus import Data.Text (Text, pack)
import GHC.Generics (Generic)
import Plutus.V1.Ledger.Api (
ExBudget (ExBudget),
ExCPU (..),
ExMemory (..),
Script,
)
import Plutus.V1.Ledger.Api qualified as Plutus
import Prettyprinter (Pretty (pretty), indent, vsep)
import Spec.Specification (
Specification (Specification),
SpecificationExpectation (Success),
SpecificationTree (..),
)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -14,20 +31,59 @@ import Plutus.V1.Ledger.Scripts qualified as Plutus
data Benchmark = Benchmark data Benchmark = Benchmark
{ name :: Text { name :: Text
-- ^ Human readable name describing script. -- ^ Human readable name describing script.
, size :: Int , cpuBudget :: ExCPU
-- ^ The on-chain execution cost of a script.
, memoryBudget :: ExMemory
-- ^ The on-chain memory budget of a script.
, scriptSize :: Int
-- ^ The on-chain size of a script. -- ^ The on-chain size of a script.
} }
deriving stock (Show, Eq, Ord) deriving stock (Show, Eq, Ord, Generic)
-- | Create a benchmark containing only the size of the script. instance Pretty Benchmark where
benchmarkSize :: Text -> Plutus.Script -> Set Benchmark pretty (Benchmark name (ExCPU (toInteger -> cpu)) (ExMemory (toInteger -> mem)) size) =
benchmarkSize name script = vsep
Set.singleton $ [ pretty name
Benchmark , indent 4 $
{ name = name vsep
, size = scriptSize script [ "CPU: " <> pretty cpu
} , "MEM: " <> pretty mem
, "SIZE: " <> pretty size
]
]
-- | Compute the size of a script on-chain. instance ToNamedRecord Benchmark where
scriptSize :: Plutus.Script -> Int toNamedRecord (Benchmark {..}) =
scriptSize = SBS.length . SBS.toShort . LBS.toStrict . serialise namedRecord
[ "name" .= name
, "cpu" .= cpuBudget
, "mem" .= memoryBudget
, "size" .= scriptSize
]
instance DefaultOrdered Benchmark where
headerOrder _ = header ["name", "cpu", "mem", "size"]
benchmarkScript :: String -> Script -> Benchmark
benchmarkScript name script = Benchmark (pack name) cpu mem size
where
(ExBudget cpu mem) = evalScriptCounting . serialiseScriptShort $ script
size = SBS.length . SBS.toShort . LBS.toStrict . serialise $ script
serialiseScriptShort :: Script -> SBS.ShortByteString
serialiseScriptShort = SBS.toShort . LBS.toStrict . serialise -- Using `flat` here breaks `evalScriptCounting`
evalScriptCounting :: Plutus.SerializedScript -> Plutus.ExBudget
evalScriptCounting script =
let costModel = fromJust Plutus.defaultCostModelParams
(_logout, e) = Plutus.evaluateScriptCounting Plutus.Verbose costModel script []
in case e of
Left evalError -> error ("Eval Error: " <> show evalError)
Right exbudget -> exbudget
specificationTreeToBenchmarks :: SpecificationTree -> [Benchmark]
specificationTreeToBenchmarks = go []
where
go names (Terminal ((Specification n ex s))) = case ex of
Success -> [benchmarkScript (intercalate "/" (names <> [n])) s]
_ -> []
go names (Group gn tree) = mconcat $ go (names <> [gn]) <$> tree

View file

@ -1,42 +1,43 @@
module Main (main) where module Main (main) where
import Agora.AuthorityToken (authorityTokenPolicy) import Bench (specificationTreeToBenchmarks)
import Agora.Effect.TreasuryWithdrawal (treasuryWithdrawalValidator) import Data.Csv (encodeDefaultOrderedByName)
import Agora.Governor (Governor (..)) import Data.Text.Lazy.Encoding (decodeUtf8)
import Agora.Governor.Scripts (governorPolicy, governorValidator) import Data.Text.Lazy.IO as I (writeFile)
import Agora.Proposal.Scripts (proposalPolicy, proposalValidator) import Prettyprinter (defaultLayoutOptions, layoutPretty, pretty)
import Agora.Stake.Scripts (stakePolicy, stakeValidator) import Prettyprinter.Render.String (renderString)
import Agora.Treasury (treasuryValidator) import Spec.AuthorityToken qualified as AuthorityToken
import Bench import Spec.Effect.GovernorMutation qualified as GovernorMutation
import Data.Foldable (for_) import Spec.Effect.TreasuryWithdrawal qualified as TreasuryWithdrawal
import Plutus.V1.Ledger.Api (CurrencySymbol) import Spec.Governor qualified as Governor
import Sample.Shared import Spec.Proposal qualified as Proposal
import Spec.Specification (group)
import Spec.Stake qualified as Stake
import Spec.Treasury qualified as Treasury
import Prelude import Prelude
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
main :: IO () main :: IO ()
main = do main = do
let benchmarks = I.writeFile "bench.csv" $
mconcat (decodeUtf8 . encodeDefaultOrderedByName) res
[ -- GATs
benchmarkSize "authorityTokenPolicy" $ compile $ authorityTokenPolicy authorityToken mapM_ (putStrLn . renderString . layoutPretty defaultLayoutOptions . pretty) res
, -- Governor where
benchmarkSize "governorValidator" $ compile $ governorValidator governor res =
, benchmarkSize "governorPolicy" $ compile $ governorPolicy governor specificationTreeToBenchmarks $
, -- Stake group
benchmarkSize "stakeValidator" $ compile $ stakeValidator stake "Agora"
, benchmarkSize "stakePolicy" $ compile $ stakePolicy governor.gtClassRef [ group
, -- Proposal "Effects"
benchmarkSize "proposalValidator" $ compile $ proposalValidator proposal [ group "Treasury Withdrawal Effect" TreasuryWithdrawal.specs
, benchmarkSize "proposalPolicy" $ compile $ proposalPolicy govAssetClass , group "Governor Mutation Effect" GovernorMutation.specs
, -- Treasury ]
benchmarkSize "treasuryValidator" $ compile $ treasuryValidator gatCS , group "Stake" Stake.specs
, -- Effect validators , group "Proposal" Proposal.specs
benchmarkSize "treasuryWithdrawalValidator" $ compile $ treasuryWithdrawalValidator gatCS , group "AuthorityToken" AuthorityToken.specs
, group "Treasury" Treasury.specs
, group "AuthorityToken" AuthorityToken.specs
, group "Governor" Governor.specs
] ]
for_ benchmarks print
gatCS :: CurrencySymbol
gatCS = "73475cb40a568e8da8a045ced110137e159f890ac4da883b6b17dc651b3a8049" -- arbitrary CS

View file

@ -7,13 +7,12 @@ Description: Tests for Authority token functions
Tests for Authority token functions Tests for Authority token functions
-} -}
module Spec.AuthorityToken (tests) where module Spec.AuthorityToken (specs) where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Agora.AuthorityToken (singleAuthorityTokenBurned) import Agora.AuthorityToken (singleAuthorityTokenBurned)
import Plutarch import Plutarch
import Test.Tasty (TestTree, testGroup)
import Prelude import Prelude
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -33,7 +32,12 @@ import Plutus.V1.Ledger.Api (
import Plutus.V1.Ledger.Interval qualified as Interval import Plutus.V1.Ledger.Interval qualified as Interval
import Plutus.V1.Ledger.Value qualified as Value import Plutus.V1.Ledger.Value qualified as Value
import PlutusTx.AssocMap qualified as AssocMap import PlutusTx.AssocMap qualified as AssocMap
import Test.Util (scriptFails, scriptSucceeds) import Spec.Specification (
SpecificationTree,
group,
scriptFails,
scriptSucceeds,
)
currencySymbol :: CurrencySymbol currencySymbol :: CurrencySymbol
currencySymbol = "deadbeef" currencySymbol = "deadbeef"
@ -65,10 +69,10 @@ singleAuthorityTokenBurnedTest mint outs =
perror perror
in compile s in compile s
tests :: [TestTree] specs :: [SpecificationTree]
tests = specs =
[ -- This is better suited for plutarch-test [ -- This is better suited for plutarch-test
testGroup group
"singleAuthorityTokenBurned" "singleAuthorityTokenBurned"
[ scriptSucceeds [ scriptSucceeds
"Correct simple" "Correct simple"

View file

@ -1,4 +1,4 @@
module Spec.Effect.GovernorMutation (tests) where module Spec.Effect.GovernorMutation (specs) where
import Agora.Effect.GovernorMutation (mutateGovernorValidator) import Agora.Effect.GovernorMutation (mutateGovernorValidator)
import Agora.Governor (GovernorDatum (..), GovernorRedeemer (MutateGovernor)) import Agora.Governor (GovernorDatum (..), GovernorRedeemer (MutateGovernor))
@ -15,14 +15,20 @@ import Sample.Effect.GovernorMutation (
validNewGovernorDatum, validNewGovernorDatum,
) )
import Sample.Shared qualified as Shared import Sample.Shared qualified as Shared
import Test.Tasty (TestTree, testGroup) import Spec.Specification (
import Test.Util (effectFailsWith, effectSucceedsWith, validatorFailsWith, validatorSucceedsWith) SpecificationTree,
effectFailsWith,
effectSucceedsWith,
group,
validatorFailsWith,
validatorSucceedsWith,
)
tests :: [TestTree] specs :: [SpecificationTree]
tests = specs =
[ testGroup [ group
"validator" "validator"
[ testGroup [ group
"valid new governor datum" "valid new governor datum"
[ validatorSucceedsWith [ validatorSucceedsWith
"governor validator should pass" "governor validator should pass"
@ -44,7 +50,7 @@ tests =
(mkEffectDatum validNewGovernorDatum) (mkEffectDatum validNewGovernorDatum)
(ScriptContext (mkEffectTxInfo validNewGovernorDatum) (Spending effectRef)) (ScriptContext (mkEffectTxInfo validNewGovernorDatum) (Spending effectRef))
] ]
, testGroup , group
"invalid new governor datum" "invalid new governor datum"
[ validatorFailsWith [ validatorFailsWith
"governor validator should fail" "governor validator should fail"

View file

@ -3,9 +3,9 @@ Module : Spec.Effect.TreasuryWithdrawalEffect
Maintainer : seungheon.ooh@gmail.com Maintainer : seungheon.ooh@gmail.com
Description: Sample based testing for Treasury Withdrawal Effect Description: Sample based testing for Treasury Withdrawal Effect
This module tests the Treasury Withdrawal Effect. This module specs the Treasury Withdrawal Effect.
-} -}
module Spec.Effect.TreasuryWithdrawal (tests) where module Spec.Effect.TreasuryWithdrawal (specs) where
import Agora.Effect.TreasuryWithdrawal ( import Agora.Effect.TreasuryWithdrawal (
TreasuryWithdrawalDatum (TreasuryWithdrawalDatum), TreasuryWithdrawalDatum (TreasuryWithdrawalDatum),
@ -25,12 +25,16 @@ import Sample.Effect.TreasuryWithdrawal (
treasuries, treasuries,
users, users,
) )
import Test.Tasty (TestTree, testGroup) import Spec.Specification (
import Test.Util (effectFailsWith, effectSucceedsWith) SpecificationTree,
effectFailsWith,
effectSucceedsWith,
group,
)
tests :: [TestTree] specs :: [SpecificationTree]
tests = specs =
[ testGroup [ group
"effect" "effect"
[ effectSucceedsWith [ effectSucceedsWith
"Simple" "Simple"

View file

@ -3,7 +3,7 @@ Module : Spec.Governor
Maintainer : connor@mlabs.city Maintainer : connor@mlabs.city
Description: Tests for Agora governor. Description: Tests for Agora governor.
Thie module exports `tests`, a list of `TestTree`s, which ensure Thie module exports `specs`, a list of `TestTree`s, which ensure
that Agora's governor component workds as intended. that Agora's governor component workds as intended.
Tests should pass when the validator or policy is given one of the Tests should pass when the validator or policy is given one of the
@ -11,7 +11,7 @@ valid script contexts, which are defined in 'Agora.Sample.Governor'.
TODO: Add negative test cases, see [#76](https://github.com/Liqwid-Labs/agora/issues/76). TODO: Add negative test cases, see [#76](https://github.com/Liqwid-Labs/agora/issues/76).
-} -}
module Spec.Governor (tests) where module Spec.Governor (specs) where
import Agora.Governor (GovernorDatum (..), GovernorRedeemer (..)) import Agora.Governor (GovernorDatum (..), GovernorRedeemer (..))
import Agora.Governor.Scripts (governorPolicy, governorValidator) import Agora.Governor.Scripts (governorPolicy, governorValidator)
@ -19,14 +19,18 @@ import Agora.Proposal (ProposalId (..))
import Data.Default.Class (Default (def)) import Data.Default.Class (Default (def))
import Sample.Governor (createProposal, mintGATs, mintGST, mutateState) import Sample.Governor (createProposal, mintGATs, mintGST, mutateState)
import Sample.Shared qualified as Shared import Sample.Shared qualified as Shared
import Test.Tasty (TestTree, testGroup) import Spec.Specification (
import Test.Util (policySucceedsWith, validatorSucceedsWith) SpecificationTree,
group,
policySucceedsWith,
validatorSucceedsWith,
)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
tests :: [TestTree] specs :: [SpecificationTree]
tests = specs =
[ testGroup [ group
"policy" "policy"
[ policySucceedsWith [ policySucceedsWith
"GST minting" "GST minting"
@ -34,7 +38,7 @@ tests =
() ()
mintGST mintGST
] ]
, testGroup , group
"validator" "validator"
[ validatorSucceedsWith [ validatorSucceedsWith
"proposal creation" "proposal creation"

View file

@ -7,7 +7,7 @@ Description: Tests for Proposal policy and validator
Tests for Proposal policy and validator Tests for Proposal policy and validator
-} -}
module Spec.Proposal (tests) where module Spec.Proposal (specs) where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -45,15 +45,19 @@ import PlutusTx.AssocMap qualified as AssocMap
import Sample.Proposal qualified as Proposal import Sample.Proposal qualified as Proposal
import Sample.Shared (signer, signer2) import Sample.Shared (signer, signer2)
import Sample.Shared qualified as Shared import Sample.Shared qualified as Shared
import Test.Tasty (TestTree, testGroup) import Spec.Specification (
import Test.Util (policySucceedsWith, validatorSucceedsWith) SpecificationTree,
group,
policySucceedsWith,
validatorSucceedsWith,
)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Stake tests. -- | Stake specs.
tests :: [TestTree] specs :: [SpecificationTree]
tests = specs =
[ testGroup [ group
"policy" "policy"
[ policySucceedsWith [ policySucceedsWith
"proposalCreation" "proposalCreation"
@ -61,9 +65,9 @@ tests =
() ()
Proposal.proposalCreation Proposal.proposalCreation
] ]
, testGroup , group
"validator" "validator"
[ testGroup [ group
"cosignature" "cosignature"
[ validatorSucceedsWith [ validatorSucceedsWith
"proposal" "proposal"
@ -97,7 +101,7 @@ tests =
WitnessStake WitnessStake
(ScriptContext (Proposal.cosignProposal [signer2]) (Spending Proposal.stakeRef)) (ScriptContext (Proposal.cosignProposal [signer2]) (Spending Proposal.stakeRef))
] ]
, testGroup , group
"voting" "voting"
[ validatorSucceedsWith [ validatorSucceedsWith
"proposal" "proposal"

View file

@ -0,0 +1,255 @@
{- |
Module : Spec.Specification
Maintainer : seungheon.ooh@gmail.com
Description: Helpers to build Specification for testing and bench-marking
Constructors for building a specification for Plutarch scripts:
- 'policySucceedsWith': checks that a minting policy succeeds.
- 'policyFailsWith': checks that a minting policy fails.
- 'validatorSucceedsWith': checks that validator succeeds.
- 'validatorFailsWith': checks that validator fails.
- 'effectSucceedsWith': checks that effect succeeds.
- 'effectFailsWith': checks that effect fails.
- 'scriptSucceeds': checks that an arbitrary script does not
`perror`.
- 'scriptFails': checks that an arbitrary script `perror`s out.
-}
module Spec.Specification (
-- * Structures
Specification (..),
SpecificationExpectation (..),
SpecificationTree (..),
-- * Spec helpers
group,
getSpecification,
getSpecificationTree,
-- * Spec builders
scriptSucceeds,
scriptFails,
policySucceedsWith,
policyFailsWith,
validatorSucceedsWith,
validatorFailsWith,
effectSucceedsWith,
effectFailsWith,
-- * Converters
toTestTree,
) where
import Plutarch.Api.V1 (PMintingPolicy, PValidator)
import Plutarch.Builtin (pforgetData)
import Plutarch.Evaluate (evalScript)
import Plutarch.Lift (PUnsafeLiftDecl (PLifted))
import Plutus.V1.Ledger.Api (Script, ScriptContext)
import PlutusTx.IsData qualified as PlutusTx (ToData)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (assertFailure, testCase)
{- | Expectations upon execution of script
@Success@ indicates a successful execution.
@Failure@ inidcates a faulty execution.
@FailureWith@ indicates a faulty execution but with expected reason for failure.
-}
data SpecificationExpectation
= Success
| Failure
| FailureWith String
deriving stock (Show)
{- | Unit of specification. @Specification@ holds name, expectation, and
script to be tested or executed later on.
-}
data Specification = Specification
{ sName :: String
, sExpectation :: SpecificationExpectation
, sScript :: Script
}
deriving stock (Show)
-- | Tree-structure to group alike specifications--modeled after @TestTree@ from tasty.
data SpecificationTree
= Terminal Specification
| Group String [SpecificationTree]
deriving stock (Show)
{- | Checks if given name exists in @SpecificationTree@ as either
group name or specification name.
-}
exists :: String -> SpecificationTree -> Bool
exists s (Terminal (Specification name _ _)) = s == name
exists s (Group name st) = or (exists s <$> st) || s == name
-- | Groups alike @SpecificationTree@s into a bigger tree.
group :: String -> [SpecificationTree] -> SpecificationTree
group name st
| or $ exists name <$> st = error $ "Name already exists: " <> name
| otherwise = Group name st
-- | Query specific @Specification@ from a tree.
getSpecification :: String -> SpecificationTree -> [Specification]
getSpecification name (Terminal spec@(Specification sn _ _))
| name == sn = [spec]
| otherwise = []
getSpecification name (Group _ st) = mconcat $ getSpecification name <$> st
-- | Query specific @SpecificationTree@ from a tree.
getSpecificationTree :: String -> SpecificationTree -> [SpecificationTree]
getSpecificationTree name specTree@(Group gn st)
| gn == name = [specTree]
| otherwise = mconcat $ getSpecificationTree name <$> st
getSpecificationTree _ _ = []
-- | Convert @SpecificationTree@ into @TestTree@ to be used as a unit test.
toTestTree :: SpecificationTree -> TestTree
toTestTree (Group name st) = testGroup name $ toTestTree <$> st
toTestTree (Terminal (Specification name expectation script)) =
testCase name $ do
case expectation of
Success -> onSuccess
Failure -> onFailure
FailureWith s -> onFailureWith s
where
(res, _budget, traces) = evalScript script
ts = " Traces: " <> show traces
onSuccess = case res of
Left e ->
assertFailure $
show e <> ts
_ -> pure ()
onFailure = case res of
Right v ->
assertFailure $
"Expected failure, but succeeded. "
<> show v
<> ts
_ -> pure ()
onFailureWith _s = case res of -- TODO: check Trace for this
Right v ->
assertFailure $
"Expected failure, but succeeded. "
<> show v
<> ts
_ -> pure ()
-- | Check that an arbitrary script doesn't error when evaluated, given a name.
scriptSucceeds :: String -> Script -> SpecificationTree
scriptSucceeds name script = Terminal $ Specification name Success script
-- | Check that an arbitrary script **does** error when evaluated, given a name.
scriptFails :: String -> Script -> SpecificationTree
scriptFails name script = Terminal $ Specification name Failure script
-- | Check that a policy script succeeds, given a name and arguments.
policySucceedsWith ::
( PLift redeemer
, PlutusTx.ToData (PLifted redeemer)
) =>
String ->
ClosedTerm PMintingPolicy ->
PLifted redeemer ->
ScriptContext ->
SpecificationTree
policySucceedsWith tag policy redeemer scriptContext =
scriptSucceeds tag $
compile
( policy
# pforgetData (pconstantData redeemer)
# pconstant scriptContext
)
-- | Check that a policy script fails, given a name and arguments.
policyFailsWith ::
( PLift redeemer
, PlutusTx.ToData (PLifted redeemer)
) =>
String ->
ClosedTerm PMintingPolicy ->
PLifted redeemer ->
ScriptContext ->
SpecificationTree
policyFailsWith tag policy redeemer scriptContext =
scriptFails tag $
compile
( policy
# pforgetData (pconstantData redeemer)
# pconstant scriptContext
)
-- | Check that a validator script succeeds, given a name and arguments.
validatorSucceedsWith ::
( PLift datum
, PlutusTx.ToData (PLifted datum)
, PLift redeemer
, PlutusTx.ToData (PLifted redeemer)
) =>
String ->
ClosedTerm PValidator ->
PLifted datum ->
PLifted redeemer ->
ScriptContext ->
SpecificationTree
validatorSucceedsWith tag validator datum redeemer scriptContext =
scriptSucceeds tag $
compile
( validator
# pforgetData (pconstantData datum)
# pforgetData (pconstantData redeemer)
# pconstant scriptContext
)
-- | Check that a validator script fails, given a name and arguments.
validatorFailsWith ::
( PLift datum
, PlutusTx.ToData (PLifted datum)
, PLift redeemer
, PlutusTx.ToData (PLifted redeemer)
) =>
String ->
ClosedTerm PValidator ->
PLifted datum ->
PLifted redeemer ->
ScriptContext ->
SpecificationTree
validatorFailsWith tag validator datum redeemer scriptContext =
scriptFails tag $
compile
( validator
# pforgetData (pconstantData datum)
# pforgetData (pconstantData redeemer)
# pconstant scriptContext
)
-- | Check that an effect succeeds, given a name and argument.
effectSucceedsWith ::
( PLift datum
, PlutusTx.ToData (PLifted datum)
) =>
String ->
ClosedTerm PValidator ->
PLifted datum ->
ScriptContext ->
SpecificationTree
effectSucceedsWith tag eff datum = validatorSucceedsWith tag eff datum ()
-- | Check that an effect fails, given a name and argument.
effectFailsWith ::
( PLift datum
, PlutusTx.ToData (PLifted datum)
) =>
String ->
ClosedTerm PValidator ->
PLifted datum ->
ScriptContext ->
SpecificationTree
effectFailsWith tag eff datum = validatorFailsWith tag eff datum ()

View file

@ -7,7 +7,7 @@ Description: Tests for Stake policy and validator
Tests for Stake policy and validator Tests for Stake policy and validator
-} -}
module Spec.Stake (tests) where module Spec.Stake (specs) where
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -15,10 +15,6 @@ import Prelude
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Test.Tasty (TestTree, testGroup)
--------------------------------------------------------------------------------
import Agora.Stake (Stake (..), StakeDatum (StakeDatum), StakeRedeemer (DepositWithdraw)) import Agora.Stake (Stake (..), StakeDatum (StakeDatum), StakeRedeemer (DepositWithdraw))
import Agora.Stake.Scripts (stakePolicy, stakeValidator) import Agora.Stake.Scripts (stakePolicy, stakeValidator)
@ -26,14 +22,21 @@ import Agora.Stake.Scripts (stakePolicy, stakeValidator)
import Sample.Stake (DepositWithdrawExample (DepositWithdrawExample, delta, startAmount), signer) import Sample.Stake (DepositWithdrawExample (DepositWithdrawExample, delta, startAmount), signer)
import Sample.Stake qualified as Stake import Sample.Stake qualified as Stake
import Test.Util (policyFailsWith, policySucceedsWith, toDatum, validatorFailsWith, validatorSucceedsWith) import Spec.Specification (
SpecificationTree,
group,
policyFailsWith,
policySucceedsWith,
validatorFailsWith,
validatorSucceedsWith,
)
import Test.Util (toDatum)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Stake tests. specs :: [SpecificationTree]
tests :: [TestTree] specs =
tests = [ group
[ testGroup
"policy" "policy"
[ policySucceedsWith [ policySucceedsWith
"stakeCreation" "stakeCreation"
@ -51,7 +54,7 @@ tests =
() ()
Stake.stakeCreationUnsigned Stake.stakeCreationUnsigned
] ]
, testGroup , group
"validator" "validator"
[ validatorSucceedsWith [ validatorSucceedsWith
"stakeDepositWithdraw deposit" "stakeDepositWithdraw deposit"

View file

@ -5,7 +5,7 @@ Module: Spec.Treasury
Description: Tests for Agora treasury. Description: Tests for Agora treasury.
Maintainer: jack@mlabs.city Maintainer: jack@mlabs.city
This module exports `tests`, a list of `TestTree`s, which ensure This module exports `specs`, a list of `TestTree`s, which ensure
that Agora's treasury component works as desired. that Agora's treasury component works as desired.
Tests need to fail when: Tests need to fail when:
@ -19,7 +19,7 @@ Tests need to fail when:
ii. A script has a GAT, the token name for which does /not/ ii. A script has a GAT, the token name for which does /not/
match the script's validator hash. match the script's validator hash.
-} -}
module Spec.Treasury (tests) where module Spec.Treasury (specs) where
import Agora.Treasury ( import Agora.Treasury (
TreasuryRedeemer (SpendTreasuryGAT), TreasuryRedeemer (SpendTreasuryGAT),
@ -48,14 +48,18 @@ import Sample.Treasury (
validCtx, validCtx,
walletIn, walletIn,
) )
import Test.Tasty (TestTree, testGroup) import Spec.Specification (
import Test.Util (validatorFailsWith, validatorSucceedsWith) SpecificationTree,
group,
validatorFailsWith,
validatorSucceedsWith,
)
tests :: [TestTree] specs :: [SpecificationTree]
tests = specs =
[ testGroup [ group
"Validator" "Validator"
[ testGroup [ group
"Positive" "Positive"
[ validatorSucceedsWith [ validatorSucceedsWith
"Allows for effect changes" "Allows for effect changes"
@ -64,9 +68,9 @@ tests =
SpendTreasuryGAT SpendTreasuryGAT
validCtx validCtx
] ]
, testGroup , group
"Negative" "Negative"
[ testGroup [ group
"Fails with ScriptPurpose not Minting" "Fails with ScriptPurpose not Minting"
[ validatorFailsWith [ validatorFailsWith
"Spending" "Spending"

View file

@ -1,11 +0,0 @@
#+Title: Agora Test
This folder is the test suite for Agora governance system.
- =/Spec= contains different tests for different elements of Agora.
- =/Spec/Model= contain =apropos-tx= model for logical suite
generation and tests.
- =/Spec/Sample= contains primitive hand-made example values.
- =Util.hs= contains helper functions
Currently, planning to introduce =plutarch-test= for unit tests,
benchmarks, and golden tests.

View file

@ -2,6 +2,7 @@ import Prelude
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import GHC.IO.Encoding (setLocaleEncoding, utf8)
import Test.Tasty (defaultMain, testGroup) import Test.Tasty (defaultMain, testGroup)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@ -16,39 +17,26 @@ import Spec.Stake qualified as Stake
import Spec.Treasury qualified as Treasury import Spec.Treasury qualified as Treasury
import Spec.Utils qualified as Utils import Spec.Utils qualified as Utils
import Spec.Specification (group, toTestTree)
-- | The Agora test suite. -- | The Agora test suite.
main :: IO () main :: IO ()
main = main = do
setLocaleEncoding utf8
defaultMain $ defaultMain $
testGroup testGroup
"test suite" "test suite"
[ testGroup [ testGroup
"Effects" "Effects"
[ testGroup [ toTestTree $ group "Treasury Withdrawal Effect" TreasuryWithdrawal.specs
"Treasury Withdrawal Effect" , toTestTree $ group "Governor Mutation Effect" GovernorMutation.specs
TreasuryWithdrawal.tests
, testGroup
"Governor Mutation Effect"
GovernorMutation.tests
] ]
, testGroup , toTestTree $ group "Stake tests" Stake.specs
"Stake tests" , toTestTree $ group "Proposal tests" Proposal.specs
Stake.tests , toTestTree $ group "AuthorityToken tests" AuthorityToken.specs
, testGroup , toTestTree $ group "Treasury tests" Treasury.specs
"Proposal tests" , toTestTree $ group "AuthorityToken tests" AuthorityToken.specs
Proposal.tests , toTestTree $ group "Governor tests" Governor.specs
, testGroup
"AuthorityToken tests"
AuthorityToken.tests
, testGroup
"Treasury tests"
Treasury.tests
, testGroup
"AuthorityToken tests"
AuthorityToken.tests
, testGroup
"Governor tests"
Governor.tests
, testGroup , testGroup
"Utility tests" "Utility tests"
Utils.tests Utils.tests

View file

@ -2,37 +2,8 @@
Module : Test.Util Module : Test.Util
Maintainer : emi@haskell.fyi Maintainer : emi@haskell.fyi
Description: Utility functions for testing Plutarch scripts with ScriptContext Description: Utility functions for testing Plutarch scripts with ScriptContext
Utility functions for testing Plutarch scripts with ScriptContext:
- 'policySucceedsWith': checks that a minting policy succeeds.
- 'policyFailsWith': checks that a minting policy fails.
- 'validatorSucceedsWith': checks that validator succeeds.
- 'validatorFailsWith': checks that validator fails.
- 'effectSucceedsWith': checks that effect succeeds.
- 'effectFailsWith': checks that effect fails.
- 'scriptSucceeds': checks that an arbitrary script does not
`perror`.
- 'scriptFails': checks that an arbitrary script `perror`s out.
-} -}
module Test.Util ( module Test.Util (
-- * Testing utils
scriptSucceeds,
scriptFails,
policySucceedsWith,
policyFailsWith,
validatorSucceedsWith,
validatorFailsWith,
effectSucceedsWith,
effectFailsWith,
-- * Plutus-land utils -- * Plutus-land utils
datumHash, datumHash,
toDatum, toDatum,
@ -53,19 +24,9 @@ import Data.ByteString.Lazy qualified as ByteString.Lazy
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
import Test.Tasty (TestTree)
import Test.Tasty.HUnit (assertFailure, testCase)
--------------------------------------------------------------------------------
import Plutarch.Api.V1 (PMintingPolicy, PValidator)
import Plutarch.Builtin (pforgetData)
import Plutarch.Crypto (pblake2b_256) import Plutarch.Crypto (pblake2b_256)
import Plutarch.Evaluate (evalScript)
import Plutarch.Lift (PUnsafeLiftDecl (PLifted))
import Plutus.V1.Ledger.Contexts (ScriptContext)
import Plutus.V1.Ledger.Interval as PlutusTx import Plutus.V1.Ledger.Interval as PlutusTx
import Plutus.V1.Ledger.Scripts (Datum (Datum), DatumHash (DatumHash), Script) import Plutus.V1.Ledger.Scripts (Datum (Datum), DatumHash (DatumHash))
import PlutusTx.AssocMap qualified as AssocMap import PlutusTx.AssocMap qualified as AssocMap
import PlutusTx.Builtins qualified as PlutusTx import PlutusTx.Builtins qualified as PlutusTx
import PlutusTx.IsData qualified as PlutusTx import PlutusTx.IsData qualified as PlutusTx
@ -73,140 +34,6 @@ import PlutusTx.Ord qualified as PlutusTx
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- | Check that a policy script succeeds, given a name and arguments.
policySucceedsWith ::
( PLift redeemer
, PlutusTx.ToData (PLifted redeemer)
) =>
String ->
ClosedTerm PMintingPolicy ->
PLifted redeemer ->
ScriptContext ->
TestTree
policySucceedsWith tag policy redeemer scriptContext =
scriptSucceeds tag $
compile
( policy
# pforgetData (pconstantData redeemer)
# pconstant scriptContext
)
-- | Check that a policy script fails, given a name and arguments.
policyFailsWith ::
( PLift redeemer
, PlutusTx.ToData (PLifted redeemer)
) =>
String ->
ClosedTerm PMintingPolicy ->
PLifted redeemer ->
ScriptContext ->
TestTree
policyFailsWith tag policy redeemer scriptContext =
scriptFails tag $
compile
( policy
# pforgetData (pconstantData redeemer)
# pconstant scriptContext
)
-- | Check that a validator script succeeds, given a name and arguments.
validatorSucceedsWith ::
( PLift datum
, PlutusTx.ToData (PLifted datum)
, PLift redeemer
, PlutusTx.ToData (PLifted redeemer)
) =>
String ->
ClosedTerm PValidator ->
PLifted datum ->
PLifted redeemer ->
ScriptContext ->
TestTree
validatorSucceedsWith tag validator datum redeemer scriptContext =
scriptSucceeds tag $
compile
( validator
# pforgetData (pconstantData datum)
# pforgetData (pconstantData redeemer)
# pconstant scriptContext
)
-- | Check that a validator script fails, given a name and arguments.
validatorFailsWith ::
( PLift datum
, PlutusTx.ToData (PLifted datum)
, PLift redeemer
, PlutusTx.ToData (PLifted redeemer)
) =>
String ->
ClosedTerm PValidator ->
PLifted datum ->
PLifted redeemer ->
ScriptContext ->
TestTree
validatorFailsWith tag validator datum redeemer scriptContext =
scriptFails tag $
compile
( validator
# pforgetData (pconstantData datum)
# pforgetData (pconstantData redeemer)
# pconstant scriptContext
)
{- | Check that a validator script succeeds, given a name and arguments.
TODO: Change docstring.
-}
effectSucceedsWith ::
( PLift datum
, PlutusTx.ToData (PLifted datum)
) =>
String ->
ClosedTerm PValidator ->
PLifted datum ->
ScriptContext ->
TestTree
effectSucceedsWith tag eff datum = validatorSucceedsWith tag eff datum ()
-- TODO: Change docstring.
{- | Check that a validator script fails, given a name and arguments.
TODO: Change docstring.
-}
effectFailsWith ::
( PLift datum
, PlutusTx.ToData (PLifted datum)
) =>
String ->
ClosedTerm PValidator ->
PLifted datum ->
ScriptContext ->
TestTree
effectFailsWith tag eff datum = validatorFailsWith tag eff datum ()
-- | Check that an arbitrary script doesn't error when evaluated, given a name.
scriptSucceeds :: String -> Script -> TestTree
scriptSucceeds name script = testCase name $ do
let (res, _budget, traces) = evalScript script
case res of
Left e -> do
assertFailure $
show e <> " Traces: " <> show traces
Right _v ->
pure ()
-- | Check that an arbitrary script **does** error when evaluated, given a name.
scriptFails :: String -> Script -> TestTree
scriptFails name script = testCase name $ do
let (res, _budget, traces) = evalScript script
case res of
Left _e ->
pure ()
Right v ->
assertFailure $
"Expected failure, but succeeded. " <> show v <> " Traces: " <> show traces
--------------------------------------------------------------------------------
{- | Create a pair from data for use in 'txInfoData'. {- | Create a pair from data for use in 'txInfoData'.
Example: Example:

View file

@ -179,26 +179,35 @@ library agora-sample
hs-source-dirs: agora-sample hs-source-dirs: agora-sample
build-depends: agora-testlib build-depends: agora-testlib
test-suite agora-test library agora-spec
import: lang, deps, test-deps import: lang, deps, test-deps
type: exitcode-stdio-1.0 exposed-modules:
main-is: Spec.hs
hs-source-dirs: agora-test
other-modules:
Spec.AuthorityToken Spec.AuthorityToken
Spec.Effect.GovernorMutation Spec.Effect.GovernorMutation
Spec.Effect.TreasuryWithdrawal Spec.Effect.TreasuryWithdrawal
Spec.Governor Spec.Governor
Spec.Model.MultiSig Spec.Model.MultiSig
Spec.Proposal Spec.Proposal
Spec.Specification
Spec.Stake Spec.Stake
Spec.Treasury Spec.Treasury
Spec.Utils Spec.Utils
hs-source-dirs: agora-spec
build-depends: build-depends:
, agora-sample , agora-sample
, agora-testlib , agora-testlib
test-suite agora-test
import: lang, deps, test-deps
type: exitcode-stdio-1.0
main-is: Spec.hs
hs-source-dirs: agora-test
build-depends:
, agora-sample
, agora-spec
, agora-testlib
benchmark agora-bench benchmark agora-bench
import: lang, deps import: lang, deps
hs-source-dirs: agora-bench hs-source-dirs: agora-bench
@ -208,6 +217,8 @@ benchmark agora-bench
build-depends: build-depends:
, agora , agora
, agora-sample , agora-sample
, agora-spec
, cassava
executable agora-scripts executable agora-scripts
import: lang, deps, exe-opts import: lang, deps, exe-opts

View file

@ -1,4 +1,23 @@
name,cpu,mem,size name,cpu,mem,size
full_scripts:authorityTokenPolicy,1756707,6000,841 Agora/Effects/Treasury Withdrawal Effect/effect/Simple,340268715,724428,3050
full_scripts:stakePolicy,3751498,12700,1610 Agora/Effects/Treasury Withdrawal Effect/effect/Simple with multiple treasuries ,570029812,1211300,3377
full_scripts:stakeValidator,3126265,10600,1500 Agora/Effects/Treasury Withdrawal Effect/effect/Mixed Assets,502351827,1071087,3242
Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/governor validator should pass,103651824,228328,7681
Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/effect validator should pass,126986096,263635,3357
Agora/Stake/policy/stakeCreation,60250773,128585,2144
Agora/Stake/validator/stakeDepositWithdraw deposit,275919558,599033,4063
Agora/Stake/validator/stakeDepositWithdraw withdraw,275919558,599033,4055
Agora/Proposal/policy/proposalCreation,34571405,70066,1585
Agora/Proposal/validator/cosignature/proposal,240007066,509127,4892
Agora/Proposal/validator/cosignature/stake,185913543,402497,4600
Agora/Proposal/validator/voting/proposal,238383906,489848,4900
Agora/Proposal/validator/voting/stake,153804848,328239,4653
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple,25177457,55883,806
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs,40266637,88241,900
Agora/Treasury/Validator/Positive/Allows for effect changes,37343572,79744,1841
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple,25177457,55883,806
Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs,40266637,88241,900
Agora/Governor/policy/GST minting,57648280,119961,1851
Agora/Governor/validator/proposal creation,329287002,679689,8196
Agora/Governor/validator/GATs minting,430385143,929607,8319
Agora/Governor/validator/mutate governor state,100840784,222602,7738

1 name cpu mem size
2 full_scripts:authorityTokenPolicy Agora/Effects/Treasury Withdrawal Effect/effect/Simple 1756707 340268715 6000 724428 841 3050
3 full_scripts:stakePolicy Agora/Effects/Treasury Withdrawal Effect/effect/Simple with multiple treasuries 3751498 570029812 12700 1211300 1610 3377
4 full_scripts:stakeValidator Agora/Effects/Treasury Withdrawal Effect/effect/Mixed Assets 3126265 502351827 10600 1071087 1500 3242
5 Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/governor validator should pass 103651824 228328 7681
6 Agora/Effects/Governor Mutation Effect/validator/valid new governor datum/effect validator should pass 126986096 263635 3357
7 Agora/Stake/policy/stakeCreation 60250773 128585 2144
8 Agora/Stake/validator/stakeDepositWithdraw deposit 275919558 599033 4063
9 Agora/Stake/validator/stakeDepositWithdraw withdraw 275919558 599033 4055
10 Agora/Proposal/policy/proposalCreation 34571405 70066 1585
11 Agora/Proposal/validator/cosignature/proposal 240007066 509127 4892
12 Agora/Proposal/validator/cosignature/stake 185913543 402497 4600
13 Agora/Proposal/validator/voting/proposal 238383906 489848 4900
14 Agora/Proposal/validator/voting/stake 153804848 328239 4653
15 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple 25177457 55883 806
16 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs 40266637 88241 900
17 Agora/Treasury/Validator/Positive/Allows for effect changes 37343572 79744 1841
18 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct simple 25177457 55883 806
19 Agora/AuthorityToken/singleAuthorityTokenBurned/Correct many inputs 40266637 88241 900
20 Agora/Governor/policy/GST minting 57648280 119961 1851
21 Agora/Governor/validator/proposal creation 329287002 679689 8196
22 Agora/Governor/validator/GATs minting 430385143 929607 8319
23 Agora/Governor/validator/mutate governor state 100840784 222602 7738