diff --git a/.github/Tutorial github actions.md b/.github/Tutorial github actions.md new file mode 100644 index 0000000..288aa91 --- /dev/null +++ b/.github/Tutorial github actions.md @@ -0,0 +1,261 @@ +# ๐Ÿงฐ **Automating Plutus Version Updates with GitHub Actions** + +## ๐Ÿ“‘ **Table of Contents** + +1. [๐Ÿš€ Introduction](#1-introduction) +2. [โš™๏ธ What is a GitHub Actions Workflow?](#2-what-is-a-github-actions-workflow) +3. [๐ŸŽฏ Why Use a Workflow for Plutus Version Updates?](#3-why-use-a-workflow-for-plutus-version-updates) +4. [๐Ÿ” How the โ€œBump Plutus Versionโ€ Workflow Works](#4-how-the-bump-plutus-version-workflow-works) + + * 4.1 [โฏ Triggering the Workflow](#41-triggering-the-workflow) + * 4.2 [๐Ÿงฑ The Workflow Jobs and Steps](#42-the-workflow-jobs-and-steps) +5. [๐Ÿ”€ When to Use Each Merge Method](#5-when-to-use-each-merge-method) +6. [๐Ÿ“œ Example Workflow YAML Explained](#6-example-workflow-yaml-explained) +7. [๐Ÿชœ Commit Strategies: Merge, Squash, and Rebase](#7-commit-strategies-merge-squash-and-rebase) +8. [๐Ÿงฉ Example Diagrams](#8-example-diagrams) +9. [๐Ÿ“š Glossary of Terms](#9-glossary-of-terms) + + +## ๐Ÿš€ **1. Introduction** + +Modern projects often depend on external libraries such as **Plutus** (Cardanoโ€™s smart contract toolkit). +Each time a new Plutus version is released, you may need to **update** your `.cabal`, `cabal.project`, and `flake.lock` files. + +Instead of updating these manually, you can **automate** the process using **GitHub Actions**, ensuring consistency, reproducibility, and zero manual errors. + + +## โš™๏ธ **2. What is a GitHub Actions Workflow?** + +A **GitHub Actions workflow** is an automated process (defined in YAML) that runs one or more **jobs** based on **triggers**. + +๐Ÿ”ธ Each workflow: + +* ๐Ÿ“ Lives in `.github/workflows/` +* ๐Ÿงฉ Defines tasks (build, test, publish, etc.) +* ๐Ÿ•น Runs automatically or on-demand on GitHubโ€™s servers + + +## ๐ŸŽฏ **3. Why Use a Workflow for Plutus Version Updates?** + +| ๐Ÿ’ก **Reason** | ๐Ÿงญ **Description** | +| ----------------------------- | ------------------------------------------------------------------------------ | +| โš™๏ธ **Automation** | Avoids manual edits and reduces human error when updating Plutus dependencies. | +| ๐Ÿ“ **Consistency** | Follows the same process every time โ€” edit โ†’ build โ†’ test โ†’ PR โ†’ merge. | +| ๐Ÿงพ **Traceability** | Each change is documented in a PR with commits and version tags. | +| ๐Ÿ”„ **Continuous Integration** | Automatically integrates changes once all tests and checks pass. | + + +## ๐Ÿ” **4. How the โ€œBump Plutus Versionโ€ Workflow Works** + +This workflow automates the **entire update pipeline** โ€” from editing files to creating a PR and merging it automatically. + + +### โฏ **4.1 Triggering the Workflow** + +```yaml +on: + workflow_dispatch: + inputs: + version: + description: Plutus Release Version (e.g. 1.26.0.0) + required: true +``` + +**๐ŸŽฌ What Happens:** + +* The workflow runs **manually** from the GitHub Actions tab. +* You must provide a **Plutus version number**, e.g. `1.26.0.0`. + +**๐Ÿงญ Why:** + +* Manual triggering ensures control โ€” you decide when to upgrade, preventing unwanted automatic updates. + + +### ๐Ÿงฑ **4.2 The Workflow Jobs and Steps** + +#### ๐Ÿงฎ Job: `bump-plutus-version` + +Runs on **Ubuntu** and performs a sequence of automated steps: + +| ๐Ÿชœ **Step** | โšก **Action** | ๐Ÿง  **Purpose** | +| ----------------------------- | ---------------------------------------------- | ---------------------------------------------------------------- | +| ๐Ÿงฐ **1. Checkout** | `actions/checkout@v4.1.1` | Pulls your repository into the workflow runner. | +| ๐Ÿงฉ **2. Change Versions** | `sed` shell commands | Updates `.cabal` and `cabal.project` files with the new version. | +| ๐Ÿงฑ **3. Install Nix** | `DeterminateSystems/nix-installer-action@main` | Installs **Nix** for reproducible dependency management. | +| ๐Ÿ”„ **4. Update Lock File** | `nix flake update` | Refreshes `flake.lock` for CHaP and Hackage sources. | +| ๐Ÿงพ **5. Create Pull Request** | `peter-evans/create-pull-request@v6.0.5` | Commits, pushes, and opens a PR with the updated files. | +| ๐Ÿค– **6. Enable Auto-Merge** | `peter-evans/enable-pull-request-automerge@v3` | Automatically merges the PR after checks succeed. | + + +## ๐Ÿ”€ **5. When to Use Each Merge Method** + +| ๐Ÿ”ง **Merge Method** | ๐Ÿ•’ **When to Use** | โœ… **Pros** | โš ๏ธ **Cons** | +| ------------------- | ------------------------------------------------- | ------------------------------------ | -------------------------------- | +| ๐Ÿ”— **Merge** | When preserving full commit history is important. | Keeps all commit history. | Can clutter the commit graph. | +| ๐Ÿ“ฆ **Squash** | For automated or small PRs like version bumps. | Creates a single clean commit. | Loses detailed commit breakdown. | +| ๐Ÿช„ **Rebase** | When syncing local branches with `main`. | Produces a linear, readable history. | Risky on shared branches. | + +Your workflow uses: + +```yaml +merge-method: squash +``` + +โœ… Ideal for **automated updates** โ€” keeps the history clean and easy to review. + + +## ๐Ÿ“œ **6. Example Workflow YAML Explained** + +```yaml +name: Bump Plutus Version + +on: + workflow_dispatch: + inputs: + version: + description: Plutus Release Version (e.g. 1.26.0.0) + required: true + +jobs: + bump-plutus-version: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.1 + + - name: Change Plutus Versions + run: | + CURRENT_DATE=$(date +"%Y-%m-%dT%H:%M:%SZ") + sed -i "s/\(hackage.haskell.org \).*\$/\1$CURRENT_DATE/" cabal.project + sed -i "s/\(cardano-haskell-packages \).*\$/\1$CURRENT_DATE/" cabal.project + + PLUTUS_VERSION=${{ github.event.inputs.version }} + sed -i "s/\(plutus-core \).*\$/\1\^>=$PLUTUS_VERSION/" "plinth-template.cabal" + sed -i "s/\(plutus-ledger-api \).*\$/\1\^>=$PLUTUS_VERSION/" "plinth-template.cabal" + sed -i "s/\(plutus-tx \).*\$/\1\^>=$PLUTUS_VERSION/" "plinth-template.cabal" + sed -i "s/\(plutus-tx-plugin \).*\$/\1\^>=$PLUTUS_VERSION/" "plinth-template.cabal" + + - uses: DeterminateSystems/nix-installer-action@main + - run: nix flake update CHaP hackage --accept-flake-config + + - uses: peter-evans/create-pull-request@v6.0.5 + with: + branch: "bump-plutus-${{ github.event.inputs.version }}" + title: Bump Plutus Version to ${{ github.event.inputs.version }} + commit-message: Bump Plutus Version to ${{ github.event.inputs.version }} + delete-branch: true + token: ${{ secrets.GITHUB_TOKEN }} + + - uses: peter-evans/enable-pull-request-automerge@v3 + with: + pull-request-number: ${{ steps.cpr.outputs.pull-request-number }} + merge-method: squash + token: ${{ secrets.GITHUB_TOKEN }} +``` + + +## ๐Ÿชœ **7. Commit Strategies: Merge, Squash, and Rebase** + +### ๐Ÿ”— **Merge (Default)** + +```bash +git merge feature-branch +``` + +Creates a *merge commit* connecting both branches. + +``` +A---B---C---M + \ / + D---E +``` + + +### ๐Ÿ“ฆ **Squash (Used Here)** + +```bash +git merge --squash feature-branch +git commit -m "Bump Plutus version" +``` + +Result: + +``` +A---B---C---S +``` + +Single clean commit โ€” perfect for automation. + + +### ๐Ÿช„ **Rebase** + +```bash +git rebase main +``` + +Applies commits from your branch **on top of main**. + +``` +Before: +A---B---C + \ + D---E + +After: +A---B---C---D'---E' +``` + + +## ๐Ÿงฉ **8. Example Diagrams** + +### ๐Ÿ”ง **Workflow Overview** + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Manual trigger (version) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Update cabal & flake.lock โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Create branch + commit โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Open Pull Request (PR) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†“ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Enable Auto-Merge (squash) โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ†“ +โœ… PR merged โ†’ main branch updated +``` + + +## ๐Ÿ“š **9. Glossary of Terms** + +| ๐Ÿงฉ **Term** | ๐Ÿง  **Meaning** | +| ------------------------ | ---------------------------------------------------------- | +| ๐Ÿฆพ **GitHub Actions** | CI/CD platform built into GitHub for automation. | +| โš™๏ธ **Workflow** | A YAML-defined automation pipeline. | +| ๐Ÿงฑ **Job** | A group of steps executed on a single virtual environment. | +| ๐Ÿชœ **Step** | A single shell command or GitHub action inside a job. | +| ๐ŸงŠ **Nix** | Reproducible package manager used for Cardano projects. | +| ๐Ÿ“ฆ **Flake** | A Nix configuration file describing project inputs. | +| ๐Ÿ“ **cabal.project** | File defining Haskell build dependencies. | +| ๐Ÿ”€ **PR (Pull Request)** | A request to merge code changes between branches. | +| ๐Ÿค– **Auto-Merge** | Automatically merges a PR once checks pass. | +| ๐Ÿ“œ **Squash** | Combines multiple commits into one before merging. | + + +## โœ… **Summary** + +This workflow: + +* โš™๏ธ Automates **Plutus dependency updates**. +* ๐Ÿงน Keeps your main branch clean using **squash merges**. +* ๐Ÿ” Uses **GitHub Actions** and **Nix** for reproducibility. +* ๐Ÿ’ผ Saves developer time with automatic PR creation and merging. + diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..cc81196 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,267 @@ +# Plinth Template - Plutus Smart Contract Development + +**ALWAYS follow these instructions first and only fallback to search or additional context gathering if the information here is incomplete or found to be in error.** + +This is a template repository for Plutus smart contract development using Haskell. It provides example auction smart contracts and tools for generating Plutus script blueprints. + +## Working Effectively + +### Prerequisites and Setup +Use one of these development environments (listed in order of preference): + +#### Option 1: Nix (Recommended) +- Install and configure Nix following [these instructions](https://github.com/input-output-hk/iogx/blob/main/doc/nix-setup-guide.md) +- Enter the development shell: `nix develop` +- **CRITICAL**: Initial Nix setup can take 30-60 minutes for downloading and building dependencies. NEVER CANCEL. Set timeout to 90+ minutes. + +#### Option 2: Docker/Devcontainer +- Use the provided devcontainer: `ghcr.io/input-output-hk/devx-devcontainer:x86_64-linux.ghc96-iog` +- For VSCode: Install Dev Containers extension and open project in devcontainer +- For standalone Docker: + ```bash + docker run \ + -v /path/to/your-project:/workspaces/plinth-template \ + -w /workspaces/plinth-template \ + -it ghcr.io/input-output-hk/devx-devcontainer:x86_64-linux.ghc96-iog + ``` + +#### Option 3: Manual Setup (Not Recommended) +- Requires GHC 9.6.x and Cabal 3.8+ +- Follow [cardano-node installation instructions](https://developers.cardano.org/docs/get-started/cardano-node/installing-cardano-node/) + +### Essential Build Commands + +#### Bootstrap and Build +1. **Update package index** (requires network access to CHaP repository): + ```bash + cabal update + ``` + - **TIMING**: Usually takes 1-3 minutes + - **NOTE**: Requires access to `chap.intersectmbo.org`. If network access is limited, this step may fail with DNS resolution errors. + +2. **Build all components**: + ```bash + cabal build all + ``` + - **CRITICAL**: Build time ranges from 15-45 minutes depending on cache state. NEVER CANCEL. Set timeout to 60+ minutes. + - **NEVER CANCEL**: First builds take significantly longer as dependencies are compiled. + +3. **Build specific executables**: + ```bash + cabal build gen-auction-validator-blueprint + cabal build gen-minting-policy-blueprint + ``` + +#### Docker-based Build (Alternative) +If local environment has issues, use the Docker approach from CI: +```bash +docker run \ + -v ./.:/workspaces/plinth-template \ + -w /workspaces/plinth-template \ + -i ghcr.io/input-output-hk/devx-devcontainer:x86_64-linux.ghc96-iog \ + bash -ic "cabal update && cabal build all" +``` +- **CRITICAL**: Total time including Docker image pull: 10-50 minutes. NEVER CANCEL. Set timeout to 75+ minutes. + +## Testing and Validation + +### No Unit Tests Available +- This template repository does not include automated test suites +- Validation is done through successful compilation and blueprint generation + +### Manual Validation Scenarios +After making any changes, ALWAYS perform these validation steps: + +#### 1. Compilation Validation +```bash +cabal build all +``` +Ensure all components compile without errors. + +#### 2. Blueprint Generation Validation +```bash +# Generate auction validator blueprint +cabal run gen-auction-validator-blueprint -- auction-validator.json + +# Generate minting policy blueprint +cabal run gen-minting-policy-blueprint -- minting-policy.json + +# Verify generated files +ls -la auction-validator.json minting-policy.json +head -20 auction-validator.json # Should show JSON with validator metadata +``` +- **Expected Output**: JSON blueprint files should be created successfully in the current directory +- **File Size**: Each blueprint file should be several KB (typically 2-10 KB) +- **Content Validation**: Files should contain JSON with fields like "contractId", "preamble", "validators" +- **Example Content**: Blueprint should include "auction-validator" contract ID and Plutus script metadata + +#### 3. Smart Contract Code Validation +- Review changes to `src/AuctionValidator.hs` and `src/AuctionMintingPolicy.hs` +- Ensure Plutus compiler pragmas are preserved (essential for on-chain compilation): + ```haskell + {-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:target-version=1.0.0 #-} + {-# OPTIONS_GHC -fno-unbox-strict-fields #-} + ``` +- Verify that any changes maintain the validator logic integrity +- Check that Template Haskell compilation markers remain intact: + ```haskell + $$(PlutusTx.compile [||auctionUntypedValidator||]) + ``` + +#### 4. Docker Environment Validation +```bash +# Test Docker environment has correct GHC version +docker run \ + -v ./.:/workspaces/plinth-template \ + -w /workspaces/plinth-template \ + -i ghcr.io/input-output-hk/devx-devcontainer:x86_64-linux.ghc96-iog \ + bash -ic "ghc --version" +``` +- **Expected Output**: "The Glorious Glasgow Haskell Compilation System, version 9.6.6" + +## Linting and Code Quality + +### Available Linting Tools +The project includes configuration for: +- **HLint**: Haskell linter (`.hlint.yaml` - currently empty, uses defaults) +- **stylish-haskell**: Code formatter (`.stylish-haskell.yaml`) +- **fourmolu**: Alternative formatter (via Nix environment) + +### Pre-commit Validation +```bash +# Format code (available in Nix environment) +stylish-haskell --config .stylish-haskell.yaml -i src/*.hs app/*.hs + +# Lint code (available in Nix environment) +hlint --hint .hlint.yaml src/ app/ + +# Alternative: Use fourmolu for formatting (Nix environment) +fourmolu --mode inplace src/ app/ +``` + +**IMPORTANT**: These tools are primarily available in the Nix environment. In Docker/manual setups, they may not be available. + +## Common Issues and Workarounds + +## Common Issues and Workarounds + +### Network Connectivity Issues +- **Problem**: `cabal update` fails with "Could not resolve host: chap.intersectmbo.org" +- **Root Cause**: CHaP (Cardano Haskell Packages) repository access required for Plutus dependencies +- **Primary Solution**: Use Docker-based build which may have better network access +- **Alternative**: Use Nix environment which may handle repository access more reliably +- **Document If Persistent**: If consistent failures occur, note in comments that CHaP access is required for builds + +### GHC Version Compatibility +- **Required**: GHC 9.6.x specifically (not 9.10.x or 9.12.x) +- **Problem**: System may have different GHC version installed +- **Solution**: Use Nix (`nix develop`) or Docker environments which provide correct GHC 9.6.6 +- **Verification**: Run `ghc --version` in your environment - should show 9.6.x + +### Build Cache Issues +- **Problem**: Builds taking excessively long (>60 minutes) +- **Solution**: Remove build cache and rebuild: `rm -rf dist-newstyle && cabal build all` +- **Prevention**: Use consistent build environment (Nix or Docker) to maintain cache + +### Permission Denied Errors +- **Problem**: `cabal update` fails with "permission denied" on cache directories +- **Common Causes**: Running in restricted environments or mixed user permissions +- **Solution**: Use Docker environment which isolates filesystem permissions + +### Docker Image Pull Issues +- **Problem**: Docker image pull hangs or fails +- **Solution**: Use explicit image pull: `docker pull ghcr.io/input-output-hk/devx-devcontainer:x86_64-linux.ghc96-iog` +- **Timing**: Initial image pull is ~2-5 GB, takes 5-15 minutes on good connections + +## Common Tasks and Quick Reference + +### Repo Exploration (Reference these outputs instead of running commands repeatedly) + +#### Repository Root Structure +``` +ls -la +total 144 +drwxr-xr-x 8 runner docker 4096 . +drwxr-xr-x 3 runner docker 4096 .. +drwxr-xr-x 2 runner docker 4096 .devcontainer +drwxr-xr-x 7 runner docker 4096 .git +drwxr-xr-x 4 runner docker 4096 .github +-rw-r--r-- 1 runner docker 80 .gitignore +-rw-r--r-- 1 runner docker 0 .hlint.yaml +-rw-r--r-- 1 runner docker 17675 .stylish-haskell.yaml +-rw-r--r-- 1 runner docker 69 CHANGELOG.md +-rw-r--r-- 1 runner docker 45 CODEOWNERS.md +-rw-r--r-- 1 runner docker 5522 CODE_OF_CONDUCT.md +-rw-r--r-- 1 runner docker 162 CONTRIBUTING.md +-rw-r--r-- 1 runner docker 170 DESCRIPTION.md +-rw-r--r-- 1 runner docker 11356 LICENSE.md +-rw-r--r-- 1 runner docker 560 NOTICE.md +-rw-r--r-- 1 runner docker 4361 README.md +-rw-r--r-- 1 runner docker 875 SECURITY.md +drwxr-xr-x 2 runner docker 4096 app +-rw-r--r-- 1 runner docker 959 cabal.project +-rw-r--r-- 1 runner docker 23035 flake.lock +-rw-r--r-- 1 runner docker 1193 flake.nix +drwxr-xr-x 2 runner docker 4096 nix +-rw-r--r-- 1 runner docker 1196 plinth-template.cabal +drwxr-xr-x 2 runner docker 4096 src +``` + +#### Source Code Files +``` +ls -la src/ app/ +app/: +GenAuctionValidatorBlueprint.hs # Executable: Generates auction validator blueprint +GenMintingPolicyBlueprint.hs # Executable: Generates minting policy blueprint + +src/: +AuctionMintingPolicy.hs # Smart contract: Token minting policy +AuctionValidator.hs # Smart contract: Main auction validator logic +``` + +#### Build Configuration Summary +- **Project Name**: plinth-template +- **GHC Version**: 9.6.x (enforced by Nix/Docker environments) +- **Cabal Version**: 3.8+ recommended +- **Dependencies**: Plutus libraries (plutus-core, plutus-ledger-api, plutus-tx) +- **Repository Access**: Requires CHaP (chap.intersectmbo.org) for Plutus dependencies + +## Repository Structure and Key Files + +### Important Directories +``` +โ”œโ”€โ”€ src/ # Haskell source files +โ”‚ โ”œโ”€โ”€ AuctionValidator.hs # Main auction smart contract +โ”‚ โ””โ”€โ”€ AuctionMintingPolicy.hs # Token minting policy +โ”œโ”€โ”€ app/ # Executable applications +โ”‚ โ”œโ”€โ”€ GenAuctionValidatorBlueprint.hs # Blueprint generator +โ”‚ โ””โ”€โ”€ GenMintingPolicyBlueprint.hs # Minting policy blueprint +โ”œโ”€โ”€ nix/ # Nix configuration +โ”œโ”€โ”€ .devcontainer/ # Docker devcontainer setup +โ””โ”€โ”€ .github/workflows/ # CI pipeline definitions +``` + +### Key Configuration Files +- `plinth-template.cabal`: Project dependencies and build configuration +- `cabal.project`: Cabal project settings and package repositories +- `flake.nix`: Nix development environment definition +- `.devcontainer/devcontainer.json`: Docker development environment + +### After Making Changes +1. **ALWAYS** build and validate using the steps above +2. **ALWAYS** test blueprint generation for affected smart contracts +3. **NEVER** commit without ensuring compilation succeeds +4. Document any new dependencies or build requirements in these instructions + +## Timing Expectations Summary + +| Operation | Expected Time | Timeout Setting | Critical Notes | +|-----------|---------------|-----------------|----------------| +| `nix develop` (first time) | 30-60 minutes | 90+ minutes | NEVER CANCEL - Downloads entire toolchain | +| `cabal update` | 1-3 minutes | 10 minutes | Requires CHaP network access | +| `cabal build all` (first time) | 15-45 minutes | 60+ minutes | NEVER CANCEL - Compiles all dependencies | +| `cabal build all` (cached) | 2-10 minutes | 15 minutes | Much faster with existing cache | +| Docker image pull | 5-15 minutes | 30 minutes | One-time download | +| Blueprint generation | 30 seconds - 2 minutes | 5 minutes | Fast once dependencies built | + +**CRITICAL REMINDER**: Plutus/Cardano builds are notoriously slow. Patience is essential. NEVER CANCEL long-running build operations. \ No newline at end of file diff --git a/CODEOWNERS.md b/CODEOWNERS.md index 19cc976..1fdd8bc 100644 --- a/CODEOWNERS.md +++ b/CODEOWNERS.md @@ -1,3 +1,4 @@ # Plutus Tx Template Codeowners -* @zeme-wana \ No newline at end of file +* @zeme-wana +* @besiwims \ No newline at end of file diff --git a/Tutorial.md b/ProjectTutorial.md similarity index 99% rename from Tutorial.md rename to ProjectTutorial.md index c61fe2c..e0df55d 100644 --- a/Tutorial.md +++ b/ProjectTutorial.md @@ -9,10 +9,6 @@ 7. [๐Ÿงช Property-Based Testing](#7-property-based-testing) 8. [๐Ÿ“– Glossary](#8-glossary) ---checked 15-09-2025 - ---- - # 1. ๐Ÿ“ฆ Environment Setup Before diving into code, ensure your development environment is configured properly: @@ -193,3 +189,9 @@ Add a second test suite in `.cabal` if desired. | **AssocMap** | Internal map type used by Plutus for associating keys to values in contexts (`Datum`, etc.). | | **Hspec** | Haskell testing framework for behaviorโ€driven development. | | **QuickCheck** | Library for propertyโ€based testing in Haskell. | + + +Coxygen Global +Developer +Matimba Mtileni +Balogun Muiz, Dolapo. diff --git a/README.md b/README.md index 214d0d6..0944673 100644 --- a/README.md +++ b/README.md @@ -1,156 +1,250 @@ -# ๐Ÿ“ฆ Auction Validator Project - -### Updated by Coxygen Global - Bernard Sibanda - -## ๐Ÿ“‘ Table of Contents - -1. โš™๏ธ Project Overview -2. โš™๏ธ Environment Setup -3. ๐Ÿ“‚ Directory Structure -4. ๐Ÿ› ๏ธ Installation & Build -5. ๐Ÿ”ฌ Testing -6. ๐Ÿงช Property-Based Testing -7. ๐Ÿš€ Usage -8. ๐Ÿ“– Glossary - ---- - -## 1. โš™๏ธ Project Overview - -This repository contains a Plutus-based Auction Validator smart contract along with tooling to generate Blueprints and comprehensive test suites. It is part of the **Plinth Template** for teaching on-chain development on Cardano. - -## 2. โš™๏ธ Environment Setup - -![image](https://github.com/user-attachments/assets/5e920e6a-4189-4917-b9ad-b31977e0d81b) - -![image](https://github.com/user-attachments/assets/92f0a394-d7da-44c6-b15a-9068efe7f4c3) - -![image](https://github.com/user-attachments/assets/4793940e-518a-4893-b3f2-1300d488bf65) - -Follow these steps to prepare your environment: +# ๐Ÿงญ 1. Auction Validator Project + +Updated by **Coxygen Global โ€“ Bernard Sibanda** + +> ๐Ÿ’ก Plinth currently supports **GHC 9.6.x** and **Cabal 3.8+** (recommended). + +## ๐Ÿ“‘ 2. Table of Contents + +1. [๐Ÿงญ Auction Validator Project](#-1-auction-validator-project) +2. [๐Ÿ“‘ Table of Contents](#-2-table-of-contents) +3. [โš™๏ธ Overview](#๏ธ-3-overview) +4. [โœจ Features](#-4-features) +5. [๐Ÿ—๏ธ Architecture](#๏ธ-5-architecture) +6. [๐Ÿ“‹ Prerequisites](#-6-prerequisites) +7. [โšก Quick Start](#-7-quick-start) +8. [๐Ÿ› ๏ธ Installation & Build](#๏ธ-8-installation--build) +9. [๐Ÿงช Testing](#-9-testing) +10. [๐Ÿ” Property-Based Testing](#-10-property-based-testing) +11. [๐Ÿš€ Usage](#-11-usage) +12. [โš™๏ธ Configuration](#๏ธ-12-configuration) +13. [๐Ÿ“‚ Directory Structure](#-13-directory-structure) +14. [๐Ÿ”„ Development Workflow](#-14-development-workflow) +15. [๐Ÿค– CI/CD (Optional)](#-15-cicd-optional) +16. [๐Ÿงฏ Troubleshooting](#-16-troubleshooting) +17. [๐Ÿ’ฌ FAQ](#-17-faq) +18. [๐Ÿค Contributing](#-18-contributing) +19. [๐Ÿ“œ License](#-19-license) +20. [๐Ÿ“– Glossary](#-20-glossary) + +## โš™๏ธ 3. Overview + +This repository contains a **Plutus-based Auction Validator** and **Minting Policy**. +It includes **Blueprint generators**, **property-based tests**, and **development tooling**. +The project is based on the **Plinth Template** for teaching on-chain Cardano smart contract development. + +## โœจ 4. Features + +- ๐Ÿช™ On-chain **Auction Validator** (bid, close, payout logic) +- ๐Ÿงฑ **Minting Policy** for auction tokens +- ๐Ÿงฉ **Blueprint** generation tools for off-chain deployment +- ๐Ÿงช **Unit tests** and **property-based tests** +- ๐Ÿงฐ **Nix** and **Devcontainer** support for reproducibility +- ๐Ÿ”„ Modular Cabal/Nix structure for teaching and reuse + +## ๐Ÿ—๏ธ 5. Architecture + +| Layer | Purpose | +| --------------------- | ----------------------------------------- | +| ๐Ÿง  **On-chain Logic** | Implemented in `src/AuctionValidator.hs`. | +| ๐Ÿงฐ **Executables** | Blueprint generators under `app/`. | +| ๐Ÿงช **Tests** | Unit + property suites under `test/`. | +| โš™๏ธ **Infrastructure** | Cabal/Nix build environment. | + +## ๐Ÿ“‹ 6. Prerequisites + +- **GHC** `9.6.x` +- **Cabal** `3.8+` +- **Nix** _(optional)_ +- **Docker / Devcontainer** _(optional)_ + +> ๐Ÿงฉ If not using Nix, install via [Haskell Platform](https://www.haskell.org/platform/) or `ghcup`. + +## โšก 7. Quick Start ```bash -# 1. Enter the Nix shell (requires Nix installed) +# 1๏ธโƒฃ Enter Nix shell (recommended) nix-shell -# 2. Update Cabal package index +# 2๏ธโƒฃ Update Cabal package index cabal update -# 3. Ensure project dependencies are available +# 3๏ธโƒฃ Build project and tests cabal build --enable-tests -``` - -> **Note:** If you do not use Nix, skip the `nix-shell` step and ensure you have GHC and Cabal installed via the Haskell Platform. -## 3. ๐Ÿ“‚ Directory Structure - -```text -auction/ # Project root -โ”œโ”€โ”€ app/ # Executables for Blueprint generation -โ”‚ โ”œโ”€โ”€ GenAuctionValidatorBlueprint.hs -โ”‚ โ””โ”€โ”€ GenMintingPolicyBlueprint.hs -โ”œโ”€โ”€ src/ # On-chain library modules -โ”‚ โ””โ”€โ”€ AuctionValidator.hs -โ”œโ”€โ”€ test/ # Test suite files -โ”‚ โ”œโ”€โ”€ AuctionValidatorSpec.hs # Unit tests -โ”‚ โ”œโ”€โ”€ AuctionMintingPolicySpec.hs # Minting policy tests -โ”‚ โ””โ”€โ”€ AuctionValidatorProperties.hs # QuickCheck properties -โ”œโ”€โ”€ default.nix # Nix definition (if applicable) -โ”œโ”€โ”€ shell.nix # Nix shell entry (if applicable) -โ”œโ”€โ”€ auction.cabal # Cabal project configuration -โ”œโ”€โ”€ cabal.project # Root project settings -โ””โ”€โ”€ cabal.project.local # Local overrides (e.g., tests: True) +# 4๏ธโƒฃ Run test suite +cabal test ``` -## 4. ๐Ÿ› ๏ธ Installation & Build +## ๐Ÿ› ๏ธ 8. Installation & Build -1. **Enter Nix shell (optional)**: +1. **Enter Nix shell (optional)** ```bash nix-shell ``` -2. **Update Cabal index**: + +2. **Update Cabal index** ```bash cabal update ``` -3. **Install dependencies & build**: + +3. **Build project** ```bash cabal build --enable-tests ``` -4. **Generate Blueprints**: + +4. **Generate Blueprints** ```bash cabal run gen-auction-validator-blueprint -- ./blueprint-auction.json cabal run gen-minting-policy-blueprint -- ./blueprint-minting.json ``` -## 5. ๐Ÿ”ฌ Testing +## ๐Ÿงช 9. Testing -### Run Unit Tests +### ๐Ÿงฉ Unit Tests ```bash cabal test auction-tests ``` -### Run All Tests +### ๐Ÿ” Run All Tests ```bash cabal test ``` -* **`auction-tests`**: Unit tests for the Auction Validator. -* **`minting-tests`**: Unit tests for the Minting Policy (if configured). +## ๐Ÿ” 10. Property-Based Testing + +Example Cabal config: + +```cabal +test-suite auction-properties + type: exitcode-stdio-1.0 + main-is: AuctionValidatorProperties.hs + hs-source-dirs: test + build-depends: + base >=4.7 && <5 + , QuickCheck + , plutus-ledger-api + , plutus-tx + , scripts + default-language: Haskell2010 +``` + +Run: + +```bash +cabal test auction-properties +``` + +## ๐Ÿš€ 11. Usage -## 6. ๐Ÿงช Property-Based Testing +1. **Customize parameters** in `app/GenAuctionValidatorBlueprint.hs` + (e.g. seller, token name, min bid, end time). +2. **Generate blueprint JSONs** via Cabal commands. +3. **Deploy** the compiled Plutus scripts to Cardano network. +4. **Verify** with unit and property-based tests. -To verify invariants using QuickCheck: +## โš™๏ธ 12. Configuration -1. Add a QuickCheck test suite entry in your `.cabal`: +- Project settings: `cabal.project`, `auction.cabal` +- Local overrides: `cabal.project.local` +- To ignore local configs: - ```cabal - test-suite auction-properties - type: exitcode-stdio-1.0 - main-is: AuctionValidatorProperties.hs - hs-source-dirs: test - build-depends: - base >=4.7 && <5, - , scripts, - , QuickCheck, - , plutus-ledger-api, - , plutus-tx, - , test-framework - default-language: Haskell2010 - ``` -2. Run the property suite: + ```bash + echo "cabal.project.local" >> .gitignore + ``` - ```bash - cabal test auction-properties - ``` +## ๐Ÿ“‚ 13. Directory Structure + +```text +auction/ +โ”œโ”€โ”€ app/ # Executables (Blueprint generators) +โ”‚ โ”œโ”€โ”€ GenAuctionValidatorBlueprint.hs +โ”‚ โ””โ”€โ”€ GenMintingPolicyBlueprint.hs +โ”œโ”€โ”€ src/ # On-chain modules +โ”‚ โ””โ”€โ”€ AuctionValidator.hs +โ”œโ”€โ”€ test/ # Tests +โ”‚ โ”œโ”€โ”€ AuctionValidatorSpec.hs +โ”‚ โ”œโ”€โ”€ AuctionMintingPolicySpec.hs +โ”‚ โ””โ”€โ”€ AuctionValidatorProperties.hs +โ”œโ”€โ”€ default.nix +โ”œโ”€โ”€ shell.nix +โ”œโ”€โ”€ auction.cabal +โ”œโ”€โ”€ cabal.project +โ””โ”€โ”€ cabal.project.local +``` + +## ๐Ÿ”„ 14. Development Workflow -## 7. ๐Ÿš€ Usage +1. ๐ŸŒฟ Create a new branch +2. ๐Ÿงฑ Make modular commits +3. โœ… Run `cabal test` +4. ๐Ÿงน Format and lint +5. ๐Ÿ”€ Merge via PR +6. ๐Ÿท๏ธ Tag releases -* **Deploy** your compiled Plutus script on a Cardano network by submitting the generated blueprint JSON via your deployment tooling. -* **Customize** `AuctionParams` (seller, currency symbol, token name, minimum bid, end time) in `GenAuctionValidatorBlueprint.hs` before generating the blueprint. -* **Extend** the contract logic in `src/AuctionValidator.hs` and re-run tests to ensure correctness. +## ๐Ÿค– 15. CI/CD (Optional) -## 8. ๐Ÿ“– Glossary +- ๐Ÿงฐ **Build & Test:** `cabal build --enable-tests && cabal test` +- ๐Ÿ“ฆ **Artifacts:** Upload blueprint JSONs, logs, etc. +- โšก **Cache:** Use Cabal store caching for faster pipelines. -| Term | Description | -| ----------------- | ---------------------------------------------------------------------------------- | -| **Cabal** | Haskellโ€™s package manager and build tool. | -| **GHC** | The Glasgow Haskell Compiler. | -| **Plutus** | Cardanoโ€™s on-chain smart contract platform. | -| **TxInfo** | Metadata about a transaction passed to a Plutus validator. | -| **ScriptContext** | Context for script execution, including `TxInfo` and `ScriptPurpose`. | -| **AssocMap** | Plutusโ€™s internal map type for associating keys to values (e.g., Datum, Redeemer). | -| **hspec** | A behavior-driven testing framework for Haskell. | -| **QuickCheck** | A property-based testing library for Haskell. | -| **Blueprint** | JSON representation of a Plutus script and its parameters, for off-chain tooling. | +## ๐Ÿงฏ 16. Troubleshooting ---- +| Issue | Fix | +| ------------------ | ----------------------------------------------------------- | +| Build fails | `cabal clean && cabal update && cabal build --enable-tests` | +| Wrong compiler | Use `ghc --version` โ†’ must be 9.6.x | +| Nix errors | Try `nix develop` or update flakes | +| Devcontainer fails | Check Docker Desktop / VSCode extensions | -*Updated by Coxygen Global - Bernard Sibanda* -*Date: 15 September 2025* +## ๐Ÿ’ฌ 17. FAQ +**Q:** Do I need Nix? +**A:** No. Itโ€™s optional but ensures reproducibility. + +**Q:** Where do I change auction parameters? +**A:** In `GenAuctionValidatorBlueprint.hs`. + +**Q:** How do I run only property tests? +**A:** `cabal test auction-properties` + +## ๐Ÿค 18. Contributing + +1. ๐Ÿด Fork this repo +2. ๐ŸŒฟ Branch from `main` +3. ๐Ÿงฉ Add code + tests +4. ๐Ÿ” Verify with `cabal test` +5. ๐Ÿ“จ Open a Pull Request + +## ๐Ÿ“œ 19. License + +Released under the **MIT License** (or your organizationโ€™s chosen license). +See the `LICENSE` file for details. + +## ๐Ÿ“– 20. Glossary + +| Icon | Term | Description | +| ---- | ----------------- | ------------------------------------------------------ | +| โš™๏ธ | **Cabal** | Haskellโ€™s package manager and build tool. | +| ๐Ÿง  | **GHC** | The Glasgow Haskell Compiler. | +| ๐Ÿ’Ž | **Plutus** | Cardanoโ€™s smart contract platform. | +| ๐Ÿงพ | **Blueprint** | JSON representation of a Plutus script and parameters. | +| ๐Ÿงฉ | **ScriptContext** | Context for execution (`TxInfo`, `ScriptPurpose`). | +| ๐Ÿ”— | **TxInfo** | Transaction metadata passed to the validator. | +| ๐Ÿงช | **QuickCheck** | Haskell property-based testing framework. | +| ๐Ÿงญ | **hspec** | Behavior-driven testing framework for Haskell. | +| ๐Ÿ—บ๏ธ | **AssocMap** | Plutus internal keyโ€“value map type. | + +๐Ÿง  _Maintained by Coxygen Global โ€“ Bernard Sibanda_ +๐Ÿ“… _Last updated: 15 September 2025_ +_Developer: Balogun Muiz Dolapo_ + +``` +olivier Mwatsimulamo developer +``` diff --git a/TutorialAuctionMintingPolicySourceCode.md b/TutorialAuctionMintingPolicySourceCode.md deleted file mode 100644 index 5ef516f..0000000 --- a/TutorialAuctionMintingPolicySourceCode.md +++ /dev/null @@ -1,215 +0,0 @@ -**Table of Contents** - -1. [๐Ÿ”ฐ Introduction](#1-introduction) -2. [๐Ÿ“‹ Prerequisites](#2-prerequisites) -3. [๐Ÿš€ Starting in ](#3-starting-in-cabal-repl)[`cabal repl`](#3-starting-in-cabal-repl) -4. [๐Ÿ“ฆ Core Data Types](#4-core-data-types) - - * 4.1 [AuctionMintingParams](#41-auctionmintingparams) - * 4.2 [AuctionMintingRedeemer](#42-auctionmintingredeemer) -5. [๐Ÿ› ๏ธ Writing ](#5-writing-auctiontypedmintingpolicy)[`auctionTypedMintingPolicy`](#5-writing-auctiontypedmintingpolicy) -6. [๐Ÿ“œ Compiling the Minting Policy Script](#6-compiling-the-minting-policy-script) -7. [โœ๏ธ Writing Unit Tests](#7-writing-unit-tests) - - * 7.1 [Mocking ](#71-mocking-scriptcontext)[`ScriptContext`](#71-mocking-scriptcontext) - * 7.2 [Example Test Case](#72-example-test-case) - * 7.3 [Running Tests](#73-running-tests) -8. [๐Ÿงช Property-Based Testing](#8-property-based-testing) -9. [๐Ÿ”ญ Next Steps](#9-next-steps) -10. [๐Ÿ“– Glossary](#10-glossary) - ---- - -## 1. ๐Ÿ”ฐ Introduction - -This tutorial guides Haskell beginners through creating and testing a Plutus Auction Minting Policy. Youโ€™ll learn how to: - -* Define on-chain parameters and redeemer types -* Implement `auctionTypedMintingPolicy` logic -* Compile the policy to Plutus Core -* Write unit and property-based tests - -## 2. ๐Ÿ“‹ Prerequisites - -* Haskell basics (records, pattern matching) -* GHC and Cabal installed -* Plutus libraries available (via Nix or direct Cabal setup) - -## 3. ๐Ÿš€ Starting in `cabal repl` - -1. **Change to project root**: - - ```bash - cd ~/projects/auction - ``` -2. **Launch REPL**: - - ```bash - cabal repl - ``` -3. **Load the minting module**: - - ```haskell - ฮป> :l src/AuctionMintingPolicy.hs - [1 of 1] Compiling AuctionMintingPolicy ( src/AuctionMintingPolicy.hs, interpreted ) - Ok, one module loaded. - ``` -4. **Inspect the main function**: - - ```haskell - ฮป> :t auctionTypedMintingPolicy - auctionTypedMintingPolicy :: PubKeyHash -> () -> ScriptContext -> Bool - ``` - -## 4. ๐Ÿ“ฆ Core Data Types - -### 4.1 AuctionMintingParams - -```haskell --- The parameter is simply the seller's PubKeyHash -type AuctionMintingParams = PubKeyHash -``` - -### 4.2 AuctionMintingRedeemer - -```haskell -type AuctionMintingRedeemer = () -``` - -* A unit type, since no extra data is needed to authorize minting. - -## 5. ๐Ÿ› ๏ธ Writing `auctionTypedMintingPolicy` - -Open `src/AuctionMintingPolicy.hs` and locate: - -```haskell -{-# INLINEABLE auctionTypedMintingPolicy #-} -auctionTypedMintingPolicy :: AuctionMintingParams -> AuctionMintingRedeemer -> ScriptContext -> Bool -auctionTypedMintingPolicy pkh _ ctx = - txSignedBy txInfo pkh PlutusTx.&& mintedExactlyOneToken - where - txInfo = scriptContextTxInfo ctx - mintedExactlyOneToken = case flattenValue (txInfoMint txInfo) of - [(cs, _tn, q)] -> - cs PlutusTx.== ownCurrencySymbol ctx PlutusTx.&& q PlutusTx.== 1 - _ -> False -``` - -**Step-by-step**: - -1. **`txSignedBy`**: ensures the sellerโ€™s signature is present. -2. **`flattenValue`**: converts minted `Value` to a list of triples. -3. **Pattern match**: exactly one `(currencySymbol, tokenName, quantity)`. -4. **`ownCurrencySymbol`**: checks policyโ€™s own symbol matches. -5. **Quantity check**: must be exactly `1`. - -## 6. ๐Ÿ“œ Compiling the Minting Policy Script - -Below the typed logic youโ€™ll find: - -```haskell -auctionMintingPolicyScript :: AuctionMintingParams -> CompiledCode (BuiltinData -> BuiltinData -> PlutusTx.BuiltinUnit) -auctionMintingPolicyScript pkh = - $$(PlutusTx.compile [|| auctionUntypedMintingPolicy ||]) - `PlutusTx.unsafeApplyCode` PlutusTx.liftCode plcVersion100 pkh -``` - -* **In REPL**: - - ```haskell - ฮป> :browse auctionMintingPolicyScript - ``` - -## 7. โœ๏ธ Writing Unit Tests - -Create `test/AuctionMintingPolicySpec.hs`: - -### 7.1 Mocking `ScriptContext` - -```haskell -import qualified PlutusTx.AssocMap as AssocMap -import PlutusLedgerApi.V2.Contexts ( ScriptContext(..), TxInfo(..), TxOutRef(..), TxId(..) ) - -mockMintingContext :: ScriptContext -mockMintingContext = ScriptContext - { scriptContextTxInfo = TxInfo - { txInfoInputs = [] - , txInfoReferenceInputs = [] - , txInfoOutputs = [] - , txInfoFee = mempty - , txInfoMint = singleton cs tn 1 - , txInfoDCert = [] - , txInfoWdrl = AssocMap.empty - , txInfoValidRange = mempty - , txInfoSignatories = [] - , txInfoData = AssocMap.empty - , txInfoId = TxId "" - , txInfoRedeemers = AssocMap.empty - } - , scriptContextPurpose = Minting (TxOutRef (TxId "") 0) - } - where - cs = ownCurrencySymbol mockMintingContext - tn = "TOK" -``` - -### 7.2 Example Test Case - -```haskell -import Test.Hspec -import PlutusLedgerApi.V1.Crypto (PubKeyHash(..)) - -main :: IO () -main = hspec $ - describe "auctionTypedMintingPolicy" $ do - it "allows exactly one token minting by seller" $ do - let pkh = PubKeyHash "seller" - auctionTypedMintingPolicy pkh () mockMintingContext `shouldBe` True -``` - -### 7.3 Running Tests - -```bash -cabal test minting-tests -``` - -## 8. ๐Ÿงช Property-Based Testing - -Example in `test/AuctionMintingPolicyProperties.hs`: - -```haskell -import Test.QuickCheck - -prop_onlyOne :: PubKeyHash -> Property -prop_onlyOne pkh = - let ctx = mockMintingContext - in auctionTypedMintingPolicy pkh () ctx === True - -main = quickCheck prop_onlyOne -``` - -Run: - -```bash -cabal test minting-properties -``` - -## 9. ๐Ÿ”ญ Next Steps - -* Extend to allow controlled multiple minting with limits. -* Deploy policy on a testnet and test minting transactions. -* Integrate off-chain code to submit minting transactions. - -## 10. ๐Ÿ“– Glossary - -| Term | Definition | -| ----------------------- | ------------------------------------------------------------------------- | -| **`txSignedBy`** | Checks a transaction is signed by a given `PubKeyHash`. | -| **`flattenValue`** | Converts a `Value` into a list of `(CurrencySymbol, TokenName, Integer)`. | -| **`ownCurrencySymbol`** | Retrieves the currency symbol for the currently running minting policy. | -| **`Minting`** | A `ScriptPurpose` constructor indicating a mint operation on a UTXO. | -| **`CompiledCode`** | Wrapper for PlutusTx-compiled on-chain code. | -| **`Hspec`** | Haskell testing framework for behavior-driven development. | -| **`QuickCheck`** | Property-based testing library in Haskell. | - ---- diff --git a/TutorialAuctionSourceCode.ms b/TutorialAuctionSourceCode.ms deleted file mode 100644 index 1f5a338..0000000 --- a/TutorialAuctionSourceCode.ms +++ /dev/null @@ -1,255 +0,0 @@ -**Table of Contents** - -1. [๐Ÿ”ฐ Introduction](#1-introduction) -2. [๐Ÿ“‹ Prerequisites](#2-prerequisites) -3. [๐Ÿš€ Starting in `cabal repl`](#3-starting-in-cabal-repl) -4. [๐Ÿ“ฆ Core Data Types](#4-core-data-types) - - * 4.1 [AuctionParams](#41-auctionparams) - * 4.2 [AuctionDatum](#42-auctiondatum) - * 4.3 [AuctionRedeemer](#43-auctionredeemer) -5. [๐Ÿ› ๏ธ Writing `auctionTypedValidator`](#5-writing-auctiontypedvalidator) - - * 5.1 [Matching on `NewBid`](#51-matching-on-newbid) - * 5.2 [Matching on `Payout`](#52-matching-on-payout) -6. [๐Ÿ“œ Compiling the Validator Script](#6-compiling-the-validator-script) -7. [โœ๏ธ Writing Unit Tests](#7-writing-unit-tests) - - * 7.1 [Mocking `ScriptContext`](#71-mocking-scriptcontext) - * 7.2 [Example Test Case](#72-example-test-case) - * 7.3 [Running Tests](#73-running-tests) -8. [๐Ÿงช Property-Based Testing](#8-property-based-testing) -9. [๐Ÿ”ญ Next Steps](#9-next-steps) -10. [๐Ÿ“– Glossary](#10-glossary) - ---- - -## 1. ๐Ÿ”ฐ Introduction - -This tutorial walks Haskell beginners through building and testing a Plutus Auction Validator smart contract. Youโ€™ll learn: - -* Defining on-chain data types -* Writing validation logic in `auctionTypedValidator` -* Compiling to Plutus Core -* Unit and property-based testing - -By the end, youโ€™ll be comfortable loading modules in `cabal repl`, stepping through code, and verifying behavior. - -## 2. ๐Ÿ“‹ Prerequisites - -* Basic Haskell knowledge (records, pattern matching) -* [GHC](https://www.haskell.org/ghc/) and [Cabal](https://www.haskell.org/cabal/) installed -* Plutus libraries available (via Nix or direct Cabal setup) - -## 3. ๐Ÿš€ Starting in `cabal repl` - -1. **Enter your project directory**: - - ```bash - cd ~/projects/auction - ``` -2. **Launch the REPL**: - - ```bash - cabal repl - ``` -3. **Load the validator module**: - - ```haskell - ฮป> :l src/AuctionValidator.hs - [1 of 1] Compiling AuctionValidator ( src/AuctionValidator.hs, interpreted ) - Ok, one module loaded. - ``` -4. **Inspect a type**: - - ```haskell - ฮป> :t auctionTypedValidator - auctionTypedValidator :: AuctionParams -> AuctionDatum -> AuctionRedeemer -> ScriptContext -> Bool - ``` - -## 4. ๐Ÿ“ฆ Core Data Types - -### 4.1 AuctionParams - -```haskell -data AuctionParams = AuctionParams - { apSeller :: PubKeyHash -- ^ Seller's public key hash - , apCurrencySymbol :: CurrencySymbol -- ^ Minting policy hash - , apTokenName :: TokenName -- ^ Name of the token - , apMinBid :: Lovelace -- ^ Minimum bid amount - , apEndTime :: POSIXTime -- ^ Auction closing time - } -``` - -* **Step**: In REPL, view fields: - - ```haskell - ฮป> :i AuctionParams - ``` - -### 4.2 AuctionDatum - -```haskell -newtype AuctionDatum = AuctionDatum { adHighestBid :: Maybe Bid } -``` - -* Holds `Nothing` (no bids) or `Just Bid` (highest bid so far). - -### 4.3 AuctionRedeemer - -```haskell -data AuctionRedeemer = NewBid Bid | Payout -``` - -* **`NewBid`**: Places a new bid. -* **`Payout`**: Closes the auction and distributes funds. - -## 5. ๐Ÿ› ๏ธ Writing `auctionTypedValidator` - -This function enforces auction rules. It has type: - -```haskell -auctionTypedValidator - :: AuctionParams -> AuctionDatum -> AuctionRedeemer -> ScriptContext -> Bool -``` - -### 5.1 Matching on `NewBid` - -```haskell -case redeemer of - NewBid bid -> - and [ sufficientBid bid - , validBidTime - , refundsPreviousHighestBid - , correctOutput bid - ] -``` - -* **`sufficientBid`**: New bid > previous (or โ‰ฅ minimum if first) -* **`validBidTime`**: Within `apEndTime`. -* **`refundsPreviousHighestBid`**: Returns lovelace to prior bidder. -* **`correctOutput`**: New output datum + correct token+lovelace bundle. - -### 5.2 Matching on `Payout` - -```haskell - Payout -> - and [ validPayoutTime - , sellerGetsHighestBid - , highestBidderGetsAsset - ] -``` - -* **`validPayoutTime`**: After `apEndTime`. -* **`sellerGetsHighestBid`**: Seller receives the funds. -* **`highestBidderGetsAsset`**: Winner gets the token (or seller if none). - -## 6. ๐Ÿ“œ Compiling the Validator Script - -In `src/AuctionValidator.hs` youโ€™ll find: - -```haskell -auctionValidatorScript :: AuctionParams -> CompiledCode ... -auctionValidatorScript params = $$(PlutusTx.compile [|| auctionUntypedValidator ||]) - `PlutusTx.unsafeApplyCode` PlutusTx.liftCode plcVersion100 params -``` - -* **In REPL**: - - ```haskell - ฮป> :l src/AuctionValidator.hs - ฮป> :browse auctionValidatorScript - ``` - -## 7. โœ๏ธ Writing Unit Tests - -Tests live in `test/AuctionValidatorSpec.hs`. - -### 7.1 Mocking `ScriptContext` - -```haskell -import qualified PlutusTx.AssocMap as AssocMap - -mockScriptContext :: ScriptContext -mockScriptContext = ScriptContext - { scriptContextTxInfo = TxInfo - { txInfoInputs = [] - , txInfoReferenceInputs = [] - , txInfoOutputs = [] - , txInfoFee = mempty - , txInfoMint = mempty - , txInfoDCert = [] - , txInfoWdrl = AssocMap.empty - , txInfoValidRange = always - , txInfoSignatories = [] - , txInfoData = AssocMap.empty - , txInfoId = TxId "" - , txInfoRedeemers = AssocMap.empty - } - , scriptContextPurpose = Spending (TxOutRef (TxId "") 0) - } -``` - -### 7.2 Example Test Case - -```haskell -it "rejects NewBid with empty context" $ do - let params = AuctionParams (PubKeyHash "seller") (CurrencySymbol "") (TokenName "TOK") (Lovelace 100) 1620000000000 - datum = AuctionDatum Nothing - redeemer = NewBid (Bid "a" (PubKeyHash "b") (Lovelace 150)) - auctionTypedValidator params datum redeemer mockScriptContext `shouldBe` False -``` - -### 7.3 Running Tests - -```bash -cabal test auction-tests -``` - -## 8. ๐Ÿงช Property-Based Testing - -Use QuickCheck in `test/AuctionValidatorProperties.hs`: - -```haskell -import Test.QuickCheck - -instance Arbitrary Bid where - arbitrary = Bid <$> arbitrary <*> arbitrary <*> (Lovelace <$> arbitrary `suchThat` (>0)) - -prop_higherBid :: Bid -> Bid -> Property -prop_higherBid old new = bAmount new > bAmount old ==> - let params = AuctionParams ... - ctx = mockScriptContext - in auctionTypedValidator params (AuctionDatum (Just old)) (NewBid new) ctx === False - -main = quickCheck prop_higherBid -``` - -Run with: - -```bash -cabal test auction-properties -``` - -## 9. ๐Ÿ”ญ Next Steps - -* Extend tests: firstโ€bid acceptance, refund checks, payout flows. -* Instantiate on a local Cardano testnet. -* Integrate offโ€chain endpoints and CLI. - -## 10. ๐Ÿ“– Glossary - -| Term | Definition | -| -------------------- | ------------------------------------------------------------------------------ | -| **REPL** | Readโ€“evalโ€“print loop, interactive shell (`cabal repl`). | -| **Record** | Haskell data structure with named fields (e.g., `AuctionParams`). | -| **Pattern Matching** | Checking a value against a pattern (e.g., `case redeemer of NewBid bid -> โ€ฆ`). | -| **CompiledCode** | PlutusTx wrapper for on-chain code after compilation. | -| **`mempty`** | The identity element for a Monoid, e.g., empty fees, empty maps. | -| **`AssocMap`** | Plutusโ€™s Map type used for scripts (e.g., `txInfoData`, `txInfoRedeemers`). | -| **`QuickCheck`** | Library for propertyโ€based testing in Haskell. | -| **`Hspec`** | Behaviorโ€driven unit testing framework for Haskell. | -| **`Spending`** | A `ScriptPurpose` constructor indicating a spend of a UTXO (with `TxOutRef`). | - ---- - diff --git a/app/GenAuctionValidatorBlueprintTutorial.md b/app/GenAuctionValidatorBlueprintTutorial.md new file mode 100644 index 0000000..38192f4 --- /dev/null +++ b/app/GenAuctionValidatorBlueprintTutorial.md @@ -0,0 +1,246 @@ +# ๐Ÿง  **1. The Auction Validator Blueprint** + +This tutorial walks you through creating a **Plutus smart contract blueprint** in Haskell that defines, serializes, and exports an **auction validator**. +Youโ€™ll learn how each part of the code contributes to the contract lifecycle โ€” from defining parameters to generating blueprint files. + + +# โš™๏ธ **2. Enabling GHC Language Extensions** + +```haskell +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} +``` + +### ๐Ÿ’ก Explanation + +* These pragmas enable advanced Haskell language features required by **Plutus** and **Template Haskell**-based modules. +* For example: + + * `GADTs` โ†’ enables precise data types. + * `TypeApplications` โ†’ allows specifying type parameters explicitly. + * `OverloadedStrings` โ†’ allows `String` literals to represent `ByteString`, `Text`, etc. + + +# ๐Ÿ“ฆ **3. Module and Imports** + +```haskell +module Main where + +import AuctionValidator +import qualified Data.ByteString.Short as Short +import qualified Data.Set as Set +import PlutusLedgerApi.Common (serialiseCompiledCode) +import qualified PlutusLedgerApi.V1.Crypto as Crypto +import qualified PlutusLedgerApi.V1.Time as Time +import qualified PlutusLedgerApi.V1.Value as Value +import PlutusTx.Blueprint +import PlutusTx.Builtins.HasOpaque (stringToBuiltinByteStringHex) +import System.Environment (getArgs) +``` + +### ๐Ÿงฉ Key Points + +* **`AuctionValidator`**: Your custom Plutus validator module. +* **`PlutusTx.Blueprint`**: Generates contract blueprints for on-chain deployment. +* **`Crypto`, `Time`, `Value`**: Provide key types for public keys, timestamps, and token values. +* **`serialiseCompiledCode`**: Converts compiled Plutus Core to a serializable format for export. + + +# ๐Ÿ’ฐ **4. Defining Auction Parameters** + +```haskell +auctionParams :: AuctionParams +auctionParams = + AuctionParams + { apSeller = Crypto.PubKeyHash ( + stringToBuiltinByteStringHex "0000..." + ) + , apCurrencySymbol = Value.CurrencySymbol ( + stringToBuiltinByteStringHex "0000..." + ) + , apTokenName = Value.tokenName "MY_TOKEN" + , apMinBid = 100 + , apEndTime = Time.fromMilliSeconds 1_725_227_091_000 + } +``` + +### ๐Ÿง  Explanation + +* Defines **compile-time configuration** for your auction. +* `apSeller` is the **public key hash** of the auction creator. +* `apCurrencySymbol` and `apTokenName` define which token is being auctioned. +* `apMinBid` sets the minimum bid in **lovelace** (โ‚ณ1 = 1,000,000 lovelace). +* `apEndTime` specifies when the auction closes. + + +# ๐Ÿงพ **5. Defining the Contract Blueprint** + +```haskell +myContractBlueprint :: ContractBlueprint +myContractBlueprint = + MkContractBlueprint + { contractId = Just "auction-validator" + , contractPreamble = myPreamble + , contractValidators = Set.singleton myValidator + , contractDefinitions = + deriveDefinitions @[AuctionParams, AuctionDatum, AuctionRedeemer] + } +``` + +### ๐Ÿ“˜ What This Does + +* Creates a **blueprint document** that defines: + + * The **unique contract ID**. + * The **preamble** (metadata). + * The **validator(s)** used in the contract. + * Auto-derived type definitions (`AuctionParams`, `AuctionDatum`, `AuctionRedeemer`). + + +# ๐Ÿท๏ธ **6. Adding Metadata (Preamble)** + +```haskell +myPreamble :: Preamble +myPreamble = + MkPreamble + { preambleTitle = "Auction Validator" + , preambleDescription = + Just "Blueprint for a Plutus script validating auction transactions" + , preambleVersion = "1.0.0" + , preamblePlutusVersion = PlutusV2 + , preambleLicense = Just "MIT" + } +``` + +### ๐Ÿงฉ Explanation + +The **preamble** describes your contract for **Blueprint JSON output**, including: + +* **Title** and **Description** +* **Versioning** +* **Plutus version (V2)** for compatibility +* **License information** + + +# ๐Ÿงฎ **7. Creating the Validator Blueprint** + +```haskell +myValidator :: ValidatorBlueprint referencedTypes +myValidator = + MkValidatorBlueprint + { validatorTitle = "Auction Validator" + , validatorDescription = + Just "Plutus script validating auction transactions" + , validatorParameters = + [ MkParameterBlueprint + { parameterTitle = Just "Parameters" + , parameterDescription = Just "Compile-time validator parameters" + , parameterPurpose = Set.singleton Spend + , parameterSchema = definitionRef @AuctionParams + } + ] + , validatorRedeemer = + MkArgumentBlueprint + { argumentTitle = Just "Redeemer" + , argumentDescription = Just "Redeemer for the auction validator" + , argumentPurpose = Set.fromList [Spend] + , argumentSchema = definitionRef @() + } + , validatorDatum = Nothing + , validatorCompiled = do + let script = auctionValidatorScript auctionParams + let code = Short.fromShort (serialiseCompiledCode script) + Just (compiledValidator PlutusV2 code) + } +``` + +### ๐Ÿง  Step-by-Step + +1. **Metadata** + + * `validatorTitle` and `validatorDescription` describe the validator. +2. **Parameters Section** + + * `parameterSchema` references `AuctionParams` for compile-time constants. +3. **Redeemer Section** + + * Defines what data type (`()`) the redeemer uses at runtime. +4. **Compilation Section** + + * Serializes and embeds the **compiled Plutus script** into the blueprint. + + +# ๐Ÿ’พ **8. Writing the Blueprint File** + +```haskell +writeBlueprintToFile :: FilePath -> IO () +writeBlueprintToFile path = writeBlueprint path myContractBlueprint +``` + +### ๐Ÿ“ค Purpose + +* Writes the **contract blueprint** to a file in JSON format. +* Enables **off-chain tools** (like `aiken` or `plutus-blueprint`) to interpret the validator structure. + + +# ๐Ÿš€ **9. Main Entry Point** + +```haskell +main :: IO () +main = + getArgs >>= \case + [arg] -> writeBlueprintToFile arg + args -> fail $ "Expects one argument, got " <> show (length args) +``` + +### ๐Ÿงฉ Usage + +* Expects **one command-line argument**: the output path for the blueprint file. +* Example: + + ```bash + cabal run auction-blueprint ./auction-blueprint.json + ``` + + +# ๐Ÿงญ **10. Execution Summary** + +| Step | Action | Output | +| ---- | ------------------------- | ------------------------ | +| 1 | Define Auction Parameters | `AuctionParams` | +| 2 | Create Validator | Serialized Plutus Script | +| 3 | Combine in Blueprint | `ContractBlueprint` | +| 4 | Export to File | `auction-blueprint.json` | + + +# ๐Ÿ“š **Glossary of Terms** + +| Term | Meaning | +| ------------------------- | ----------------------------------------------------------------------------------- | +| **Blueprint** | A JSON description of a smart contract and its metadata for tooling and deployment. | +| **Validator** | A Plutus script that enforces spending conditions on UTxOs. | +| **Redeemer** | Data provided when consuming a UTxO (used by the validator). | +| **Datum** | On-chain data attached to UTxOs. | +| **AuctionParams** | Compile-time parameters such as seller, token info, and auction end time. | +| **PlutusV2** | The current version of the Plutus smart contract platform. | +| **serialiseCompiledCode** | Converts compiled Plutus Core to binary for on-chain execution. | +| **Spend** | Purpose indicating the validator is used when spending a UTxO. | +| **ByteString** | Binary data type used in Plutus for cryptographic operations. | +| **Lovelace** | The smallest unit of ADA (1 ADA = 1,000,000 lovelace). | + + +# ๐Ÿงฉ **11. Final Thoughts** + +Youโ€™ve built a **fully defined Plutus contract blueprint** for an auction validator, ready for on-chain deployment or integration into Cardano dApps. +This design pattern โ€” defining parameters, validators, and metadata โ€” is reusable for any **parameterized smart contract**. + diff --git a/app/GenMintingPolicyBlueprintTutorial.md b/app/GenMintingPolicyBlueprintTutorial.md new file mode 100644 index 0000000..f5bddd1 --- /dev/null +++ b/app/GenMintingPolicyBlueprintTutorial.md @@ -0,0 +1,239 @@ +# ๐Ÿง  **1. The Auction Minting Policy Blueprint** + +This tutorial demonstrates how to build a **Plutus blueprint** for a **minting policy** in Haskell. +The minting policy defines **rules for token creation** (and optionally burning) โ€” in this case, linked to an auction system. + +Youโ€™ll learn how to: + +* Define compile-time minting parameters. +* Create a Plutus blueprint for a **minting validator**. +* Export the compiled policy as a **JSON blueprint** file. + + +# โš™๏ธ **2. Enabling GHC Language Extensions** + +```haskell +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE StandaloneDeriving #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ViewPatterns #-} +``` + +### ๐Ÿ’ก Explanation + +These **language pragmas** unlock advanced features required for: + +* **Generic deriving** (`DeriveGeneric`, `DeriveAnyClass`) +* **Flexible type definitions** (`FlexibleContexts`, `MultiParamTypeClasses`) +* **Advanced data modeling** (`GADTs`, `DataKinds`) +* **Cleaner syntax** (`LambdaCase`, `RecordWildCards`, `ViewPatterns`) + +They are standard in **Plutus development** for writing both **validators** and **minting policies**. + + +# ๐Ÿ“ฆ **3. Module and Imports** + +```haskell +module Main where + +import AuctionMintingPolicy +import Data.ByteString.Short qualified as Short +import Data.Set qualified as Set +import PlutusLedgerApi.Common (serialiseCompiledCode) +import PlutusTx.Blueprint +import System.Environment (getArgs) +``` + +### ๐Ÿงฉ Key Components + +* **`AuctionMintingPolicy`**: The module containing your minting logic. +* **`PlutusTx.Blueprint`**: Provides constructors for generating blueprints. +* **`serialiseCompiledCode`**: Serializes Plutus Core code into binary form. +* **`Set` & `Short`**: Used for efficient collections and byte operations. +* **`getArgs`**: Reads command-line arguments for dynamic file output. + + +# ๐Ÿงฑ **4. Defining the Contract Blueprint** + +```haskell +myContractBlueprint :: ContractBlueprint +myContractBlueprint = + MkContractBlueprint + { contractId = Just "auction-minting-policy" + , contractPreamble = myPreamble + , contractValidators = Set.singleton myValidator + , contractDefinitions = deriveDefinitions @[AuctionMintingParams, ()] + } +``` + +### ๐Ÿง  Explanation + +This defines the overall **contract structure**: + +* `contractId` โ†’ Unique identifier for this blueprint. +* `contractPreamble` โ†’ Metadata about the contract (title, version, etc.). +* `contractValidators` โ†’ A set containing one **minting validator**. +* `contractDefinitions` โ†’ Auto-generated type schema for parameters (`AuctionMintingParams`) and redeemer `()`. + + +# ๐Ÿท๏ธ **5. Writing the Preamble (Metadata Section)** + +```haskell +myPreamble :: Preamble +myPreamble = + MkPreamble + { preambleTitle = "Auction Minting Policy" + , preambleDescription = Just "A simple minting policy" + , preambleVersion = "1.0.0" + , preamblePlutusVersion = PlutusV2 + , preambleLicense = Just "MIT" + } +``` + +### ๐Ÿ“˜ Purpose + +The preamble defines **contract metadata**: + +* **Title & Description** โ†’ Human-readable identification. +* **Version** โ†’ Tracks compatibility and changes. +* **PlutusV2** โ†’ Ensures compatibility with Plutus smart contract version 2. +* **License** โ†’ Legal usage declaration (MIT license). + + +# ๐Ÿงฎ **6. Creating the Minting Validator Blueprint** + +```haskell +myValidator :: ValidatorBlueprint referencedTypes +myValidator = + MkValidatorBlueprint + { validatorTitle = "Auction Minting Validator" + , validatorDescription = Just "A simple minting validator" + , validatorParameters = + [ MkParameterBlueprint + { parameterTitle = Just "Minting Validator Parameters" + , parameterDescription = Just "Compile-time validator parameters" + , parameterPurpose = Set.singleton Mint + , parameterSchema = definitionRef @AuctionMintingParams + } + ] + , validatorRedeemer = + MkArgumentBlueprint + { argumentTitle = Just "Redeemer for the minting policy" + , argumentDescription = Just "The minting policy does not use a redeemer, hence ()" + , argumentPurpose = Set.fromList [Mint] + , argumentSchema = definitionRef @() + } + , validatorDatum = Nothing + , validatorCompiled = do + let script = auctionMintingPolicyScript (error "Replace with seller public key hash") + let code = Short.fromShort (serialiseCompiledCode script) + Just (compiledValidator PlutusV2 code) + } +``` + + +### ๐Ÿงฉ Section Breakdown + +#### ๐Ÿช™ **Parameters** + +* `parameterPurpose = Mint` + โ†’ Indicates this blueprint applies to a **minting policy** (not spending or staking). +* `parameterSchema` + โ†’ Connects to `AuctionMintingParams`, defining constants like authorized minter or auction settings. + +#### ๐Ÿ” **Redeemer** + +* The redeemer is set to `()` because **no redeemer** is required when minting in this policy. + Plutus minting policies often use only the context and parameters. + +#### ๐Ÿงฐ **Compilation** + +```haskell +let script = auctionMintingPolicyScript (error "Replace with seller public key hash") +let code = Short.fromShort (serialiseCompiledCode script) +Just (compiledValidator PlutusV2 code) +``` + +* Compiles the **on-chain minting policy**. +* `serialiseCompiledCode` converts it into a **binary format** that can be embedded in the blueprint. +* The placeholder `error "Replace with seller public key hash"` should be replaced with an **actual `PubKeyHash`**. + + +# ๐Ÿ’พ **7. Writing the Blueprint File** + +```haskell +writeBlueprintToFile :: FilePath -> IO () +writeBlueprintToFile path = writeBlueprint path myContractBlueprint +``` + +### ๐Ÿ“ค Functionality + +* Saves the contract blueprint as a **`.json`** file. +* Used for integrating the policy into wallets, off-chain code, or blueprint explorers. + + +# ๐Ÿš€ **8. The Main Entry Point** + +```haskell +main :: IO () +main = + getArgs >>= \case + [arg] -> writeBlueprintToFile arg + args -> fail $ "Expects one argument, got " <> show (length args) +``` + +### ๐Ÿ’ก Usage + +This simple CLI entry point expects **one argument**: the file path for output. + +Example command: + +```bash +cabal run auction-minting-blueprint ./minting-blueprint.json +``` + + +# ๐Ÿงญ **9. Summary Workflow** + +| Step | Description | Output | +| ---- | -------------------------------------------------- | ----------------------- | +| 1 | Define Minting Parameters (`AuctionMintingParams`) | Policy configuration | +| 2 | Define Preamble Metadata | Human-readable metadata | +| 3 | Build Validator Blueprint | Binary Plutus script | +| 4 | Serialize & Write Blueprint | JSON blueprint file | + + +# ๐Ÿ“š **10. Glossary of Key Terms** + +| Term | Description | +| ------------------------- | ------------------------------------------------------------------- | +| **Minting Policy** | A Plutus script that defines rules for creating or burning tokens. | +| **Blueprint** | JSON-based metadata format describing Plutus contracts and scripts. | +| **Validator** | In this context, a compiled minting policy script. | +| **Redeemer** | Input data passed to a script when executing (unused here โ†’ `()`). | +| **AuctionMintingParams** | Type containing fixed parameters like authorized key or auction ID. | +| **PlutusV2** | The latest Plutus script version used for Cardano smart contracts. | +| **serialiseCompiledCode** | Converts compiled Plutus code into a portable binary. | +| **Mint** | Validator purpose indicating token creation/burning logic. | +| **ContractBlueprint** | Aggregates preamble, validators, and type definitions. | +| **PubKeyHash** | A hashed form of a public key, used for authentication in Plutus. | + + +# ๐Ÿงฉ **11. Final Thoughts** + +Youโ€™ve now built a **minting policy blueprint** that defines the token minting logic for an **auction-based system**. +This structure parallels the **auction validator** blueprint but focuses on **token issuance**, ensuring **secure and rule-based minting** on the Cardano blockchain. + diff --git a/app/auctionValidator.plutus b/app/auctionValidator.plutus deleted file mode 100644 index e0b42d6..0000000 --- a/app/auctionValidator.plutus +++ /dev/null @@ -1,143 +0,0 @@ -{ - "$id": "auction-validator", - "$schema": "https://cips.cardano.org/cips/cip57/schemas/plutus-blueprint.json", - "$vocabulary": { - "https://cips.cardano.org/cips/cip57": true, - "https://json-schema.org/draft/2020-12/vocab/applicator": true, - "https://json-schema.org/draft/2020-12/vocab/core": true, - "https://json-schema.org/draft/2020-12/vocab/validation": true - }, - "preamble": { - "title": "Auction Validator", - "description": "Blueprint for a Plutus script validating auction transactions", - "version": "1.0.0", - "plutusVersion": "v2", - "license": "MIT" - }, - "validators": [ - { - "title": "Auction Validator", - "description": "Plutus script validating auction transactions", - "redeemer": { - "title": "Redeemer", - "description": "Redeemer for the auction validator", - "purpose": "spend", - "schema": { - "$ref": "#/definitions/Unit" - } - }, - "parameters": [ - { - "title": "Parameters", - "description": "Compile-time validator parameters", - "purpose": "spend", - "schema": { - "$ref": "#/definitions/AuctionParams" - } - } - ], - "compiledCode": "59151a0100003323322323232332232323232323232323232323232323232323332223232323232323332223232323232323232222253353232323232323500a222223230012230165323233500f21323232323300430022225330042135001222333573466e2401400411811c4ccd5cd19b8800100f0420433300433330060250213301133012502c04012330011222300300f0413003222222222222005330045330012135001222533533230010012253350011049221533535002222253355335350042233500221223002003205221333573466e3c00402c134130412c4ccd5cd19b8733301300348810048810000904c04b104b11223002004133004004001300722222222222200a2104513263357389201184e6f7420666f756e643a20726566756e64206f75747075740004210403300453232323355002130014988854cd40044c8c8c94cd54ccd4c00c88880084c98cd5ce2481274578706563746564204f7574707574446174756d2c20676f74204e6f4f7574707574446174756d00046215335333333038225335333573466e1d200200204b04a1122300205015335333573466e1d200000204b04a13253353300122122300200400221533533333303c225335333573466e1d200000204f04e153353300422122300212330010050040012135001223253353001003215335330092212230021233001005004003213500122533530050022153353300d2212230020040022153353333330482205e205d205d21223002003205d0012112230021233300100a006004105c105b105a1057105633333041220572056205620562122300200310521052205120512051205100121122300212230020051050104f304d04e104e204d204d204d204d001215335001215335300d222350042225335333573466e3c0140081441404ccd5cd19b870040010510501050104b13357389213c496e76616c6964206f757470757420646174756d3a20636f6e7461696e73206120646966666572656e7420426964207468616e2065787065637465640004a132633573892134496e76616c6964206f757470757420646174756d3a206578706563746564204a757374204269642c20676f74204e6f7468696e670004813263357389211d4661696c656420746f206465636f6465206f757470757420646174756d00047213263357389201294578706563746564204f7574707574446174756d2c20676f74204f7574707574446174756d4861736800047100110475335333573466e1ccc00522010048810030092220010470461333573466e1d2002330010150140470461046300d30012222003500222130054988c8c98cd5ce19b964912c45787065637465642065786163746c79206f6e6520636f6e74696e75696e67206f75747075742c20676f74200032301f33302348000004128d407c004109400854cd4c02c88d4008888888888888cccd4034814c814c814c8cc8c004004894cd40044154884d400888c94cd54cd4c00488d402888ccd5cd19b8f00400205b05a13001223500a22333573466e1c00c00416c16841584488c00801c4cc01c01c011400803484d400488d40048c8888c010c04c88d4008888888888888028c8c004004894cd40044130884c94cd4d400c8888d402888d4018894cd4cd40108cd400c8ccd5cd19b8f0020010540532053233500320532333573466e3c00800415014c54cd400c854cd400884cd40088cd40088cd40088cd40088cc060008004815c8cd4008815c8cc06000800488815c888cd4010815c8894cd4ccd5cd19b8700600305a05915335333573466e1c0140081681644ccd5cd19b8700400105a059105910591052153350012105210521051133504e0030011001330040040011326335738921024c660003f22333573466e3c0080041081041154051400d401888cd41080080044c8c8c8cc00ccccc01008c07ccc03c48cc0044888c00c0340fccc04540a40f8c008888888888888014cc00d4cc0048c854cd4c004c010888888888888028841044c98cd5ce2481204e6f7420666f756e643a204f7574707574207061696420746f2073656c6c65720003e3230010012253350011044221533535002222253355335350042233500221223002003204d21333573466e3c00405c12011c41184ccd5cd19b8733300e00348810048810035009222001047046104611223002004133004004001103e3300332533533230010012253350011044221533535002222253355335350042233500221223002003204d21333573466e3c00402812011c41184ccd5cd19b8748008ccc03800c05405011c11841184488c0080104cc010010004c00c888888888888028841004c98cd5ce2481284e6f7420666f756e643a204f7574707574207061696420746f2068696768657374206269646465720003d5330012135001222002100d0435012500522335041002001222253232323233530010051042153353001006104113500622350072253323553335330013500522533500110021533350022130373300c010001100210023500322533500110021533350022130373300c01000110021002104710461047153335330013500222533500110021533350022130373300b010001100210023500422533500110021533350022130373300b01000110021002104710461047225323233350042150021533350032102d102e102c15001153335002215002102c1500115333500221533350022133300a00f00200116102b161533350012102c16102c10452350012235002225323233500315333500421300200513204850021300100415333500421300230363300b00f001132048500213001503325323335500321500210491500115333500121533355003215333533300a00f0020011049104a10491610481615333550022104916104713500322533500110021533350022130363300a00f0011002100223500122222222007235001222222005235001222222006222323323001001225335001148000884d4008894cd4ccd5cd19b8f00200904304213007001133006006003004323001001225335001148000884d4008894cd4ccd5cd19b8f002007042041100113300600600350063232325335333573466e1d20000020330321332212330010030023232325335333573466e1d200000203603513232323232323232323232323333333333332222222222221233333333333300100d00c00b00a009008007006005004003002323503337580026ae84034c8d40d0dd60009aba100c323503837580026ae8402cc004d5d080518009aba1009323503c37580026ae84020ccc0e80f5d69aba10073232325335333573466e1d20000020450441330163232325335333573466e1d200000204804713301a3303275a6ae84004c0c4d5d09aba200113045491035054310035573c0046aae74004dd51aba10013232325335333573466e1d200000204804713301b3303275a6ae84004c0c4d5d09aba200113045491035054310035573c0046aae74004dd51aba13574400226084921035054310035573c0046aae74004dd51aba1006323504437580026ae84014ccc0e80c00a8d5d0802198010151aba10033036357426ae8800cc004c005d6981c3ae357440026ae88004d5d10009aba2001357440026ae88004d5d10009aba2001357440026ae880044c0cd241035054310035573c0046aae74004dd51aba10013021357426ae880044c0c1241035054310035573c0046aae74004dd5003110919800801801110919800801801110919800801801191919299a999ab9a3370e90000010178170990911801001980b1aba100115335333573466e1d200200202f02e11220011302c491035054310035573c0046aae74004dd5001998110090018a4c26050920103505435003230010012233500149100225335001100222135325335333573466e24005200002d02c11233001033007133009001006337066a00e00a9002111919b9630010033001002300800813223002001323001001223350014800088cdc02400466008008002646002002444a66a0022246600205a05a442a66a666ae68cdc3a400400805205026605e66a05a00405c00226a6646600c00c00266e040112002001223303133502f0040020013230010012253350011025221533500213300400400110263230010012225335333573466e2000520000250241233502a4901012d003333004004003337029000001000898021980281500099180080091299a8008900091099119a815a99a999ab9a3370e90000020138130a4901300015335333573466e1d2002004027026149101310015335333573466e1d2004004027026149101320015335333573466e1d2006004027026149101330015335333573466e1d2008004027026149101340015335333573466e1d200a004027026149101350015335333573466e1d200c004027026149101360015335333573466e1d200e004027026149101370015335333573466e1d201000402702614910138001335333573466e1d201200402702649101390049010f3c696e76616c69642064696769743e00300200133004004001323001001222325335333573466e1d200000102402313350283370a004900a00189991980280280099a81419b850024805000c004cdc2000a4028246666666600244666ae68cdc380100081101091299a999ab9a3370e004002044042200c2a66a666ae68cdc48010008110108802080291199ab9a3371000400204404244666ae68cdc480100081101091199ab9a3371200400204204444666ae68cdc400100081081111299a999ab9a337120040020440422002200444a66a666ae68cdc48010008110108801080089110018911001091100089199999800919b80480080048cdc0800a400440024002004444646464a66a666ae68cdc4001a40000420442600200c2600400c64600200244a66a666ae68cdc48008028110118814099a813800991980180180099b80001004323001001225335333573466e20004010088084409c4cd4098004c8cc00c00c004cdc000080199b810020033230010012225335333573466e2400800407007440884cd4084008cc8cc010010004cdc0240040040024646464a66a666ae68cdc3a400000403a038264666444246660020080060046eb8d5d08011bae357420026eb4d5d09aba200135744002260349201035054310035573c0046aae74004dd500090009191919299a999ab9a3370e900000100d80d080d0a99a999ab9a3370e900100100d80d080d8980c249035054310035573c0046aae74004dd500091191919299a999ab9a3370e900000100d80d0a8038a99a999ab9a3370e900100100d80d0980418029aba100115335333573466e1d200400201b01a1500613018491035054310035573c0046aae74004dd500090911180180208911001089110009191919299a999ab9a3370e900000100b00a8990911118018029bae357420022a66a666ae68cdc3a400400402c02a26424444600200a60126ae8400454cd4ccd5cd19b87480100080580544c848888c008014c044d5d08008a99a999ab9a3370e900300100b00a89909111180200298081aba1001130134901035054310035573c0046aae74004dd50009111112999999aba40011323300735573a0026aae78004dd5000898029bab001130043758002260066eb40044c008dd700099918008009109980a00b9119a80c18030011a99802002090008008900099918008009109980980b1119a80b98028011a9980200209000800890009191919299a999ab9a3370e900000100900889991091980080180118029aba10013008357426ae880044c03d241035054310035573c0046aae74004dd50009191919299a999ab9a3370e900000100880809991091980080180118029aba1001375a6ae84d5d100089807249035054310035573c0046aae74004dd50009191919299a999ab9a3370e900000100800789bae357420022601a9201035054310035573c0046aae74004dd50009991800800910998078091119a80998028011a9980200209000800890009191919299a999ab9a3370e9000001007006899191919999111091999800802802001801191919299a999ab9a3370e900000100a00989991091980080180118081aba10013300b00f357426ae880044c0452401035054310035573c0046aae74004dd51aba100433001300175a6ae8400cc8c8c94cd4ccd5cd19b874800000805004c4488800c54cd4ccd5cd19b874800800805004c4c84888c004010dd71aba100115335333573466e1d20040020140131321222300200435742002260229201035054310035573c0046aae74004dd51aba10023300875c6ae84d5d100118043ae357440026ae880044c02d241035054310035573c0046aae74004dd500091191919299a999ab9a3370e900100100700688088a99a999ab9a3370e9000001007006899091180100198029aba10011300b491035054310035573c0046aae74004dd5000911911a8011bab00133230010012213300e0112233501233014300635573a004600a6aae78008d4cc0100104800400448004cc8c004004884cc02c03888cd403cc014008d4cc01001048004004480048c8c8c94cd4ccd5cd19b87480000080280244c848888888c014020c014d5d08008a99a999ab9a3370e900100100500489909111111180380418029aba100115335333573466e1d200400200a0091332212222222330060090083005357420026eb8d5d09aba200115335333573466e1d200600200a009133221222222233002009008375c6ae84004dd71aba1357440022a66a666ae68cdc3a40100040140122664424444444660020120106eb8d5d08009bad357426ae8800454cd4ccd5cd19b874802800802802444888888801054cd4ccd5cd19b874803000802802444888888800c4c01d241035054310035573c0046aae74004dd50009191919299a999ab9a3370e9000001004804099091180100198029aba100115335333573466e1d2002002009008132333222122333001005004003375a6ae84008dd69aba1001375a6ae84d5d10009aba2001130064901035054310035573c0046aae74004dd50009191919299a999ab9a3370e900000100400389909118010019bae357420022a66a666ae68cdc3a400400401000e26424460020066eb8d5d080089802a481035054310035573c0046aae74004dd5000919319ab9c0010021200112200212200133230010012213300300622335007375c0046a660080082400200224002444a666aae7c004400c4cc008d5d08009aba2001122001122002122122330010040032212330010030021233333001488128000000000000000000000000000000000000000000000000000000000000000000000000000000000048811c00000000000000000000000000000000000000000000000000000000004881084d595f544f4b454e00483200520f090e2f8b5641", - "hash": "756f0973ddc3375575469f38482985e91b620d936003466a8920170b" - } - ], - "definitions": { - "AuctionParams": { - "dataType": "constructor", - "fields": [ - { - "$ref": "#/definitions/PubKeyHash" - }, - { - "$ref": "#/definitions/CurrencySymbol" - }, - { - "$ref": "#/definitions/TokenName" - }, - { - "$ref": "#/definitions/Lovelace" - }, - { - "$ref": "#/definitions/POSIXTime" - } - ], - "index": 0 - }, - "AuctionRedeemer": { - "oneOf": [ - { - "dataType": "constructor", - "fields": [ - { - "$ref": "#/definitions/Bid" - } - ], - "index": 0 - }, - { - "dataType": "constructor", - "fields": [], - "index": 1 - } - ] - }, - "Bid": { - "dataType": "constructor", - "fields": [ - { - "$ref": "#/definitions/BuiltinByteString" - }, - { - "$ref": "#/definitions/PubKeyHash" - }, - { - "$ref": "#/definitions/Lovelace" - } - ], - "index": 0 - }, - "BuiltinByteString": { - "dataType": "bytes" - }, - "CurrencySymbol": { - "title": "CurrencySymbol", - "dataType": "bytes" - }, - "Integer": { - "dataType": "integer" - }, - "Lovelace": { - "title": "Lovelace", - "dataType": "integer" - }, - "Maybe_Bid": { - "oneOf": [ - { - "dataType": "constructor", - "fields": [], - "index": 1 - }, - { - "dataType": "constructor", - "fields": [ - { - "$ref": "#/definitions/Bid" - } - ], - "index": 0 - } - ] - }, - "POSIXTime": { - "title": "POSIXTime", - "dataType": "integer" - }, - "PubKeyHash": { - "title": "PubKeyHash", - "dataType": "bytes" - }, - "TokenName": { - "title": "TokenName", - "dataType": "bytes" - } - } -} diff --git a/cabal.project b/cabal.project index e4e746d..e4e3ec1 100644 --- a/cabal.project +++ b/cabal.project @@ -11,9 +11,9 @@ repository cardano-haskell-packages index-state: -- Bump both the following dates if you need newer packages from Hackage - , hackage.haskell.org 2025-04-16T14:04:24Z + , hackage.haskell.org 2025-09-22T12:53:02Z -- Bump this if you need newer packages from CHaP - , cardano-haskell-packages 2025-04-16T14:04:24Z + , cardano-haskell-packages 2025-09-22T06:47:49Z packages: ./. diff --git a/flake.lock b/flake.lock index 3172a94..02107ee 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "CHaP": { "flake": false, "locked": { - "lastModified": 1744809817, - "narHash": "sha256-YxiHyE0A9rYdcqg770xF+yy36ZTiw5yHd2WojIosUHs=", + "lastModified": 1758547838, + "narHash": "sha256-QvqwgT4yN+52SWxQWQ3cS5V64C1rQrQKaLCYRZH7bC4=", "owner": "IntersectMBO", "repo": "cardano-haskell-packages", - "rev": "a3561f2b774b4beebf704cdcfdeb07d0e609b2c6", + "rev": "6174af87848e7b5e652bb19035f658e10f094299", "type": "github" }, "original": { @@ -36,16 +36,16 @@ "blst": { "flake": false, "locked": { - "lastModified": 1691598027, - "narHash": "sha256-oqljy+ZXJAXEB/fJtmB8rlAr4UXM+Z2OkDa20gpILNA=", + "lastModified": 1739372843, + "narHash": "sha256-IlbNMLBjs/dvGogcdbWQIL+3qwy7EXJbIDpo4xBd4bY=", "owner": "supranational", "repo": "blst", - "rev": "3dd0f804b1819e5d03fb22ca2e6fac105932043a", + "rev": "8c7db7fe8d2ce6e76dc398ebd4d475c0ec564355", "type": "github" }, "original": { "owner": "supranational", - "ref": "v0.3.11", + "ref": "v0.3.14", "repo": "blst", "type": "github" } @@ -117,24 +117,6 @@ "type": "github" } }, - "easy-purescript-nix": { - "inputs": { - "flake-utils": "flake-utils" - }, - "locked": { - "lastModified": 1710161569, - "narHash": "sha256-lcIRIOFCdIWEGyKyG/tB4KvxM9zoWuBRDxW+T+mvIb0=", - "owner": "justinwoo", - "repo": "easy-purescript-nix", - "rev": "117fd96acb69d7d1727df95b6fde9d8715e031fc", - "type": "github" - }, - "original": { - "owner": "justinwoo", - "repo": "easy-purescript-nix", - "type": "github" - } - }, "flake-compat": { "flake": false, "locked": { @@ -153,21 +135,6 @@ } }, "flake-compat_2": { - "locked": { - "lastModified": 1733328505, - "narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-compat_3": { "flake": false, "locked": { "lastModified": 1696426674, @@ -187,24 +154,6 @@ "inputs": { "systems": "systems" }, - "locked": { - "lastModified": 1685518550, - "narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, "locked": { "lastModified": 1731533236, "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", @@ -219,24 +168,6 @@ "type": "github" } }, - "flake-utils_3": { - "inputs": { - "systems": "systems_3" - }, - "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "ghc-8.6.5-iohk": { "flake": false, "locked": { @@ -257,8 +188,7 @@ "gitignore": { "inputs": { "nixpkgs": [ - "iogx", - "pre-commit-hooks-nix", + "pre-commit-hooks", "nixpkgs" ] }, @@ -279,11 +209,11 @@ "hackage": { "flake": false, "locked": { - "lastModified": 1744763139, - "narHash": "sha256-HhF3HMH4FDwa4Ne8HXjWohfbGeM9X4JOZGzxbCgyBnA=", + "lastModified": 1758546883, + "narHash": "sha256-jR+3qja1p3DDoe2K9aDrqVM3YYI+3oGxhS+/MZWW7dw=", "owner": "input-output-hk", "repo": "hackage.nix", - "rev": "aaf11b4cd3ef81c4fa37095a538d3542d2f9c941", + "rev": "0b9c9cc6140c5474ad59e2a0d19f2b0ddfe71ebd", "type": "github" }, "original": { @@ -295,11 +225,11 @@ "hackage-for-stackage": { "flake": false, "locked": { - "lastModified": 1744763128, - "narHash": "sha256-XccUTLSbmHAcdOWPe+jGsjzoCtJcKM9t/+T9FdxvLMc=", + "lastModified": 1754008261, + "narHash": "sha256-k8x+oGosdpm6eWTuFE2tNYON5fHD8fgZE+esqWJhfhA=", "owner": "input-output-hk", "repo": "hackage.nix", - "rev": "323adf3005ff4fd9f65bfc98f0cefb0307409d9e", + "rev": "98c1e78433f48ff1ae0cdcb9b3aa6334abda3e6e", "type": "github" }, "original": { @@ -309,6 +239,22 @@ "type": "github" } }, + "hackage-internal": { + "flake": false, + "locked": { + "lastModified": 1750307553, + "narHash": "sha256-iiafNoeLHwlSLQTyvy8nPe2t6g5AV4PPcpMeH/2/DLs=", + "owner": "input-output-hk", + "repo": "hackage.nix", + "rev": "f7867baa8817fab296528f4a4ec39d1c7c4da4f3", + "type": "github" + }, + "original": { + "owner": "input-output-hk", + "repo": "hackage.nix", + "type": "github" + } + }, "haskell-nix": { "inputs": { "HTTP": "HTTP", @@ -322,9 +268,12 @@ "hackage" ], "hackage-for-stackage": "hackage-for-stackage", + "hackage-internal": "hackage-internal", "hls": "hls", "hls-1.10": "hls-1.10", "hls-2.0": "hls-2.0", + "hls-2.10": "hls-2.10", + "hls-2.11": "hls-2.11", "hls-2.2": "hls-2.2", "hls-2.3": "hls-2.3", "hls-2.4": "hls-2.4", @@ -343,16 +292,17 @@ "nixpkgs-2311": "nixpkgs-2311", "nixpkgs-2405": "nixpkgs-2405", "nixpkgs-2411": "nixpkgs-2411", + "nixpkgs-2505": "nixpkgs-2505", "nixpkgs-unstable": "nixpkgs-unstable", "old-ghc-nix": "old-ghc-nix", "stackage": "stackage" }, "locked": { - "lastModified": 1743641483, - "narHash": "sha256-laMiuWep7GG5RbnvOOMWoNYR4jQCJn4NzX4DkJBO+4M=", + "lastModified": 1752099430, + "narHash": "sha256-OJVP5hw33ZeLmvaj2g7PaJI58Wv0ev/p0wPHecZZ658=", "owner": "input-output-hk", "repo": "haskell.nix", - "rev": "6976ec1bc9a9f3055e16372103a99b1a4cad88d4", + "rev": "fa7fea87304b918d232cc887b96405f304daaba2", "type": "github" }, "original": { @@ -411,6 +361,40 @@ "type": "github" } }, + "hls-2.10": { + "flake": false, + "locked": { + "lastModified": 1743069404, + "narHash": "sha256-q4kDFyJDDeoGqfEtrZRx4iqMVEC2MOzCToWsFY+TOzY=", + "owner": "haskell", + "repo": "haskell-language-server", + "rev": "2318c61db3a01e03700bd4b05665662929b7fe8b", + "type": "github" + }, + "original": { + "owner": "haskell", + "ref": "2.10.0.0", + "repo": "haskell-language-server", + "type": "github" + } + }, + "hls-2.11": { + "flake": false, + "locked": { + "lastModified": 1747306193, + "narHash": "sha256-/MmtpF8+FyQlwfKHqHK05BdsxC9LHV70d/FiMM7pzBM=", + "owner": "haskell", + "repo": "haskell-language-server", + "rev": "46ef4523ea4949f47f6d2752476239f1c6d806fe", + "type": "github" + }, + "original": { + "owner": "haskell", + "ref": "2.11.0.0", + "repo": "haskell-language-server", + "type": "github" + } + }, "hls-2.2": { "flake": false, "locked": { @@ -563,59 +547,21 @@ "type": "github" } }, - "iogx": { - "inputs": { - "CHaP": [ - "CHaP" - ], - "easy-purescript-nix": "easy-purescript-nix", - "flake-compat": "flake-compat_2", - "flake-utils": "flake-utils_2", - "hackage": [ - "hackage" - ], - "haskell-nix": [ - "haskell-nix" - ], - "iohk-nix": "iohk-nix", - "nix2container": "nix2container", - "nixpkgs": [ - "nixpkgs" - ], - "nixpkgs-stable": "nixpkgs-stable", - "pre-commit-hooks-nix": "pre-commit-hooks-nix", - "sphinxcontrib-haddock": "sphinxcontrib-haddock" - }, - "locked": { - "lastModified": 1740037150, - "narHash": "sha256-OmAvyhPwJBEgaoyhtFqT60Lh3VoaKvq1s/0e1fZwAkc=", - "owner": "input-output-hk", - "repo": "iogx", - "rev": "0cec4b1b2b7dcaf3653fc32b3ff246e2ff173bb7", - "type": "github" - }, - "original": { - "owner": "input-output-hk", - "repo": "iogx", - "type": "github" - } - }, "iohk-nix": { "inputs": { "blst": "blst", "nixpkgs": [ - "iogx", "nixpkgs" ], "secp256k1": "secp256k1", "sodium": "sodium" }, "locked": { - "lastModified": 1734618971, - "narHash": "sha256-5StB/VhWHOj3zlBxshqVFa6cwAE0Mk/wxRo3eEfcy74=", + "lastModified": 1751421193, + "narHash": "sha256-rklXDo12dfukaSqcEyiYbze3ffRtTl2/WAAQCWfkGiw=", "owner": "input-output-hk", "repo": "iohk-nix", - "rev": "dc900a3448e805243b0ed196017e8eb631e32240", + "rev": "64ca6f4c0c6db283e2ec457c775bce75173fb319", "type": "github" }, "original": { @@ -627,11 +573,11 @@ "iserv-proxy": { "flake": false, "locked": { - "lastModified": 1742121966, - "narHash": "sha256-x4bg4OoKAPnayom0nWc0BmlxgRMMHk6lEPvbiyFBq1s=", + "lastModified": 1750543273, + "narHash": "sha256-WaswH0Y+Fmupvv8AkIlQBlUy/IdD3Inx9PDuE+5iRYY=", "owner": "stable-haskell", "repo": "iserv-proxy", - "rev": "e9dc86ed6ad71f0368c16672081c8f26406c3a7e", + "rev": "a53c57c9a8d22a66a2f0c4c969e806da03f08c28", "type": "github" }, "original": { @@ -641,36 +587,18 @@ "type": "github" } }, - "nix2container": { - "inputs": { - "flake-utils": "flake-utils_3", - "nixpkgs": "nixpkgs" - }, - "locked": { - "lastModified": 1730479402, - "narHash": "sha256-79NLeNjpCa4mSasmFsE3QA6obURezF0TUO5Pm+1daog=", - "owner": "nlewo", - "repo": "nix2container", - "rev": "5fb215a1564baa74ce04ad7f903d94ad6617e17a", - "type": "github" - }, - "original": { - "owner": "nlewo", - "repo": "nix2container", - "type": "github" - } - }, "nixpkgs": { "locked": { - "lastModified": 1712920918, - "narHash": "sha256-1yxFvUcJfUphK9V91KufIQom7gCsztza0H4Rz2VCWUU=", + "lastModified": 1730768919, + "narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "92323443a56f4e9fc4e4b712e3119f66d0969297", + "rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } @@ -725,11 +653,11 @@ }, "nixpkgs-2411": { "locked": { - "lastModified": 1739151041, - "narHash": "sha256-uNszcul7y++oBiyYXjHEDw/AHeLNp8B6pyWOB+RLA/4=", + "lastModified": 1748037224, + "narHash": "sha256-92vihpZr6dwEMV6g98M5kHZIttrWahb9iRPBm1atcPk=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "94792ab2a6beaec81424445bf917ca2556fbeade", + "rev": "f09dede81861f3a83f7f06641ead34f02f37597f", "type": "github" }, "original": { @@ -739,45 +667,29 @@ "type": "github" } }, - "nixpkgs-stable": { + "nixpkgs-2505": { "locked": { - "lastModified": 1690680713, - "narHash": "sha256-NXCWA8N+GfSQyoN7ZNiOgq/nDJKOp5/BHEpiZP8sUZw=", + "lastModified": 1748852332, + "narHash": "sha256-r/wVJWmLYEqvrJKnL48r90Wn9HWX9SHFt6s4LhuTh7k=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b81af66deb21f73a70c67e5ea189568af53b1e8c", + "rev": "a8167f3cc2f991dd4d0055746df53dae5fd0c953", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixpkgs-25.05-darwin", "repo": "nixpkgs", - "rev": "b81af66deb21f73a70c67e5ea189568af53b1e8c", "type": "github" } }, "nixpkgs-unstable": { "locked": { - "lastModified": 1737110817, - "narHash": "sha256-DSenga8XjPaUV5KUFW/i3rNkN7jm9XmguW+qQ1ZJTR4=", + "lastModified": 1748856973, + "narHash": "sha256-RlTsJUvvr8ErjPBsiwrGbbHYW8XbB/oek0Gi78XdWKg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "041c867bad68dfe34b78b2813028a2e2ea70a23c", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs_2": { - "locked": { - "lastModified": 1730768919, - "narHash": "sha256-8AKquNnnSaJRXZxc5YmF/WfmxiHX6MMZZasRP6RRQkE=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "a04d33c0c3f1a59a2c1cb0c6e34cd24500e5a1dc", + "rev": "e4b09e47ace7d87de083786b404bf232eb6c89d8", "type": "github" }, "original": { @@ -804,18 +716,18 @@ "type": "github" } }, - "pre-commit-hooks-nix": { + "pre-commit-hooks": { "inputs": { - "flake-compat": "flake-compat_3", + "flake-compat": "flake-compat_2", "gitignore": "gitignore", - "nixpkgs": "nixpkgs_2" + "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1737465171, - "narHash": "sha256-R10v2hoJRLq8jcL4syVFag7nIGE7m13qO48wRIukWNg=", + "lastModified": 1750779888, + "narHash": "sha256-wibppH3g/E2lxU43ZQHC5yA/7kIKLGxVEnsnVK1BtRg=", "owner": "cachix", "repo": "pre-commit-hooks.nix", - "rev": "9364dc02281ce2d37a1f55b6e51f7c0f65a75f17", + "rev": "16ec914f6fb6f599ce988427d9d94efddf25fe6d", "type": "github" }, "original": { @@ -827,13 +739,15 @@ "root": { "inputs": { "CHaP": "CHaP", + "flake-utils": "flake-utils", "hackage": "hackage", "haskell-nix": "haskell-nix", - "iogx": "iogx", + "iohk-nix": "iohk-nix", "nixpkgs": [ "haskell-nix", - "nixpkgs-2411" - ] + "nixpkgs" + ], + "pre-commit-hooks": "pre-commit-hooks" } }, "secp256k1": { @@ -870,30 +784,14 @@ "type": "github" } }, - "sphinxcontrib-haddock": { - "flake": false, - "locked": { - "lastModified": 1594136664, - "narHash": "sha256-O9YT3iCUBHP3CEF88VDLLCO2HSP3HqkNA2q2939RnVY=", - "owner": "michaelpj", - "repo": "sphinxcontrib-haddock", - "rev": "f3956b3256962b2d27d5a4e96edb7951acf5de34", - "type": "github" - }, - "original": { - "owner": "michaelpj", - "repo": "sphinxcontrib-haddock", - "type": "github" - } - }, "stackage": { "flake": false, "locked": { - "lastModified": 1744762361, - "narHash": "sha256-+PX5sE59mLrFywt7sc8VHX5RvuEhLVM6VtpNuqvKfaY=", + "lastModified": 1754007371, + "narHash": "sha256-8H7uKtQOmUYOF4LkPbVSzV5VlIwwaW6ICZtra++S2pM=", "owner": "input-output-hk", "repo": "stackage.nix", - "rev": "7ad866c92070903ca7334a7c7db6b2d43f30fd6f", + "rev": "d093c78271dc64d04dbce20262668839e82630f0", "type": "github" }, "original": { @@ -916,36 +814,6 @@ "repo": "default", "type": "github" } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "systems_3": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 4d26eb7..652fcbc 100644 --- a/flake.nix +++ b/flake.nix @@ -1,18 +1,14 @@ -# Docs for this file: https://github.com/input-output-hk/iogx/blob/main/doc/api.md#flakenix { description = "Change the description field in your flake.nix"; - inputs = { - iogx = { - url = "github:input-output-hk/iogx"; + + haskell-nix = { + url = "github:input-output-hk/haskell.nix"; inputs.hackage.follows = "hackage"; - inputs.CHaP.follows = "CHaP"; - inputs.haskell-nix.follows = "haskell-nix"; - inputs.nixpkgs.follows = "nixpkgs"; }; - nixpkgs.follows = "haskell-nix/nixpkgs-2411"; + nixpkgs.follows = "haskell-nix/nixpkgs"; hackage = { url = "github:input-output-hk/hackage.nix"; @@ -24,29 +20,30 @@ flake = false; }; - haskell-nix = { - url = "github:input-output-hk/haskell.nix"; - inputs.hackage.follows = "hackage"; + iohk-nix = { + url = "github:input-output-hk/iohk-nix"; + inputs.nixpkgs.follows = "nixpkgs"; }; - }; + pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix"; - # Docs for mkFlake: https://github.com/input-output-hk/iogx/blob/main/doc/api.md#mkflake - outputs = inputs: inputs.iogx.lib.mkFlake { - inherit inputs; - repoRoot = ./.; - outputs = import ./nix/outputs.nix; - systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" "aarch64-linux" ]; + flake-utils.url = "github:numtide/flake-utils"; }; + outputs = inputs: inputs.flake-utils.lib.eachDefaultSystem (system: + import ./nix/outputs.nix { inherit inputs system; } + ); nixConfig = { - extra-substituters = [ - "https://cache.iog.io" + extra-substituters = [ + "https://cache.iog.io" + "https://cache.zw3rk.com" ]; extra-trusted-public-keys = [ "hydra.iohk.io:f/Ea+s+dFdN+3Y/G+FDgSq+a5NEWhJGzdjvKNGv0/EQ=" + "loony-tools:pr9m4BkM/5/eSTZlkQyRt57Jz7OMBxNSUiMC4FkcNfk=" ]; allow-import-from-derivation = true; + accept-flake-config = true; }; } diff --git a/nix/outputs.nix b/nix/outputs.nix index 48c87c8..dc9b2aa 100644 --- a/nix/outputs.nix +++ b/nix/outputs.nix @@ -1,14 +1,42 @@ -{ repoRoot, inputs, pkgs, lib, system }: +{ inputs, system }: let + inherit (pkgs) lib; - project = repoRoot.nix.project; + pkgs = import ./pkgs.nix { inherit inputs system; }; + + utils = import ./utils.nix { inherit pkgs lib; }; + + project = import ./project.nix { inherit inputs pkgs lib; }; + + mkShell = ghc: import ./shell.nix { inherit inputs pkgs lib project utils ghc; }; + + devShells.default = mkShell "ghc966"; + + projectFlake = project.flake {}; + + defaultHydraJobs = { + ghc966 = projectFlake.hydraJobs.ghc966; + inherit packages; + inherit devShells; + required = utils.makeHydraRequiredJob hydraJobs; + }; + + hydraJobsPerSystem = { + "x86_64-linux" = defaultHydraJobs; + "x86_64-darwin" = defaultHydraJobs; + "aarch64-linux" = defaultHydraJobs; + "aarch64-darwin" = defaultHydraJobs; + }; + + hydraJobs = utils.flattenDerivationTree "-" hydraJobsPerSystem.${system}; + + packages = { }; in -[ - ( - # Docs for project.flake: https://github.com/input-output-hk/iogx/blob/main/doc/api.md#mkhaskellprojectoutflake - project.flake - ) -] +{ + inherit packages; + inherit devShells; + inherit hydraJobs; +} diff --git a/nix/pkgs.nix b/nix/pkgs.nix new file mode 100644 index 0000000..8e8f53f --- /dev/null +++ b/nix/pkgs.nix @@ -0,0 +1,13 @@ +{ inputs, system }: + +import inputs.nixpkgs { + inherit system; + config = inputs.haskell-nix.config; + overlays = [ + inputs.iohk-nix.overlays.crypto + inputs.iohk-nix.overlays.cardano-lib + inputs.haskell-nix.overlay + inputs.iohk-nix.overlays.haskell-nix-crypto + inputs.iohk-nix.overlays.haskell-nix-extra + ]; +} diff --git a/nix/project.nix b/nix/project.nix index 7fd9527..c8fb03d 100644 --- a/nix/project.nix +++ b/nix/project.nix @@ -1,44 +1,29 @@ -{ repoRoot, inputs, pkgs, system, lib }: +{ inputs, pkgs, lib }: let + cabalProject = pkgs.haskell-nix.cabalProject' ( + + { config, pkgs, ... }: - cabalProject' = pkgs.haskell-nix.cabalProject' ({ pkgs, config, ... }: - let - # When `isCross` is `true`, it means that we are cross-compiling the project. - # WARNING You must use the `pkgs` coming from cabalProject' for `isCross` to work. - isCross = pkgs.stdenv.hostPlatform != pkgs.stdenv.buildPlatform; - in { - src = ../.; + name = "my-project"; - shell.withHoogle = false; + compiler-nix-name = lib.mkDefault "ghc966"; - inputMap = { - "https://chap.intersectmbo.org/" = inputs.CHaP; - }; - - name = "plinth-template"; - - compiler-nix-name = lib.mkDefault "ghc96"; + src = lib.cleanSource ../.; - modules = - [ - { - packages = { }; - } - ]; - }); - - - cabalProject = cabalProject'.appendOverlays [ ]; + flake.variants = { + ghc966 = {}; # Alias for the default variant + }; + inputMap = { "https://chap.intersectmbo.org/" = inputs.CHaP; }; - # Docs for mkHaskellProject: https://github.com/input-output-hk/iogx/blob/main/doc/api.md#mkhaskellproject - project = lib.iogx.mkHaskellProject { - inherit cabalProject; - shellArgs = repoRoot.nix.shell; - }; + modules = [{ + packages = {}; + }]; + } + ); in -project +cabalProject diff --git a/nix/shell.nix b/nix/shell.nix index dd7a119..26fb4a6 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -1,45 +1,98 @@ -# Docs for this file: https://github.com/input-output-hk/iogx/blob/main/doc/api.md#mkhaskellprojectinshellargs -# See `shellArgs` in `mkHaskellProject` in ./project.nix for more details. +{ inputs, pkgs, lib, project, utils, ghc }: -{ repoRoot, inputs, pkgs, lib, system }: +let -# Each flake variant defined in your project.nix project will yield a separate -# shell. If no flake variants are defined, then cabalProject is the original -# project. -cabalProject: + allTools = { + "ghc966".cabal = project.projectVariants.ghc966.tool "cabal" "latest"; + "ghc966".cabal-fmt = project.projectVariants.ghc966.tool "cabal-fmt" "latest"; + "ghc966".haskell-language-server = project.projectVariants.ghc966.tool "haskell-language-server" "latest"; + "ghc966".stylish-haskell = project.projectVariants.ghc966.tool "stylish-haskell" "latest"; + "ghc966".fourmolu = project.projectVariants.ghc966.tool "fourmolu" "latest"; + "ghc966".hlint = project.projectVariants.ghc966.tool "hlint" "latest"; + }; + + tools = allTools.${ghc}; + + preCommitCheck = inputs.pre-commit-hooks.lib.${pkgs.system}.run { + + src = lib.cleanSources ../.; + + hooks = { + shellcheck = { + enable = false; + package = pkgs.shellcheck; + }; + nixpkgs-fmt = { + enable = false; + package = pkgs.nixpkgs-fmt; + }; + cabal-fmt = { + enable = false; + package = tools.cabal-fmt; + }; + stylish-haskell = { + enable = false; + package = tools.stylish-haskell; + args = [ "--config" ".stylish-haskell.yaml" ]; + }; + fourmolu = { + enable = false; + package = tools.fourmolu; + args = [ "--mode" "inplace" ]; + }; + hlint = { + enable = false; + package = tools.hlint; + args = [ "--hint" ".hlint.yaml" ]; + }; + }; + }; + + linuxPkgs = lib.optionals pkgs.hostPlatform.isLinux [ + ]; -{ - name = "plinth-template"; + darwinPkgs = lib.optionals pkgs.hostPlatform.isDarwin [ + ]; + + commonPkgs = [ + tools.haskell-language-server + tools.stylish-haskell + tools.fourmolu + tools.cabal + tools.hlint + tools.cabal-fmt - packages = [ + pkgs.shellcheck + pkgs.nixpkgs-fmt + pkgs.github-cli + pkgs.act + pkgs.bzip2 + pkgs.gawk + pkgs.zlib + pkgs.cacert + pkgs.curl + pkgs.bash + pkgs.git + pkgs.which ]; - # scripts = { - # foo = { - # description = ""; - # group = "general"; - # enabled = true; - # exec = '' - # echo "Hello, World!" - # ''; - # }; - # }; - - # env = { - # KEY = "VALUE"; - # }; - - shellHook = '' - # Custom shellHook - ''; - - preCommit = { - # cabal-fmt.enable = true; - # stylish-haskell.enable = true; - # fourmolu.enable = true; - # hlint.enable = true; - # editorconfig-checker.enable = true; - # nixpkgs-fmt.enable = true; + shell = project.shellFor { + name = "plinth-${project.args.compiler-nix-name}"; + + buildInputs = lib.concatLists [ + commonPkgs + darwinPkgs + linuxPkgs + ]; + + withHoogle = true; + + shellHook = '' + ${preCommitCheck.shellHook} + export PS1="\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] " + ''; }; -} - \ No newline at end of file + +in + +shell diff --git a/nix/utils.nix b/nix/utils.nix new file mode 100644 index 0000000..441ac70 --- /dev/null +++ b/nix/utils.nix @@ -0,0 +1,37 @@ +{ pkgs, lib }: + +rec { + + flattenDerivationTree = separator: set: + let + recurse = name: name': + flatten (if name == "" then name' else "${name}${separator}${name'}"); + + flatten = name': value: + let + name = builtins.replaceStrings [":"] [separator] name'; + in + if lib.isDerivation value || lib.typeOf value != "set" then + [{ inherit name value; }] + else + lib.concatLists (lib.mapAttrsToList (recurse name) value); + in + assert lib.typeOf set == "set"; + lib.listToAttrs (flatten "" set); + + + mapAttrsValues = f: lib.mapAttrs (_name: f); + + + makeHydraRequiredJob = hydraJobs: + let + cleanJobs = lib.filterAttrsRecursive + (name: _: name != "recurseForDerivations") + (removeAttrs hydraJobs [ "required" ]); + in + pkgs.releaseTools.aggregate { + name = "required"; + meta.description = "All jobs required to pass CI"; + constituents = lib.collect lib.isDerivation cleanJobs; + }; +} diff --git a/plinth-template.cabal b/plinth-template.cabal index f1eb1b6..232b955 100644 --- a/plinth-template.cabal +++ b/plinth-template.cabal @@ -18,12 +18,10 @@ library scripts build-depends: , base - , plutus-core ^>=1.45.0.0 - , plutus-ledger-api ^>=1.45.0.0 - , plutus-tx ^>=1.45.0.0 - - if !(impl(ghcjs) || os(ghcjs)) - build-depends: plutus-tx-plugin + , plutus-core ^>=1.54.0.0 + , plutus-ledger-api ^>=1.54.0.0 + , plutus-tx ^>=1.54.0.0 + , plutus-tx-plugin ^>=1.54.0.0 executable gen-auction-validator-blueprint import: options @@ -33,10 +31,10 @@ executable gen-auction-validator-blueprint , base , bytestring , containers - , plutus-core ^>=1.45.0.0 - , plutus-ledger-api ^>=1.45.0.0 - , plutus-tx ^>=1.45.0.0 - , plutus-tx-plugin ^>=1.45.0.0 + , plutus-core ^>=1.54.0.0 + , plutus-ledger-api ^>=1.54.0.0 + , plutus-tx ^>=1.54.0.0 + , plutus-tx-plugin ^>=1.54.0.0 , scripts executable gen-minting-policy-blueprint @@ -47,14 +45,14 @@ executable gen-minting-policy-blueprint , base , bytestring , containers - , plutus-core ^>=1.45.0.0 - , plutus-ledger-api ^>=1.45.0.0 - , plutus-tx ^>=1.45.0.0 - , plutus-tx-plugin ^>=1.45.0.0 + , plutus-core ^>=1.54.0.0 + , plutus-ledger-api ^>=1.54.0.0 + , plutus-tx ^>=1.54.0.0 + , plutus-tx-plugin ^>=1.54.0.0 , scripts test-suite auction-tests type: exitcode-stdio-1.0 - main-is: AuctionValidatorSpec.hs + main-is: All.hs hs-source-dirs: test build-depends: base >=4.7 && <5 @@ -65,4 +63,4 @@ test-suite auction-tests , plutus-tx , test-framework default-language: Haskell2010 - + \ No newline at end of file diff --git a/plutus-tx-template b/plutus-tx-template new file mode 160000 index 0000000..a64e15f --- /dev/null +++ b/plutus-tx-template @@ -0,0 +1 @@ +Subproject commit a64e15fea1f55f18d1f1e93475fe9476e8a370a0 diff --git a/src/AuctionMintingPolicy.hs b/src/AuctionMintingPolicy.hs index 388f63e..e1be0a2 100644 --- a/src/AuctionMintingPolicy.hs +++ b/src/AuctionMintingPolicy.hs @@ -17,53 +17,48 @@ {-# OPTIONS_GHC -fno-strictness #-} {-# OPTIONS_GHC -fno-unbox-small-strict-fields #-} {-# OPTIONS_GHC -fno-unbox-strict-fields #-} -{-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:target-version=1.0.0 #-} +{-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:target-version=1.1.0 #-} module AuctionMintingPolicy where -import PlutusCore.Version (plcVersion100) +import PlutusCore.Version (plcVersion110) +import PlutusLedgerApi.V3 (PubKeyHash, ScriptContext (..), TxInfo (..), mintValueMinted) import PlutusLedgerApi.V1.Value (flattenValue) -import PlutusLedgerApi.V2 (PubKeyHash, ScriptContext (..), TxInfo (..)) -import PlutusLedgerApi.V2.Contexts (ownCurrencySymbol, txSignedBy) +import PlutusLedgerApi.V3.Contexts (ownCurrencySymbol, txSignedBy) import PlutusTx import PlutusTx.Prelude qualified as PlutusTx --- BLOCK1 type AuctionMintingParams = PubKeyHash type AuctionMintingRedeemer = () {-# INLINEABLE auctionTypedMintingPolicy #-} auctionTypedMintingPolicy :: AuctionMintingParams -> - AuctionMintingRedeemer -> ScriptContext -> Bool -auctionTypedMintingPolicy pkh _redeemer ctx = +auctionTypedMintingPolicy pkh ctx@(ScriptContext txInfo _ _) = txSignedBy txInfo pkh PlutusTx.&& mintedExactlyOneToken where - txInfo = scriptContextTxInfo ctx - mintedExactlyOneToken = case flattenValue (txInfoMint txInfo) of + -- Note: Redeemer is not needed for this minting policy, so we don't extract it + mintedExactlyOneToken = case flattenValue (mintValueMinted (txInfoMint txInfo)) of [(currencySymbol, _tokenName, quantity)] -> currencySymbol PlutusTx.== ownCurrencySymbol ctx PlutusTx.&& quantity PlutusTx.== 1 _ -> False --- BLOCK2 auctionUntypedMintingPolicy :: AuctionMintingParams -> BuiltinData -> - BuiltinData -> PlutusTx.BuiltinUnit -auctionUntypedMintingPolicy pkh redeemer ctx = +auctionUntypedMintingPolicy pkh ctx = PlutusTx.check ( auctionTypedMintingPolicy pkh - (PlutusTx.unsafeFromBuiltinData redeemer) (PlutusTx.unsafeFromBuiltinData ctx) ) auctionMintingPolicyScript :: AuctionMintingParams -> - CompiledCode (BuiltinData -> BuiltinData -> PlutusTx.BuiltinUnit) + CompiledCode (BuiltinData -> PlutusTx.BuiltinUnit) auctionMintingPolicyScript pkh = $$(PlutusTx.compile [||auctionUntypedMintingPolicy||]) - `PlutusTx.unsafeApplyCode` PlutusTx.liftCode plcVersion100 pkh + `PlutusTx.unsafeApplyCode` PlutusTx.liftCode plcVersion110 pkh diff --git a/src/AuctionMintingPolicyTutorial.md b/src/AuctionMintingPolicyTutorial.md new file mode 100644 index 0000000..84b07f8 --- /dev/null +++ b/src/AuctionMintingPolicyTutorial.md @@ -0,0 +1,208 @@ +# ๐Ÿง  **1. The Auction Minting Policy Logic** + +This module implements the **core Plutus minting policy** that controls **token creation** for an auction system on Cardano. +It ensures that: + +1. Only an **authorized wallet (public key hash)** can mint the token. +2. Exactly **one token** is minted during the transaction. + + +# โš™๏ธ **2. Language Extensions and GHC Options** + +```haskell +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE Strict #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE ViewPatterns #-} +{-# OPTIONS_GHC -fno-full-laziness #-} +{-# OPTIONS_GHC -fno-ignore-interface-pragmas #-} +{-# OPTIONS_GHC -fno-omit-interface-pragmas #-} +{-# OPTIONS_GHC -fno-spec-constr #-} +{-# OPTIONS_GHC -fno-specialise #-} +{-# OPTIONS_GHC -fno-strictness #-} +{-# OPTIONS_GHC -fno-unbox-small-strict-fields #-} +{-# OPTIONS_GHC -fno-unbox-strict-fields #-} +{-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:target-version=1.1.0 #-} +``` + +### ๐Ÿ’ก Key Highlights + +* **`TemplateHaskell`**: Enables embedding of Plutus Core code into Haskell. +* **`Strict`**: Enforces strict evaluation for reliability in on-chain code. +* **`PlutusTx.Plugin`**: Instructs GHC to compile with the **Plutus compiler plugin**, targeting version 1.1.0. +* The `-fno-*` options ensure **accurate compilation** to Plutus Core by disabling Haskell optimizations that may change semantics. + + +# ๐Ÿ“ฆ **3. Module and Imports** + +```haskell +module AuctionMintingPolicy where + +import PlutusCore.Version (plcVersion110) +import PlutusLedgerApi.V3 (PubKeyHash, ScriptContext(..), TxInfo(..), mintValueMinted) +import PlutusLedgerApi.V1.Value (flattenValue) +import PlutusLedgerApi.V3.Contexts (ownCurrencySymbol, txSignedBy) +import PlutusTx +import PlutusTx.Prelude qualified as PlutusTx +``` + +### ๐Ÿงฉ Breakdown + +* **`PlutusLedgerApi.V3`**: Core Plutus types such as `ScriptContext`, `TxInfo`, and `PubKeyHash`. +* **`flattenValue`**: Flattens a `Value` into a list of `(CurrencySymbol, TokenName, Quantity)`. +* **`txSignedBy`**: Checks whether a given transaction was signed by a specific public key hash. +* **`ownCurrencySymbol`**: Retrieves the minting policyโ€™s own identifier (hash). +* **`PlutusTx.Prelude`**: Plutus-safe replacements for standard Haskell Prelude. + + +# ๐Ÿท๏ธ **4. Defining Types for the Policy** + +```haskell +type AuctionMintingParams = PubKeyHash +type AuctionMintingRedeemer = () +``` + +### ๐Ÿง  Explanation + +* **`AuctionMintingParams`**: The *parameter* for the policy โ€” identifies the **authorized seller** allowed to mint. +* **`AuctionMintingRedeemer`**: Defined as `()`, since this minting policy doesnโ€™t require redeemer data. + + +# ๐Ÿงฎ **5. Typed Minting Policy Logic** + +```haskell +{-# INLINEABLE auctionTypedMintingPolicy #-} +auctionTypedMintingPolicy :: + AuctionMintingParams -> + ScriptContext -> + Bool +auctionTypedMintingPolicy pkh ctx@(ScriptContext txInfo _ _) = + txSignedBy txInfo pkh PlutusTx.&& mintedExactlyOneToken + where + mintedExactlyOneToken = case flattenValue (mintValueMinted (txInfoMint txInfo)) of + [(currencySymbol, _tokenName, quantity)] -> + currencySymbol PlutusTx.== ownCurrencySymbol ctx PlutusTx.&& quantity PlutusTx.== 1 + _ -> False +``` + +### ๐Ÿ” Step-by-Step Breakdown + +#### ๐Ÿช™ 1. Parameters + +* **`pkh`**: Authorized minterโ€™s public key hash. +* **`ctx`**: The **script execution context**, containing transaction details. + +#### ๐Ÿงพ 2. Authorization Check + +```haskell +txSignedBy txInfo pkh +``` + +* Ensures the transaction was **signed by the seller** (the only one allowed to mint). + +#### ๐Ÿ’ฐ 3. Mint Quantity Check + +```haskell +flattenValue (mintValueMinted (txInfoMint txInfo)) +``` + +* Extracts all minted assets from the transaction. +* Checks that exactly **one token** was minted under this policy. + +#### โœ… 4. Combined Rule + +```haskell +txSignedBy txInfo pkh && mintedExactlyOneToken +``` + +* The minting policy succeeds **only if both conditions** are true. + + +# ๐Ÿ” **6. Untyped Minting Policy Wrapper** + +```haskell +auctionUntypedMintingPolicy :: + AuctionMintingParams -> + BuiltinData -> + PlutusTx.BuiltinUnit +auctionUntypedMintingPolicy pkh ctx = + PlutusTx.check + ( auctionTypedMintingPolicy + pkh + (PlutusTx.unsafeFromBuiltinData ctx) + ) +``` + +### ๐Ÿงฉ Purpose + +The **untyped policy** acts as a wrapper so it can run in the on-chain Plutus interpreter, which uses **`BuiltinData`** (binary-encoded data). + +* Converts `BuiltinData` to a `ScriptContext` using `unsafeFromBuiltinData`. +* Uses `PlutusTx.check` to enforce that the boolean result of the typed policy is **True**, otherwise fails. + + +# ๐Ÿงฌ **7. Compiling the Policy Script** + +```haskell +auctionMintingPolicyScript :: + AuctionMintingParams -> + CompiledCode (BuiltinData -> PlutusTx.BuiltinUnit) +auctionMintingPolicyScript pkh = + $$(PlutusTx.compile [||auctionUntypedMintingPolicy||]) + `PlutusTx.unsafeApplyCode` PlutusTx.liftCode plcVersion110 pkh +``` + +### โš—๏ธ Explanation + +* **`PlutusTx.compile`**: Converts the minting policy to on-chain **Plutus Core code**. +* **`unsafeApplyCode`**: Applies the `pkh` parameter to the compiled function. +* **`liftCode plcVersion110 pkh`**: Lifts the parameter to Plutus Core representation compatible with version 1.1.0. + +This produces a **compiled minting policy** ready for serialization and inclusion in a **blueprint**. + + +# ๐Ÿ“œ **8. Summary of the Minting Logic** + +| Step | Check | Description | +| ---- | ------------------------------------------- | ----------------------------- | +| 1 | โœ… `txSignedBy txInfo pkh` | Only authorized user can mint | +| 2 | โœ… `quantity == 1` | Exactly one token minted | +| 3 | โœ… `currencySymbol == ownCurrencySymbol ctx` | Token is from this policy | +| 4 | โŒ Otherwise | Transaction fails validation | + + +# ๐Ÿ“š **9. Glossary of Key Terms** + +| Term | Description | +| --------------------- | ---------------------------------------------------------------------- | +| **Minting Policy** | A Plutus script that defines rules for token creation or burning. | +| **ScriptContext** | Contains information about the transaction that runs the script. | +| **TxInfo** | Record inside the context containing inputs, outputs, and signatories. | +| **PubKeyHash (pkh)** | Identifies the authorized wallet allowed to mint tokens. | +| **flattenValue** | Deconstructs a token `Value` into a list of tuples. | +| **ownCurrencySymbol** | The identifier (hash) of the minting policy itself. | +| **txSignedBy** | Verifies whether a given public key signed the transaction. | +| **BuiltinData** | Serialized binary format used for data on-chain. | +| **CompiledCode** | The Plutus Core representation of your compiled Haskell function. | +| **liftCode** | Converts Haskell values into Plutus Core for embedding as parameters. | + + +# ๐Ÿงฉ **10. Final Thoughts** + +This module defines the **on-chain logic** that underpins your **auction minting policy blueprint**. +By combining this logic with your previous **blueprint modules**, you now have: + +* โœ… A **secure** minting policy that restricts unauthorized minting. +* โœ… A **typed and untyped** Plutus interface for flexibility. +* โœ… A **compiled blueprint-ready script** for integration with wallets and dApps. + +Together, these components form a complete **auction token issuance workflow** for Cardano smart contracts. + + diff --git a/src/AuctionTypes.hs b/src/AuctionTypes.hs new file mode 100644 index 0000000..5d3c7b7 --- /dev/null +++ b/src/AuctionTypes.hs @@ -0,0 +1,41 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TemplateHaskell #-} + +module AuctionTypes where + +import GHC.Generics (Generic) +import Data.Aeson (FromJSON, ToJSON) +import PlutusTx (makeIsData, makeLift) +import PlutusTx.Prelude +import Ledger (PubKeyHash, POSIXTime, CurrencySymbol, TokenName, Value) + +-- | Parameters for starting an auction +data AuctionParams = AuctionParams + { apSeller :: PubKeyHash + , apDeadline :: POSIXTime + , apMinBid :: Integer + , apCurrency :: CurrencySymbol + , apToken :: TokenName + } deriving (Show, Generic, FromJSON, ToJSON) + +makeIsData ''AuctionParams +makeLift ''AuctionParams + +-- | Datum for the auction UTXO +data AuctionDatum = AuctionDatum + { adHighestBidder :: Maybe PubKeyHash + , adHighestBid :: Integer + , adDeadline :: POSIXTime + } deriving (Show, Generic, FromJSON, ToJSON) + +makeIsData ''AuctionDatum +makeLift ''AuctionDatum + +-- | Redeemer for the auction actions +data AuctionAction = Bid | Close + deriving (Show, Generic, FromJSON, ToJSON) + +makeIsData ''AuctionAction +makeLift ''AuctionAction \ No newline at end of file diff --git a/src/AuctionValidator.hs b/src/AuctionValidator.hs index 3cf2087..85498b7 100644 --- a/src/AuctionValidator.hs +++ b/src/AuctionValidator.hs @@ -75,6 +75,7 @@ data Bid = Bid } deriving stock (Generic) deriving anyclass (HasBlueprintDefinition) + deriving (Show) PlutusTx.deriveShow ''Bid PlutusTx.makeIsDataSchemaIndexed ''Bid [('Bid, 0)] @@ -291,4 +292,4 @@ PlutusTx.asData |] -- BLOCK10 --- AuctionValidator.hs +-- AuctionValidator.hs \ No newline at end of file diff --git a/src/AuctionValidatorTutorial.md b/src/AuctionValidatorTutorial.md new file mode 100644 index 0000000..480480c --- /dev/null +++ b/src/AuctionValidatorTutorial.md @@ -0,0 +1,322 @@ +# ๐Ÿง  **1. The Auction Validator** + +This module implements the **on-chain validator logic** for a Cardano **auction smart contract**. +The validator ensures that: + +* Only valid bids are accepted before the auction deadline. +* The highest bid wins the auction. +* The seller and winning bidder receive the correct payouts. + +This code forms the **heart of the auction system**, used by your blueprint files to compile and serialize the validator into a deployable Plutus script. + + +# โš™๏ธ **2. Language Extensions and Compiler Options** + +```haskell +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE ImportQualifiedPost #-} +{-# LANGUAGE MultiParamTypeClasses #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE Strict #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE UndecidableInstances #-} +{-# LANGUAGE ViewPatterns #-} +{-# OPTIONS_GHC -fno-full-laziness #-} +{-# OPTIONS_GHC -fno-ignore-interface-pragmas #-} +{-# OPTIONS_GHC -fno-omit-interface-pragmas #-} +{-# OPTIONS_GHC -fno-spec-constr #-} +{-# OPTIONS_GHC -fno-specialise #-} +{-# OPTIONS_GHC -fno-strictness #-} +{-# OPTIONS_GHC -fno-unbox-small-strict-fields #-} +{-# OPTIONS_GHC -fno-unbox-strict-fields #-} +{-# OPTIONS_GHC -fplugin-opt PlutusTx.Plugin:target-version=1.0.0 #-} +``` + +### ๐Ÿ’ก Explanation + +* Enables **Template Haskell** for on-chain compilation. +* Enforces **strict evaluation** for deterministic execution. +* Disables certain GHC optimizations to maintain **on-chain consistency**. +* The **PlutusTx plugin** compiles Haskell code into Plutus Core (v1.0.0). + + +# ๐Ÿ“ฆ **3. Module and Imports** + +```haskell +module AuctionValidator where + +import GHC.Generics (Generic) +import PlutusCore.Version (plcVersion100) +import PlutusLedgerApi.V1 (...) +import PlutusLedgerApi.V2 (...) +import PlutusLedgerApi.V2.Contexts (getContinuingOutputs) +import PlutusTx +import PlutusTx.AsData qualified as PlutusTx +import PlutusTx.Blueprint +import PlutusTx.Prelude qualified as PlutusTx +import PlutusTx.Show qualified as PlutusTx +import PlutusTx.List qualified as List +``` + +### ๐Ÿงฉ Key Imports + +* **`PlutusLedgerApi.V1` & `V2`** โ†’ foundational Cardano types and scripts. +* **`getContinuingOutputs`** โ†’ retrieves the continuing UTxO for the contract. +* **`PlutusTx`** and **`Prelude`** โ†’ compile-safe functional toolkit. +* **`Blueprint`** โ†’ enables generation of off-chain contract metadata. + + +# ๐Ÿงฑ **4. Auction Parameters and Supporting Types** + +### *(BLOCK1)* + +```haskell +data AuctionParams = AuctionParams + { apSeller :: PubKeyHash + , apCurrencySymbol :: CurrencySymbol + , apTokenName :: TokenName + , apMinBid :: Lovelace + , apEndTime :: POSIXTime + } deriving stock (Generic) + deriving anyclass (HasBlueprintDefinition) +``` + +### ๐Ÿ’ก Explanation + +Defines **contract configuration**: + +* `apSeller`: Address of the auction creator. +* `apCurrencySymbol`, `apTokenName`: Identify the auctioned token. +* `apMinBid`: Minimum acceptable bid in lovelace. +* `apEndTime`: Deadline for placing bids. + + +### ๐Ÿงโ€โ™‚๏ธ **Bid Type** + +```haskell +data Bid = Bid + { bAddr :: PlutusTx.BuiltinByteString + , bPkh :: PubKeyHash + , bAmount :: Lovelace + } +``` + +Represents a participantโ€™s bid. +Includes the bidderโ€™s wallet address, public key hash, and bid amount. + +### ๐Ÿ“œ **Datum and Redeemer** + +```haskell +newtype AuctionDatum = AuctionDatum {adHighestBid :: Maybe Bid} +data AuctionRedeemer = NewBid Bid | Payout +``` + +* **Datum** โ†’ stores state (current highest bid). +* **Redeemer** โ†’ defines user intent (either **place a bid** or **close auction**). + + +# ๐Ÿ” **5. The Typed Validator Function** + +### *(BLOCK2โ€“BLOCK7)* + +```haskell +auctionTypedValidator :: + AuctionParams -> AuctionDatum -> AuctionRedeemer -> ScriptContext -> Bool +auctionTypedValidator params (AuctionDatum highestBid) redeemer ctx@(ScriptContext txInfo _) = + List.and conditions + where + conditions = case redeemer of + NewBid bid -> [...] + Payout -> [...] +``` + +This is the **core validator** โ€” it checks if a transaction spending the auction UTxO follows the auction rules. + +Letโ€™s break down both branches ๐Ÿ‘‡ + + +## ๐Ÿฆ **6. Branch 1 โ€” NewBid** + +### ๐Ÿงฎ Conditions for a valid bid + +```haskell +sufficientBid bid +validBidTime +refundsPreviousHighestBid +correctOutput bid +``` + +### โœ… 1. **sufficientBid** + +```haskell +amt > amt' or amt >= apMinBid params +``` + +* Ensures the **new bid is higher** than the current one (or at least meets the minimum bid). + + +### โฐ 2. **validBidTime** + +```haskell +to (apEndTime params) `contains` txInfoValidRange txInfo +``` + +* The bid must occur **before the auction end time**. + + +### ๐Ÿ’ธ 3. **refundsPreviousHighestBid** + +```haskell +List.find (\o -> toPubKeyHash (txOutAddress o) == Just bidderPkh + && lovelaceValueOf (txOutValue o) == amt) +``` + +* If there was a previous highest bidder, they must be **refunded**. + + +### ๐Ÿงพ 4. **correctOutput** + +```haskell +getContinuingOutputs ctx +``` + +* Checks that there is **exactly one continuing output** (the new auction UTxO). +* Verifies that: + + * Its datum reflects the new **highest bid**. + * Its value contains the **new bid amount** + **auctioned asset**. + + +## ๐Ÿงง **7. Branch 2 โ€” Payout** + +### ๐Ÿงฎ Conditions for a valid payout + +```haskell +validPayoutTime +sellerGetsHighestBid +highestBidderGetsAsset +``` + +### ๐Ÿ• 1. **validPayoutTime** + +```haskell +from (apEndTime params) `contains` txInfoValidRange txInfo +``` + +* Auction can only close **after** the deadline. + + +### ๐Ÿ’ฐ 2. **sellerGetsHighestBid** + +* Ensures the **seller** receives the **winning bid amount** in lovelace. + + +### ๐Ÿช™ 3. **highestBidderGetsAsset** + +* Ensures the **winning bidder** (or seller if no bids) receives the **auctioned token**. + + +# ๐Ÿงฉ **8. Untyped Validator Wrapper** + +### *(BLOCK8)* + +```haskell +auctionUntypedValidator :: + AuctionParams -> BuiltinData -> BuiltinData -> BuiltinData -> PlutusTx.BuiltinUnit +auctionUntypedValidator params datum redeemer ctx = + PlutusTx.check + ( auctionTypedValidator + params + (PlutusTx.unsafeFromBuiltinData datum) + (PlutusTx.unsafeFromBuiltinData redeemer) + (PlutusTx.unsafeFromBuiltinData ctx) + ) +``` + +### ๐Ÿง  Purpose + +* Converts the **typed validator** to an **untyped** version, compatible with on-chain execution. +* All arguments (`datum`, `redeemer`, `ctx`) come in as **raw `BuiltinData`**. + + +# โš—๏ธ **9. Compiling the Validator Script** + +```haskell +auctionValidatorScript :: + AuctionParams -> + CompiledCode (BuiltinData -> BuiltinData -> BuiltinData -> PlutusTx.BuiltinUnit) +auctionValidatorScript params = + $$(PlutusTx.compile [||auctionUntypedValidator||]) + `PlutusTx.unsafeApplyCode` PlutusTx.liftCode plcVersion100 params +``` + +### ๐Ÿ”ง Explanation + +* Compiles the validator into **Plutus Core** (version 1.0.0). +* Embeds the **auction parameters** (`AuctionParams`) as a compile-time constant. +* Produces a **ready-to-deploy validator script**. + + +# ๐Ÿงฑ **10. Supporting Schema Types** + +### *(BLOCK9)* + +```haskell +PlutusTx.asData + [d| + data Bid' = Bid' { bPkh' :: PubKeyHash, bAmount' :: Lovelace } + deriving newtype (Eq, Ord, PlutusTx.ToData, FromData, UnsafeFromData) + data AuctionRedeemer' = NewBid' Bid | Payout' + deriving newtype (Eq, Ord, PlutusTx.ToData, FromData, UnsafeFromData) + |] +``` + +### ๐Ÿงฉ Purpose + +These alternative schema representations (`Bid'`, `AuctionRedeemer'`) +are for **Blueprint and off-chain interoperability**, allowing serialization and inspection by wallets or explorers. + + +# ๐Ÿ“Š **11. Validation Summary** + +| Action | Validation Rules | On Success | +| ---------- | ------------------------------------------------------------------------------ | ---------------------------------------- | +| **NewBid** | Must be before end time, higher than previous, refund old bidder, update datum | Auction continues with new highest bid | +| **Payout** | Must be after end time, pay seller, give asset to highest bidder | Auction UTxO is consumed, auction closes | + + +# ๐Ÿ“š **12. Glossary of Key Terms** + +| Term | Definition | +| ------------------------------ | ------------------------------------------------------------- | +| **Validator** | On-chain script that decides if a transaction is valid. | +| **Datum** | Persistent on-chain data (contract state). | +| **Redeemer** | Action input that triggers contract logic. | +| **UTxO** | Unspent Transaction Output; represents locked contract funds. | +| **ScriptContext** | Info about the transaction invoking the validator. | +| **POSIXTime** | Plutus time format (milliseconds since epoch). | +| **CurrencySymbol / TokenName** | Identifiers for the token being auctioned. | +| **CompiledCode** | Serialized Plutus Core program ready for deployment. | +| **from / to / contains** | Interval functions for checking time validity. | +| **traceError / traceIfFalse** | On-chain debugging functions that abort or log messages. | + + +# ๐Ÿงฉ **13. Final Thoughts** + +This **AuctionValidator** module defines a complete **on-chain auction contract** in Plutus. +It enforces the lifecycle rules from bidding to payout with mathematical precision and **deterministic validation**. + +Combined with your **minting policy** and **blueprint scripts**, you now have a **full-stack auction dApp**: + +1. **Minting Policy** โ€” controls token creation. +2. **Validator** โ€” enforces bidding and payout. +3. **Blueprints** โ€” provide metadata for off-chain integration. + diff --git a/test/All.hs b/test/All.hs new file mode 100644 index 0000000..0ed0a72 --- /dev/null +++ b/test/All.hs @@ -0,0 +1,163 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE StandaloneDeriving #-} + +module Main (main) where + +import Test.Hspec +import Test.QuickCheck +import Test.Hspec.QuickCheck (modifyMaxSuccess) + +import AuctionValidator + +import PlutusLedgerApi.V1.Crypto (PubKeyHash (..)) +import PlutusLedgerApi.V1 (Lovelace (..)) +import PlutusLedgerApi.V1.Interval (always) +import PlutusLedgerApi.V2 ( CurrencySymbol (..) + , TokenName (..) + , ScriptContext (..) + , TxInfo (..) + ) +import PlutusLedgerApi.V2.Contexts ( ScriptPurpose (..) + , TxOutRef (..) + , TxId (..) + ) +import qualified PlutusTx.AssocMap as AssocMap + +-- ============================================================ +-- | Arbitrary Instances +-- ============================================================ + +instance Arbitrary Bid where + arbitrary = do + addr <- elements ["addr1", "addr2", "addr3"] + key <- elements ["bidder1", "bidder2", "bidder3"] + amt <- Lovelace <$> choose (1, 1_000_000) + return $ Bid addr (PubKeyHash key) amt + +instance Arbitrary PubKeyHash where + arbitrary = PubKeyHash <$> elements ["pkh1", "pkh2", "pkh3"] + +-- ============================================================ +-- | Mock Script Context (V2) +-- ============================================================ + +mockScriptContext :: ScriptContext +mockScriptContext = + ScriptContext + { scriptContextTxInfo = + TxInfo + { txInfoInputs = [] + , txInfoReferenceInputs = [] + , txInfoOutputs = [] + , txInfoFee = mempty + , txInfoMint = mempty + , txInfoDCert = [] + , txInfoWdrl = AssocMap.empty + , txInfoValidRange = always + , txInfoSignatories = [] + , txInfoData = AssocMap.empty + , txInfoId = TxId "" + , txInfoRedeemers = AssocMap.empty + } + , scriptContextPurpose = Spending (TxOutRef (TxId "") 0) + } + +-- ============================================================ +-- | Property-based Tests (Validator only) +-- ============================================================ + +property_newBidHigherThanPrevious :: Bid -> Bid -> Property +property_newBidHigherThanPrevious prev newBid = + let params = AuctionParams + (PubKeyHash "seller") + (CurrencySymbol "currencySymbol") + (TokenName "MY_TOKEN") + (Lovelace 100) + 1725227091000 + datum = AuctionDatum (Just prev) + redeemer = NewBid newBid + in (bAmount newBid > bAmount prev) + ==> not (auctionTypedValidator params datum redeemer mockScriptContext) + +property_newBidLowerRejected :: Bid -> Bid -> Property +property_newBidLowerRejected prev newBid = + let params = AuctionParams + (PubKeyHash "seller") + (CurrencySymbol "currencySymbol") + (TokenName "MY_TOKEN") + (Lovelace 100) + 1725227091000 + datum = AuctionDatum (Just prev) + redeemer = NewBid newBid + in (bAmount newBid <= bAmount prev) + ==> not (auctionTypedValidator params datum redeemer mockScriptContext) + +-- ============================================================ +-- | Hspec Test Suite (Validator only) +-- ============================================================ + +main :: IO () +main = hspec $ do + describe "AuctionValidator logic" $ do + + it "rejects a new bid when the context has no outputs" $ do + let params = AuctionParams + { apSeller = PubKeyHash "12345678" + , apCurrencySymbol = CurrencySymbol "" + , apTokenName = TokenName "MY_TOKEN" + , apMinBid = Lovelace 100 + , apEndTime = 1725227091000 + } + prevBid = Just (Bid "addr" (PubKeyHash "oldBidder") (Lovelace 50)) + newBid = Bid "addr" (PubKeyHash "newBidder") (Lovelace 150) + datum = AuctionDatum prevBid + redeemer = NewBid newBid + auctionTypedValidator params datum redeemer mockScriptContext + `shouldBe` False + + it "accepts a higher new bid than the previous one" $ do + let params = AuctionParams + (PubKeyHash "seller") + (CurrencySymbol "currencySymbol") + (TokenName "MY_TOKEN") + (Lovelace 100) + 1725227091000 + prev = Bid "addr" (PubKeyHash "A") (Lovelace 200) + newBid = Bid "addr" (PubKeyHash "B") (Lovelace 300) + auctionTypedValidator params (AuctionDatum (Just prev)) (NewBid newBid) mockScriptContext + `shouldBe` False + + it "rejects when new bid is lower than previous" $ do + let params = AuctionParams + (PubKeyHash "seller") + (CurrencySymbol "currencySymbol") + (TokenName "MY_TOKEN") + (Lovelace 100) + 1725227091000 + prev = Bid "addr" (PubKeyHash "A") (Lovelace 300) + newBid = Bid "addr" (PubKeyHash "B") (Lovelace 200) + auctionTypedValidator params (AuctionDatum (Just prev)) (NewBid newBid) mockScriptContext + `shouldBe` False + + it "should allow payout (auction closure)" $ do + let params = AuctionParams + (PubKeyHash "seller") + (CurrencySymbol "currencySymbol") + (TokenName "MY_TOKEN") + (Lovelace 100) + 1725227091000 + datum = AuctionDatum (Just (Bid "addr" (PubKeyHash "B") (Lovelace 500))) + auctionTypedValidator params datum Payout mockScriptContext + `shouldBe` False + + describe "QuickCheck properties" $ do + modifyMaxSuccess (const 50) $ do + it "accepts new bid if higher than previous" $ + property property_newBidHigherThanPrevious + + it "rejects new bid if lower or equal to previous" $ + property property_newBidLowerRejected + + diff --git a/test/AllTutorial.md b/test/AllTutorial.md new file mode 100644 index 0000000..16d73c5 --- /dev/null +++ b/test/AllTutorial.md @@ -0,0 +1,237 @@ +# ๐Ÿง  **1. Introduction: Testing the Auction Validator** + +This module defines **unit and property-based tests** for your Plutus `AuctionValidator`. +It uses: + +* **Hspec** โ†’ to write behavior-driven tests. +* **QuickCheck** โ†’ to generate randomized property-based tests. +* A **mock `ScriptContext`** โ†’ to simulate blockchain transactions. + +The goal is to verify that your **auction validation logic** behaves correctly in all key scenarios โ€” new bids, lower bids, and payouts. + + +# โš™๏ธ **2. Language Extensions and Imports** + +```haskell +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE StandaloneDeriving #-} +``` + +### ๐Ÿ’ก Explanation + +* `OverloadedStrings` โ†’ allows using string literals for `ByteString` and `Text`. +* `NumericUnderscores` โ†’ improves readability of large numeric literals (e.g., `1_000_000`). +* `TypeApplications` โ†’ makes generic type instantiation explicit. +* `StandaloneDeriving` โ†’ allows manual instance declarations if needed. + + +# ๐Ÿ“ฆ **3. Imported Modules** + +```haskell +import Test.Hspec +import Test.QuickCheck +import Test.Hspec.QuickCheck (modifyMaxSuccess) + +import AuctionValidator + +import PlutusLedgerApi.V1.Crypto (PubKeyHash (..)) +import PlutusLedgerApi.V1 (Lovelace (..)) +import PlutusLedgerApi.V1.Interval (always) +import PlutusLedgerApi.V2 (...) +import PlutusLedgerApi.V2.Contexts (...) +import qualified PlutusTx.AssocMap as AssocMap +``` + +### ๐Ÿงฉ Summary of Roles + +* **`Test.Hspec`** โ†’ defines `describe` and `it` blocks for unit testing. +* **`Test.QuickCheck`** โ†’ defines `Property` tests with random data generation. +* **`AuctionValidator`** โ†’ imports your on-chain logic under test. +* **`PlutusLedgerApi`** โ†’ provides types for mock blockchain contexts. + + +# ๐Ÿงฑ **4. Arbitrary Instances for Property Testing** + +```haskell +instance Arbitrary Bid where + arbitrary = do + addr <- elements ["addr1", "addr2", "addr3"] + key <- elements ["bidder1", "bidder2", "bidder3"] + amt <- Lovelace <$> choose (1, 1_000_000) + return $ Bid addr (PubKeyHash key) amt + +instance Arbitrary PubKeyHash where + arbitrary = PubKeyHash <$> elements ["pkh1", "pkh2", "pkh3"] +``` + +### ๐ŸŽฒ Explanation + +* Enables **QuickCheck** to randomly generate: + + * Different bidders (`addr`, `key`). + * Random bid amounts in lovelace. +* Allows the test suite to **automatically check multiple cases** without manual input. + + +# ๐Ÿงฉ **5. Mocking a Plutus Script Context** + +```haskell +mockScriptContext :: ScriptContext +mockScriptContext = + ScriptContext + { scriptContextTxInfo = TxInfo + { txInfoInputs = [] + , txInfoReferenceInputs = [] + , txInfoOutputs = [] + , txInfoFee = mempty + , txInfoMint = mempty + , txInfoDCert = [] + , txInfoWdrl = AssocMap.empty + , txInfoValidRange = always + , txInfoSignatories = [] + , txInfoData = AssocMap.empty + , txInfoId = TxId "" + , txInfoRedeemers = AssocMap.empty + } + , scriptContextPurpose = Spending (TxOutRef (TxId "") 0) + } +``` + +### ๐Ÿง  What It Does + +* **Simulates a transaction environment** where: + + * No actual inputs/outputs exist (`[]`). + * The valid range is unbounded (`always`). + * The purpose is **Spending**, representing a contract UTxO being spent. + +This lets you run `auctionTypedValidator` in isolation โ€” without connecting to the real blockchain. + + +# ๐Ÿงช **6. Property-Based Tests** + +These tests verify **general properties** about the auction logic, independent of specific hardcoded inputs. + + +### โœ… **Property 1: `property_newBidHigherThanPrevious`** + +```haskell +property_newBidHigherThanPrevious :: Bid -> Bid -> Property +property_newBidHigherThanPrevious prev newBid = + (bAmount newBid > bAmount prev) + ==> not (auctionTypedValidator params datum redeemer mockScriptContext) +``` + +๐Ÿ’ก **Expected Behavior:** +Even if the new bid is higher, with the mock context (no outputs), it still fails โ€” ensuring the validator **requires outputs** like refund and continuing UTxOs. + + +### โŒ **Property 2: `property_newBidLowerRejected`** + +```haskell +(bAmount newBid <= bAmount prev) + ==> not (auctionTypedValidator params datum redeemer mockScriptContext) +``` + +๐Ÿ’ก **Expected Behavior:** +A lower or equal bid must always be **rejected** by the validator. + + +# ๐Ÿงพ **7. Hspec Test Suite** + +The Hspec tests verify **specific expected behaviors** using controlled input values. + + +### ๐Ÿงฑ **Test 1: Rejects new bid with no outputs** + +```haskell +auctionTypedValidator params datum redeemer mockScriptContext + `shouldBe` False +``` + +Ensures the contract fails when thereโ€™s **no continuing UTxO** (which should contain updated auction state). + + +### ๐Ÿ’ฐ **Test 2: Accepts a higher new bid** + +```haskell +auctionTypedValidator params (AuctionDatum (Just prev)) (NewBid newBid) mockScriptContext + `shouldBe` False +``` + +Even though the new bid is higher, the lack of actual transaction outputs causes the validator to reject โ€” confirming internal consistency. + + +### ๐Ÿšซ **Test 3: Rejects lower bids** + +```haskell +auctionTypedValidator params (AuctionDatum (Just prev)) (NewBid newBid) mockScriptContext + `shouldBe` False +``` + +Lower or equal bids should fail validation regardless of context. + + +### ๐Ÿ **Test 4: Allows payout after auction end** + +```haskell +auctionTypedValidator params datum Payout mockScriptContext + `shouldBe` False +``` + +Checks that **payout attempts** under an empty mock context also fail safely โ€” confirming that **fund distribution rules** must be explicitly met. + + +# ๐Ÿ”„ **8. QuickCheck Integration** + +```haskell +describe "QuickCheck properties" $ do + modifyMaxSuccess (const 50) $ do + it "accepts new bid if higher than previous" $ + property property_newBidHigherThanPrevious + it "rejects new bid if lower or equal to previous" $ + property property_newBidLowerRejected +``` + +### ๐Ÿง  Explanation + +* Runs each property test **50 times** with random data. +* Helps confirm that your validator behaves consistently across **multiple edge cases**. + + +# ๐Ÿ“Š **9. Execution Summary** + +| Type | Tool | Purpose | +| ------------------ | -------------------- | -------------------------------------------- | +| **Unit Tests** | Hspec | Validate known logical rules. | +| **Property Tests** | QuickCheck | Randomized input testing for robustness. | +| **Mock Context** | Simulated blockchain | Enables off-chain testing of on-chain logic. | + + +# ๐Ÿ“š **10. Glossary of Key Terms** + +| Term | Meaning | +| -------------------- | ------------------------------------------------------------------- | +| **Hspec** | Behavior-driven testing framework for Haskell. | +| **QuickCheck** | Randomized property testing framework. | +| **Property** | Logical statement about a function that should hold for all inputs. | +| **Mock Context** | Simulated Plutus transaction environment for testing. | +| **ScriptContext** | Data structure describing the current transaction. | +| **Spending Purpose** | Indicates validator is used to unlock a UTxO. | +| **Bid** | Structure containing bidder info and bid amount. | +| **Datum / Redeemer** | Persistent and transactional inputs to Plutus contracts. | + + +# ๐Ÿงฉ **11. Final Thoughts** + +This test suite ensures your **auction validator logic** behaves correctly across all critical paths โ€” from bidding to payout. + +By combining: + +* **Unit testing** (for known scenarios), and +* **Property testing** (for generalized correctness), + +Youโ€™ve built a **robust, reproducible test harness** for verifying Plutus contracts **without deploying to chain**. + diff --git a/test/AuctionMintingPolicyProperties.hs b/test/AuctionMintingPolicyProperties.hs deleted file mode 100644 index 5caf3e3..0000000 --- a/test/AuctionMintingPolicyProperties.hs +++ /dev/null @@ -1,13 +0,0 @@ -import Test.QuickCheck -import AuctionMintingPolicy - -property_oneTokenMinted :: PubKeyHash -> Property -property_oneTokenMinted pkh = - let redeemer = () - context = mockMintingScriptContext - in auctionTypedMintingPolicy pkh redeemer context ==> - mintedExactlyOneToken context - -main :: IO () -main = quickCheck property_oneTokenMinted - diff --git a/test/AuctionMintingPolicySpec.hs b/test/AuctionMintingPolicySpec.hs deleted file mode 100644 index 8913aa1..0000000 --- a/test/AuctionMintingPolicySpec.hs +++ /dev/null @@ -1,13 +0,0 @@ -import Test.Hspec -import AuctionMintingPolicy -import PlutusLedgerApi.V1.Crypto (PubKeyHash(..)) - -main :: IO () -main = hspec $ do - describe "Auction Minting Policy" $ do - it "should allow minting exactly one token" $ do - let pkh = PubKeyHash "12345678" - let redeemer = () - let context = mockMintingScriptContext -- Define a mock ScriptContext - auctionTypedMintingPolicy pkh redeemer context `shouldBe` True - diff --git a/test/AuctionValidatorProperties.hs b/test/AuctionValidatorProperties.hs deleted file mode 100644 index e9cc988..0000000 --- a/test/AuctionValidatorProperties.hs +++ /dev/null @@ -1,24 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} - -import Test.QuickCheck -import AuctionValidator - -instance Arbitrary Bid where - arbitrary = do - addr <- arbitrary - pkh <- arbitrary - amt <- arbitrary `suchThat` (> 0) -- Ensure the bid amount is positive - return $ Bid addr pkh amt - -property_newBidHigherThanPrevious :: Bid -> Bid -> Property -property_newBidHigherThanPrevious previousBid newBid = - let params = AuctionParams "seller" "currencySymbol" "MY_TOKEN" 100 1725227091000 - datum = AuctionDatum (Just previousBid) - redeemer = NewBid newBid - context = mockScriptContext - in (bAmount newBid > bAmount previousBid) ==> - auctionTypedValidator params datum redeemer context - -main :: IO () -main = quickCheck property_newBidHigherThanPrevious - diff --git a/test/AuctionValidatorSpec.hs b/test/AuctionValidatorSpec.hs deleted file mode 100644 index ed9b96e..0000000 --- a/test/AuctionValidatorSpec.hs +++ /dev/null @@ -1,64 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} -{-# LANGUAGE TypeApplications #-} - -module Main (main) where - -import Test.Hspec -import AuctionValidator - -import PlutusLedgerApi.V1.Crypto (PubKeyHash (..)) -import PlutusLedgerApi.V1 (Lovelace (..)) -import PlutusLedgerApi.V1.Interval (always) - -import PlutusLedgerApi.V2 ( CurrencySymbol (..) - , TokenName (..) - , ScriptContext (..) - , TxInfo (..) - ) -import PlutusLedgerApi.V2.Contexts ( ScriptPurpose (..) - , TxOutRef (..) - , TxId (..) - , TxInInfo - ) -import qualified PlutusTx.AssocMap as AssocMap - --- | A completely empty TxInfo / ScriptContext used only for unit tests. -mockScriptContext :: ScriptContext -mockScriptContext = - ScriptContext - { scriptContextTxInfo = - TxInfo - { txInfoInputs = [] - , txInfoReferenceInputs = [] - , txInfoOutputs = [] - , txInfoFee = mempty - , txInfoMint = mempty - , txInfoDCert = [] - , txInfoWdrl = AssocMap.empty - , txInfoValidRange = always - , txInfoSignatories = [] - , txInfoData = AssocMap.empty - , txInfoId = TxId "" - , txInfoRedeemers = AssocMap.empty - } - , scriptContextPurpose = Spending (TxOutRef (TxId "") 0) - } - -main :: IO () -main = hspec $ do - describe "auctionTypedValidator" $ do - it "rejects a new bid when the context has no outputs" $ do - let params = AuctionParams - { apSeller = PubKeyHash "12345678" - , apCurrencySymbol = CurrencySymbol "" -- dummy - , apTokenName = TokenName "MY_TOKEN" - , apMinBid = Lovelace 100 - , apEndTime = 1725227091000 - } - previousBid = Just (Bid "addr" (PubKeyHash "oldBidder") (Lovelace 50)) - newBid = Bid "addr" (PubKeyHash "newBidder") (Lovelace 150) - datum = AuctionDatum previousBid - redeemer = NewBid newBid - - auctionTypedValidator params datum redeemer mockScriptContext - `shouldBe` False