55
66module PlutusCore.Value (
77 Value , -- Do not expose data constructor
8+ K , -- Do not expose data constructor
9+ k ,
10+ unK ,
11+ maxKeyLen ,
812 NestedMap ,
913 unpack ,
1014 pack ,
@@ -23,11 +27,12 @@ module PlutusCore.Value (
2327 unValueData ,
2428) where
2529
26- import Codec.Serialise ( Serialise )
30+ import Codec.Serialise qualified as CBOR
2731import Control.DeepSeq (NFData )
2832import Data.Bifunctor
2933import Data.Bitraversable
3034import Data.ByteString (ByteString )
35+ import Data.ByteString qualified as B
3136import Data.ByteString.Base64 qualified as Base64
3237import Data.Functor
3338import Data.Hashable (Hashable )
@@ -39,11 +44,45 @@ import Data.Map.Strict qualified as Map
3944import Data.Maybe
4045import Data.Text.Encoding qualified as Text
4146import GHC.Generics
47+
4248import PlutusCore.Builtin.Result
4349import PlutusCore.Data (Data (.. ))
50+ import PlutusCore.Flat qualified as Flat
4451import PlutusPrelude (Pretty (.. ))
4552
46- type NestedMap = Map ByteString (Map ByteString Integer )
53+ -- Max length (in bytes) for currency symbols and token names in `Value`,
54+ -- both of which cannot exceed 32 bytes. Currency symbols are in fact either
55+ -- empty or 28 bytes, but for simplicity we allow anything between 0 and 32 bytes.
56+ maxKeyLen :: Int
57+ maxKeyLen = 32
58+ {-# INLINE maxKeyLen #-}
59+
60+ -- | A `ByteString` with maximum length of `maxKeyLen` bytes.
61+ newtype K = UnsafeK { unK :: ByteString }
62+ deriving newtype (Eq , Ord , Show , Hashable , NFData )
63+ deriving stock (Generic )
64+
65+ k :: ByteString -> Maybe K
66+ k b = if B. length b <= maxKeyLen then Just (UnsafeK b) else Nothing
67+ {-# INLINEABLE k #-}
68+
69+ instance Flat. Flat K where
70+ encode (UnsafeK b) = Flat. encode b
71+ {-# INLINE encode #-}
72+ decode = do
73+ b <- Flat. decode
74+ maybe (fail $ " Invalid Value key: " <> show (B. unpack b)) pure (k b)
75+ {-# INLINEABLE decode #-}
76+
77+ instance CBOR. Serialise K where
78+ encode (UnsafeK b) = CBOR. encode b
79+ {-# INLINE encode #-}
80+ decode = do
81+ b <- CBOR. decode
82+ maybe (fail $ " Invalid Value key: " <> show (B. unpack b)) pure (k b)
83+ {-# INLINEABLE decode #-}
84+
85+ type NestedMap = Map K (Map K Integer )
4786
4887-- | The underlying type of the UPLC built-in type @Value@.
4988data Value
@@ -61,11 +100,23 @@ data Value
61100 Invariant: all values are positive.
62101 -}
63102 {- # UNPACK #-} !Int
64- {-^ Total size, i.e., sum total of inner map sizes. This avoids recomputing
103+ {- ^ Total size, i.e., sum total of inner map sizes. This avoids recomputing
65104 the total size during the costing of operations like `unionValue`.
66105 -}
67106 deriving stock (Eq , Show , Generic )
68- deriving anyclass (Hashable , Serialise , NFData )
107+ deriving anyclass (Hashable , NFData )
108+
109+ instance CBOR. Serialise Value where
110+ encode (Value v _ _) = CBOR. encode v
111+ {-# INLINE encode #-}
112+ decode = pack <$> CBOR. decode
113+ {-# INLINE decode #-}
114+
115+ instance Flat. Flat Value where
116+ encode (Value v _ _) = Flat. encode v
117+ {-# INLINE encode #-}
118+ decode = pack <$> Flat. decode
119+ {-# INLINE decode #-}
69120
70121{-| Unpack a `Value` into a map from (currency symbol, token name) to amount.
71122
@@ -110,19 +161,20 @@ empty :: Value
110161empty = Value mempty mempty 0
111162{-# INLINE empty #-}
112163
113- toList :: Value -> [(ByteString , [(ByteString , Integer )])]
164+ toList :: Value -> [(K , [(K , Integer )])]
114165toList = Map. toList . Map. map Map. toList . unpack
115166{-# INLINEABLE toList #-}
116167
117- toFlatList :: Value -> [(ByteString , ByteString , Integer )]
168+ toFlatList :: Value -> [(K , K , Integer )]
118169toFlatList (toList -> xs) = [(c, t, a) | (c, ys) <- xs, (t, a) <- ys]
119170{-# INLINEABLE toFlatList #-}
120171
121- fromList :: [(ByteString , [(ByteString , Integer )])] -> Value
172+ fromList :: [(K , [(K , Integer )])] -> Value
122173fromList =
123174 pack
124175 . Map. fromListWith (Map. unionWith (+) )
125176 . fmap (second (Map. fromListWith (+) ))
177+ {-# INLINEABLE fromList #-}
126178
127179normalize :: NestedMap -> NestedMap
128180normalize = Map. filter (not . Map. null ) . Map. map (Map. filter (/= 0 ))
@@ -131,7 +183,7 @@ normalize = Map.filter (not . Map.null) . Map.map (Map.filter (/= 0))
131183instance Pretty Value where
132184 pretty = pretty . fmap (bimap toText (fmap (first toText))) . toList
133185 where
134- toText = Text. decodeLatin1 . Base64. encode
186+ toText = Text. decodeLatin1 . Base64. encode . unK
135187
136188{-| \(O(\log \max(m, k))\), where \(m\) is the size of the outer map, and \(k\) is
137189the size of the largest inner map.
@@ -140,38 +192,40 @@ insertCoin :: ByteString -> ByteString -> Integer -> Value -> Value
140192insertCoin currency token amt v@ (Value outer sizes size)
141193 | amt == 0 = deleteCoin currency token v
142194 | otherwise =
143- let (mold, outer') = Map. alterF f currency outer
195+ let (mold, outer') = Map. alterF f ( UnsafeK currency) outer
144196 (sizes', size') = case mold of
145197 Just old -> (updateSizes old (old + 1 ) sizes, size + 1 )
146198 Nothing -> (sizes, size)
147199 in Value outer' sizes' size'
148200 where
149201 f
150- :: Maybe (Map ByteString Integer )
202+ :: Maybe (Map K Integer )
151203 -> ( -- Just (old size of inner map) if the total size grows by 1, otherwise Nothing
152204 Maybe Int
153- , Maybe (Map ByteString Integer )
205+ , Maybe (Map K Integer )
154206 )
155207 f = \ case
156- Nothing -> (Just 0 , Just (Map. singleton token amt))
208+ Nothing -> (Just 0 , Just (Map. singleton ( UnsafeK token) amt))
157209 Just inner ->
158- let (isJust -> exists, inner') = Map. insertLookupWithKey (\ _ _ _ -> amt) token amt inner
210+ let (isJust -> exists, inner') =
211+ Map. insertLookupWithKey (\ _ _ _ -> amt) (UnsafeK token) amt inner
159212 in (if exists then Nothing else Just (Map. size inner), Just inner')
160213{-# INLINEABLE insertCoin #-}
161214
162215-- | \(O(\log \max(m, k))\)
163216deleteCoin :: ByteString -> ByteString -> Value -> Value
164- deleteCoin currency token (Value outer sizes size) = Value outer' sizes' size'
217+ deleteCoin (UnsafeK -> currency) (UnsafeK -> token) (Value outer sizes size) =
218+ Value outer' sizes' size'
165219 where
166220 (mold, outer') = Map. alterF f currency outer
167221 (sizes', size') = case mold of
168222 Just old -> (updateSizes old (old - 1 ) sizes, size - 1 )
169223 Nothing -> (sizes, size)
170224 f
171- :: Maybe (Map ByteString Integer )
225+ :: Maybe (Map K Integer )
172226 -> ( -- Just (old size of inner map) if the total size shrinks by 1, otherwise Nothing
173227 Maybe Int
174- , Maybe (Map ByteString Integer )
228+ , Maybe (Map K Integer )
175229 )
176230 f = \ case
177231 Nothing -> (Nothing , Nothing )
@@ -181,9 +235,10 @@ deleteCoin currency token (Value outer sizes size) = Value outer' sizes' size'
181235
182236-- | \(O(\log \max(m, k))\)
183237lookupCoin :: ByteString -> ByteString -> Value -> Integer
184- lookupCoin currency token (unpack -> outer) = case Map. lookup currency outer of
185- Nothing -> 0
186- Just inner -> Map. findWithDefault 0 token inner
238+ lookupCoin (UnsafeK -> currency) (UnsafeK -> token) (unpack -> outer) =
239+ case Map. lookup currency outer of
240+ Nothing -> 0
241+ Just inner -> Map. findWithDefault 0 token inner
187242
188243{-| \(O(n_{2}\log \max(m_{1}, k_{1}))\), where \(n_{2}\) is the total size of the second
189244`Value`, \(m_{1}\) is the size of the outer map in the first `Value` and \(k_{1}\) is
@@ -200,7 +255,7 @@ valueContains v = Map.foldrWithKey' go True . unpack
200255 where
201256 goInner t a2 =
202257 (&&)
203- ( let a1 = lookupCoin c t v
258+ ( let a1 = lookupCoin (unK c) (unK t) v
204259 in if a2 > 0
205260 then a1 >= a2
206261 else a1 == a2
@@ -240,10 +295,10 @@ unionValue (unpack -> vA) (unpack -> vB) =
240295This is the denotation of @ValueData@ in Plutus V1, V2 and V3.
241296-}
242297valueData :: Value -> Data
243- valueData = Map . fmap (bimap B tokensData) . Map. toList . unpack
298+ valueData = Map . fmap (bimap ( B . unK) tokensData) . Map. toList . unpack
244299 where
245- tokensData :: Map ByteString Integer -> Data
246- tokensData = Map . fmap (bimap B I ) . Map. toList
300+ tokensData :: Map K Integer -> Data
301+ tokensData = Map . fmap (bimap ( B . unK) I ) . Map. toList
247302{-# INLINEABLE valueData #-}
248303
249304{-| \(O(n \log n)\). Decodes `Data` into `Value`, in the same way as non-builtin @Value@.
@@ -255,17 +310,17 @@ unValueData =
255310 Map cs -> fmap (Map. fromListWith (Map. unionWith (+) )) (traverse (bitraverse unB unTokens) cs)
256311 _ -> fail " unValueData: non-Map constructor"
257312 where
258- unB :: Data -> BuiltinResult ByteString
313+ unB :: Data -> BuiltinResult K
259314 unB = \ case
260- B b -> pure b
315+ B b -> pure ( UnsafeK b)
261316 _ -> fail " unValueData: non-B constructor"
262317
263318 unI :: Data -> BuiltinResult Integer
264319 unI = \ case
265320 I i -> pure i
266321 _ -> fail " unValueData: non-I constructor"
267322
268- unTokens :: Data -> BuiltinResult (Map ByteString Integer )
323+ unTokens :: Data -> BuiltinResult (Map K Integer )
269324 unTokens = \ case
270325 Map ts -> fmap (Map. fromListWith (+) ) (traverse (bitraverse unB unI) ts)
271326 _ -> fail " unValueData: non-Map constructor"
0 commit comments