Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- New builtin `multiIndexArray` ([CIP-0156](https://cips.cardano.org/cip/CIP-0156)) of type `forall a. list integer -> array a -> list a`, returning the array elements at the given indices in order (duplicates preserved) and failing the whole call on any out-of-bounds index. Placeholder costing; gated under `futurePV`, so it is not available in any released protocol version.
21 changes: 21 additions & 0 deletions plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ data DefaultFun
| ValueData
| UnValueData
| ScaleValue
| -- Batch 7
MultiIndexArray
deriving stock (Show, Eq, Ord, Enum, Bounded, Generic, Ix)
deriving anyclass (NFData, Hashable, PrettyBy PrettyConfigPlc)

Expand Down Expand Up @@ -2471,6 +2473,23 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where
in makeBuiltinMeaning
scaleValueDenotation
(runCostingFunTwoArguments . paramScaleValue)
toBuiltinMeaning _semvar MultiIndexArray =
let multiIndexArrayDenotation
:: [Integer] -> SomeConstant uni (Vector a) -> BuiltinResult (Opaque val [a])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

  1. Need an explanation of why it uses Integer while indexArray uses Int
  2. The argument order is different from indexArray's and I see no good reason for it.

multiIndexArrayDenotation indices (SomeConstant (Some (ValueOf uni vec))) =
case uni of
DefaultUniArray uniA ->
let len = toInteger (Vector.length vec)
lookupIndex i
| 0 <= i && i < len = pure $ Vector.unsafeIndex vec $ fromInteger i
| otherwise = fail "Array index out of bounds"
in fromValueOf (DefaultUniList uniA) <$> traverse lookupIndex indices
_ ->
throwError $ structuralUnliftingError "Expected an array but got something else"
{-# INLINE multiIndexArrayDenotation #-}
in makeBuiltinMeaning
multiIndexArrayDenotation
(runCostingFunTwoArguments . unimplementedCostingFun)
-- See Note [Inlining meanings of builtins].
{-# INLINE toBuiltinMeaning #-}

Expand Down Expand Up @@ -2614,6 +2633,7 @@ instance Flat DefaultFun where
ValueData -> 98
UnValueData -> 99
ScaleValue -> 100
MultiIndexArray -> 101

decode = go =<< decodeBuiltin
where
Expand Down Expand Up @@ -2718,6 +2738,7 @@ instance Flat DefaultFun where
go 98 = pure ValueData
go 99 = pure UnValueData
go 100 = pure ScaleValue
go 101 = pure MultiIndexArray
go t = fail $ "Failed to decode builtin tag, got: " ++ show t

size _ n = n + builtinTagWidth
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
all a. list integer -> array a -> list a
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
forall a. [Integer] -> SomeConstant DefaultUni (Vector (TyVarRep * ('TyNameRep * "a" 0))) -> BuiltinResult (Opaque Val [TyVarRep * ('TyNameRep * "a" 0)])
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,4 @@ isCommutative = \case
ValueData -> False
UnValueData -> False
ScaleValue -> False
MultiIndexArray -> False
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Parsable functions are [ addInteger
, mkNilPairData
, mkPairData
, modInteger
, multiIndexArray
, multiplyInteger
, nullList
, orByteString
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,57 @@ test_BuiltinArray =
term = mkIterAppNoAnn (tyInst () (builtin () IndexArray) integer) [arrayOfInts, index]
typecheckEvaluateCekNoEmit def defaultBuiltinCostModelForTesting term
@?= Right (EvaluationSuccess expectedValue)
, testCase "multiIndexArray" do
-- Order preserved and duplicate indices return the same element.
let indices = mkConstant @[Integer] @DefaultUni () [2, 0, 0, 1]
arrayOfInts = mkConstant @(Vector Integer) @DefaultUni () (Vector.fromList [10, 20, 30])
expected = mkConstant @[Integer] @DefaultUni () [30, 10, 10, 20]
term = mkIterAppNoAnn (tyInst () (builtin () MultiIndexArray) integer) [indices, arrayOfInts]
typecheckEvaluateCekNoEmit def defaultBuiltinCostModelForTesting term
@?= Right (EvaluationSuccess expected)
, testCase "multiIndexArray-bool-elements" do
-- Polymorphic in the element type.
let indices = mkConstant @[Integer] @DefaultUni () [1, 0]
arrayOfBools = mkConstant @(Vector Bool) @DefaultUni () (Vector.fromList [False, True])
expected = mkConstant @[Bool] @DefaultUni () [True, False]
term = mkIterAppNoAnn (tyInst () (builtin () MultiIndexArray) bool) [indices, arrayOfBools]
typecheckEvaluateCekNoEmit def defaultBuiltinCostModelForTesting term
@?= Right (EvaluationSuccess expected)
, testCase "multiIndexArray-empty-indices" do
let indices = mkConstant @[Integer] @DefaultUni () []
arrayOfInts = mkConstant @(Vector Integer) @DefaultUni () (Vector.fromList [10, 20, 30])
expected = mkConstant @[Integer] @DefaultUni () []
term = mkIterAppNoAnn (tyInst () (builtin () MultiIndexArray) integer) [indices, arrayOfInts]
typecheckEvaluateCekNoEmit def defaultBuiltinCostModelForTesting term
@?= Right (EvaluationSuccess expected)
, testCase "multiIndexArray-index-equals-length-fails" do
-- An index equal to the length is out of bounds; the whole call fails.
let indices = mkConstant @[Integer] @DefaultUni () [0, 3]
arrayOfInts = mkConstant @(Vector Integer) @DefaultUni () (Vector.fromList [10, 20, 30])
term = mkIterAppNoAnn (tyInst () (builtin () MultiIndexArray) integer) [indices, arrayOfInts]
typecheckEvaluateCekNoEmit def defaultBuiltinCostModelForTesting term
@?= Right EvaluationFailure
, testCase "multiIndexArray-negative-index-fails" do
-- Negative indices are out of bounds, not wrap-around.
let indices = mkConstant @[Integer] @DefaultUni () [-1]
arrayOfInts = mkConstant @(Vector Integer) @DefaultUni () (Vector.fromList [10, 20, 30])
term = mkIterAppNoAnn (tyInst () (builtin () MultiIndexArray) integer) [indices, arrayOfInts]
typecheckEvaluateCekNoEmit def defaultBuiltinCostModelForTesting term
@?= Right EvaluationFailure
, testCase "multiIndexArray-empty-array-fails" do
let indices = mkConstant @[Integer] @DefaultUni () [0]
emptyArray = mkConstant @(Vector Integer) @DefaultUni () (Vector.fromList [])
term = mkIterAppNoAnn (tyInst () (builtin () MultiIndexArray) integer) [indices, emptyArray]
typecheckEvaluateCekNoEmit def defaultBuiltinCostModelForTesting term
@?= Right EvaluationFailure
, testCase "multiIndexArray-huge-index-fails" do
-- The bounds check is in the 'Integer' domain, so an index exceeding
-- 'maxBound :: Int' is out of bounds rather than wrapping on conversion.
let indices = mkConstant @[Integer] @DefaultUni () [2 ^ (64 :: Integer)]
arrayOfInts = mkConstant @(Vector Integer) @DefaultUni () (Vector.fromList [10, 20, 30])
term = mkIterAppNoAnn (tyInst () (builtin () MultiIndexArray) integer) [indices, arrayOfInts]
typecheckEvaluateCekNoEmit def defaultBuiltinCostModelForTesting term
@?= Right EvaluationFailure
]

test_BuiltinPair :: TestTree
Expand Down
10 changes: 10 additions & 0 deletions plutus-ledger-api/src/PlutusLedgerApi/Common/Versions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ module PlutusLedgerApi.Common.Versions
, batch4b
, batch5
, batch6
, batch7
, MaxBounds (..)
, maxBoundsByPV
) where
Expand Down Expand Up @@ -302,6 +303,12 @@ batch6 =
, ScaleValue
]

-- Builtins that are implemented but not yet approved for release in any protocol
-- version. See Note [Adding new builtins: protocol versions].
batch7 :: [DefaultFun]
batch7 =
[MultiIndexArray]

{-| Given a ledger language, return a map indicating which builtin functions were
introduced in which 'MajorProtocolVersion'. This __must__ be updated when new
builtins are added. It is not necessary to add entries for protocol versions
Expand All @@ -314,19 +321,22 @@ builtinsIntroducedIn =
Map.fromList
[ (alonzoPV, Set.fromList batch1)
, (vanRossemPV, Set.fromList (batch2 ++ batch3 ++ batch4 ++ batch5 ++ batch6))
, (futurePV, Set.fromList batch7)
]
PlutusV2 ->
Map.fromList
[ (vasilPV, Set.fromList (batch1 ++ batch2))
, (valentinePV, Set.fromList batch3)
, (plominPV, Set.fromList batch4b)
, (vanRossemPV, Set.fromList (batch4a ++ batch5 ++ batch6))
, (futurePV, Set.fromList batch7)
]
PlutusV3 ->
Map.fromList
[ (changPV, Set.fromList (batch1 ++ batch2 ++ batch3 ++ batch4))
, (plominPV, Set.fromList batch5)
, (vanRossemPV, Set.fromList batch6)
, (futurePV, Set.fromList batch7)
]

{-| Return a set containing the builtins which are available in a given LL in a
Expand Down
10 changes: 7 additions & 3 deletions plutus-ledger-api/test/Spec/Data/Versions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,9 @@ builtins5 = mkScriptsForBuiltins batch5
builtins6 :: [(String, SerialisedScript)]
builtins6 = mkScriptsForBuiltins batch6

builtins7 :: [(String, SerialisedScript)]
builtins7 = mkScriptsForBuiltins batch7

allBuiltins :: [(String, SerialisedScript)]
allBuiltins =
builtins1
Expand All @@ -209,6 +212,7 @@ allBuiltins =
++ builtins4b
++ builtins5
++ builtins6
++ builtins7

{-| Test that the builtins that we expect to be allowed in each LL/PV
combination can be successfully deserialised and that the rest cannot. This
Expand Down Expand Up @@ -251,7 +255,7 @@ testPermittedBuiltins =
, mkTest valentinePV builtins1
, mkTest changPV builtins1
, mkTest plominPV builtins1
, mkTest newestPV allBuiltins
, mkTest newestPV (allBuiltins \\ builtins7)
]
, let mkTest = testBuiltins PlutusV2 V2.deserialiseScript
in testGroup
Expand All @@ -264,7 +268,7 @@ testPermittedBuiltins =
, mkTest valentinePV $ builtins1 ++ builtins2 ++ builtins3
, mkTest changPV $ builtins1 ++ builtins2 ++ builtins3
, mkTest plominPV $ builtins1 ++ builtins2 ++ builtins3 ++ builtins4b
, mkTest newestPV allBuiltins
, mkTest newestPV (allBuiltins \\ builtins7)
]
, let mkTest = testBuiltins PlutusV3 V3.deserialiseScript
in testGroup
Expand All @@ -277,7 +281,7 @@ testPermittedBuiltins =
, mkTest valentinePV []
, mkTest changPV $ builtins1 ++ builtins2 ++ builtins3 ++ builtins4a ++ builtins4b
, mkTest plominPV $ builtins1 ++ builtins2 ++ builtins3 ++ builtins4a ++ builtins4b ++ builtins5
, mkTest newestPV allBuiltins
, mkTest newestPV (allBuiltins \\ builtins7)
]
]

Expand Down
10 changes: 7 additions & 3 deletions plutus-ledger-api/test/Spec/Versions.hs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ builtins5 = mkScriptsForBuiltins batch5
builtins6 :: [(String, SerialisedScript)]
builtins6 = mkScriptsForBuiltins batch6

builtins7 :: [(String, SerialisedScript)]
builtins7 = mkScriptsForBuiltins batch7

allBuiltins :: [(String, SerialisedScript)]
allBuiltins =
builtins1
Expand All @@ -219,6 +222,7 @@ allBuiltins =
++ builtins4b
++ builtins5
++ builtins6
++ builtins7

{-| Test that the builtins that we expect to be allowed in each LL/PV
combination can be successfully deserialised and that the rest cannot. This
Expand Down Expand Up @@ -261,7 +265,7 @@ testPermittedBuiltins =
, mkTest valentinePV builtins1
, mkTest changPV builtins1
, mkTest plominPV builtins1
, mkTest newestPV allBuiltins
, mkTest newestPV (allBuiltins \\ builtins7)
]
, let mkTest = testBuiltins PlutusV2 V2.deserialiseScript
in testGroup
Expand All @@ -274,7 +278,7 @@ testPermittedBuiltins =
, mkTest valentinePV $ builtins1 ++ builtins2 ++ builtins3
, mkTest changPV $ builtins1 ++ builtins2 ++ builtins3
, mkTest plominPV $ builtins1 ++ builtins2 ++ builtins3 ++ builtins4b
, mkTest newestPV allBuiltins
, mkTest newestPV (allBuiltins \\ builtins7)
]
, let mkTest = testBuiltins PlutusV3 V3.deserialiseScript
in testGroup
Expand All @@ -287,7 +291,7 @@ testPermittedBuiltins =
, mkTest valentinePV []
, mkTest changPV $ builtins1 ++ builtins2 ++ builtins3 ++ builtins4a ++ builtins4b
, mkTest plominPV $ builtins1 ++ builtins2 ++ builtins3 ++ builtins4a ++ builtins4b ++ builtins5
, mkTest newestPV allBuiltins
, mkTest newestPV (allBuiltins \\ builtins7)
]
]

Expand Down
2 changes: 2 additions & 0 deletions plutus-tx-plugin/src/PlutusTx/Compiler/Builtins.hs
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,8 @@ defineBuiltinTerms = do
PLC.LengthOfArray -> defineBuiltinInl 'Builtins.lengthOfArray
PLC.ListToArray -> defineBuiltinInl 'Builtins.listToArray
PLC.IndexArray -> defineBuiltinInl 'Builtins.indexArray
-- Not exposed to Plinth yet: core-language only for now (CIP-0156).
PLC.MultiIndexArray -> pure ()
-- Data
PLC.ChooseData -> defineBuiltinInl 'Builtins.chooseData
PLC.EqualsData -> defineBuiltinInl 'Builtins.equalsData
Expand Down
Loading