11{-# LANGUAGE DeriveAnyClass #-}
22{-# LANGUAGE FlexibleInstances #-}
33{-# LANGUAGE LambdaCase #-}
4+ {-# LANGUAGE TupleSections #-}
45{-# LANGUAGE ViewPatterns #-}
56
67module PlutusCore.Value (
@@ -9,6 +10,7 @@ module PlutusCore.Value (
910 k ,
1011 unK ,
1112 maxKeyLen ,
13+ negativeAmounts ,
1214 NestedMap ,
1315 unpack ,
1416 pack ,
@@ -34,14 +36,13 @@ import Data.Bitraversable
3436import Data.ByteString (ByteString )
3537import Data.ByteString qualified as B
3638import Data.ByteString.Base64 qualified as Base64
37- import Data.Functor
38- import Data.Hashable (Hashable )
39+ import Data.Hashable (Hashable (.. ))
3940import Data.IntMap.Strict (IntMap )
4041import Data.IntMap.Strict qualified as IntMap
4142import Data.Map.Merge.Strict qualified as M
4243import Data.Map.Strict (Map )
4344import Data.Map.Strict qualified as Map
44- import Data.Maybe
45+ import Data.Monoid ( All ( .. ))
4546import Data.Text.Encoding qualified as Text
4647import GHC.Generics
4748
@@ -103,17 +104,25 @@ data Value
103104 {- ^ Total size, i.e., sum total of inner map sizes. This avoids recomputing
104105 the total size during the costing of operations like `unionValue`.
105106 -}
107+ {- # UNPACK #-} !Int
108+ -- ^ The number of negative amounts it contains.
106109 deriving stock (Eq , Show , Generic )
107- deriving anyclass (Hashable , NFData )
110+ deriving anyclass (NFData )
111+
112+ instance Hashable Value where
113+ hash = hash . unpack
114+ {-# INLINE hash #-}
115+ hashWithSalt salt = hashWithSalt salt . unpack
116+ {-# INLINE hashWithSalt #-}
108117
109118instance CBOR. Serialise Value where
110- encode (Value v _ _) = CBOR. encode v
119+ encode (Value v _ _ _ ) = CBOR. encode v
111120 {-# INLINE encode #-}
112121 decode = pack <$> CBOR. decode
113122 {-# INLINE decode #-}
114123
115124instance Flat. Flat Value where
116- encode (Value v _ _) = Flat. encode v
125+ encode (Value v _ _ _ ) = Flat. encode v
117126 {-# INLINE encode #-}
118127 decode = pack <$> Flat. decode
119128 {-# INLINE decode #-}
@@ -123,7 +132,7 @@ instance Flat.Flat Value where
123132The map is guaranteed to not contain empty inner map or zero amount.
124133-}
125134unpack :: Value -> NestedMap
126- unpack (Value v _ _) = v
135+ unpack (Value v _ _ _ ) = v
127136{-# INLINE unpack #-}
128137
129138{-| Pack a map from (currency symbol, token name) to amount into a `Value`.
@@ -136,29 +145,34 @@ pack = pack' . normalize
136145
137146-- | Like `pack` but does not normalize.
138147pack' :: NestedMap -> Value
139- pack' v = Value v sizes size
148+ pack' v = Value v sizes size neg
140149 where
141- (sizes, size) = Map. foldl' alg (mempty , 0 ) v
142- alg (ss, s) inner =
150+ (sizes, size, neg ) = Map. foldl' alg (mempty , 0 , 0 ) v
151+ alg (ss, s, n ) inner =
143152 ( IntMap. alter (maybe (Just 1 ) (Just . (+ 1 ))) (Map. size inner) ss
144153 , s + Map. size inner
154+ , n + Map. size (Map. filter (< 0 ) inner)
145155 )
146156{-# INLINEABLE pack' #-}
147157
148158{-| Total size, i.e., the number of distinct `(currency symbol, token name)` pairs
149159contained in the `Value`.
150160-}
151161totalSize :: Value -> Int
152- totalSize (Value _ _ size) = size
162+ totalSize (Value _ _ size _ ) = size
153163{-# INLINE totalSize #-}
154164
155165-- | Size of the largest inner map.
156166maxInnerSize :: Value -> Int
157- maxInnerSize (Value _ sizes _) = maybe 0 fst (IntMap. lookupMax sizes)
167+ maxInnerSize (Value _ sizes _ _ ) = maybe 0 fst (IntMap. lookupMax sizes)
158168{-# INLINE maxInnerSize #-}
159169
170+ negativeAmounts :: Value -> Int
171+ negativeAmounts (Value _ _ _ neg) = neg
172+ {-# INLINE negativeAmounts #-}
173+
160174empty :: Value
161- empty = Value mempty mempty 0
175+ empty = Value mempty mempty 0 0
162176{-# INLINE empty #-}
163177
164178toList :: Value -> [(K , [(K , Integer )])]
@@ -189,52 +203,70 @@ instance Pretty Value where
189203the size of the largest inner map.
190204-}
191205insertCoin :: ByteString -> ByteString -> Integer -> Value -> BuiltinResult Value
192- insertCoin currency token amt v@ (Value outer sizes size)
206+ insertCoin currency token amt v@ (Value outer sizes size neg )
193207 | amt == 0 = pure $ deleteCoin currency token v
194208 | otherwise = case (k currency, k token) of
195209 (Nothing , _) -> fail $ " insertCoin: invalid currency: " <> show (B. unpack currency)
196210 (_, Nothing ) -> fail $ " insertCoin: invalid token: " <> show (B. unpack token)
197211 (Just ck, Just tk) ->
198212 let f
199213 :: Maybe (Map K Integer )
200- -> ( -- Just (old size of inner map) if the total size grows by 1,
201- -- otherwise Nothing
202- Maybe Int
214+ -> ( -- Left (old size of inner map) if the total size grows by 1,
215+ -- otherwise, Right (old amount)
216+ Either Int Integer
203217 , Maybe (Map K Integer )
204218 )
205219 f = \ case
206- Nothing -> (Just 0 , Just (Map. singleton tk amt))
220+ Nothing -> (Left 0 , Just (Map. singleton tk amt))
207221 Just inner ->
208- let (isJust -> exists , inner') =
222+ let (moldAmt , inner') =
209223 Map. insertLookupWithKey (\ _ _ _ -> amt) tk amt inner
210- in (if exists then Nothing else Just (Map. size inner), Just inner')
211- (mold, outer') = Map. alterF f ck outer
212- (sizes', size') = case mold of
213- Just old -> (updateSizes old (old + 1 ) sizes, size + 1 )
214- Nothing -> (sizes, size)
215- in pure $ Value outer' sizes' size'
224+ in (maybe (Left (Map. size inner)) Right moldAmt, Just inner')
225+ (res, outer') = Map. alterF f ck outer
226+ (sizes', size', neg') = case res of
227+ Left oldSize ->
228+ ( updateSizes oldSize (oldSize + 1 ) sizes
229+ , size + 1
230+ , if amt < 0 then neg + 1 else neg
231+ )
232+ Right oldAmt ->
233+ ( sizes
234+ , size
235+ , if oldAmt < 0 && amt > 0
236+ then neg - 1
237+ else
238+ if oldAmt > 0 && amt < 0
239+ then neg + 1
240+ else neg
241+ )
242+ in pure $ Value outer' sizes' size' neg'
216243{-# INLINEABLE insertCoin #-}
217244
218245-- | \(O(\log \max(m, k))\)
219246deleteCoin :: ByteString -> ByteString -> Value -> Value
220- deleteCoin (UnsafeK -> currency) (UnsafeK -> token) (Value outer sizes size) =
221- Value outer' sizes' size'
247+ deleteCoin (UnsafeK -> currency) (UnsafeK -> token) (Value outer sizes size neg ) =
248+ Value outer' sizes' size' neg'
222249 where
223250 (mold, outer') = Map. alterF f currency outer
224- (sizes', size') = case mold of
225- Just old -> (updateSizes old (old - 1 ) sizes, size - 1 )
226- Nothing -> (sizes, size)
251+ (sizes', size', neg') = case mold of
252+ Just (oldSize, oldAmt) ->
253+ ( updateSizes oldSize (oldSize - 1 ) sizes
254+ , size - 1
255+ , if oldAmt < 0 then neg - 1 else neg
256+ )
257+ Nothing -> (sizes, size, neg)
227258 f
228259 :: Maybe (Map K Integer )
229- -> ( -- Just (old size of inner map) if the total size shrinks by 1, otherwise Nothing
230- Maybe Int
260+ -> ( -- Just (old size of inner map, old amount) if the total size shrinks by 1,
261+ -- otherwise Nothing
262+ Maybe (Int , Integer )
231263 , Maybe (Map K Integer )
232264 )
233265 f = \ case
234266 Nothing -> (Nothing , Nothing )
235267 Just inner ->
236268 let (amt, inner') = Map. updateLookupWithKey (\ _ _ -> Nothing ) token inner
237- in (amt $> Map. size inner, if Map. null inner' then Nothing else Just inner')
269+ in (( Map. size inner,) <$> amt , if Map. null inner' then Nothing else Just inner')
238270
239271-- | \(O(\log \max(m, k))\)
240272lookupCoin :: ByteString -> ByteString -> Value -> Integer
@@ -251,18 +283,16 @@ the size of the largest inner map in the first `Value`.
251283@lookup currency token a >= amount@, and if @amount < 0@, then
252284@lookup currency token a == amount@.
253285-}
254- valueContains :: Value -> Value -> Bool
255- valueContains v = Map. foldrWithKey' go True . unpack
286+ valueContains :: Value -> Value -> BuiltinResult Bool
287+ valueContains v1 v2
288+ | negativeAmounts v1 > 0 = fail " valueContains: first value contains negative amounts"
289+ | negativeAmounts v2 > 0 = fail " valueContains: second value contains negative amounts"
290+ | otherwise = BuiltinSuccess . getAll $ Map. foldrWithKey' go mempty (unpack v2)
256291 where
257- go c inner = (&& ) (Map. foldrWithKey' goInner True inner)
292+ go c inner = (<> ) (Map. foldrWithKey' goInner mempty inner)
258293 where
259- goInner t a2 =
260- (&&)
261- ( let a1 = lookupCoin (unK c) (unK t) v
262- in if a2 > 0
263- then a1 >= a2
264- else a1 == a2
265- )
294+ goInner t a2 = (<>) (All $ lookupCoin (unK c) (unK t) v1 >= a2)
295+ {-# INLINEABLE valueContains #-}
266296
267297{-| The precise complexity is complicated, but an upper bound
268298is \(O(n_{1} \log n_{2}) + O(m)\), where \(n_{1}\) is the total size of the smaller
0 commit comments