-- SPDX-FileCopyrightText: 2021 Oxhead Alpha -- SPDX-License-Identifier: LicenseRef-MIT-OA {-# LANGUAGE OverloadedLists #-} module Test.Serialization.Typed.Michelson ( spec_Serialization ) where import Data.Aeson qualified as J import Data.Aeson.QQ (aesonQQ) import Data.Typeable (typeRep) import Test.Hspec (Spec, describe, it) import Morley.Michelson.Interpret (runUnpack) import Morley.Michelson.Interpret.Pack (packValue') import Morley.Michelson.Text import Morley.Michelson.Typed import Morley.Tezos.Address (Address(..), parseAddress) import Morley.Tezos.Core (Mutez, Timestamp, parseChainId, timestampFromSeconds) import Morley.Tezos.Crypto (Bls12381Fr, Bls12381G1, Bls12381G2, KeyHash(..), parseKeyHash, parsePublicKey, parseSignature) import Morley.Tezos.Crypto.BLS12381 qualified as BLS import Test.Cleveland.Instances () import Test.Serialization.Util spec_Serialization :: Spec spec_Serialization = do describe "serialization tests for comparable typed values" $ do bls12381Test keyHashTest timestampTest addressTest keyTest chainIdTest signatureTest natTest mutezTest describe "serialization tests for non-comparable values" $ do contractTest ticketTest setTest pairTest deserializeNegTest -- | Test method that checks that the conversion result is equal -- to the result of 'tezos-client hash data ... of type ...' toBinaryTestMethod :: ( HasCallStack , PackedValScope t ) => [TestMethod (Value t)] toBinaryTestMethod = toBinaryTestMethodImpl packValue' -- | Test method that checks that the conversion result is equal -- to the result of 'tezos-client convert data ... from michelson to binary' fromBinaryTestMethod :: forall t. ( HasCallStack , UnpackedValScope t ) => [TestMethod (Value t)] fromBinaryTestMethod = fromBinaryTestMethodImpl (runUnpack @t) -- | Test method that checks that the conversion result is equal -- to the result of 'tezos-client convert data ... from michelson to json' toJsonTestMethod :: ( HasCallStack , HasNoOp t ) => [TestMethod (Value t)] toJsonTestMethod = toJsonTestMethodImpl -- | Test method that checks that the conversion result is equal -- to the result of 'tezos-client convert data ... from json to michelson' fromJsonTestMethod :: forall t. ( HasCallStack , SingI t ) => [TestMethod (Value t)] fromJsonTestMethod = fromJsonTestMethodImpl @(Value t) allTestMethods :: ( UnpackedValScope t , HasCallStack ) => [TestMethod (Value t)] allTestMethods = mconcat [ toBinaryTestMethod , fromBinaryTestMethod , toJsonTestMethod , fromJsonTestMethod ] serializeSpecManual :: forall x (t :: T). HasCallStack => (x -> Value t) -> String -> [TestMethod (Value t)] -> [Example (x, (Text, J.Value))] -> Spec serializeSpecManual toVal' name testMethods suites = forM_ @[_] testMethods $ \(TestMethod mname method) -> describe mname $ describe name $ forM_ suites $ \(Example exampleName (x, (hex, json))) -> it exampleName $ method (toVal' x) hex json serializeSpec :: forall x (t :: T). ( IsoValue x, ToT x ~ t , UnpackedValScope t , Typeable t , HasCallStack ) => [Example (x, (Text, J.Value))] -> Spec serializeSpec = serializeSpecManual toVal typeName allTestMethods where typeName = show $ typeRep (Proxy @(Value t)) deserializeNegSpec :: forall t. UnpackedValScope t => String -> Text -> J.Value -> Spec deserializeNegSpec = deserializeNegSpecImpl @(Value t) runUnpack rightCombedPairSpec :: forall x. ( IsoValue x , UnpackedValScope (ToT x) ) => [Example (x, Text)] -> Spec rightCombedPairSpec suites = serializeSpecManual @x toVal name (toBinaryTestMethod <> fromBinaryTestMethod) (mapSuites suites) where name = "Right-combed pairs are packed without optimizations" mapSuites :: [Example (x, Text)] -> [Example (x, (Text, J.Value))] mapSuites = fmap (\(Example n (v, b)) -> Example n (v, (b, J.Null))) bls12381Test :: Spec bls12381Test = do serializeSpec @Bls12381Fr [ Example "Bls12381Fr" $ (1 :: Bls12381Fr) ~: "0x050a000000200100000000000000000000000000000000000000000000000000000000000000" ~: [aesonQQ| { "bytes": "0100000000000000000000000000000000000000000000000000000000000000" } |] ] serializeSpec @Bls12381G1 [ Example "Bls12381G1" $ BLS.g1One ~: "0x050a0000006017f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1" ~: [aesonQQ| { "bytes": "17f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb08b3f481e3aaa0f1a09e30ed741d8ae4fcf5e095d5d00af600db18cb2c04b3edd03cc744a2888ae40caa232946c5e7e1" } |] ] serializeSpec @Bls12381G2 [ Example "Bls12381G2" $ BLS.g2One ~: "0x050a000000c013e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801" ~: [aesonQQ| { "bytes": "13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801" } |] ] mutezTest :: Spec mutezTest = serializeSpec @Mutez [ Example "0" $ 0 ~: "050000" ~: [aesonQQ| { "int": "0" } |] , Example "1" $ 1 ~: "050001" ~: [aesonQQ| { "int": "1" } |] , Example "63" $ 63 ~: "05003f" ~: [aesonQQ| { "int": "63" } |] , Example "64" $ 64 ~: "05008001" ~: [aesonQQ| { "int": "64" } |] , Example "65" $ 65 ~: "05008101" ~: [aesonQQ| { "int": "65" } |] , Example "127" $ 127 ~: "0500bf01" ~: [aesonQQ| { "int": "127" } |] , Example "128" $ 128 ~: "05008002" ~: [aesonQQ| { "int": "128" } |] , Example "129" $ 129 ~: "05008102" ~: [aesonQQ| { "int": "129" } |] , Example "191" $ 191 ~: "0500bf02" ~: [aesonQQ| { "int": "191" } |] , Example "192" $ 192 ~: "05008003" ~: [aesonQQ| { "int": "192" } |] , Example "193" $ 193 ~: "05008103" ~: [aesonQQ| { "int": "193" } |] ] natTest :: Spec natTest = serializeSpec @Natural [ Example "0" $ 0 ~: "050000" ~: [aesonQQ| { "int": "0" }|] , Example "1" $ 1 ~: "050001" ~: [aesonQQ| { "int": "1" }|] , Example "63" $ 63 ~: "05003f" ~: [aesonQQ| { "int": "63" }|] , Example "64" $ 64 ~: "05008001" ~: [aesonQQ| { "int": "64" }|] , Example "65" $ 65 ~: "05008101" ~: [aesonQQ| { "int": "65" }|] , Example "127" $ 127 ~: "0500bf01" ~: [aesonQQ| { "int": "127" }|] , Example "128" $ 128 ~: "05008002" ~: [aesonQQ| { "int": "128" }|] , Example "129" $ 129 ~: "05008102" ~: [aesonQQ| { "int": "129" }|] , Example "191" $ 191 ~: "0500bf02" ~: [aesonQQ| { "int": "191" }|] , Example "192" $ 192 ~: "05008003" ~: [aesonQQ| { "int": "192" }|] , Example "193" $ 193 ~: "05008103" ~: [aesonQQ| { "int": "193" }|] ] setTest :: Spec setTest = serializeSpec @(Set Integer) [ Example "Empty set" $ [] ~: "050200000000" ~: [aesonQQ| [ ] |] , Example "Singleton set" $ [1] ~: "0502000000020001" ~: [aesonQQ| [ { "int": "1" } ] |] , Example "Set with many elements" $ [0, 10, 24, 35, 100, 1000] ~: "05020000000e0000000a0018002300a40100a80f" ~: [aesonQQ| [ { "int": "0" }, { "int": "10" }, { "int": "24" }, { "int": "35" }, { "int": "100" }, { "int": "1000" } ] |] ] keyHashTest :: Spec keyHashTest = do serializeSpec @KeyHash [ Example "KeyHash" $ unsafe (parseKeyHash "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") ~: "050a000000150002298c03ed7d454a101eb7022bc95f7e5f41ac78" ~: [aesonQQ| { "bytes": "0002298c03ed7d454a101eb7022bc95f7e5f41ac78" } |] ] timestampTest :: Spec timestampTest = do serializeSpec @Timestamp $ examples $ convertTimestamps [ 205027200 ~: "050080dec3c301" ~: [aesonQQ| { "int": "205027200" } |] , 1552564995 ~: "0500838cd2c80b" ~: [aesonQQ| { "int": "1552564995" } |] ] where convertTimestamps = map . first $ timestampFromSeconds addressTest :: Spec addressTest = do serializeSpec @Address $ examples $ parseAddrs [ "tz1PYgf9fBGLXvwx8ag8sdwjLJzmyGdNiswM" ~: "050a0000001600002addb327dbca405f07aeef318bba0ec2f714a755" ~: [aesonQQ| { "bytes": "00002addb327dbca405f07aeef318bba0ec2f714a755" } |] , "tz1Z1nn9Y7vzyvtf6rAYMPhPNGqMJXw88xGH" ~: "050a00000016000092b72c0fa1064331a641131f572e7f2abb9a890b" ~: [aesonQQ| { "bytes": "000092b72c0fa1064331a641131f572e7f2abb9a890b" } |] , "tz2EfqCbLmpfv7mNiLcMmhxAwdgHtPTcwR4W" ~: "050a00000016000145b5e7d31bf6612e61ebfa7a6d929ce7800a55a4" ~: [aesonQQ| { "bytes": "000145b5e7d31bf6612e61ebfa7a6d929ce7800a55a4" } |] , "tz3UoffC7FG7zfpmvmjUmUeAaHvzdcUvAj6r" ~: "050a0000001600025cfa532f50de3e12befc0ad21603835dd7698d35" ~: [aesonQQ| { "bytes": "00025cfa532f50de3e12befc0ad21603835dd7698d35" } |] , "KT1AEseqMV6fk2vtvQCVyA7ZCaxv7cpxtXdB" ~: "050a0000001601122d038abd69be91b4b6803f2f098a088e259e7200" ~: [aesonQQ| { "bytes": "01122d038abd69be91b4b6803f2f098a088e259e7200" } |] , "KT1NSrmSJrSueZiWPKrcAUScYr6k2BkUVALr" ~: "050a00000016019812c669d9e8ff1a61bf8c57e33b955f074d832600" ~: [aesonQQ| { "bytes": "019812c669d9e8ff1a61bf8c57e33b955f074d832600" } |] ] where parseAddrs = map $ first (unsafe . parseAddress) keyTest :: Spec keyTest = serializeSpecManual VKey "key" allTestMethods $ examples [ item "edpkupH22qrz1sNQt5HSvWfRJFfyJ9dhNbZLptE6GR4JbMoBcACZZH" "050a00000021009a85e0f3f47852869ae667adc3b03a20fa9f324d046174dff6834e7d1fab0e8d" [aesonQQ| { "bytes": "009a85e0f3f47852869ae667adc3b03a20fa9f324d046174dff6834e7d1fab0e8d" } |] , item "edpkuwTWKgQNnhR5v17H2DYHbfcxYepARyrPGbf1tbMoGQAj8Ljr3V" "050a0000002100aad3f16293766169f7db278c5e0e9db4fb82ffe1cbcc35258059617dc0fec082" [aesonQQ| { "bytes": "00aad3f16293766169f7db278c5e0e9db4fb82ffe1cbcc35258059617dc0fec082" } |] , item "sppk7cdA7Afj8MvuBFrP6KsTLfbM5DtH9GwYaRZwCf5tBVCz6UKGQFR" "050a000000220103b524d0184276467c848ac13557fb0ff8bec5907960f72683f22af430503edfc1" [aesonQQ| { "bytes": "0103b524d0184276467c848ac13557fb0ff8bec5907960f72683f22af430503edfc1" } |] , item "sppk7Ze7NMs6EHF2uB8qq8GrEgJvE9PWYkUijN3LcesafzQuGyniHBD" "050a0000002201022c380cd1ff286a0a1a7c3aad6e891d237fa82e2a7cdeec08ccb55e90fdef995f" [aesonQQ| { "bytes": "01022c380cd1ff286a0a1a7c3aad6e891d237fa82e2a7cdeec08ccb55e90fdef995f" } |] , item "p2pk67K1dwkDFPB63RZU5H3SoMCvmJdKZDZszc7U4FiGKN2YypKdDCB" "050a00000022020368afbb09255d849813712108a4144237dc1fdd5bb74e68335f4c68c12c1e5723" [aesonQQ| { "bytes": "020368afbb09255d849813712108a4144237dc1fdd5bb74e68335f4c68c12c1e5723" } |] , item "p2pk68C6tJr7pNLvgBH63K3hBVoztCPCA36zcWhXFUGywQJTjYBfpxk" "050a000000220203dcb1916c475902f2b1083212e1b4e6f8ce1531710218c7d34340439f47040e7c" [aesonQQ| { "bytes": "0203dcb1916c475902f2b1083212e1b4e6f8ce1531710218c7d34340439f47040e7c" } |] ] where item keyStr keyBytes json = ( unsafe $ parsePublicKey keyStr, (keyBytes, json) ) chainIdTest :: Spec chainIdTest = serializeSpecManual VChainId "chain_id" allTestMethods $ examples [ unsafe (parseChainId "NetXUdfLh6Gm88t") ~: "0x050a00000004458aa837" ~: [aesonQQ| { "bytes": "458aa837" } |] ] signatureTest :: Spec signatureTest = serializeSpecManual VSignature "signature" allTestMethods $ examples [ item "edsigtrs8bK7vNfiR4Kd9dWasVa1bAWaQSu2ipnmLGZuwQa8\ \ktCEMYVKqbWsbJ7zTS8dgYT9tiSUKorWCPFHosL5zPsiDwBQ6vb" "050a0000004091ac1e7fd668854fc7a40feec4034e42c06c068cce10622c607fda232d\ \b34c8cf5d8da83098dd891cd4cb4299b3fa0352ae323ad99b24541e54b91888fdc8201" [aesonQQ| { "bytes" : "91ac1e7fd668854fc7a40feec4034e42c06c068cce10622c607fda232db34c8cf5d8da83098dd891cd4cb4299b3fa0352ae323ad99b24541e54b91888fdc8201"} |] , item "spsig1Ng2bs4PXCbjaFGuojk9K5Pt3CkfbUZyHLLrBxHSmTq\ \rUUxQggi4yJBit3Ljqnqr61UpdTewTLiu4schSCfZvaRwu412oZ" "0x050a0000004080e4e72ffecf72953789625b1125e9f45f432c14e53a01ec68a1e1b77\ \d60cfe96a97443733ba0f7f42db3a56d7a433df2b4fc0035c05ab92d062f33c5bab0244" [aesonQQ| { "bytes": "80e4e72ffecf72953789625b1125e9f45f432c14e53a01ec68a1e1b77d60cfe96a97443733ba0f7f42db3a56d7a433df2b4fc0035c05ab92d062f33c5bab0244" } |] , item "p2sigRmXDp38VNVaEQH28LYukfLPn8QB5hPEberhvQrrUpRs\ \cDZJrrApbRh2u46PTVTwKXjxTLKNN9dyLhPQU6U6jWPGxe4d9v" "0x050a00000040222222222222222222222222222222222222222222222222222222222\ \22222222222222222222222222222222222222222222222222222222222222222222222" [aesonQQ| { "bytes": "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222" } |] , item "sigrZRt6CTBNtRzjMFQYSZhUm1QcDg5gopVgiRTLMQsgikeR\ \LmrmsA5vmFqjrnBhofzqvKtc9k5VhTzCMCio5epRvu9no73S" "0x050a00000040da632d7f267673fab5a40562778a6890b6ada9665d53d7ff318e3399e\ \032b3986588dadcf3bf3b549592f7b8ea1365273fbef5f4883c3430ed32e8ae24017be1" [aesonQQ| { "bytes": "da632d7f267673fab5a40562778a6890b6ada9665d53d7ff318e3399e032b3986588dadcf3bf3b549592f7b8ea1365273fbef5f4883c3430ed32e8ae24017be1" } |] ] where item sigStr sigBytes json = (unsafe $ parseSignature sigStr, (sigBytes, json)) contractTest :: Spec contractTest = do serializeSpecManual (addressToVContract @'TUnit) "simple contract" (toBinaryTestMethod <> toJsonTestMethod) $ examples $ parseAddrs $ [ "tz1PYgf9fBGLXvwx8ag8sdwjLJzmyGdNiswM" ~: "050a0000001600002addb327dbca405f07aeef318bba0ec2f714a755" ~: [aesonQQ| { "bytes": "00002addb327dbca405f07aeef318bba0ec2f714a755" } |] , "tz1Z1nn9Y7vzyvtf6rAYMPhPNGqMJXw88xGH" ~: "050a00000016000092b72c0fa1064331a641131f572e7f2abb9a890b" ~: [aesonQQ| { "bytes": "000092b72c0fa1064331a641131f572e7f2abb9a890b" } |] ] serializeSpecManual (addressToVContract @'TInt) "non-unit contract" (toBinaryTestMethod <> toJsonTestMethod) $ examples $ parseAddrs $ [ "KT1AEseqMV6fk2vtvQCVyA7ZCaxv7cpxtXdB" ~: "0x050a0000001601122d038abd69be91b4b6803f2f098a088e259e7200" ~: [aesonQQ| { "bytes": "01122d038abd69be91b4b6803f2f098a088e259e7200" } |] ] serializeSpecManual (mkEpVContract @'TInt) "contract with entrypoint" toBinaryTestMethod $ examples $ parseEpAddrs [ "KT1AEseqMV6fk2vtvQCVyA7ZCaxv7cpxtXdB%" ~: "0x050a0000001601122d038abd69be91b4b6803f2f098a088e259e7200" ~: [aesonQQ| { "bytes": "01122d038abd69be91b4b6803f2f098a088e259e7200" } |] , "KT1AEseqMV6fk2vtvQCVyA7ZCaxv7cpxtXdB%abacaba" ~: "0x050a0000001d01122d038abd69be91b4b6803f2f098a088e259e720061626163616261" ~: [aesonQQ| { "bytes": "01122d038abd69be91b4b6803f2f098a088e259e720061626163616261" } |] , "tz1Z1nn9Y7vzyvtf6rAYMPhPNGqMJXw88xGH%a" ~: "0x050a00000017000092b72c0fa1064331a641131f572e7f2abb9a890b61" ~: [aesonQQ| { "bytes": "000092b72c0fa1064331a641131f572e7f2abb9a890b61" } |] ] where parseAddrs = map $ first (unsafe . parseAddress) parseEpAddrs = map $ first (unsafe . parseEpAddress) mkEpVContract :: forall p. ParameterScope p => EpAddress -> Value ('TContract p) mkEpVContract EpAddress{..} = VContract eaAddress (SomeEpc unsafeEpcCallRoot{ epcName = eaEntrypoint }) ticketTest :: Spec ticketTest = do serializeSpecManual toVal "simple ticket" toJsonTestMethod $ examples $ [ Ticket { tTicketer = unsafe $ parseAddress "tz1Zk7f1Ywii8QS11L63YqqtcneB64QWBas9" , tData = [mt|a|] , tAmount = 10 } ~: "" -- tickets are not packable, not testing the binary representation ~: [aesonQQ| [ { "bytes": "00009ab866724d89b5077cd943dfd00547e95c963d75" }, { "string": "a" }, { "int": "10" } ] |] ] deserializeNegTest :: Spec deserializeNegTest = do describe "Bad entries order" $ do deserializeNegSpec @('TSet 'TInt) "Unordered set elements" "0x050200000006000300020001" -- { 3; 2; 1 } [aesonQQ| [ { "int": "3" }, { "int": "2" }, { "int": "1" } ] |] deserializeNegSpec @('TMap 'TInt $ 'TInt) "Unordered map elements" "0x05020000000c070400020006070400010007" -- { Elt 2 6; Elt 1 7 } [aesonQQ| [ { "prim": "Elt", "args": [ { "int": "2" }, { "int": "6" } ] }, { "prim": "Elt", "args": [ { "int": "1" }, { "int": "7" } ] } ] |] describe "Type check failures" $ do deserializeNegSpec @('TUnit) "Value type mismatch" "0x050008" -- 8 [aesonQQ| { "int": "8" } |] deserializeNegSpec @('TLambda 'TUnit 'TKey) "Lambda type mismatch" "0x050200000000" -- {} [aesonQQ| [] |] deserializeNegSpec @('TLambda 'TUnit 'TKey) "Lambda too large output stack size" "0x0502000000060743035b0005" -- {PUSH int 5} [aesonQQ| [ { "prim": "PUSH", "args": [ { "prim": "int" }, { "int": "5" } ] } ] |] deserializeNegSpec @('TLambda 'TUnit 'TKey) "Lagmbda empty output stack size" "0x0502000000020320" -- {DROP} [aesonQQ| [ { "prim": "DROP" } ] |] pairTest :: Spec pairTest = do rightCombedPairSpec @(Integer, (Integer, Integer)) $ examples [ (1, (1, 2)) -- > tezos-client hash data 'Pair 1 (Pair 1 2)' of type 'pair int int int' ~: "0507070001070700010002" ] rightCombedPairSpec @((Integer, (MText, Integer)), ((Integer, (MText, Integer)), (Integer, (MText, Integer)))) $ examples [ ((1, ("a", 2)), ((3, ("b", 4)), (5, ("c", 6)))) -- > tezos-client hash data '(Pair (Pair 1 (Pair "a" 2)) (Pair (Pair 3 (Pair "b" 4)) (Pair 5 (Pair "c" 6))))' -- of type 'pair (pair int string int) (pair int string int) int string int' ~: "0507070707000107070100000001610002070707070003070701000000016200040707000507070100000001630006" ] rightCombedPairSpec @(MText, Integer) $ examples [ ("Good Night!", 5) -- > tezos-client hash data 'Pair "Good Night!" 5' of type 'pair string int' ~: "050707010000000b476f6f64204e69676874210005" ] rightCombedPairSpec @(Integer, (Integer, (MText, Integer))) $ examples [ (1, (2, ("Some text", 3))) -- > tezos-client hash data 'Pair 1 (Pair 2 (Pair "Some text" 3))' of type 'pair int int string int' ~: "05070700010707000207070100000009536f6d6520746578740003" ]