Skip to content

Commit

Permalink
Adding a wrapper module with conveniency type classes (#411)
Browse files Browse the repository at this point in the history
This aims at simplifying the Output.hs module and the overall usability of cooked-validators by providing various type classes to summarize existing features of plutus elements.

As a side effect, this also:
-  updates the README 
- updates our dependency to cardano-node-emulator
- adds a few utxoSearches
- adds the ExtendedDefaultRules language extension by default
  • Loading branch information
mmontin authored May 31, 2024
1 parent fcdc11e commit 7c3af03
Show file tree
Hide file tree
Showing 42 changed files with 538 additions and 658 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
context within on-chain code
- `validatorToTypedValidator` which does what its name indicates
- Adding support for `PrettyCooked` for `TxLbl`
- Module `Wrapper.hs` to expose translation type classes
- A type class `ToPubKeyHash` to allow direct payments to wallets
- A type class `ToAddress` to retrieve addresses from various entities
- New utxos searches `vanillaOutputsAtSearch`, `scriptOutputsSearch`
and `referenceScriptOutputsSearch`

### Removed
- Extraneous dependencies in package.yaml
Expand All @@ -39,7 +44,7 @@
- Default era from Babbage to Conway
- No longer rely on deprecated plutus-apps, but instead
[cardano-node-emulator](https://github.com/IntersectMBO/cardano-node-emulator)
- From GHC 8.10.4 to 9.6.3
- From GHC 8.10.4 to 9.6.5 and associated versions of HLS
- Rely mostly on
[CHaP](https://github.com/IntersectMBO/cardano-haskell-packages?tab=readme-ov-file)
instead of direct git sources
Expand Down
98 changes: 58 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,33 @@

Copyright Tweag I/O 2024

With `cooked-validators` you can test Cardano smart contracts by writing potentially malicious offchain code.
You can also use the library to write "normal" offchain code in a comfortable and flexible way.
`cooked-validators` is a Haskell library to conveniently and efficiently write
off-chain code for Cardano smart contracts. This offchain code will be
specifically geared to testing and auditing the smart contract in question with
further builtin capabilities of the library.

In particular, `cooked-validators` helps you:
- interact with smart contracts written in Plutus (as well as any other language
that compiles to [UPLC](https://plutonomicon.github.io/plutonomicon/uplc),
like for example [Plutarch](https://github.com/Plutonomicon/plutarch-plutus)
or [Aiken](https://aiken-lang.org/), by loading contracts from byte strings)
- generate and submit transactions declaratively, while automatically taking
care of missing inputs and outputs, balancing, and minimum-Ada constraints
In particular, `cooked-validators` allows the user to:
- interact with smart contracts written in Plutus or any other language that
compiles to [UPLC](https://plutonomicon.github.io/plutonomicon/uplc), like for
example [Plutarch](https://github.com/Plutonomicon/plutarch-plutus) or
[Aiken](https://aiken-lang.org/), by loading contracts from byte strings
- define transactions in a high level, type-retaining data structure
- submit transactions for validation, while automatically taking care of missing
inputs and outputs, balancing, minimum-Ada constraints, collaterals and fees
- construct sequences of transactions in an easy-to-understand abstraction of
"the blockchain", which can be instantiated to different actual
implementations
- run sequences of transactions in a simulated blockchain,
- run sequences of transactions in a simulated blockchain
- apply "tweaks" to transactions right before submitting them, where "tweaks"
are modifications that are aware of the current state of the simulated
blockchain
- compose and deploy tweaks with flexible idioms inspired by linear temporal
logic, in order to turn one sequence of transactions into many sequences that
might be useful test cases, generalized in [Graft](https://github.com/tweag/graft)

The library is geared specifically towards testing and auditing on-chain code.
might be useful test cases, generalized in
[Graft](https://github.com/tweag/graft)
- deploy automated attacks over existing sequences of transactions, such as
datum hijacking or double satisfaction attacks, in an attempt to uncover
vulnerabilities

You are free to copy, modify, and distribute `cooked-validators` under the terms
of the MIT license. We provide `cooked-validators` as a research prototype under
Expand All @@ -32,39 +37,47 @@ the [license](LICENSE) for details.

## How to integrate `cooked-validators` in a project

This guide shows you how to use `cooked-validators` in a haskell project
using [Cabal](https://cabal.readthedocs.io/en/stable/)
to create and validate a simple transaction.

Before using `cooked-validators`, you need
- [GHC](https://www.haskell.org/ghc/download_ghc_8_10_7.html) version 8.10.7
- [Cabal](https://www.haskell.org/cabal)
To use `cooked-validators`, you need
- [GHC](https://www.haskell.org/ghc/download_ghc_9_6_5.html) version 9.6.5
- [Cabal](https://www.haskell.org/cabal) version 3.10 or later

1. If you have no constraint on the version of `plutus-apps`, copy the file
[`cabal.project`](./cabal.project) to your project and
[adapt](https://cabal.readthedocs.io/en/stable/cabal-project.html#specifying-the-local-packages)
the `packages` stanza.
1. `cooked-validators` depends on
[cardano-haskell-packages](https://github.com/input-output-hk/cardano-haskell-packages)
to get cardano-related packages and on
[cardano-node-emulator](https://github.com/IntersectMBO/cardano-node-emulator)
directly. If you have no constraint on the version of this package, copy the
file [`cabal.project`](./cabal.project) to your project and
[adapt](https://cabal.readthedocs.io/en/stable/cabal-project.html#specifying-the-local-packages)
the `packages` stanza.

2. Add the following stanza to the file `cabal.project`
```cabal.project
source-repository-package
type: git
location: https://github.com/tweag/cooked-validators
tag: v3.0.0
tag: myTag
subdir:
cooked-validators
.
```
3. Make your project
where `myTag` is either a commit hash in the repo, or a tag, such as v4.0.0
(see [available
releases](https://github.com/tweag/cooked-validators/releases)).

### Example

1. Make your project
[depend](https://cabal.readthedocs.io/en/stable/getting-started.html#adding-dependencies)
on `cooked-validators` and `plutus-script-utils`

3. Enter a Cabal read-eval-print-loop (with `cabal repl`)
and create and validate a transaction which transfers 10 Ada
from wallet 1 to wallet 2:
```haskell
> import Cooked
> import qualified Plutus.Script.Utils.Ada as Pl
> import qualified Plutus.Script.Utils.Ada as Script
> printCooked . runMockChain . validateTxSkel $
txSkelTemplate
{ txSkelOuts = [paysPK (walletPKHash $ wallet 2) (Pl.adaValueOf 10)],
{ txSkelOuts = [paysPK (wallet 2) (Script.adaValueOf 10)],
txSkelSigners = [wallet 1]
}
[...]
Expand All @@ -84,17 +97,22 @@ Before using `cooked-validators`, you need

## Documentation

The rendered Haddock for the current `main` branch can be found at
[https://tweag.github.io/cooked-validators/](https://tweag.github.io/cooked-validators/).
- The rendered Haddock for the current `main` branch can be found
[here](https://tweag.github.io/cooked-validators/).

- The [CHEATSHEET](doc/CHEATSHEET.md) contains many code snippets to quickly get
an intuition of how to do things. Use it to discover or search for how to use
features of `cooked-validators`. Note that this is not a tutorial nor a
ready-to-use recipes book.

The [CHEATSHEET](doc/CHEATSHEET.md) is a nice entry point and helper to keep on
sight. It contains many code snippets to quickly get an intuition of how to do
things. Use it to discover or search for how to use features of
`cooked-validators`. Note that this is not a tutorial nor a ready-to-use
recipes book.
- The [IMPORTS](doc/IMPORTS.md) file describes and helps to understand our
dependencies and naming conventions for imports.

We also have a [repository](https://github.com/tweag/cooked-smart-contracts) of example contracts with offchain code and tests written using `cooked-validators`.
- We also have a [repository](https://github.com/tweag/cooked-smart-contracts)
of example contracts with offchain code and tests written using
`cooked-validators`. Note that some examples are not maintained and thus written
using older versions of cooked-validators.

Please also look at our
[issues](https://github.com/tweag/cooked-validators/issues) for problems that
we're already aware of, and feel free to open new issues!
- Feel free to visit our [issue
tracker](https://github.com/tweag/cooked-validators/issues) to seek help about
known problems, or report new issues!
5 changes: 2 additions & 3 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ package cardano-crypto-praos

source-repository-package
type: git
location: https://github.com/input-output-hk/cardano-node-emulator
-- This should follow the Conway branch until it is merged to main
tag: 1ad4cace0a8982c167f0c02ccf34a21931f2ce5f
location: https://github.com/IntersectMBO/cardano-node-emulator
tag: f522eefd57a1bd08220b677e118dea48f5529866
subdir:
cardano-node-emulator
plutus-ledger
Expand Down
9 changes: 9 additions & 0 deletions cooked-validators.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ library
Cooked.Attack.DatumHijacking
Cooked.Attack.DoubleSat
Cooked.Attack.DupToken
Cooked.Conversion
Cooked.Conversion.ToAddress
Cooked.Conversion.ToCredential
Cooked.Conversion.ToOutputDatum
Cooked.Conversion.ToPubKeyHash
Cooked.Conversion.ToScript
Cooked.Conversion.ToScriptHash
Cooked.Conversion.ToValue
Cooked.Currencies
Cooked.InitialDistribution
Cooked.Ltl
Expand Down Expand Up @@ -84,6 +92,7 @@ library
TypeFamilies
TypeOperators
TypeSynonymInstances
ViewPatterns
ghc-options: -Wall -Wno-missed-extra-shared-lib -fobject-code -fno-ignore-interface-pragmas -fno-omit-interface-pragmas -fplugin-opt PlutusTx.Plugin:defer-errors
build-depends:
QuickCheck
Expand Down
3 changes: 2 additions & 1 deletion package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ library:
- TypeFamilies
- TypeOperators
- TypeSynonymInstances
- ViewPatterns

tests:
spec:
Expand All @@ -101,4 +102,4 @@ tests:
- TupleSections
- TypeApplications
- TypeFamilies
- ViewPatterns
- ViewPatterns
1 change: 1 addition & 0 deletions src/Cooked.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module Cooked
where

import Cooked.Attack as X
import Cooked.Conversion as X
import Cooked.Currencies as X
import Cooked.InitialDistribution as X
import Cooked.Ltl qualified as Ltl
Expand Down
8 changes: 3 additions & 5 deletions src/Cooked/Attack/AddToken.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Data.Map qualified as Map
import Plutus.Script.Utils.Scripts qualified as Script
import Plutus.Script.Utils.Value qualified as Script
import PlutusLedgerApi.V3 qualified as Api
import PlutusTx.Numeric qualified as ScriptutusTx
import PlutusTx.Numeric qualified as PlutusTx

-- | This attack adds extra tokens, depending on the minting policy. It is
-- different from the 'dupTokenAttack' in that it does not merely try to
Expand Down Expand Up @@ -44,13 +44,11 @@ addTokenAttack extraTokens attacker = do
map
( \(tName, amount) ->
let newMints = addToTxSkelMints (policy, redeemer, tName, amount) oldMints
increment =
txSkelMintsValue newMints
<> ScriptutusTx.negate (txSkelMintsValue oldMints)
increment = txSkelMintsValue newMints <> PlutusTx.negate (txSkelMintsValue oldMints)
in if increment `Script.geq` mempty
then do
setTweak txSkelMintsL newMints
addOutputTweak $ paysPK (walletPKHash attacker) increment
addOutputTweak $ paysPK attacker increment
return increment
else failingTweak
)
Expand Down
2 changes: 1 addition & 1 deletion src/Cooked/Attack/DoubleSat.hs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ doubleSatAttack groupings optic change attacker = do
addDoubleSatDeltaTweak delta
addedValue <- deltaBalance delta
if addedValue `Script.gt` mempty
then addOutputTweak $ paysPK (walletPKHash attacker) addedValue
then addOutputTweak $ paysPK attacker addedValue
else failingTweak
addLabelTweak DoubleSatLbl
where
Expand Down
2 changes: 1 addition & 1 deletion src/Cooked/Attack/DupToken.hs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ dupTokenAttack ::
m Api.Value
dupTokenAttack change attacker = do
totalIncrement <- changeMintAmountsTweak
addOutputTweak $ paysPK (walletPKHash attacker) totalIncrement
addOutputTweak $ paysPK attacker totalIncrement
addLabelTweak DupTokenLbl
return totalIncrement
where
Expand Down
10 changes: 10 additions & 0 deletions src/Cooked/Conversion.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- | Re-exports all conversion type classes and instances
module Cooked.Conversion (module X) where

import Cooked.Conversion.ToAddress as X
import Cooked.Conversion.ToCredential as X
import Cooked.Conversion.ToOutputDatum as X
import Cooked.Conversion.ToPubKeyHash as X
import Cooked.Conversion.ToScript as X
import Cooked.Conversion.ToScriptHash as X
import Cooked.Conversion.ToValue as X
18 changes: 18 additions & 0 deletions src/Cooked/Conversion/ToAddress.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
-- | Objects from which addresses can be extracted
module Cooked.Conversion.ToAddress where

import Cooked.Wallet
import Plutus.Script.Utils.Typed qualified as Script
import PlutusLedgerApi.V3 qualified as Api

class ToAddress a where
toAddress :: a -> Api.Address

instance ToAddress Wallet where
toAddress = walletAddress

instance ToAddress Api.Address where
toAddress = id

instance ToAddress (Script.TypedValidator a) where
toAddress = Script.validatorAddress
34 changes: 34 additions & 0 deletions src/Cooked/Conversion/ToCredential.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
-- | Objects from which a credential can be extracted
module Cooked.Conversion.ToCredential where

import Cooked.Conversion.ToScriptHash
import Plutus.Script.Utils.Scripts qualified as Script
import Plutus.Script.Utils.Typed qualified as Script
import PlutusLedgerApi.V3 qualified as Api

class ToCredential a where
toCredential :: a -> Api.Credential

instance ToCredential Api.Credential where
toCredential = id

instance ToCredential Api.PubKeyHash where
toCredential = Api.PubKeyCredential

instance ToCredential Api.ScriptHash where
toCredential = Api.ScriptCredential

instance ToCredential Script.ValidatorHash where
toCredential = toCredential . toScriptHash

instance ToCredential (Script.Versioned Script.Script) where
toCredential = toCredential . toScriptHash

instance ToCredential (Script.Versioned Script.Validator) where
toCredential = toCredential . toScriptHash

instance ToCredential (Script.TypedValidator a) where
toCredential = toCredential . toScriptHash

instance ToCredential (Script.Versioned Script.MintingPolicy) where
toCredential = toCredential . toScriptHash
22 changes: 22 additions & 0 deletions src/Cooked/Conversion/ToOutputDatum.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- | Objects from which an output datum can be extracted
module Cooked.Conversion.ToOutputDatum where

import PlutusLedgerApi.V3 qualified as Api

class ToOutputDatum a where
toOutputDatum :: a -> Api.OutputDatum

instance ToOutputDatum Api.OutputDatum where
toOutputDatum = id

instance ToOutputDatum Api.Datum where
toOutputDatum = Api.OutputDatum

instance ToOutputDatum () where
toOutputDatum = const Api.NoOutputDatum

instance ToOutputDatum Api.DatumHash where
toOutputDatum = Api.OutputDatumHash

instance ToOutputDatum Api.BuiltinData where
toOutputDatum = toOutputDatum . Api.Datum
14 changes: 14 additions & 0 deletions src/Cooked/Conversion/ToPubKeyHash.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- | Objects from which a public key hash can be extracted
module Cooked.Conversion.ToPubKeyHash where

import Cooked.Wallet
import PlutusLedgerApi.V3 qualified as Api

class ToPubKeyHash a where
toPubKeyHash :: a -> Api.PubKeyHash

instance ToPubKeyHash Api.PubKeyHash where
toPubKeyHash = id

instance ToPubKeyHash Wallet where
toPubKeyHash = walletPKHash
20 changes: 20 additions & 0 deletions src/Cooked/Conversion/ToScript.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- | Objects from which a versioned script can be extracted
module Cooked.Conversion.ToScript where

import Plutus.Script.Utils.Scripts qualified as Script
import Plutus.Script.Utils.Typed qualified as Script

class ToScript a where
toScript :: a -> Script.Versioned Script.Script

instance ToScript (Script.Versioned Script.Script) where
toScript = id

instance ToScript (Script.Versioned Script.Validator) where
toScript (Script.Versioned (Script.Validator script) version) = Script.Versioned script version

instance ToScript (Script.TypedValidator a) where
toScript = toScript . Script.vValidatorScript

instance ToScript (Script.Versioned Script.MintingPolicy) where
toScript (Script.Versioned (Script.MintingPolicy script) version) = Script.Versioned script version
Loading

0 comments on commit 7c3af03

Please sign in to comment.