Skip to content

Commit e30c886

Browse files
committed
Fix FromJSON instance for CostModels
The problem lies in the fact that starting with Conway era number of cost model can vary, while number of parameters in any genesis file must stay the same, since that was the number of parameters with which era was initiated with. This PR: * Fixes the parsing where addition of new cost model parameters in a newer version of plutus results in a failure, unless new parameters are added to the genesis file, which would be a wrong thing to do. * Fixes the total number of parameters with which Conway era has started with. This was a not really a problem, since parsing for cost model parameters in Conway did not enforce the initial number * Start enforcing the initial number of parameters in the Conway Genesis
1 parent 693218d commit e30c886

File tree

8 files changed

+101
-43
lines changed

8 files changed

+101
-43
lines changed

eras/alonzo/impl/src/Cardano/Ledger/Alonzo/Genesis.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import Cardano.Ledger.Binary.Coders (
5050
)
5151
import Cardano.Ledger.Core
5252
import Cardano.Ledger.Genesis (EraGenesis (..))
53+
import Cardano.Ledger.Plutus.CostModels (parseCostModels)
5354
import Data.Aeson (FromJSON (..), ToJSON (..), (.:), (.=))
5455
import qualified Data.Aeson as Aeson
5556
import Data.Functor.Identity (Identity)
@@ -170,7 +171,7 @@ instance ToCBOR AlonzoGenesis where
170171
instance FromJSON AlonzoGenesis where
171172
parseJSON = Aeson.withObject "Alonzo Genesis" $ \o -> do
172173
agCoinsPerUTxOWord <- o .: "lovelacePerUTxOWord"
173-
agCostModels <- o .: "costModels"
174+
agCostModels <- parseCostModels False =<< o .: "costModels"
174175
agPrices <- o .: "executionPrices"
175176
agMaxTxExUnits <- o .: "maxTxExUnits"
176177
agMaxBlockExUnits <- o .: "maxBlockExUnits"

eras/alonzo/impl/testlib/Test/Cardano/Ledger/Alonzo/Binary/CostModelsSpec.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ validCostModelProp = do
5757
ppuRes <- expectRight ppuDecoded
5858
ppuRes `shouldSatisfy` \ppu -> (validCm <$> ppu ^. ppuCostModelsL) == SJust True
5959
where
60-
genValidCostModelEnc lang = genCostModelEncForLanguage lang (costModelParamsCount lang)
60+
genValidCostModelEnc lang = genCostModelEncForLanguage lang (costModelInitParamCount lang)
6161
validCm cms =
6262
not (null (costModelsValid cms)) && null (costModelsUnknown cms)
6363

@@ -84,7 +84,7 @@ underspecifiedCostModelProp = do
8484
cmRes `shouldSatisfy` not . null . costModelsValid
8585
where
8686
genUnderspecifiedCostModelEnc lang = do
87-
let validCount = costModelParamsCount lang
87+
let validCount = costModelInitParamCount lang
8888
count <- choose (0, validCount - 1)
8989
genCostModelEncForLanguage lang count
9090

eras/conway/impl/src/Cardano/Ledger/Conway/PParams.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ import Cardano.Ledger.Plutus.CostModels (
135135
CostModel,
136136
decodeCostModel,
137137
encodeCostModel,
138-
mkCostModel,
139138
mkCostModels,
139+
parseCostModelAsArray,
140140
)
141141
import Cardano.Ledger.Plutus.Language (Language (PlutusV3))
142142
import Cardano.Ledger.Plutus.ToPlutusData (ToPlutusData (..))
@@ -1079,7 +1079,7 @@ instance FromJSON (UpgradeConwayPParams Identity) where
10791079
<*> o .: "dRepDeposit"
10801080
<*> o .: "dRepActivity"
10811081
<*> o .: "minFeeRefScriptCostPerByte"
1082-
<*> (either (fail . show) pure . mkCostModel PlutusV3 =<< o .: "plutusV3CostModel")
1082+
<*> (parseCostModelAsArray False PlutusV3 =<< o .: "plutusV3CostModel")
10831083

10841084
upgradeConwayPParams ::
10851085
forall f.

libs/cardano-ledger-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## 1.18.0.0
44

5+
* Deprecate `costModelParamsCount` in favor of `costModelInitParamCount`
6+
* Add `costModelInitParamNames`, `costModelInitParamCount`, `parseCostModelAsArray` and `parseCostModelAsMap`
57
* Export `credToDRep` and `dRepToCred`
68
* Deprecate `PoolParams` in favor of `StakePoolState`. #5196
79
* Move the `PoolParams` module to `Cardano.Ledger.State.StakePool` and export from there.

libs/cardano-ledger-core/src/Cardano/Ledger/Plutus/CostModels.hs

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,13 @@
11
{-# LANGUAGE DataKinds #-}
2-
{-# LANGUAGE DeriveAnyClass #-}
3-
{-# LANGUAGE DeriveFunctor #-}
42
{-# LANGUAGE DeriveGeneric #-}
53
{-# LANGUAGE DerivingVia #-}
64
{-# LANGUAGE FlexibleContexts #-}
75
{-# LANGUAGE FlexibleInstances #-}
8-
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
9-
{-# LANGUAGE LambdaCase #-}
106
{-# LANGUAGE MultiParamTypeClasses #-}
11-
{-# LANGUAGE NamedFieldPuns #-}
127
{-# LANGUAGE OverloadedStrings #-}
13-
{-# LANGUAGE PatternSynonyms #-}
148
{-# LANGUAGE ScopedTypeVariables #-}
15-
{-# LANGUAGE StandaloneDeriving #-}
169
{-# LANGUAGE TypeApplications #-}
1710
{-# LANGUAGE TypeFamilies #-}
18-
{-# LANGUAGE TypeOperators #-}
1911
{-# LANGUAGE UndecidableInstances #-}
2012

2113
module Cardano.Ledger.Plutus.CostModels (
@@ -33,12 +25,17 @@ module Cardano.Ledger.Plutus.CostModels (
3325
costModelFromMap,
3426
costModelParamsCount,
3527
decodeCostModel,
28+
costModelInitParamNames,
29+
costModelInitParamCount,
30+
parseCostModelAsMap,
31+
parseCostModelAsArray,
3632

3733
-- * Cost Models
3834
CostModels,
3935
mkCostModels,
4036
emptyCostModels,
4137
updateCostModels,
38+
parseCostModels,
4239
decodeCostModelsLenient,
4340
decodeCostModelsFailing,
4441
costModelsValid,
@@ -63,9 +60,10 @@ import Cardano.Ledger.Plutus.Language (
6360
nonNativeLanguages,
6461
)
6562
import Control.DeepSeq (NFData (..), deepseq)
66-
import Control.Monad (forM, when)
63+
import Control.Monad (forM, unless, when)
6764
import Control.Monad.Trans.Writer (WriterT (runWriterT))
6865
import Data.Aeson (
66+
Array,
6967
FromJSON (..),
7068
Object,
7169
ToJSON (..),
@@ -137,36 +135,72 @@ instance NFData CostModel where
137135
rnf (CostModel lang cm ectx) = lang `deepseq` cm `deepseq` rnf ectx
138136

139137
instance FromJSON CostModels where
140-
parseJSON = withObject "CostModels" $ \o -> do
141-
cms <- mapM (parseCostModel o) nonNativeLanguages
138+
parseJSON = parseCostModels True
139+
140+
parseCostModels ::
141+
-- | Do not restrict number of parameters to the initial count and allow parsing of cost models
142+
-- for unknown plutus versions.
143+
Bool ->
144+
Value ->
145+
Parser CostModels
146+
parseCostModels isLenient =
147+
withObject "CostModels" $ \o -> do
148+
cms <- mapM (parseCostModel isLenient o) nonNativeLanguages
142149
let cmsMap = Map.fromList [(cmLanguage cm, cm) | Just cm <- cms]
143-
unknown <- o .:? "Unknown" .!= mempty
144-
unknownCostModels <- mkCostModelsLenient unknown
150+
unknownCostModels <-
151+
if isLenient
152+
then do
153+
unknown <- o .:? "Unknown" .!= mempty
154+
mkCostModelsLenient unknown
155+
else
156+
pure mempty
145157
pure $ mkCostModels cmsMap <> unknownCostModels
146158

147-
-- | The costmodel parameters in Alonzo Genesis are represented as a map. Plutus API does
148-
-- no longer require the map as a parameter to `mkEvaluationContext`, but the list of
149-
-- integers representing the values of the map. The expectation on this list of integers
150-
-- is that they are sorted in the order given by the `ParamName` enum, so even though we
151-
-- just have to pass the list to plutus, we still need to use the names of the parameters
152-
-- in order to sort the list. In new versions, we want to represent the costmodel
153-
-- parameters directly as a list, so we can avoid this reordering.
154-
parseCostModel :: Object -> Language -> Parser (Maybe CostModel)
155-
parseCostModel o lang = do
159+
-- | The costmodel parameters in Alonzo Genesis are represented as a map. Plutus API does no longer
160+
-- require the map as a parameter to `mkEvaluationContext`, but the list of integers representing
161+
-- the values of the map. The expectation on this list of integers is that they are sorted in the
162+
-- order given by the `ParamName` enum, so even though we just have to pass the list to plutus, we
163+
-- still need to use the names of the parameters in order to sort the list. In new versions, we
164+
-- represent the costmodel parameters directly as a list, so we can avoid this reordering.
165+
parseCostModel :: Bool -> Object -> Language -> Parser (Maybe CostModel)
166+
parseCostModel isLenient o lang = do
156167
plutusCostModelValueMaybe <- o .:? fromString (show lang)
157168
forM plutusCostModelValueMaybe $ \plutusCostModelValue ->
158169
case plutusCostModelValue of
159-
Object _ -> costModelFromMap lang =<< parseJSON plutusCostModelValue
160-
Array _ -> validateCostModel lang =<< parseJSON plutusCostModelValue
170+
Object m -> parseCostModelAsMap isLenient lang m
171+
Array a -> parseCostModelAsArray isLenient lang a
161172
_ -> fail $ "Expected either an Array or an Object, but got: " ++ show plutusCostModelValue
162173

174+
parseCostModelAsMap :: Bool -> Language -> Object -> Parser CostModel
175+
parseCostModelAsMap isLenient lang m = do
176+
costModel <- costModelFromMap lang =<< parseJSON (Object m)
177+
unless isLenient $ guardNumberOfParameters lang m
178+
pure costModel
179+
180+
parseCostModelAsArray :: Bool -> Language -> Array -> Parser CostModel
181+
parseCostModelAsArray isLenient lang a = do
182+
costModel <- validateCostModel lang =<< parseJSON (Array a)
183+
unless isLenient $ guardNumberOfParameters lang a
184+
pure costModel
185+
186+
guardNumberOfParameters :: (Foldable f, MonadFail m) => Language -> f a -> m ()
187+
guardNumberOfParameters lang ps =
188+
let suppliedParameterCount = length ps
189+
expectedParameterCount = costModelInitParamCount lang
190+
in unless (suppliedParameterCount == expectedParameterCount) $
191+
fail $
192+
"Number of parameters supplied "
193+
<> show suppliedParameterCount
194+
<> " does not match the expected number of "
195+
<> show expectedParameterCount
196+
163197
costModelFromMap :: MonadFail m => Language -> Map Text Int64 -> m CostModel
164198
costModelFromMap lang cmMap =
165199
either (fail . unlines . (header :) . NE.toList) (validateCostModel lang) $
166200
validationToEither (traverse lookupFail paramNames)
167201
where
168202
header = "Cost model language: " ++ show lang
169-
paramNames = costModelParamNames lang
203+
paramNames = costModelInitParamNames lang
170204
lookupFail paramName =
171205
case Map.lookup paramName cmMap of
172206
Nothing -> failure $ " Parameter name missing from cost model: " ++ show paramName
@@ -180,6 +214,24 @@ costModelParamNames :: Language -> [Text]
180214
costModelParamNames PlutusV1 = plutusV1ParamNames
181215
costModelParamNames lang = plutusVXParamNames lang
182216

217+
-- | List of parameter names as when they were introduced upon a hard fork to a specific era for a
218+
-- corresponding plutus version.
219+
costModelInitParamNames :: Language -> [Text]
220+
costModelInitParamNames lang = take (costModelInitParamCount lang) $ costModelParamNames lang
221+
222+
-- | Number of `CostModel` parameters for a specified plutus version as when it was initially
223+
-- added. This is useful for genesis files, which shouldn't have the number of parameters vary over
224+
-- time.
225+
costModelInitParamCount :: Language -> Int
226+
costModelInitParamCount lang =
227+
case lang of
228+
PlutusV1 -> 166
229+
PlutusV2 -> 175
230+
PlutusV3 -> 251
231+
PlutusV4 ->
232+
-- This number will continue to change until we are ready to hard fork into Dijkstra era
233+
251
234+
183235
-- | There is a difference in 6 parameter names between the ones appearing alonzo genesis
184236
-- files and the values returned by plutus via `P.showParamName` on the `ParamName` enum.
185237
-- This listed is sorted in the order given by `ParamName` enum, so we can use it to sort
@@ -264,6 +316,10 @@ costModelParamsCount PlutusV1 = 166
264316
costModelParamsCount PlutusV2 = 175
265317
costModelParamsCount PlutusV3 = 231
266318
costModelParamsCount PlutusV4 = 231
319+
{-# DEPRECATED
320+
costModelParamsCount
321+
"Deprecated in favor of `costModelInitParamCount`, since this function provided an incorrect value of 231 for PlutusV3, where it should have been 251"
322+
#-}
267323

268324
decodeCostModelLegacy :: Language -> Decoder s CostModel
269325
decodeCostModelLegacy lang = do
@@ -272,7 +328,7 @@ decodeCostModelLegacy lang = do
272328
"Legacy CostModel decoding is not supported for " ++ show lang ++ " language version"
273329
values <- decCBOR
274330
let numValues = length values
275-
expectedNumValues = costModelParamsCount lang
331+
expectedNumValues = costModelInitParamCount lang
276332
when (numValues /= expectedNumValues) $
277333
fail $
278334
"Expected array with "

libs/cardano-ledger-core/testlib/Test/Cardano/Ledger/Core/Arbitrary.hs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ import Cardano.Ledger.Keys (BootstrapWitness (..), ChainCode (..), VKey (..), Wi
8686
import Cardano.Ledger.Plutus.CostModels (
8787
CostModel,
8888
CostModels,
89-
costModelParamsCount,
89+
costModelInitParamCount,
9090
mkCostModel,
9191
mkCostModels,
9292
mkCostModelsLenient,
@@ -910,7 +910,7 @@ instance Arbitrary PV1.Data where
910910

911911
genValidCostModel :: Language -> Gen CostModel
912912
genValidCostModel lang = do
913-
newParamValues <- vectorOf (costModelParamsCount lang) arbitrary
913+
newParamValues <- vectorOf (costModelInitParamCount lang) arbitrary
914914
either (\err -> error $ "Corrupt cost model: " ++ show err) pure $
915915
mkCostModel lang newParamValues
916916

@@ -953,14 +953,14 @@ genUnknownCostModelValues = do
953953
genCostModelValues :: Language -> Gen (Word8, [Int64])
954954
genCostModelValues lang = do
955955
Positive sub <- arbitrary
956-
(,) lang'
956+
(,) langWord8
957957
<$> oneof
958-
[ listAtLeast (costModelParamsCount lang) -- Valid Cost Model for known language
958+
[ listAtLeast (costModelInitParamCount lang) -- Valid Cost Model for known language
959959
, take (tooFew sub) <$> arbitrary -- Invalid Cost Model for known language
960960
]
961961
where
962-
lang' = fromIntegral (fromEnum lang)
963-
tooFew sub = costModelParamsCount lang - sub
962+
langWord8 = fromIntegral (fromEnum lang)
963+
tooFew sub = costModelInitParamCount lang - sub
964964
listAtLeast :: Int -> Gen [Int64]
965965
listAtLeast x = do
966966
NonNegative y <- arbitrary

libs/cardano-ledger-core/testlib/Test/Cardano/Ledger/Core/JSON.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,15 +66,15 @@ roundTripJsonEraSpec =
6666

6767
goldenJsonPParamsSpec ::
6868
forall era.
69-
EraPParams era =>
69+
(HasCallStack, EraPParams era) =>
7070
SpecWith FilePath
7171
goldenJsonPParamsSpec =
72-
it "Golden JSON specs for PParams " $
72+
it "Golden JSON specs for PParams" $
7373
eitherDecodeFileStrict @(PParams era) >=> expectRightDeepExpr_
7474

7575
goldenJsonPParamsUpdateSpec ::
7676
forall era.
77-
EraTest era =>
77+
(HasCallStack, EraTest era) =>
7878
SpecWith FilePath
7979
goldenJsonPParamsUpdateSpec =
8080
it "Golden JSON specs for PParamsUpdate" $ \file -> do

libs/cardano-ledger-core/testlib/Test/Cardano/Ledger/Plutus.hs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{-# LANGUAGE GADTs #-}
22
{-# LANGUAGE LambdaCase #-}
3-
{-# LANGUAGE TypeApplications #-}
43

54
module Test.Cardano.Ledger.Plutus (
65
PlutusArgs (..),
@@ -32,7 +31,7 @@ import Cardano.Ledger.Binary.Plain (decodeFullFromHexText)
3231
import Cardano.Ledger.Plutus.CostModels (
3332
CostModel,
3433
CostModels,
35-
costModelParamsCount,
34+
costModelInitParamCount,
3635
getCostModelEvaluationContext,
3736
mkCostModel,
3837
mkCostModels,
@@ -62,7 +61,7 @@ import Test.Cardano.Ledger.Plutus.ScriptTestContext (
6261

6362
-- | Construct a test cost model where all parameters are set to the same value
6463
mkCostModelConst :: HasCallStack => Language -> Int64 -> CostModel
65-
mkCostModelConst lang = mkCostModel' lang . replicate (costModelParamsCount lang)
64+
mkCostModelConst lang = mkCostModel' lang . replicate (costModelInitParamCount lang)
6665

6766
mkCostModel' :: (Integral i, Show i, HasCallStack) => Language -> [i] -> CostModel
6867
mkCostModel' lang params =

0 commit comments

Comments
 (0)