Skip to content

Commit 6825737

Browse files
committed
Remove Generic-based Flat derivation machinery (#7542)
The internal flat library contained Generic-based Flat class derivation via GHC.Generics that had a bug: deserializing large enum types (512+ constructors) consumed infinite memory. While not a security risk (UPLC on-chain code uses only manual Flat instances), the buggy dead code was distracting. This commit removes the entire Generic derivation machinery. Changes: 1. Add manual Flat instances to all types that previously relied on Generic defaults: Maybe, Either, Complex, [], NonEmpty, tuples (2-7), Tree, Filler, PostAligned, PreAligned, SrcSpan, SrcSpans, and all PIR types (Recursivity, Strictness, Datatype, Binding, Program, Term). Manual encoding is bit-identical to the Generic encoding. 2. Add missing `size` methods to partial Flat instances in FlatInstances.hs (Name, Version, VarDecl, TyVarDecl, Program, NamedDeBruijn) and Value.hs (K, Quantity, Value) that previously relied on the Generic default for `size`. 3. Gut Class.hs: remove ~440 lines of GFlat*/GEncode*/GSize* classes, NumConstructors type family, all Generic default method implementations, and associated imports/pragmas/re-exports. 4. Remove ENUM_LARGE test infrastructure (E256/E258 types, Flat/Arbitrary instances, ~530 lines) and delete Core.hs inspection tests. 5. Fix transitive import breakage: files that got Generic from the PlutusCore.Flat re-export now import GHC.Generics directly. Remove `hiding (to)` from imports that no longer re-export Generics. 6. Strip Generic-specific doctests from Tutorial.hs. All existing tests pass (5281 across 4 suites). Manual instances produce bit-identical encoding verified by round-trip tests and golden files.
1 parent b6d66c6 commit 6825737

File tree

22 files changed

+607
-1433
lines changed

22 files changed

+607
-1433
lines changed

plutus-core/flat/src/PlutusCore/Flat/Class.hs

Lines changed: 14 additions & 460 deletions
Large diffs are not rendered by default.

plutus-core/flat/src/PlutusCore/Flat/Filler.hs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ module PlutusCore.Flat.Filler
1717

1818
import Control.DeepSeq (NFData)
1919
import Data.Typeable (Typeable)
20-
import PlutusCore.Flat.Class (Flat (..), Generic)
20+
import GHC.Generics (Generic)
21+
import PlutusCore.Flat.Class (Flat (..))
22+
import PlutusCore.Flat.Decoder.Prim (dBool)
2123
import PlutusCore.Flat.Decoder.Types (Get)
2224
import PlutusCore.Flat.Encoder.Strict (eFiller, sFillerMax)
2325

@@ -33,6 +35,9 @@ data Filler
3335
instance Flat Filler where
3436
encode _ = eFiller
3537
size = sFillerMax
38+
decode = do
39+
tag <- dBool
40+
if tag then pure FillerEnd else FillerBit <$> decode
3641

3742
-- use generated decode
3843

@@ -50,15 +55,25 @@ instance Flat a => Flat (PostAligned a) where
5055
#else
5156

5257
data PostAligned a = PostAligned { postValue :: a, postFiller :: Filler }
53-
deriving (Show, Eq, Ord, Generic, NFData,Flat)
58+
deriving (Show, Eq, Ord, Generic, NFData)
59+
60+
instance Flat a => Flat (PostAligned a) where
61+
encode (PostAligned v f) = encode v <> encode f
62+
decode = PostAligned <$> decode <*> decode
63+
size (PostAligned v f) n = size v (size f n)
5464

5565
#endif
5666

5767
{-| A Pre aligned value, a value preceded by a filler
5868
5969
Useful to prealign ByteArrays, Texts and any structure that can be encoded more efficiently when byte aligned. -}
6070
data PreAligned a = PreAligned {preFiller :: Filler, preValue :: a}
61-
deriving (Show, Eq, Ord, Generic, NFData, Flat)
71+
deriving (Show, Eq, Ord, Generic, NFData)
72+
73+
instance Flat a => Flat (PreAligned a) where
74+
encode (PreAligned f v) = encode f <> encode v
75+
decode = PreAligned <$> decode <*> decode
76+
size (PreAligned f v) n = size f (size v n)
6277

6378
-- | Length of a filler in bits
6479
fillerLength :: Num a => Filler -> a

plutus-core/flat/src/PlutusCore/Flat/Instances/Base.hs

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
{-# LANGUAGE CPP #-}
2-
{-# LANGUAGE DeriveGeneric #-}
32
{-# LANGUAGE FlexibleContexts #-}
43
{-# LANGUAGE FlexibleInstances #-}
5-
{-# LANGUAGE StandaloneDeriving #-}
64

75
-- | Flat instances for the base library
86
module PlutusCore.Flat.Instances.Base () where
@@ -206,7 +204,14 @@ instance Flat Char where
206204
>>> test (Just False::Maybe Bool)
207205
(True,2,"10")
208206
-}
209-
instance Flat a => Flat (Maybe a)
207+
instance Flat a => Flat (Maybe a) where
208+
encode Nothing = eFalse
209+
encode (Just x) = eTrue <> encode x
210+
decode = do
211+
tag <- dBool
212+
if tag then Just <$> decode else pure Nothing
213+
size Nothing n = 1 + n
214+
size (Just x) n = 1 + size x n
210215

211216
{- |
212217
>>> test (Left False::Either Bool ())
@@ -215,7 +220,14 @@ instance Flat a => Flat (Maybe a)
215220
>>> test (Right ()::Either Bool ())
216221
(True,1,"1")
217222
-}
218-
instance (Flat a, Flat b) => Flat (Either a b)
223+
instance (Flat a, Flat b) => Flat (Either a b) where
224+
encode (Left x) = eFalse <> encode x
225+
encode (Right x) = eTrue <> encode x
226+
decode = do
227+
tag <- dBool
228+
if tag then Right <$> decode else Left <$> decode
229+
size (Left x) n = 1 + size x n
230+
size (Right x) n = 1 + size x n
219231

220232
{- |
221233
>>> test (MkFixed 123 :: Fixed E0)
@@ -598,7 +610,10 @@ instance Flat Double where
598610
>>> test (4 :+ 2 :: Complex Word8)
599611
(True,16,"00000100 00000010")
600612
-}
601-
instance Flat a => Flat (Complex a)
613+
instance Flat a => Flat (Complex a) where
614+
encode (r :+ i) = encode r <> encode i
615+
decode = (:+) <$> decode <*> decode
616+
size (r :+ i) n = size r (size i n)
602617

603618
{- |
604619
Ratios are encoded as tuples of (numerator,denominator)
@@ -624,7 +639,14 @@ instance (Integral a, Flat a) => Flat (Ratio a) where
624639
This instance and other similar ones are declared as @OVERLAPPABLE@, because for better encoding/decoding
625640
performance it can be useful to declare instances of concrete types, such as @[Char]@ (not provided out of the box).
626641
-}
627-
instance {-# OVERLAPPABLE #-} Flat a => Flat [a]
642+
instance {-# OVERLAPPABLE #-} Flat a => Flat [a] where
643+
encode [] = eFalse
644+
encode (x:xs) = eTrue <> encode x <> encode xs
645+
decode = do
646+
tag <- dBool
647+
if tag then (:) <$> decode <*> decode else pure []
648+
size [] n = 1 + n
649+
size (x:xs) n = 1 + size x (size xs n)
628650

629651
{-
630652
>>> import Weigh
@@ -658,7 +680,10 @@ instance {-# OVERLAPPABLE #-} Flat a => Flat [a]
658680
>>> test (B.fromList [False,False])
659681
(True,4,"0100")
660682
-}
661-
instance {-# OVERLAPPABLE #-} Flat a => Flat (B.NonEmpty a)
683+
instance {-# OVERLAPPABLE #-} Flat a => Flat (B.NonEmpty a) where
684+
encode (x B.:| xs) = encode x <> encode xs
685+
decode = (B.:|) <$> decode <*> decode
686+
size (x B.:| xs) n = size x (size xs n)
662687

663688
-- #endif
664689

@@ -681,24 +706,39 @@ tst (1::Int,"2","3","4","5","6","7","8")
681706
-}
682707

683708
-- Not sure if these should be OVERLAPPABLE
684-
instance {-# OVERLAPPABLE #-} (Flat a, Flat b) => Flat (a, b)
709+
instance {-# OVERLAPPABLE #-} (Flat a, Flat b) => Flat (a, b) where
710+
encode (a, b) = encode a <> encode b
711+
decode = (,) <$> decode <*> decode
712+
size (a, b) n = size a (size b n)
685713

686-
instance {-# OVERLAPPABLE #-} (Flat a, Flat b, Flat c) => Flat (a, b, c)
714+
instance {-# OVERLAPPABLE #-} (Flat a, Flat b, Flat c) => Flat (a, b, c) where
715+
encode (a, b, c) = encode a <> encode b <> encode c
716+
decode = (,,) <$> decode <*> decode <*> decode
717+
size (a, b, c) n = size a (size b (size c n))
687718

688719
instance
689720
{-# OVERLAPPABLE #-}
690721
(Flat a, Flat b, Flat c, Flat d) =>
691-
Flat (a, b, c, d)
722+
Flat (a, b, c, d) where
723+
encode (a, b, c, d) = encode a <> encode b <> encode c <> encode d
724+
decode = (,,,) <$> decode <*> decode <*> decode <*> decode
725+
size (a, b, c, d) n = size a (size b (size c (size d n)))
692726

693727
instance
694728
{-# OVERLAPPABLE #-}
695729
(Flat a, Flat b, Flat c, Flat d, Flat e) =>
696-
Flat (a, b, c, d, e)
730+
Flat (a, b, c, d, e) where
731+
encode (a, b, c, d, e) = encode a <> encode b <> encode c <> encode d <> encode e
732+
decode = (,,,,) <$> decode <*> decode <*> decode <*> decode <*> decode
733+
size (a, b, c, d, e) n = size a (size b (size c (size d (size e n))))
697734

698735
instance
699736
{-# OVERLAPPABLE #-}
700737
(Flat a, Flat b, Flat c, Flat d, Flat e, Flat f) =>
701-
Flat (a, b, c, d, e, f)
738+
Flat (a, b, c, d, e, f) where
739+
encode (a, b, c, d, e, f) = encode a <> encode b <> encode c <> encode d <> encode e <> encode f
740+
decode = (,,,,,) <$> decode <*> decode <*> decode <*> decode <*> decode <*> decode
741+
size (a, b, c, d, e, f) n = size a (size b (size c (size d (size e (size f n)))))
702742

703743
instance
704744
{-# OVERLAPPABLE #-}
@@ -710,4 +750,7 @@ instance
710750
, Flat f
711751
, Flat g
712752
) =>
713-
Flat (a, b, c, d, e, f, g)
753+
Flat (a, b, c, d, e, f, g) where
754+
encode (a, b, c, d, e, f, g) = encode a <> encode b <> encode c <> encode d <> encode e <> encode f <> encode g
755+
decode = (,,,,,,) <$> decode <*> decode <*> decode <*> decode <*> decode <*> decode <*> decode
756+
size (a, b, c, d, e, f, g) n = size a (size b (size c (size d (size e (size f (size g n))))))

plutus-core/flat/src/PlutusCore/Flat/Instances/Containers.hs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
1-
{-# LANGUAGE CPP #-}
2-
{-# LANGUAGE DeriveGeneric #-}
31
{-# LANGUAGE FlexibleContexts #-}
42
{-# LANGUAGE MonoLocalBinds #-}
53
{-# LANGUAGE RankNTypes #-}
64
{-# LANGUAGE ScopedTypeVariables #-}
7-
{-# LANGUAGE StandaloneDeriving #-}
85
{-# LANGUAGE TypeSynonymInstances #-}
96
{-# LANGUAGE NoMonomorphismRestriction #-}
107

@@ -20,6 +17,7 @@ import Data.Map
2017
import Data.Sequence
2118
import Data.Set
2219
import Data.Tree
20+
import PlutusCore.Flat.Class qualified as Flat
2321
import PlutusCore.Flat.Instances.Base ()
2422
import PlutusCore.Flat.Instances.Mono
2523
import PlutusCore.Flat.Instances.Util
@@ -108,8 +106,7 @@ instance (Flat a, Ord a) => Flat (Set a) where
108106
{-|
109107
>>> tst (Node (1::Word8) [Node 2 [Node 3 []], Node 4 []])
110108
(True,39,[1,129,64,200,32]) -}
111-
#if ! MIN_VERSION_containers(0,5,8)
112-
deriving instance Generic (Tree a)
113-
#endif
114-
115-
instance Flat a => Flat (Tree a)
109+
instance Flat a => Flat (Tree a) where
110+
encode (Node x ts) = Flat.encode x <> Flat.encode ts
111+
decode = Node <$> Flat.decode <*> Flat.decode
112+
size (Node x ts) n = Flat.size x (Flat.size ts n)

plutus-core/flat/src/PlutusCore/Flat/Instances/Extra.hs

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

33
module PlutusCore.Flat.Instances.Extra where
44

5-
import PlutusCore.Flat.Class (Flat)
5+
import PlutusCore.Flat.Class (Flat (..))
6+
import PlutusCore.Flat.Decoder (dBool)
7+
import PlutusCore.Flat.Encoder (eFalse, eTrue, (<>))
68
import PlutusCore.Flat.Instances.Base ()
9+
import Prelude hiding ((<>))
710

811
{-$setup
912
>>> import PlutusCore.Flat.Instances.Test -}
@@ -16,4 +19,11 @@ For better encoding/decoding performance, it is useful to declare instances of c
1619
1720
>>> tstBits "aaa"
1821
(True,28,"10110000 11011000 01101100 0010") -}
19-
instance {-# OVERLAPPING #-} Flat [Char]
22+
instance {-# OVERLAPPING #-} Flat [Char] where
23+
encode [] = eFalse
24+
encode (x : xs) = eTrue <> encode x <> encode xs
25+
decode = do
26+
tag <- dBool
27+
if tag then (:) <$> decode <*> decode else pure []
28+
size [] n = 1 + n
29+
size (x : xs) n = 1 + size x (size xs n)

plutus-core/flat/src/PlutusCore/Flat/Tutorial.hs

Lines changed: 2 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,83 +7,29 @@ where
77
{-$setup
88
To (de)serialise a data type, make it an instance of the 'Flat.Class.Flat' class.
99
10-
There is <https://hackage.haskell.org/package/base/docs/GHC-Generics.html Generics> based support to automatically derive a correct instance.
11-
12-
Let’s see some code.
13-
14-
We need a couple of extensions:
15-
16-
>>> :set -XDeriveGeneric -XDeriveAnyClass
10+
Instances should define 'Flat.Class.encode', 'Flat.Class.decode' and 'Flat.Class.size' methods.
1711
1812
The @Flat@ top module:
1913
2014
>>> import PlutusCore.Flat
2115
2216
And, just for fun, a couple of functions to display an encoded value as a sequence of bits:
2317
24-
>>> import PlutusCore.Flat.Instances.Test (flatBits,allBits)
25-
26-
Define a few custom data types, deriving @Generic@ and @Flat@:
27-
28-
>>> data Result = Bad | Good deriving (Show,Generic,Flat)
29-
30-
>>> data Direction = North | South | Center | East | West deriving (Show,Generic,Flat)
31-
32-
>>> data List a = Nil | Cons a (List a) deriving (Show,Generic,Flat) -}
18+
>>> import PlutusCore.Flat.Instances.Test (flatBits,allBits) -}
3319

3420
{-$main
35-
Now we can encode a List of Directions using 'Flat.Run.flat':
36-
37-
>>> flat $ Cons North (Cons South Nil)
38-
"\149"
39-
40-
The result is a strict <https://hackage.haskell.org/package/bytestring/docs/Data-ByteString.html ByteString>.
41-
42-
And decode it back using 'Flat.Run.unflat':
43-
44-
>>> unflat . flat $ Cons North (Cons South Nil) :: Decoded (List Direction)
45-
Right (Cons North (Cons South Nil))
46-
47-
The result is a 'Flat.Decoded' value: 'Either' a 'Flat.DecodeException' or the actual value.
48-
4921
=== Optimal Bit-Encoding
5022
#optimal-bit-encoding#
5123
5224
A pecularity of Flat is that it uses an optimal bit-encoding rather than
5325
the usual byte-oriented one.
5426
55-
One bit is sufficient to encode a 'Result' or an empty 'List':
56-
57-
>>> flatBits Good
58-
"1"
59-
60-
>>> flatBits (Nil::List Direction)
61-
"0"
62-
63-
Two or three bits suffice for a 'Direction':
64-
65-
>>> flatBits South
66-
"01"
67-
68-
>>> flatBits West
69-
"111"
70-
7127
For the serialisation to work with byte-oriented devices or storage, we need to add some padding.
7228
7329
To do so, rather than encoding a plain value, 'Flat.Run.flat' encodes a 'Flat.Filler.PostAligned' value, that's to say a value followed by a 'Flat.Filler.Filler' that stretches till the next byte boundary.
7430
7531
In practice, the padding is a, possibly empty, sequence of 0s followed by a 1.
7632
77-
For example, this list encodes as 7 bits:
78-
79-
>>> flatBits $ Cons North (Cons South Nil)
80-
"1001010"
81-
82-
And, with the added padding of a final "1", will snugly fit in a single byte:
83-
84-
>>> allBits $ Cons North (Cons South Nil)
85-
"10010101"
86-
8733
But .. you don't need to worry about these details as byte-padding is automatically added by the function 'Flat.Run.flat' and removed by 'Flat.Run.unflat'.
8834
8935
=== Pre-defined Instances

0 commit comments

Comments
 (0)