11{-# LANGUAGE BlockArguments #-}
22{-# LANGUAGE ImportQualifiedPost #-}
3+ {-# LANGUAGE LambdaCase #-}
34{-# LANGUAGE NumericUnderscores #-}
45{-# LANGUAGE TupleSections #-}
56
@@ -10,14 +11,19 @@ import Prelude
1011import Common
1112import Control.Monad (replicateM )
1213import Criterion.Main (Benchmark )
14+ import Data.Bits (shiftR , (.&.) )
1315import Data.ByteString (ByteString )
16+ import Data.ByteString qualified as BS
1417import Data.Int (Int64 )
18+ import Data.Word (Word8 )
19+ import GHC.Stack (HasCallStack )
1520import PlutusCore (DefaultFun (LookupCoin , UnValueData , ValueContains , ValueData ))
21+ import PlutusCore.Builtin (BuiltinResult (BuiltinFailure , BuiltinSuccess , BuiltinSuccessWithLogs ))
1622import PlutusCore.Evaluation.Machine.ExMemoryUsage (ValueLogOuterOrMaxInner (.. ),
1723 ValueTotalSize (.. ))
1824import PlutusCore.Value (K , Value )
1925import PlutusCore.Value qualified as Value
20- import System.Random.Stateful (StatefulGen , StdGen , runStateGen_ , uniformByteStringM , uniformRM )
26+ import System.Random.Stateful (StatefulGen , StdGen , runStateGen_ , uniformRM )
2127
2228----------------------------------------------------------------------------------------------------
2329-- Benchmarks --------------------------------------------------------------------------------------
@@ -90,10 +96,11 @@ valueContainsArgs gen = runStateGen_ gen \g -> replicateM 100 do
9096
9197 -- Group selected entries back by policy
9298 let contained =
93- Value. fromList
94- [ (policyId, [(tokenName, quantity)])
95- | (policyId, tokenName, quantity) <- selectedEntries
96- ]
99+ unsafeFromBuiltinResult $
100+ Value. fromList
101+ [ (policyId, [(tokenName, quantity)])
102+ | (policyId, tokenName, quantity) <- selectedEntries
103+ ]
97104
98105 pure (container, contained)
99106
@@ -162,25 +169,75 @@ generateConstrainedValue numPolicies tokensPerPolicy g = do
162169 policyIds <- replicateM numPolicies (generateKey g)
163170 tokenNames <- replicateM tokensPerPolicy (generateKey g)
164171
165- let quantity :: Integer
166- quantity = fromIntegral (maxBound :: Int64 )
172+ let
173+ qty :: Value. Quantity
174+ qty = case Value. quantity (fromIntegral (maxBound :: Int64 )) of
175+ Just q -> q
176+ Nothing -> error " generateConstrainedValue: Int64 maxBound should be valid Quantity"
167177
168- nestedMap :: [(K , [(K , Integer )])]
169- nestedMap = (,(,quantity ) <$> tokenNames) <$> policyIds
178+ nestedMap :: [(K , [(K , Value. Quantity )])]
179+ nestedMap = (,(,qty ) <$> tokenNames) <$> policyIds
170180
171- pure $ Value. fromList nestedMap
181+ pure $ unsafeFromBuiltinResult $ Value. fromList nestedMap
172182
173183----------------------------------------------------------------------------------------------------
174184-- Other Generators --------------------------------------------------------------------------------
175185
176- -- | Generate random key (always maxKeyLen bytes for Cardano compliance)
186+ {-| Generate a worst-case key for benchmarking ByteString comparisons.
187+
188+ ByteString comparison is lexicographic and short-circuits on the first differing byte.
189+ Random keys typically differ in the first 1-2 bytes, making comparisons artificially cheap.
190+
191+ For accurate worst-case costing, we generate keys with a common prefix (0xFF bytes) that
192+ differ only in the last bytes. This forces full-length comparisons during Map lookups,
193+ providing conservative cost estimates for adversarial on-chain scenarios.
194+
195+ We use a random integer to ensure uniqueness while maintaining the worst-case prefix pattern.
196+ -}
177197generateKey :: (StatefulGen g m ) => g -> m K
178198generateKey g = do
179- bs <- uniformByteStringM Value. maxKeyLen g
180- case Value. k bs of
199+ -- Generate a random integer for uniqueness
200+ n <- uniformRM (0 , maxBound :: Int ) g
201+ case Value. k (mkWorstCaseKey n) of
181202 Just key -> pure key
182203 Nothing -> error " Internal error: maxKeyLen key should always be valid"
183204
184- -- | Generate random key as ByteString (for lookup arguments)
205+ {-| Generate worst-case key as ByteString (for lookup arguments).
206+
207+ Like generateKey, but returns ByteString directly for use in lookup operations.
208+ Uses BS.copy to ensure physical distinctness for worst-case equality testing.
209+ -}
185210generateKeyBS :: (StatefulGen g m ) => g -> m ByteString
186- generateKeyBS = uniformByteStringM Value. maxKeyLen
211+ generateKeyBS g = BS. copy . mkWorstCaseKey <$> uniformRM (0 , maxBound :: Int ) g
212+
213+ {-| Helper: Create a worst-case ByteString key from an integer
214+ The key has maxKeyLen-4 bytes of 0xFF prefix, followed by 4 bytes encoding the integer
215+ -}
216+ mkWorstCaseKey :: Int -> ByteString
217+ mkWorstCaseKey n =
218+ let prefixLen = Value. maxKeyLen - 4
219+ prefix = BS. replicate prefixLen (0xFF :: Word8 )
220+ -- Encode the integer in big-endian format (last 4 bytes)
221+ b0 = fromIntegral $ (n `shiftR` 24 ) .&. 0xFF
222+ b1 = fromIntegral $ (n `shiftR` 16 ) .&. 0xFF
223+ b2 = fromIntegral $ (n `shiftR` 8 ) .&. 0xFF
224+ b3 = fromIntegral $ n .&. 0xFF
225+ suffix = BS. pack [b0, b1, b2, b3]
226+ in prefix <> suffix
227+
228+ ----------------------------------------------------------------------------------------------------
229+ -- Helper Functions --------------------------------------------------------------------------------
230+
231+ {-| Extract value from BuiltinResult for benchmark data generation.
232+
233+ This function is intended for use in test and benchmark code where BuiltinResult
234+ failures indicate bugs in the generator code, not runtime errors.
235+
236+ Errors if BuiltinResult indicates failure (should never happen with valid inputs).
237+ The call stack will provide context about where the error occurred.
238+ -}
239+ unsafeFromBuiltinResult :: (HasCallStack ) => BuiltinResult a -> a
240+ unsafeFromBuiltinResult = \ case
241+ BuiltinSuccess x -> x
242+ BuiltinSuccessWithLogs _ x -> x
243+ BuiltinFailure _ err -> error $ " BuiltinResult failed: " <> show err
0 commit comments