Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions plutus-conformance/agda/Spec.hs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ failingEvaluationTests =
, "test-cases/uplc/evaluation/builtin/semantics/valueContains/pos-neg"
, "test-cases/uplc/evaluation/builtin/semantics/valueContains/reflexive"
, "test-cases/uplc/evaluation/builtin/semantics/valueContains/token-missing"
, "test-cases/uplc/evaluation/builtin/semantics/scaleValue/by-zero"
, "test-cases/uplc/evaluation/builtin/semantics/scaleValue/by-pos"
, "test-cases/uplc/evaluation/builtin/semantics/scaleValue/by-neg"
]

{-| A list of budget tests which are currently expected to fail. Once a fix for
Expand Down Expand Up @@ -285,6 +288,9 @@ failingBudgetTests =
, "test-cases/uplc/evaluation/builtin/semantics/valueContains/pos-neg"
, "test-cases/uplc/evaluation/builtin/semantics/valueContains/reflexive"
, "test-cases/uplc/evaluation/builtin/semantics/valueContains/token-missing"
, "test-cases/uplc/evaluation/builtin/semantics/scaleValue/by-zero"
, "test-cases/uplc/evaluation/builtin/semantics/scaleValue/by-pos"
, "test-cases/uplc/evaluation/builtin/semantics/scaleValue/by-neg"
]

-- Run the tests: see Note [Evaluation with and without costing] above.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(program 1.0.0
[ (builtin scaleValue)
(con integer -2)
(con value [(#aa, [(#aa, 5), (#bb, -15), (#cc, 20)])])
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
({cpu: 100000080100
| mem: 100000000600})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(program 1.0.0 (con value [(#aa, [(#aa, -10), (#bb, 30), (#cc, -40)])]))
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(program 1.0.0
[ (builtin scaleValue)
(con integer 2)
(con value [(#aa, [(#aa, 5), (#bb, -15), (#cc, 20)])])
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
({cpu: 100000080100
| mem: 100000000600})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(program 1.0.0 (con value [(#aa, [(#aa, 10), (#bb, -30), (#cc, 40)])]))
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(program 1.0.0
[ (builtin scaleValue)
(con integer 0)
(con value [(#aa, [(#aa, 5), (#bb, -15), (#cc, 20)])])
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
({cpu: 100000080100
| mem: 100000000600})
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(program 1.0.0 (con value []))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Added

- Implementations of `ScaleValue` primitives.
213 changes: 112 additions & 101 deletions plutus-core/plutus-core/src/PlutusCore/Default/Builtins.hs
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ data DefaultFun
| ValueContains
| ValueData
| UnValueData
| ScaleValue
deriving stock (Show, Eq, Ord, Enum, Bounded, Generic, Ix)
deriving anyclass (NFData, Hashable, PrettyBy PrettyConfigPlc)

Expand Down Expand Up @@ -2094,6 +2095,14 @@ instance uni ~ DefaultUni => ToBuiltinMeaning uni DefaultFun where
unValueDataDenotation
(runCostingFunOneArgument . unimplementedCostingFun)

toBuiltinMeaning _semvar ScaleValue =
let unValueDataDenotation :: Integer -> Value -> BuiltinResult Value
unValueDataDenotation = Value.scaleValue
{-# INLINE unValueDataDenotation #-}
in makeBuiltinMeaning
unValueDataDenotation
(runCostingFunTwoArguments . unimplementedCostingFun)

-- See Note [Inlining meanings of builtins].
{-# INLINE toBuiltinMeaning #-}

Expand Down Expand Up @@ -2251,109 +2260,111 @@ instance Flat DefaultFun where
ValueContains -> 97
ValueData -> 98
UnValueData -> 99
ScaleValue -> 100

decode = go =<< decodeBuiltin
where go 0 = pure AddInteger
go 1 = pure SubtractInteger
go 2 = pure MultiplyInteger
go 3 = pure DivideInteger
go 4 = pure QuotientInteger
go 5 = pure RemainderInteger
go 6 = pure ModInteger
go 7 = pure EqualsInteger
go 8 = pure LessThanInteger
go 9 = pure LessThanEqualsInteger
go 10 = pure AppendByteString
go 11 = pure ConsByteString
go 12 = pure SliceByteString
go 13 = pure LengthOfByteString
go 14 = pure IndexByteString
go 15 = pure EqualsByteString
go 16 = pure LessThanByteString
go 17 = pure LessThanEqualsByteString
go 18 = pure Sha2_256
go 19 = pure Sha3_256
go 20 = pure Blake2b_256
go 21 = pure VerifyEd25519Signature
go 22 = pure AppendString
go 23 = pure EqualsString
go 24 = pure EncodeUtf8
go 25 = pure DecodeUtf8
go 26 = pure IfThenElse
go 27 = pure ChooseUnit
go 28 = pure Trace
go 29 = pure FstPair
go 30 = pure SndPair
go 31 = pure ChooseList
go 32 = pure MkCons
go 33 = pure HeadList
go 34 = pure TailList
go 35 = pure NullList
go 36 = pure ChooseData
go 37 = pure ConstrData
go 38 = pure MapData
go 39 = pure ListData
go 40 = pure IData
go 41 = pure BData
go 42 = pure UnConstrData
go 43 = pure UnMapData
go 44 = pure UnListData
go 45 = pure UnIData
go 46 = pure UnBData
go 47 = pure EqualsData
go 48 = pure MkPairData
go 49 = pure MkNilData
go 50 = pure MkNilPairData
go 51 = pure SerialiseData
go 52 = pure VerifyEcdsaSecp256k1Signature
go 53 = pure VerifySchnorrSecp256k1Signature
go 54 = pure Bls12_381_G1_add
go 55 = pure Bls12_381_G1_neg
go 56 = pure Bls12_381_G1_scalarMul
go 57 = pure Bls12_381_G1_equal
go 58 = pure Bls12_381_G1_compress
go 59 = pure Bls12_381_G1_uncompress
go 60 = pure Bls12_381_G1_hashToGroup
go 61 = pure Bls12_381_G2_add
go 62 = pure Bls12_381_G2_neg
go 63 = pure Bls12_381_G2_scalarMul
go 64 = pure Bls12_381_G2_equal
go 65 = pure Bls12_381_G2_compress
go 66 = pure Bls12_381_G2_uncompress
go 67 = pure Bls12_381_G2_hashToGroup
go 68 = pure Bls12_381_millerLoop
go 69 = pure Bls12_381_mulMlResult
go 70 = pure Bls12_381_finalVerify
go 71 = pure Keccak_256
go 72 = pure Blake2b_224
go 73 = pure IntegerToByteString
go 74 = pure ByteStringToInteger
go 75 = pure AndByteString
go 76 = pure OrByteString
go 77 = pure XorByteString
go 78 = pure ComplementByteString
go 79 = pure ReadBit
go 80 = pure WriteBits
go 81 = pure ReplicateByte
go 82 = pure ShiftByteString
go 83 = pure RotateByteString
go 84 = pure CountSetBits
go 85 = pure FindFirstSetBit
go 86 = pure Ripemd_160
go 87 = pure ExpModInteger
go 88 = pure DropList
go 89 = pure LengthOfArray
go 90 = pure ListToArray
go 91 = pure IndexArray
go 92 = pure Bls12_381_G1_multiScalarMul
go 93 = pure Bls12_381_G2_multiScalarMul
go 94 = pure InsertCoin
go 95 = pure LookupCoin
go 96 = pure UnionValue
go 97 = pure ValueContains
go 98 = pure ValueData
go 99 = pure UnValueData
go t = fail $ "Failed to decode builtin tag, got: " ++ show t
where go 0 = pure AddInteger
go 1 = pure SubtractInteger
go 2 = pure MultiplyInteger
go 3 = pure DivideInteger
go 4 = pure QuotientInteger
go 5 = pure RemainderInteger
go 6 = pure ModInteger
go 7 = pure EqualsInteger
go 8 = pure LessThanInteger
go 9 = pure LessThanEqualsInteger
go 10 = pure AppendByteString
go 11 = pure ConsByteString
go 12 = pure SliceByteString
go 13 = pure LengthOfByteString
go 14 = pure IndexByteString
go 15 = pure EqualsByteString
go 16 = pure LessThanByteString
go 17 = pure LessThanEqualsByteString
go 18 = pure Sha2_256
go 19 = pure Sha3_256
go 20 = pure Blake2b_256
go 21 = pure VerifyEd25519Signature
go 22 = pure AppendString
go 23 = pure EqualsString
go 24 = pure EncodeUtf8
go 25 = pure DecodeUtf8
go 26 = pure IfThenElse
go 27 = pure ChooseUnit
go 28 = pure Trace
go 29 = pure FstPair
go 30 = pure SndPair
go 31 = pure ChooseList
go 32 = pure MkCons
go 33 = pure HeadList
go 34 = pure TailList
go 35 = pure NullList
go 36 = pure ChooseData
go 37 = pure ConstrData
go 38 = pure MapData
go 39 = pure ListData
go 40 = pure IData
go 41 = pure BData
go 42 = pure UnConstrData
go 43 = pure UnMapData
go 44 = pure UnListData
go 45 = pure UnIData
go 46 = pure UnBData
go 47 = pure EqualsData
go 48 = pure MkPairData
go 49 = pure MkNilData
go 50 = pure MkNilPairData
go 51 = pure SerialiseData
go 52 = pure VerifyEcdsaSecp256k1Signature
go 53 = pure VerifySchnorrSecp256k1Signature
go 54 = pure Bls12_381_G1_add
go 55 = pure Bls12_381_G1_neg
go 56 = pure Bls12_381_G1_scalarMul
go 57 = pure Bls12_381_G1_equal
go 58 = pure Bls12_381_G1_compress
go 59 = pure Bls12_381_G1_uncompress
go 60 = pure Bls12_381_G1_hashToGroup
go 61 = pure Bls12_381_G2_add
go 62 = pure Bls12_381_G2_neg
go 63 = pure Bls12_381_G2_scalarMul
go 64 = pure Bls12_381_G2_equal
go 65 = pure Bls12_381_G2_compress
go 66 = pure Bls12_381_G2_uncompress
go 67 = pure Bls12_381_G2_hashToGroup
go 68 = pure Bls12_381_millerLoop
go 69 = pure Bls12_381_mulMlResult
go 70 = pure Bls12_381_finalVerify
go 71 = pure Keccak_256
go 72 = pure Blake2b_224
go 73 = pure IntegerToByteString
go 74 = pure ByteStringToInteger
go 75 = pure AndByteString
go 76 = pure OrByteString
go 77 = pure XorByteString
go 78 = pure ComplementByteString
go 79 = pure ReadBit
go 80 = pure WriteBits
go 81 = pure ReplicateByte
go 82 = pure ShiftByteString
go 83 = pure RotateByteString
go 84 = pure CountSetBits
go 85 = pure FindFirstSetBit
go 86 = pure Ripemd_160
go 87 = pure ExpModInteger
go 88 = pure DropList
go 89 = pure LengthOfArray
go 90 = pure ListToArray
go 91 = pure IndexArray
go 92 = pure Bls12_381_G1_multiScalarMul
go 93 = pure Bls12_381_G2_multiScalarMul
go 94 = pure InsertCoin
go 95 = pure LookupCoin
go 96 = pure UnionValue
go 97 = pure ValueContains
go 98 = pure ValueData
go 99 = pure UnValueData
go 100 = pure ScaleValue
go t = fail $ "Failed to decode builtin tag, got: " ++ show t

size _ n = n + builtinTagWidth

Expand Down
36 changes: 36 additions & 0 deletions plutus-core/plutus-core/src/PlutusCore/Value.hs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module PlutusCore.Value (
maxInnerSize,
insertCoin,
deleteCoin,
scaleValue,
lookupCoin,
valueContains,
unionValue,
Expand Down Expand Up @@ -148,6 +149,11 @@ addQuantity :: Quantity -> Quantity -> Maybe Quantity
addQuantity (UnsafeQuantity x) (UnsafeQuantity y) = quantity (x + y)
{-# INLINEABLE addQuantity #-}

-- | Safely scale a quantitie with given integer, checking for overflow.
scaleQuantity :: Integer -> Quantity -> Maybe Quantity
scaleQuantity x (UnsafeQuantity y) = quantity (x * y)
{-# INLINEABLE scaleQuantity #-}

----------------------------------------------------------------------------------------------------
-- Builtin Value definition ------------------------------------------------------------------------

Expand Down Expand Up @@ -470,3 +476,33 @@ updateSizes old new = dec . inc
then id
else IntMap.update (\n -> if n <= 1 then Nothing else Just (n - 1)) old
{-# INLINEABLE updateSizes #-}

-- | \(O(n)\). Scale each tokens by the given constant factor.
scaleValue :: Integer -> Value -> BuiltinResult Value
scaleValue c (Value outer sizes size neg)
-- When scaling by positive factor, no need to change sizes and number of negative amounts.
| c > 0 = do
outer' <- go outer
BuiltinSuccess $ Value outer' sizes size neg
-- When scaling by negative factor, only need to "flip" negative amounts.
| c < 0 = do
outer' <- go outer
-- let
-- neg' = Map.foldl' alg 0 outer'
-- alg n inner = n + Map.size (Map.filter (< zeroQuantity) inner)

-- TODO: make sure (size - neg) is correct
BuiltinSuccess $ Value outer' sizes size (size - neg)
Copy link
Collaborator Author

@SeungheonOh SeungheonOh Nov 3, 2025

Choose a reason for hiding this comment

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

please check here!

Need to make sure if I can just size - neg here or if I need to count all negative quantities again.

Copy link
Member

Choose a reason for hiding this comment

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

I think this is correct, but it's a good idea to add a prop_scaleValueBookkeeping test.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay, added bookkeeping property as well. It seems to be fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that's right.

-- Scaling by 0 is always empty value
| otherwise = BuiltinSuccess empty
where
go :: NestedMap -> BuiltinResult NestedMap
go x = traverse (traverse goScale) x
goScale :: Quantity -> BuiltinResult Quantity
goScale x =
case scaleQuantity c x of
Nothing ->
fail $
"scaleValue: quantity out of bounds: "
<> show c <> " * " <> show (unQuantity x)
Just q -> pure q
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
integer -> value -> value
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Integer -> Value -> BuiltinResult Value
Loading