Skip to content

Commit 0ac70fa

Browse files
committed
Optimal non-builtin valueOf in plutus-ledger-api Data.Value
Rewrites `PlutusLedgerApi.V1.Data.Value.valueOf` so the non-builtin lookup path walks the underlying `BuiltinList` directly via `unsafeDataAsMap` / `unsafeDataAsB` / `unsafeDataAsI`, compares keys with `equalsByteString`, and short-circuits on the first match. No `Maybe` is materialised: the "absent" answer is `0`, returned in-place by the `nilCase` of each traversal. Avoids `withCurrencySymbol`'s continuation + `Map.lookup`'s `Maybe`-wrapping, and bypasses the `ToData k`/`UnsafeFromData a` dictionary work that `AssocMap.lookup` does per element. Semantics preserved. Adds `Spec.Data.Value.test_valueOf`: a QuickCheck property that compiles `valueOf` via TH, evaluates it on the CEK machine, and compares the result against the host-Haskell `valueOf` for the same inputs. Differential test against the Plinth compiler — any divergence is a compilation bug, not a semantics bug. Budget evidence (lookup matrix, `unsafeDataAsValue` baseline) lives on the companion experimental branch `yura/issue-2242-valueof-evidence`, kept out of this PR to avoid carrying ~96 golden files that would only ever regenerate on upstream plugin/cost-model changes. For IntersectMBO/plutus-private#2242.
1 parent 22a716e commit 0ac70fa

4 files changed

Lines changed: 43 additions & 5 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Changed
2+
3+
- `PlutusLedgerApi.V1.Data.Value.valueOf` rewritten to walk the underlying `BuiltinList` directly via `unsafeDataAsMap` / `unsafeDataAsB` / `unsafeDataAsI` and short-circuit on the first match. The previous implementation went through `Map.lookup`, which materialised a `Maybe` only to deconstruct it immediately, and paid `ToData k` / `UnsafeFromData a` dictionary work per element. Semantics are unchanged.

plutus-ledger-api/src/PlutusLedgerApi/V1/Data/Value.hs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,18 @@ instance MeetSemiLattice Value where
336336
{-| Get the quantity of the given currency in the 'Value'.
337337
Assumes that the underlying map doesn't contain duplicate keys. -}
338338
valueOf :: Value -> CurrencySymbol -> TokenName -> Integer
339-
valueOf value cur tn =
340-
withCurrencySymbol cur value 0 \tokens ->
341-
case Map.lookup tn tokens of
342-
Nothing -> 0
343-
Just v -> v
339+
valueOf (Value mp) (CurrencySymbol curBs) (TokenName tnBs) =
340+
goOuter (Map.toBuiltinList mp)
341+
where
342+
goOuter = B.caseList' 0 \hd ->
343+
if B.equalsByteString curBs (BI.unsafeDataAsB (BI.fst hd))
344+
then \_ -> goInner (BI.unsafeDataAsMap (BI.snd hd))
345+
else goOuter
346+
347+
goInner = B.caseList' 0 \hd ->
348+
if B.equalsByteString tnBs (BI.unsafeDataAsB (BI.fst hd))
349+
then \_ -> BI.unsafeDataAsI (BI.snd hd)
350+
else goInner
344351
{-# INLINEABLE valueOf #-}
345352

346353
{-| Apply a continuation function to the token quantities of the given currency

plutus-tx-plugin/test-ledger-api/Spec.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ tests =
2727
, Spec.Data.Budget.tests
2828
, Spec.Data.ScriptContext.tests
2929
, Spec.Data.Value.test_EqValue
30+
, Spec.Data.Value.test_valueOf
3031
, Spec.Data.MintValue.V3.tests
3132
, Spec.Envelope.tests
3233
, Spec.ReturnUnit.V1.tests

plutus-tx-plugin/test-ledger-api/Spec/Data/Value.hs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE BlockArguments #-}
12
{-# LANGUAGE DataKinds #-}
23
{-# LANGUAGE OverloadedStrings #-}
34
{-# LANGUAGE TemplateHaskell #-}
@@ -22,6 +23,7 @@ import PlutusTx.Numeric
2223
import PlutusTx.Prelude hiding (integerToByteString)
2324
import PlutusTx.Show (toDigits)
2425
import PlutusTx.TH (compile)
26+
import PlutusTx.Test.Run.Code (evaluationResultMatchesHaskell)
2527
import PlutusTx.Traversable qualified as Tx
2628

2729
import PlutusCore.Builtin qualified as PLC
@@ -31,12 +33,16 @@ import UntypedPlutusCore qualified as PLC
3133
import UntypedPlutusCore.Evaluation.Machine.Cek qualified as PLC
3234

3335
import Control.Exception qualified as Haskell
36+
import Data.ByteString qualified as BS
3437
import Data.Functor qualified as Haskell
3538
import Data.List qualified as Haskell
3639
import Data.Map qualified as Map
40+
import PlutusLedgerApi.Test.V1.Data.Value ()
3741
import Prettyprinter qualified as Pretty
42+
import Test.QuickCheck (Arbitrary (arbitrary), forAll, (===))
3843
import Test.Tasty
3944
import Test.Tasty.Extras
45+
import Test.Tasty.QuickCheck (testProperty)
4046

4147
scalingFactor :: Integer
4248
scalingFactor = 4
@@ -258,3 +264,24 @@ test_EqValue =
258264
$ [ test_EqCurrencyList "Short" currencyListOptions
259265
, test_EqCurrencyList "Long" currencyLongListOptions
260266
]
267+
268+
{-| Check that running the compiled 'valueOf' on CEK produces the same
269+
'Integer' as the host-Haskell 'valueOf', for arbitrary 'Value's and
270+
arbitrary @(CurrencySymbol, TokenName)@ pairs. -}
271+
test_valueOf :: TestTree
272+
test_valueOf =
273+
testProperty "valueOf on CEK matches host Haskell" \value ->
274+
let genBytes = Haskell.fmap BS.pack arbitrary
275+
genKeyPair =
276+
Haskell.liftA2
277+
(\bs1 bs2 -> (currencySymbol bs1, tokenName bs2))
278+
genBytes
279+
genBytes
280+
in forAll genKeyPair \(cs, tn) ->
281+
let compiled =
282+
$$(compile [||valueOf||])
283+
`unsafeApplyCode` liftCodeDef value
284+
`unsafeApplyCode` liftCodeDef cs
285+
`unsafeApplyCode` liftCodeDef tn
286+
expected = valueOf value cs tn
287+
in evaluationResultMatchesHaskell compiled (===) expected

0 commit comments

Comments
 (0)