Skip to content

Add ScriptContext Builder testlib and refactor LinearVesting tests#7643

Merged
Unisay merged 3 commits intomasterfrom
yura/issue-2091-script-context-builder
Mar 10, 2026
Merged

Add ScriptContext Builder testlib and refactor LinearVesting tests#7643
Unisay merged 3 commits intomasterfrom
yura/issue-2091-script-context-builder

Conversation

@Unisay
Copy link
Contributor

@Unisay Unisay commented Mar 4, 2026

Summary

This is PR 1 of a split from @colll78's PR #7562. It lands the ScriptContext Builder module and associated test refactors.

  • ScriptContext Builder: Adds PlutusLedgerApi.Test.ScriptContextBuilder to plutus-ledger-api testlib with composable combinators (mkMintingScriptContext, mkSpendingScriptContext, addMint, withFee, etc.) for constructing ScriptContext values in tests
  • Lenses: TH-generated lenses for all V3 ledger types (TxInfo, TxOut, Value, etc.)
  • LinearVesting refactor: Migrates benchmark tests from hand-crafted script contexts to the new builder
  • AsData fix: Changes destructSum-manual test to use manual IsData instances instead of asData, regenerating golden files

Changes from Philip's original code

Review comments on the diff annotate each change made to the original code from #7562.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 4, 2026

Execution Budget Golden Diff

aa3766c (master) vs 8abe36d

output

plutus-benchmark/linear-vesting/test/9.6/main.golden.eval

Metric Old New Δ%
Flat Size 2_351 2_490 +5.91%

This comment will get updated when changes are made.

-- | Convert a hex encoded Haskell 'String' to a 'CurrencySymbol'.
currencySymbolFromHex :: String -> CurrencySymbol
currencySymbolFromHex hexStr =
case Base16.decode (BS8.pack hexStr) of
Copy link
Contributor Author

@Unisay Unisay Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Replaced stringToBuiltinByteStringHex (which isn't meant to be applied to a non-statically known argument) with Base16.decode from the base16-bytestring package. This removes custom hex-parsing code in favor of a well-tested library function.

@@ -0,0 +1,632 @@
{-# LANGUAGE BlockArguments #-}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Removed 8 unused language pragmas that were present in the original (DataKinds, DeriveAnyClass, FlexibleInstances, MultiParamTypeClasses, ScopedTypeVariables, TemplateHaskell, TypeApplications, ViewPatterns). Only the three pragmas actually needed are kept.

, txInfoReferenceInputsL
, txInfoSignatoriesL
)
import PlutusLedgerApi.V3
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Modernized imports from PlutusLedgerApi.V1 / PlutusLedgerApi.V1.Value to PlutusLedgerApi.V3. The original mixed V1 and V3 imports; this version imports consistently from V3 since the builder targets V3 script contexts.

deriving stock (Generic)

-- | Create a 'Value' containing only ADA from a 'Lovelace' amount.
mkAdaValue :: Lovelace -> Value
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Changed mkAdaValue to take Lovelace instead of Integer. This leverages the Lovelace newtype for type safety and aligns with how fees and other ADA amounts are represented in the ledger API.

mkAdaValue = singleton adaSymbol adaToken . getLovelace

-- | Add a minting entry to an existing 'ScriptContext'.
addMint :: ScriptContext -> Value -> BuiltinData -> ScriptContext
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Refactored field updates to use lens combinators (e.g. over txInfoMintL, set txInfoRedeemerL) instead of manual record updates with nested pattern matching. The lenses are TH-generated in the companion Lenses module. This makes the code more composable and less error-prone when the ledger types change.

}

-- | Create a minimal 'ScriptContext' for a minting script.
mkMintingScriptWithPurpose :: Value -> BuiltinData -> ScriptContext
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Refactored mkMintingScriptWithPurpose (and the other mk*ScriptContext functions) to use a shared baseScriptContext helper with lens-based modifications, reducing code duplication across minting/spending/certifying/rewarding/proposing variants.


-- | Set the transaction fee.
withFee :: Integer -> ScriptContextBuilder
withFee fee = ScriptContextBuilder \scb -> scb {scbFee = fee}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Enabled BlockArguments and removed $ operators throughout in favor of block argument syntax (e.g. ScriptContextBuilder \scb -> ... instead of ScriptContextBuilder $ \scb -> ...). This follows the project's preferred style.

instance PlutusTx.Eq.Eq ScriptPurpose where
a == b = PlutusTx.toBuiltinData a == PlutusTx.toBuiltinData b

-- | Convert a hex encoded Haskell 'String' to a 'CurrencySymbol'.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Added Haddock documentation comments to all exported functions. The original had no doc comments; these explain each combinator's purpose and behavior.


{-| Extract the single currency symbol from a 'Value'. Errors if the value
contains zero or more than one currency symbol. -}
singleCurrencySymbol :: Value -> CurrencySymbol
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562: Replaced uses of partial head (on the list of currency symbols in a Value) with a new singleCurrencySymbol function that pattern-matches and gives a clear error message if the value doesn't contain exactly one currency symbol. This eliminates a partial function and improves debuggability.

@Unisay Unisay self-assigned this Mar 4, 2026
, txInfoReferenceInputsL
, txInfoSignatoriesL
)
import PlutusLedgerApi.V1.Address (toPubKeyHash, toScriptHash)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562 (10): Replaced local isPubKeyAddress/isScriptAddress helpers (defined in where clauses of balanceWithChangeOutput, withInput, and withScriptInput) with toPubKeyHash/toScriptHash from PlutusLedgerApi.V1.Address. These existing API functions return Maybe PubKeyHash/Maybe ScriptHash, so the call sites use isJust . toPubKeyHash and isJust . toScriptHash.

import PlutusTx qualified
import PlutusTx.AssocMap qualified as Map
import PlutusTx.Builtins.Internal (BuiltinByteString (..))
import PlutusTx.Eq qualified
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562 (11): Replaced negateValue (which manually negated all quantities via nested Map.mapWithKey) with PlutusTx.negate from PlutusTx.Numeric. Value already has an AdditiveGroup instance (via deriving via (Additive Value) in PlutusLedgerApi.V1.Value), so PlutusTx.negate works directly. Removed the export and definition.

}
where
finalState = runBuilder modify defaultScriptContextBuilderState

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change from #7562 (12): Exported currencySymbolFromHex and singleCurrencySymbol (previously internal) and relocated them to a dedicated Helpers section at the bottom of the module. These are test-specific utilities with no equivalent in the main ledger API.

@Unisay Unisay requested a review from a team March 4, 2026 15:55
Unisay and others added 2 commits March 5, 2026 10:14
Introduce a ScriptContext builder module in plutus-ledger-api testlib
that provides composable combinators for constructing ScriptContext
values in tests. This replaces hand-crafted script contexts with a
declarative builder pattern using lenses.

Key changes:
- Add PlutusLedgerApi.Test.ScriptContextBuilder.Builder with combinators
  for minting, spending, certifying, rewarding, and proposing contexts
- Add PlutusLedgerApi.Test.ScriptContextBuilder.Lenses with TH-generated
  lenses for all V3 ledger types
- Refactor LinearVesting benchmark tests to use the new builder
- Fix AsData destructSum-manual test to use manual IsData instances
  instead of asData, regenerating golden files

Based on work from PR #7562.

Co-authored-by: Philip DiSarro <philip-disarro@users.noreply.github.com>
- Replace local isPubKeyAddress/isScriptAddress with
  toPubKeyHash/toScriptHash from PlutusLedgerApi.V1.Address
- Replace negateValue with PlutusTx.negate (Value has AdditiveGroup)
- Export and relocate currencySymbolFromHex and singleCurrencySymbol
  to a dedicated Helpers section at the bottom of Builder.hs

Co-authored-by: Philip DiSarro <philip-disarro@users.noreply.github.com>
@Unisay Unisay force-pushed the yura/issue-2091-script-context-builder branch from 62c1011 to 1d6054f Compare March 5, 2026 09:31
@Unisay
Copy link
Contributor Author

Unisay commented Mar 6, 2026

Why golden files changed (plutus-benchmark/linear-vesting/test/9.6/)

The ScriptContext builder produces a more complete/populated ScriptContext than the previous hand-written one:

  • txInfoInputs went from empty to containing an actual TxInInfo (with credential, datum, and a TxOut with asset details)
  • txInfoRedeemers went from empty to containing a spending redeemer entry
  • txInfoCurrentTreasuryAmount changed from a script hash to the new test value

This makes the compiled UPLC constant larger, which is why Flat Size grew from 2,351 → 2,490 bytes. The script still evaluates to (con unit ()) successfully — the logic is unchanged, only the test input data is richer.

@Unisay Unisay marked this pull request as ready for review March 6, 2026 17:15
Copy link
Member

@zliu41 zliu41 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - this should be very useful for different purposes.

@Unisay Unisay merged commit 318c729 into master Mar 10, 2026
10 checks passed
@Unisay Unisay deleted the yura/issue-2091-script-context-builder branch March 10, 2026 09:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants