diff --git a/CHANGELOG.md b/CHANGELOG.md index b398ebea..e5c7a0dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ ### Added +### Removed + +### Changed + +### Fixed + +## [[8.0.0]](https://github.com/tweag/cooked-validators/releases/tag/v8.0.0) - 2026-01-19 + +### Added + - `viewByRef` and `previewByRef` which call `txSkelOutByRef` and apply a getter and an affine fold on it, respectively. - Optics working on values in `Cooked.Skeleton.Output` diff --git a/README.md b/README.md index 675a9a62..c11a5750 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,58 @@ # [Cooked Validators](https://github.com/tweag/cooked-validators/) -Copyright Tweag I/O 2025 - -`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` 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 -- 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) -- 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 -active development, and it comes _as is_ with no guarantees whatsoever. Check -the [license](LICENSE) for details. +Copyright Tweag I/O 2026 + +`cooked-validators` is a Haskell library for writing reliable, concise, and +expressive off-chain code for Cardano smart contracts, with a primary focus on +testing, auditing, and behavioral exploration. + +It allows you to describe transactions at a high level (via what we call +transaction skeletons) and automatically turn them into complete, valid +transactions by handling all mechanical aspects such UTxO selection, balancing, +minimum-Ada constraints, collaterals or fees. + +The library is designed to: +- drastically reduce off-chain boilerplate, +- make test scenarios more readable and maintainable, +- facilitate adversarial testing and vulnerability discovery. + +Importantly, `cooked-validators` is non-opinionated: everything it automates can +also be done manually if needed, allowing users to retain full control over +transaction construction when desired. + +## Core features + +With `cooked-validators`, you can: +- Interact with smart contracts written in Plutus or any language that compiles +to [UPLC](https://plutonomicon.github.io/plutonomicon/uplc), such as +[Plutarch](https://github.com/Plutonomicon/plutarch-plutus) or +[Aiken](https://aiken-lang.org/), by loading contracts from bytestrings. +- Define transactions using a high-level, type-preserving data structure. +- Submit transactions for validation while the library automatically: + * fills in missing inputs and outputs, + * performs balancing, + * enforces minimum-Ada constraints, + * computes and attaches optimal collaterals and fees, + * automatically adds script witnesses, including from reference inputs. +- Construct sequences of transactions in a clear, implementation-independent + abstraction of the blockchain. +- Run transaction sequences in an emulated blockchain. +- Define modifications aware of the current blockchain state (tweaks), and apply + them to transactions just before submission. +- Compose and deploy tweaks on sequences of transactions using idioms inspired + by linear temporal logic. +- Deploy automated attacks on existing transaction sequences, such as datum + hijacking or double satisfaction attacks, to uncover vulnerabilities. +- Express expected outcomes on the result of running a trace in a precise and + declarative way, for example by: + * specifying the expected number of outcomes in case branching occurred, + * asserting exact error messages in case of failure, + * ensuring a specific event was triggered during the run, + * checking that some specific assets are present at a given address in the + final blockchain state. ## How to integrate `cooked-validators` in a project -To use `cooked-validators`, you need -- [GHC](https://www.haskell.org/ghc/download_ghc_9_6_6.html) version 9.6.6 -- [Cabal](https://www.haskell.org/cabal) version 3.10 or later - 1. `cooked-validators` depends on [cardano-haskell-packages](https://github.com/input-output-hk/cardano-haskell-packages) to get cardano-related packages and on @@ -59,40 +71,84 @@ the `packages` stanza. subdir: . ``` - where `myTag` is either a commit hash in the repo, or a tag, such as v7.0.0 + where `myTag` is either a commit hash in the repo, or a tag, such as v8.0.0 (see [available releases](https://github.com/tweag/cooked-validators/releases)). +3. Each release of `cooked-validators` is pinned to a specific version of + [`cardano-api`](https://github.com/IntersectMBO/cardano-api) which in turn + pins the versions of all other Cardano-related dependencies (including + Plutus). Make sure your project relies on the same version. + ## Example -1. Make your project - [depend](https://cabal.readthedocs.io/en/stable/getting-started.html#adding-dependencies) - on `cooked-validators` and `plutus-script-utils` - -2. 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 +This example shows how to create and validate a simple transaction that +transfers 10 Ada from wallet 1 to wallet 2, without manually handling fees or +balancing. + +1. Enter a Cabal read-eval-print-loop (with `cabal repl`) + +2. Import your required dependencies + ``` haskell > import Cooked > import qualified Plutus.Script.Utils.Value as Script - > printCooked . runMockChain . validateTxSkel $ - txSkelTemplate - { txSkelOuts = [wallet 2 `receives` Value (Script.ada 10)], - txSkelSigners = [wallet 1] - } - [...] - - UTxO state: - • pubkey wallet 1 - - Lovelace: 89_828_471 - - (×4) Lovelace: 100_000_000 - • pubkey wallet 2 - - Lovelace: 10_000_000 - - (×5) Lovelace: 100_000_000 - • pubkey wallet 3 - - (×5) Lovelace: 100_000_000 - • pubkey wallet 4 - - (×5) Lovelace: 100_000_000 - [...] + ``` + +3. Define a transaction which transfers 10 Ada from wallet 1 to wallet 2 + ``` haskell + let myTransaction = txSkelTemplate {txSkelOuts = [wallet 2 `receives` Value (Script.ada 10)], txSkelSignatories = txSkelSignatoriesFromList [wallet 1]} + ``` + +4. Send the transaction for validation, and request the printing of the run + ``` haskell + printCooked . runMockChain . validateTxSkel_ $ myTransaction + ``` + +5. Observe the log of the run, including: + - The original skeleton, and its balanced counterpart + - The associated fee and collaterals + - The final mockchain state, with every wallet's assets (notice the 10 ADA + payment owned by wallet 2) + - The value returned by the run (here `()` as we used `validateTxSkel_`) + ```haskell + 📖 MockChain run log: + ⁍ New raw skeleton submitted to the adjustment pipeline: + - Validity interval: (-∞ , +∞) + - Signatories: + - wallet 1 [balancing] + - Outputs: + - Pays to pubkey wallet 2 + - Lovelace: 10_000_000 + ⁍ New adjusted skeleton submitted for validation: + - Validity interval: (-∞ , +∞) + - Signatories: + - wallet 1 [balancing] + - Inputs: + - Spends #4480b35!3 from pubkey wallet 1 + - Redeemer () + - Lovelace: 100_000_000 + - Outputs: + - Pays to pubkey wallet 2 + - Lovelace: 10_000_000 + - Pays to pubkey wallet 1 + - Lovelace: 89_828_383 + - Fee: Lovelace: 171_617 + - No collateral required + ⁍ New transaction successfully validated: + - Transaction id: #c095342 + - Number of new outputs: 2 + ✅ UTxO state: + • pubkey wallet 1 + - Lovelace: 89_828_383 + - (×3) Lovelace: 100_000_000 + • pubkey wallet 2 + - Lovelace: 10_000_000 + - (×4) Lovelace: 100_000_000 + • pubkey wallet 3 + - (×4) Lovelace: 100_000_000 + • pubkey wallet 4 + - (×4) Lovelace: 100_000_000 + 🟢 Returned value: () ``` ## Documentation @@ -112,12 +168,35 @@ the `packages` stanza. automated balancing mechanism and associated options (including options revolving around fees and collaterals). -- The [CONWAY](doc/CONWAY.md) file describes the Conway features that are - currently supported by `cooked-validators`. - - The [OPTICS](doc/OPTICS.md) file describes our usage of optics to navigate our data structures. +## Blog posts + +Several blog posts have been written about `cooked-validators`. As the library +evolves, some code snippets in these posts may have become outdated. However, +the core philosophy remains unchanged, and these articles still provide valuable +insight into how to use the library. + +1. [An article](https://www.tweag.io/blog/2023-05-11-audit-smart-contract/) + explaining how we use `cooked-validators` to conduct smart contract audits. + +2. [An + article](https://www.tweag.io/blog/2025-02-20-transaction-generation-automation-with-cooked-validators/) + describing how transaction skeletons are built in `cooked-validators` and how + the library constructs complete transactions from them. + +3. [An + article](https://www.tweag.io/blog/2022-01-26-property-based-testing-of-monadic-code/) + presenting the original idea of using temporal modalities to modify sequences + of transactions. + + +4. [An article](https://www.tweag.io/blog/2022-10-14-ltl-attacks/) explaining + how [linear temporal + logic](https://en.wikipedia.org/wiki/Linear_temporal_logic) is used in + `cooked-validators` to deploy modifications over time. + ## Additional resources - We have a [repository](https://github.com/tweag/cooked-smart-contracts) of @@ -138,3 +217,10 @@ the `packages` stanza. - `cooked-validators` comes with a [template repository](https://github.com/tweag/cooked-template) which can be used to develop offchain code and/or audit code with the tool. + +## License + +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 +active development, and it comes _as is_ with no guarantees whatsoever. Check +the [license](LICENSE) for details. diff --git a/cooked-validators.cabal b/cooked-validators.cabal index a4f40571..fd4ae74a 100644 --- a/cooked-validators.cabal +++ b/cooked-validators.cabal @@ -5,7 +5,7 @@ cabal-version: 3.4 -- see: https://github.com/sol/hpack name: cooked-validators -version: 7.0.0 +version: 8.0.0 license: MIT license-file: LICENSE build-type: Simple diff --git a/flake.nix b/flake.nix index 4fa9ba99..2d743bce 100644 --- a/flake.nix +++ b/flake.nix @@ -4,8 +4,15 @@ inputs.pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; inputs.pre-commit-hooks.inputs.nixpkgs.follows = "nixpkgs"; - outputs = { self, nixpkgs, flake-utils, pre-commit-hooks }: - flake-utils.lib.eachDefaultSystem (system: + outputs = + { + self, + nixpkgs, + flake-utils, + pre-commit-hooks, + }: + flake-utils.lib.eachDefaultSystem ( + system: let pkgs = nixpkgs.legacyPackages.${system}; hpkgs = pkgs.haskell.packages.ghc96; @@ -15,21 +22,20 @@ ## is due to a bug where older processors (>= 10 years) ## would not be supported. This should not change anything ## on newer machines. This could be revised in the future. - blst-portable = pkgs.blst.overrideAttrs (_: _: { - buildPhase = '' - runHook preBuild - ./build.sh -shared -D__BLST_PORTABLE__ ${ - pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isWindows - "flavour=mingw64" - } - runHook postBuild - ''; - }); + blst-portable = pkgs.blst.overrideAttrs ( + _: _: { + buildPhase = '' + runHook preBuild + ./build.sh -shared -D__BLST_PORTABLE__ ${pkgs.lib.optionalString pkgs.stdenv.hostPlatform.isWindows "flavour=mingw64"} + runHook postBuild + ''; + } + ); pre-commit = pre-commit-hooks.lib.${system}.run { src = ./.; hooks = { - nixfmt-classic.enable = true; + nixfmt.enable = true; ormolu.enable = true; hpack.enable = true; }; @@ -45,75 +51,79 @@ ## for more information. }; }; - in { - formatter = pkgs.nixfmt-classic; + in + { + formatter = pkgs.nixfmt; - devShells = let - ## The minimal dependency set to build the project with `cabal`. - buildInputs = [ - blst-portable - pkgs.pkg-config - pkgs.glibcLocales - pkgs.zlib - pkgs.libsodium - pkgs.secp256k1 - pkgs.lmdb - hpkgs.ghc - hpkgs.cabal-install - ]; + devShells = + let + ## The minimal dependency set to build the project with `cabal`. + buildInputs = [ + blst-portable + pkgs.pkg-config + pkgs.glibcLocales + pkgs.zlib + pkgs.libsodium + pkgs.secp256k1 + pkgs.lmdb + hpkgs.ghc + hpkgs.cabal-install + ]; - ## Folders in which to find ".so" files - LD_LIBRARY_PATH = pkgs.lib.strings.makeLibraryPath [ - pkgs.xz - pkgs.zlib - pkgs.lmdb - pkgs.openssl_3_6 - pkgs.postgresql # For cardano-node-emulator - pkgs.openldap # For freer-extras‽ - pkgs.libsodium - pkgs.secp256k1 - pkgs.lmdb - blst-portable - ]; + ## Folders in which to find ".so" files + LD_LIBRARY_PATH = pkgs.lib.strings.makeLibraryPath [ + pkgs.xz + pkgs.zlib + pkgs.lmdb + pkgs.openssl_3_6 + pkgs.postgresql # For cardano-node-emulator + pkgs.openldap # For freer-extras‽ + pkgs.libsodium + pkgs.secp256k1 + pkgs.lmdb + blst-portable + ]; - LANG = "C.UTF-8"; + LANG = "C.UTF-8"; - in { - ci = pkgs.mkShell { - inherit buildInputs; - inherit LD_LIBRARY_PATH; - inherit LANG; - }; + in + { + ci = pkgs.mkShell { + inherit buildInputs; + inherit LD_LIBRARY_PATH; + inherit LANG; + }; - default = pkgs.mkShell { - buildInputs = buildInputs ++ [ - pkgs.hpack - pkgs.hlint - hpkgs.ormolu - hpkgs.haskell-language-server - ]; + default = pkgs.mkShell { + buildInputs = buildInputs ++ [ + pkgs.hpack + pkgs.hlint + hpkgs.ormolu + hpkgs.haskell-language-server + ]; - inherit LD_LIBRARY_PATH; - inherit LANG; + inherit LD_LIBRARY_PATH; + inherit LANG; - # In addition to the pre-commit hooks, this redefines a cabal - # command that gets rid of annoying "Writing: .....*.html" output - # when running cabal test. - shellHook = pre-commit.shellHook + '' - function cabal() { - if [ "$1" != "test" ]; then - command cabal "$@" - else - command cabal --test-option=--color=always "$@" | grep -vE --color=never "^Writing:.*html$" - fi - } - export -f cabal - ''; + # In addition to the pre-commit hooks, this redefines a cabal + # command that gets rid of annoying "Writing: .....*.html" output + # when running cabal test. + shellHook = pre-commit.shellHook + '' + function cabal() { + if [ "$1" != "test" ]; then + command cabal "$@" + else + command cabal --test-option=--color=always "$@" | grep -vE --color=never "^Writing:.*html$" + fi + } + export -f cabal + ''; + }; }; - }; checks = { inherit pre-commit; }; - }); + } + ); nixConfig = { extra-trusted-substituters = [ diff --git a/package.yaml b/package.yaml index 65265092..d3199ffd 100644 --- a/package.yaml +++ b/package.yaml @@ -2,7 +2,7 @@ verbatim: cabal-version: 3.4 name: cooked-validators -version: 7.0.0 +version: 8.0.0 dependencies: - QuickCheck