plutus icon indicating copy to clipboard operation
plutus copied to clipboard

QuickCheck `Arbitrary` instances for Ledger types

Open SeungheonOh opened this issue 1 year ago • 14 comments

Describe the feature you'd like

Currently, each projects need to write some property testing generator and shrinker on per-project biases, making testing not only more difficult but possibly more inconsistent and un-standardized. Quickcheck Arbirary instances for each types that PlutusLedgerApi exports would immensely help when testing scripts, generating some place-holder values, or even maintaining eDSL equivalent for ledger api--for example, Plutarch.

SeungheonOh avatar Jun 13 '24 20:06 SeungheonOh

That would be great to have indeed. I've brought it to the team and the team agreed.

I'm going to mark this issue as "low priority" though, since it's not a bug or similar.

PR would be very welcome.

effectfully avatar Jun 18 '24 22:06 effectfully

Property Testing! cool, It looks like something I could implement. Mentorship is welcome 😇

My questions are:

  1. What do I start with? Common.hs seem to be good to start with, but there are 94 lines of types and functions, and among the types it is likely not all are made equal in terms of how useful it is to to have random value generation for them. Are these good enough candidates? https://github.com/IntersectMBO/plutus/blob/19f192d6a7d944fb1c7037143deefc70e464a509/plutus-ledger-api/src/PlutusLedgerApi/Common.hs#L53-L61

  2. Where do the instances go? It should be something like Property.hs in plutus-ledger-api/testlib/ , right?

Thanks!

aleeusgr avatar Jul 12 '24 07:07 aleeusgr

@aleeusgr thank you for volunteering! How about you take the work done by MLabs and upstream it into our repo? While improving everything that you can think of along the way.

Where do the instances go? It should be something like Property.hs in plutus-ledger-api/testlib/ , right?

We have a generator for Value in plutus-ledger-api/testlib/PlutusLedgerApi/Test/V1/Value.hs. Given that MLabs folks put their generators under V2, it makes sense to just continue with this convention and put generators under the appropriate version in testlib.

effectfully avatar Jul 16 '24 00:07 effectfully

It would be great if you guys can get our instances upstreamed. That was our original plan, but given we have to complete Plutus Ledger API V3 in time, we had to vendor it temporarily.

Most instances come with all necessary parts with some degree of assurance over invariants(e.g. value's entries being sorted): generator, shrinker, and utilities for function generators. Some of the more tricky ones like PScriptContext and PTxInfo doesn't have shrinker due to their complex invariants and some fields being illogical to shrink. You might be able to improve that. Generators for these "tricky" types are quite slow as well. So, that's another part you can improve.

Rest should be good in terms of quality, documentation, and code structure. We've actually used this to check data encoding for Plutarch ledger API and already caught handful of bugs. Property testing rocks!

SeungheonOh avatar Jul 16 '24 01:07 SeungheonOh

I've been digging into the Value type and its Arbitrary instance in testlib, comparing it to FeeValue and its generator in plutarch-plutus. It looks like in some cases I may disregard wrappers and reuse their generators with minimal code changes.

Now the goal is to find the types that do not have generators in plutus but do have them in plutarch-plutus; 🪫🇪🇬🥵💡☯️

I added a test: https://github.com/aleeusgr/plutus/tree/ledger-types-testing

now I am looking for a way to list all types that need to be covered.

references

  • https://medium.com/@alexeusgr/build-in-public-open-source-contribution-log-1-73719f29762f
  • https://stackoverflow.com/questions/16440208/how-to-generate-arbitrary-instances-of-a-simple-type-for-quickcheck
  • https://hackage.haskell.org/package/QuickCheck-2.15.0.1/docs/Test-QuickCheck-Arbitrary.html
  • https://stackoverflow.com/questions/58417342/get-a-list-of-what-is-exported-by-a-module
  • https://www.perplexity.ai/search/write-a-haskell-function-that-jZ.kqXyBRD6s1RxjBQHx1g
  • https://www.perplexity.ai/search/the-glasgow-haskell-compiler-g-LUM9ehlRRRaHQAVuDu_aLQ

aleeusgr avatar Jul 18 '24 07:07 aleeusgr

I've been digging into the Value type and its Arbitrary instance in testlib

This one is pretty good when it comes to the distribution of generated trees, but it doesn't attempt to satisfy various invariants like the one by MLabs does. It would be ideal to preserve the current generation of trees and add the invariants logic.

Now I focus on finding the types that do not have generators in plutus but do have them in plutarch-plutus

Thank you for doing this!

effectfully avatar Jul 18 '24 12:07 effectfully

Hello! Sadly I report that I am still stuck with this issue. I explored the repo and the PR suggested above, and although I made some progress initially at the moment I am struggling with formulating next actionable steps.

My first hunch was to automate reasoning about "Type X has an Arbitrary", at which I partially succeeded. I wrote a test that fails to build if a Type has no RNG. But then I failed to find a way to list all the exported types.

Then I decided to not bother with high level and try to copy and paste an RNG from plutarch. Here I don't understand what my acceptance criteria would look like. To illustrate, lets look at Value.

first I load the module to repl cabal repl plutus-ledger-api and I get "ok, 40 modules loaded" From there I can :l PlutusLedgerApi.V1.Value and then

ghci> :info PlutusLedgerApi.V1.Value.Value 
type Value :: *
newtype Value
  = Value {getValue :: PlutusTx.AssocMap.Map
                         CurrencySymbol (PlutusTx.AssocMap.Map TokenName Integer)}
        -- Defined at src/PlutusLedgerApi/V1/Value.hs:278:1
instance Eq Value
  -- Defined at src/PlutusLedgerApi/V1/Value.hs:307:10
instance Monoid Value
  -- Defined at src/PlutusLedgerApi/V1/Value.hs:321:10
instance Semigroup Value
  -- Defined at src/PlutusLedgerApi/V1/Value.hs:314:10
instance Show Value
  -- Defined at src/PlutusLedgerApi/V1/Value.hs:279:46

as you can see there is no Arbitrary Fair enough you say, its not in the main lib but in the testlib loading it to repl, then I use :info PlutusLedgerApi.Test.V1.Value. and press Tab to show the options. I get the following:

ghci> :info PlutusLedgerApi.Test.V1.Value.
PlutusLedgerApi.Test.V1.Value.FaceValue               PlutusLedgerApi.Test.V1.Value.unFaceValue
PlutusLedgerApi.Test.V1.Value.NoArbitrary             PlutusLedgerApi.Test.V1.Value.unNoArbitrary
PlutusLedgerApi.Test.V1.Value.genShortHex             PlutusLedgerApi.Test.V1.Value.uniqueNames
PlutusLedgerApi.Test.V1.Value.listsToValue            PlutusLedgerApi.Test.V1.Value.valueToLists
PlutusLedgerApi.Test.V1.Value.toCellCandidatesNumber

There is no Arbitrary there! but if I go to testlib/PLA/Test/V1/Value.hs its there on Line 83.

I am really confused! Should I not user repl in this case? Please help me with getting the acceptance criteria!

For example the PR defines Arbitrary

instance Arbitrary PLA.Credential where
  {-# INLINEABLE arbitrary #-}
  arbitrary =
    oneof
      [ PLA.PubKeyCredential <$> arbitrary
      , PLA.ScriptCredential <$> arbitrary
      ]

-- | @since 1.0.0
instance CoArbitrary PLA.Credential where
  {-# INLINEABLE coarbitrary #-}
  coarbitrary = \case
    PLA.PubKeyCredential pkh -> variant (0 :: Int) . coarbitrary pkh
    PLA.ScriptCredential sh -> variant (1 :: Int) . coarbitrary sh

Let me implement it! But how do I know if it needs to be done and when I do it how do I know I am successful?

Thank you!

In case you are open to a pair programming session, lets discuss it! We can schedule a transcribed call and I'll use the transcript to write a docpage or an article about testing in plutus.

aleeusgr avatar Oct 07 '24 12:10 aleeusgr

There is no Arbitrary there! but if I go to testlib/PLA/Test/V1/Value.hs its there on Line 83.

I am really confused! Should I not user repl in this case?

Surprised about that as well, I'd expect GHCi to pick up the instance.

Let me implement it! But how do I know if it needs to be done and when I do it how do I know I am successful?

As for the first part of the question, plutus-ledger-api is not a big library and plutus-ledger-api/testlib is quite small, so if you want to check whether something's in there, you can just grep it. git grep Arbitrary plutus-ledger-api gives me 18 results, you need a minute to figure out from those results what types we already provide an Arbitrary instance for.

The second part of the question is harder though. If you want to make this as painless as possible, just add some very simple yet still helpful test requiring some straightforward Arbitrary instance and make a PR, we'll take it from there. You don't want to create a 2k lines PR and end up having 400+ comments on it (like it happened here for example).

effectfully avatar Oct 09 '24 02:10 effectfully

many thanks! Finally I have progress. I found out that the only types that provide arbitraries are Value and FaceValue, hpc report showed that for FaceValue it is never executed. I confirmed it using git grep. so I added a test:

test_roundTripFaceValue = testProperty "unFaceValue followed by FaceValue returns the original" $
      \(faceValue :: FaceValue) -> unFaceValue (FaceValue $ unFaceValue faceValue) === unFaceValue faceValue

This test needs a Show instance to execute, adding it I found that code for FaceValue is duplicated: image

Should I duplicate Show instance?

aleeusgr avatar Oct 10 '24 07:10 aleeusgr

Continuing on I find the types for V2 (because the plutarch-orphanage PR has types for it),

12:19 $ grep -r --include="*.hs" '^data\>' ./src/PlutusLedgerApi/V2/
./src/PlutusLedgerApi/V2/Tx.hs:data OutputDatum = NoOutputDatum | OutputDatumHash DatumHash | OutputDatum Datum
./src/PlutusLedgerApi/V2/Tx.hs:data TxOut = TxOut
./src/PlutusLedgerApi/V2/ParamName.hs:data ParamName =
./src/PlutusLedgerApi/V2/Contexts.hs:data TxInInfo = TxInInfo
./src/PlutusLedgerApi/V2/Contexts.hs:data TxInfo = TxInfo
./src/PlutusLedgerApi/V2/Contexts.hs:data ScriptContext = ScriptContext
./src/PlutusLedgerApi/V2/Data/Tx.hs:data OutputDatum = NoOutputDatum | OutputDatumHash DatumHash | OutputDatum Datum
./src/PlutusLedgerApi/V2/Data/Tx.hs:data TxOut = TxOut {
./src/PlutusLedgerApi/V2/Data/Contexts.hs:data TxInInfo = TxInInfo
./src/PlutusLedgerApi/V2/Data/Contexts.hs:data TxInfo = TxInfo
./src/PlutusLedgerApi/V2/Data/Contexts.hs:data ScriptContext = ScriptContext

I chose OutputDatum as the first to upstream (because its up top) and building tests for it I get errors:

No instance for ‘Arbitrary DatumHash’                                                                            
        arising from a use of ‘arbitrary’
...
No instance for ‘Arbitrary Datum’                                                                            
        arising from a use of ‘arbitrary’

So it looks like there should be the instances for these types for the OutputDatum Arbitrary to work and they aren't there. Likely I need to find them in plutarch as well.

aleeusgr avatar Oct 20 '24 06:10 aleeusgr

Please recommend tools I could use to generate a list of Types a package exports! I am working on a issue in plutus-ledger-api; my goal is to write Arbitrary generators for the Types the package exports.

@coot says:

There's no such tool that I am aware of. You could write a ghc plugin for that (similar to ghc-tags-plugin).

Thanks coot, I will look into that.

aleeusgr avatar Oct 28 '24 05:10 aleeusgr

https://github.com/coot/ghc-tags-plugin

coot avatar Oct 28 '24 06:10 coot

Hey! Looks like I'll be moving the instances that we have in Plutarch upstream. Is that fine with you to put Arbitrary instances in modules that define given datatype or would you rather have them in testlib to avoid QuickCheck dependency on the main lib?

t4ccer avatar Oct 29 '24 23:10 t4ccer

There are some in testlib now, so I think we prefer to keep it consistent and add new ones there as well.

effectfully avatar Oct 29 '24 23:10 effectfully