From c0c18615028f89f951d2869a190b9bb8b170d695 Mon Sep 17 00:00:00 2001 From: jmcardon Date: Thu, 31 Oct 2024 10:56:21 -0400 Subject: [PATCH] Tables as values in pact --- gasmodel/Pact/Core/GasModel/Utils.hs | 1 - .../Pact/Core/Test/JSONRoundtripTests.hs | 3 + pact-tests/Pact/Core/Test/LexerParserTests.hs | 44 ++++++- .../legacy-serial-tests/coin-v5/coin-v5.repl | 6 +- .../marmalade-v2/marmalade.repl | 2 +- pact-tests/pact-tests/base64.repl | 10 +- pact-tests/pact-tests/caps.repl | 6 +- pact-tests/pact-tests/coin-v1.repl | 6 +- pact-tests/pact-tests/coin-v5.repl | 6 +- pact-tests/pact-tests/db.repl | 80 ++++++++++--- pact-tests/pact-tests/defpact.repl | 2 +- pact-tests/pact-tests/gov.repl | 2 +- pact-tests/pact-tests/keysets.repl | 14 +-- .../pact-tests/marmalade/pact/marmalade.repl | 2 +- pact-tests/pact-tests/nested-defpacts.repl | 10 +- pact-tests/pact-tests/strings.repl | 6 +- pact/Pact/Core/Errors.hs | 9 +- pact/Pact/Core/IR/Eval/CEK/CoreBuiltin.hs | 7 +- pact/Pact/Core/IR/Eval/CEK/Types.hs | 8 +- pact/Pact/Core/IR/Eval/CEK/Utils.hs | 2 +- pact/Pact/Core/IR/Eval/Direct/CoreBuiltin.hs | 8 +- pact/Pact/Core/IR/Eval/Direct/ReplBuiltin.hs | 5 +- pact/Pact/Core/IR/Eval/Direct/Types.hs | 10 +- pact/Pact/Core/IR/Eval/Runtime/Types.hs | 14 +-- pact/Pact/Core/IR/Eval/Runtime/Utils.hs | 31 +++++ pact/Pact/Core/IR/ModuleHashing.hs | 3 + pact/Pact/Core/Names.hs | 17 +++ pact/Pact/Core/PactValue.hs | 20 ++++ pact/Pact/Core/Persistence/SQLite.hs | 11 +- pact/Pact/Core/Repl.hs | 1 - pact/Pact/Core/Repl/Runtime/ReplBuiltin.hs | 8 +- pact/Pact/Core/Serialise/CBOR_V1.hs | 20 ++++ pact/Pact/Core/SizeOf.hs | 2 + pact/Pact/Core/StableEncoding.hs | 108 ++++++++++++++++++ pact/Pact/Core/Type.hs | 6 +- test-utils/Pact/Core/Gen.hs | 18 ++- 36 files changed, 402 insertions(+), 106 deletions(-) diff --git a/gasmodel/Pact/Core/GasModel/Utils.hs b/gasmodel/Pact/Core/GasModel/Utils.hs index 3b95b35ea..824033f59 100644 --- a/gasmodel/Pact/Core/GasModel/Utils.hs +++ b/gasmodel/Pact/Core/GasModel/Utils.hs @@ -24,7 +24,6 @@ import Pact.Core.Literal import Pact.Core.Type import Pact.Core.Capabilities import Pact.Core.IR.Desugar -import Pact.Core.IR.Eval.Runtime import Pact.Core.IR.Eval.CEK.CoreBuiltin import Pact.Core.PactValue import Pact.Core.IR.Term diff --git a/pact-tests/Pact/Core/Test/JSONRoundtripTests.hs b/pact-tests/Pact/Core/Test/JSONRoundtripTests.hs index 0e903a050..fa2ca287b 100644 --- a/pact-tests/Pact/Core/Test/JSONRoundtripTests.hs +++ b/pact-tests/Pact/Core/Test/JSONRoundtripTests.hs @@ -62,7 +62,10 @@ tests = testGroup "JSON Roundtrips" $ stableEncodings ++ jsonRoundtrips , StableEncodingCase defPactExecGen , StableEncodingCase namespaceGen , StableEncodingCase pactEventGen + , StableEncodingCase tableNameGen + , StableEncodingCase tableValueGen , StableEncodingCase spanInfoGen + , StableEncodingCase typeGen ] jsonRoundtrips = fmap testJSONRoundtrip $ [ EncodingCase signerGen diff --git a/pact-tests/Pact/Core/Test/LexerParserTests.hs b/pact-tests/Pact/Core/Test/LexerParserTests.hs index 556bc81a7..e25061275 100644 --- a/pact-tests/Pact/Core/Test/LexerParserTests.hs +++ b/pact-tests/Pact/Core/Test/LexerParserTests.hs @@ -1,3 +1,5 @@ +{-# LANGUAGE ExistentialQuantification #-} +{-# LANGUAGE PatternSynonyms #-} module Pact.Core.Test.LexerParserTests where import Test.Tasty @@ -12,10 +14,8 @@ import qualified Hedgehog.Gen as Gen import qualified Hedgehog.Range as Range import qualified Data.Text as T -import Pact.Core.Gen - (moduleHashGen, parsedTyNameGen, parsedNameGen - , moduleNameGen, identGen - , decimalGen) +import Pact.Core.Gen hiding + (typeGen, argGen, defunGen, importGen, defGen, ifDefGen, stepGen) import qualified Pact.Core.Syntax.Lexer as Lisp import qualified Pact.Core.Syntax.LexUtils as Lisp import Pact.Core.Syntax.ParseTree as Lisp @@ -25,6 +25,8 @@ import Pact.Core.Literal import Pact.Core.Pretty import Pact.Core.Hash import Pact.Core.Info +import Data.Typeable +import Pact.Core.Names showPretty :: Pretty a => a -> T.Text showPretty = T.pack . show . pretty @@ -382,6 +384,38 @@ parserRoundtrip = property $ do res <- evalEither $ Lisp.parseProgram =<< Lisp.lexer (renderCompactText ptok) [ptok] === (void <$> res) +data RenderTest = + forall a. (Eq a, Show a, Typeable a) => MkRenderTest + { _renderGen :: Gen a + , _renderText :: a -> Text + , _renderParse :: Text -> Maybe a + , _renderProxy :: Proxy a + } + +renderTest :: (Eq a, Typeable a, Show a) => Gen a -> (a -> Text) -> (Text -> Maybe a) -> RenderTest +renderTest gen render parse = MkRenderTest gen render parse Proxy + +parseExprMaybe :: Text -> Maybe (Lisp.Expr ()) +parseExprMaybe = fmap void . either (const Nothing) Just . (Lisp.lexer >=> Lisp.parseExpr) + +runRenderTest :: RenderTest -> TestTree +runRenderTest (MkRenderTest gen render parse prx) = + testProperty ("Render/parse test for: " <> show (typeRep prx)) $ withTests (1000 :: TestLimit) $ + property $ do + v <- forAll gen + parse (render v) === Just v + +renderTests :: [RenderTest] +renderTests = + -- Render test litmus using expr + [ renderTest exprGen renderCompactText parseExprMaybe + , renderTest fullyQualifiedNameGen renderFullyQualName parseFullyQualifiedName + , renderTest moduleNameGen renderModuleName parseModuleName + , renderTest tableNameGen jsonSafeRenderTableName parseJsonSafeTableName + , renderTest parsedTyNameGen renderParsedTyName parseParsedTyName + , renderTest hashedModuleNameGen renderHashedModuleName parseHashedModuleName + ] + -- | Here we will test that slicing from generated source -- will produce accurate source locations -- @@ -400,7 +434,7 @@ sliceRoundtrip = property $ do tests :: TestTree tests = testGroup "Lexer and Parser Tests" [ testProperty "lexer roundtrip" lexerRoundtrip - , testProperty "parser roundtrips for exprs" $ withTests (1000 :: TestLimit) exprParserRoundtrip , testProperty "parser roundtrips for all toplevels" $ withTests (1000 :: TestLimit) parserRoundtrip , testProperty "source slices correspond with their source code locations" $ withTests (1000 :: TestLimit) sliceRoundtrip + , testGroup "Render/parse tests" $ runRenderTest <$> renderTests ] diff --git a/pact-tests/legacy-serial-tests/coin-v5/coin-v5.repl b/pact-tests/legacy-serial-tests/coin-v5/coin-v5.repl index f3de8fe83..575097afa 100644 --- a/pact-tests/legacy-serial-tests/coin-v5/coin-v5.repl +++ b/pact-tests/legacy-serial-tests/coin-v5/coin-v5.repl @@ -544,7 +544,7 @@ ; using Pact40 error messages (expect-failure "create side of cross-chain transfer fails yield on wrong chain" - "yield provenance" + "Yield provenance does not match" (continue-pact 1 false (hash "burn-create") { "create-account": 'doug , "create-account-guard": (read-keyset 'doug) @@ -562,7 +562,7 @@ ; double spends are disallowed by construction (expect-failure "cross-chain transfer pact prevents double spends" - "pact completed" + "Requested defpact already completed" (continue-pact 1 false (hash "burn-create"))) ; account guard rotation @@ -768,7 +768,7 @@ (expect-failure "account creation fails when no keyset corresponds with keyset ref" - "no such keyset defined" + "Cannot find keyset in database: 'brandon" (create-allocation-account "brandon" (time "2020-10-31T00:00:00Z") "brandon" 200000.0)) ; successful keyset refs require defined keyset diff --git a/pact-tests/legacy-serial-tests/marmalade-v2/marmalade.repl b/pact-tests/legacy-serial-tests/marmalade-v2/marmalade.repl index 74d755a10..ce10690f2 100644 --- a/pact-tests/legacy-serial-tests/marmalade-v2/marmalade.repl +++ b/pact-tests/legacy-serial-tests/marmalade-v2/marmalade.repl @@ -274,7 +274,7 @@ }) (expect-failure "offer fails when called directly" - "pact-id: not in pact execution" + "Attempted to fetch defpact data, but currently not within defpact execution" (offer (read-string "token-id") (read-string "account") 1.0) ) diff --git a/pact-tests/pact-tests/base64.repl b/pact-tests/pact-tests/base64.repl index e3a20ba19..85b7b4c7a 100644 --- a/pact-tests/pact-tests/base64.repl +++ b/pact-tests/pact-tests/base64.repl @@ -22,22 +22,22 @@ (expect-failure "base64 decoding fails on non base64-encoded input" - "Could not decode string" + "Decoding error: invalid b64 encoding" (base64-decode "aGVsbG8gd29ybGQh%")) (expect-failure "base64 decoding fails on garbage input 1" - "Could not decode string" + "Decoding error: invalid b64 encoding" (base64-decode "aaa")) (expect-failure "base64 decoding fails on garbage input 2" - "Could not decode string" + "Decoding error: invalid b64 encoding" (base64-decode "asdflk")) (expect-failure "base64 decoding fails on garbage input 3" - "Could not decode string" + "Decoding error: invalid b64 encoding" (base64-decode "!@#$%&")) ; Todo: unicode escape codes? @@ -53,5 +53,5 @@ (expect-failure "base64 decoding fails on non-canonical encodings" - "Could not base64-decode string" + "Decoding error: invalid b64 encoding" (base64-decode "ZE==")) diff --git a/pact-tests/pact-tests/caps.repl b/pact-tests/pact-tests/caps.repl index e11782592..4aa8b5cf7 100644 --- a/pact-tests/pact-tests/caps.repl +++ b/pact-tests/pact-tests/caps.repl @@ -752,12 +752,12 @@ (expect-failure "emit-event: must be in module" - "Unable to resolve current calling module" + "Emitted event does not match module: events" (emit-event (EV))) (expect-failure "emit-event: non-event" - "must be managed or event defcap" + "Invalid event capability events.NON.{Pl17Zu2iMddv942W_ChGFDyPWN4zFVjtVvI8HP4_0AY}" (emit-non-event)) (expect @@ -993,7 +993,7 @@ ) (defun emit-a(a:integer) (emit-event (A_EVENT a a)))) - + (use B) (call-a) diff --git a/pact-tests/pact-tests/coin-v1.repl b/pact-tests/pact-tests/coin-v1.repl index 8d1e78023..b91efb6cf 100644 --- a/pact-tests/pact-tests/coin-v1.repl +++ b/pact-tests/pact-tests/coin-v1.repl @@ -526,7 +526,7 @@ ; (env-exec-config ['DisablePact40, 'DisableInlineMemCheck, 'DisablePact43, "DisablePact44", "DisablePact45"]) (expect-failure "create side of cross-chain transfer fails yield on wrong chain" - "does not match (chain" + "Yield provenance does not match" (continue-pact 1 false (hash "burn-create") { "create-account": 'doug , "create-account-guard": (read-keyset 'doug) @@ -548,7 +548,7 @@ ; double spends are disallowed by construction (expect-failure "cross-chain transfer pact prevents double spends" - "pact completed" + "Requested defpact already completed" (continue-pact 1 false (hash "burn-create"))) ; account guard rotation @@ -723,7 +723,7 @@ (expect-failure "account creation fails when no keyset corresponds with keyset ref" - "no such keyset defined" + "Cannot find keyset in database: 'brandon" (create-allocation-account "brandon" (time "2020-10-31T00:00:00Z") "brandon" 200000.0)) ; successful keyset refs require defined keyset diff --git a/pact-tests/pact-tests/coin-v5.repl b/pact-tests/pact-tests/coin-v5.repl index 1f14db5e6..7cffefa1e 100644 --- a/pact-tests/pact-tests/coin-v5.repl +++ b/pact-tests/pact-tests/coin-v5.repl @@ -555,7 +555,7 @@ ; using Pact40 error messages (expect-failure "create side of cross-chain transfer fails yield on wrong chain" - "yield provenance" + "Yield provenance does not match" (continue-pact 1 false (hash "burn-create") { "create-account": 'doug , "create-account-guard": (read-keyset 'doug) @@ -573,7 +573,7 @@ ; double spends are disallowed by construction (expect-failure "cross-chain transfer pact prevents double spends" - "pact completed" + "Requested defpact already completed: defpact id:UT9N17TRzn2FmWlZdT_S7y-AC1A_yOugNbfRSz4VFPE" (continue-pact 1 false (hash "burn-create"))) ; account guard rotation @@ -779,7 +779,7 @@ (expect-failure "account creation fails when no keyset corresponds with keyset ref" - "no such keyset defined" + "Cannot find keyset in database: 'brandon" (create-allocation-account "brandon" (time "2020-10-31T00:00:00Z") "brandon" 200000.0)) ; successful keyset refs require defined keyset diff --git a/pact-tests/pact-tests/db.repl b/pact-tests/pact-tests/db.repl index 38503d78f..590fdfa46 100644 --- a/pact-tests/pact-tests/db.repl +++ b/pact-tests/pact-tests/db.repl @@ -64,35 +64,46 @@ (env-exec-config []) ;; clear disable history flag except pre-4.2.0 (begin-tx) (use dbtest) +(expect-failure "module admin protected by admin-key" "Keyset failure (keys-all): [admin...]" (acquire-module-admin dbtest)) (expect-failure - "write protected by admin key" "Keyset failure" + "write protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (write persons "foo" ROW_A)) (expect-failure - "update protected by admin key" "Keyset failure" + "update protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (update persons "foo" ROW_A)) (expect-failure - "insert protected by admin key" "Keyset failure" + "insert protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (insert persons "foo" ROW_A)) (expect-failure - "keys protected by admin key" "Keyset failure" + "keys protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (keys persons)) (expect-failure - "read protected by admin key" "Keyset failure" + "read protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (read persons ID_A)) (expect-failure - "with-read protected by admin key" "Keyset failure" + "with-read protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (with-read persons ID_A { 'name:= name } name)) (expect-failure - "with-default-read protected by admin key" "Keyset failure" + "with-default-read protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (with-default-read persons ID_A { 'name: "stu" } { 'name:= name } name)) (expect-failure - "select protected by admin key" "Keyset failure" + "select protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (select persons (constantly true))) (expect-failure - "keys protected by admin key" "Keyset failure" + "keys protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (keys persons)) (expect-failure - "create-table protected by admin key" "Keyset failure" + "create-table protected by admin key" + "Module admin necessary for operation but has not been acquired:dbtest" (create-table persons2)) ;; just making sure this doesn't blow up, output is still TBD on better Term output in general @@ -103,13 +114,16 @@ (env-exec-config ["AllowReadInLocal"]) (use dbtest) (expect-failure - "write protected by admin key in local" "Keyset failure" + "write protected by admin key in local" + "Module admin necessary for operation but has not been acquired:dbtest" (write persons "foo" ROW_A)) (expect-failure - "update protected by admin key in local" "Keyset failure" + "update protected by admin key in local" + "Module admin necessary for operation but has not been acquired:dbtest" (update persons "foo" ROW_A)) (expect-failure - "insert protected by admin key in local" "Keyset failure" + "insert protected by admin key in local" + "Module admin necessary for operation but has not been acquired:dbtest" (insert persons "foo" ROW_A)) (expect "keys allowed in local" [ID_A] @@ -130,7 +144,8 @@ "keys allowed in local" [ID_A] (keys persons)) (expect-failure - "create-table protected by admin key in local" "Keyset failure" + "create-table protected by admin key in local" + "Module admin necessary for operation but has not been acquired:dbtest" (create-table persons2)) ;; test nested commits @@ -287,3 +302,40 @@ (expect "selecting with fields on empty fields" [{"a": 2} {"a": 3}] (select tbl ["a"] (constantly true))) (expect "selecting with fields with no fields results in empty objects" [{} {}] (select tbl [] (constantly true))) (commit-tx) + +; DB regression +(begin-tx) +(module table-abstraction g (defcap g () true) + (defschema sc a:integer b:integer) + (defschema sc2 a:integer b:integer c:string) + (defschema sc3 a:integer b:integer) + (deftable tbl:{sc}) + (deftable tbl2:{sc2}) + (deftable tbl3:{sc3}) + (deftable tbl4:{sc}) + + (defun write-to-table:unit (tv:table{sc} k:string v:object{sc}) + (write tv k v) + () + ) + + (defun read-from-tables:[object{sc}] (tvs:[table{sc}] k:string) + (map (lambda (tv) (read tv k)) tvs) + ) + +) + +(create-table tbl) +(create-table tbl2) +(create-table tbl3) +(create-table tbl4) + +(expect "can write to table tbl" () (write-to-table tbl "jose" {"a": 1, "b": 2})) +(expect-failure "Cannot write to tbl2: different schema" "Runtime typecheck failure" (write-to-table tbl2 "jose" {"a": 2, "b": 2})) +(expect "can write to table tbl4: same schema" () (write-to-table tbl4 "jose" {"a": 3, "b": 2})) +(expect "can write to table tbl3: different schema, same structure" () (write-to-table tbl3 "jose" {"a": 4, "b": 2})) + +(expect "can read from tables with the same schema" [{"a": 1,"b": 2} {"a": 3,"b": 2} {"a": 4,"b": 2}] (read-from-tables [tbl, tbl4, tbl3] "jose")) +(expect-failure "Cannot read from tables with different schema" "Runtime typecheck failure" (read-from-tables [tbl, tbl2] "jose")) + +(commit-tx) diff --git a/pact-tests/pact-tests/defpact.repl b/pact-tests/pact-tests/defpact.repl index 284f238a7..4f04861ab 100644 --- a/pact-tests/pact-tests/defpact.repl +++ b/pact-tests/pact-tests/defpact.repl @@ -19,7 +19,7 @@ (use defcap-module) (expect "should return 'step-0'" "step-0" (defpact-single-step)) -(expect-failure "should not executing step again" "not-step-0" (defpact-single-step)) +(expect-failure "should not executing step again" "defpact execution context already in the environment" (defpact-single-step)) (commit-tx) diff --git a/pact-tests/pact-tests/gov.repl b/pact-tests/pact-tests/gov.repl index 76ba169a4..3256433f8 100644 --- a/pact-tests/pact-tests/gov.repl +++ b/pact-tests/pact-tests/gov.repl @@ -79,5 +79,5 @@ (expect-failure "gov not granted after install" "autonomous-ns-gov" - (write user.ns-gov.t "a" { 's: "x" })) + (acquire-module-admin user.ns-gov)) diff --git a/pact-tests/pact-tests/keysets.repl b/pact-tests/pact-tests/keysets.repl index 4700d1840..3cffbf582 100644 --- a/pact-tests/pact-tests/keysets.repl +++ b/pact-tests/pact-tests/keysets.repl @@ -78,18 +78,18 @@ (expect-failure "Defining un-namespaced keys fails - env keys, name failure" - "Cannot define a keyset outside of a namespace" + "Cannot define keyset outside of a namespace" (define-keyset 'alice-keys)) ;; Show failure on lookup for keys (expect-failure "Defining un-namespaced keys fails - actual keys, name failure" - "Cannot define a keyset outside of a namespace" + "Cannot define keyset outside of a namespace" (define-keyset 'alice (read-keyset 'alice-keys))) (expect-failure "Defining namespaced key fails - env keys, outside namespace" - "Cannot define a keyset outside of a namespace" + "Cannot define keyset outside of a namespace" (define-keyset "alice.alice-keys")) (namespace 'alice) @@ -130,7 +130,7 @@ (expect-failure "keyset definition parsing is not permissive post-pact-4.4 - define-keyset" - "Cannot define a keyset outside" + "Cannot define keyset outside" (define-keyset "TEST <2>.")) (expect-failure @@ -140,17 +140,17 @@ (namespace 'alice) (expect-failure "keyset name format is not permissive post-pact-4.4 - empty-keyset - define-keyset" - "incorrect keyset name format" + "Invalid keyset name format" (define-keyset "")) (expect-failure "keyset name format is not permissive post-pact-4.4 - empty-keyset - enforce-keyset" - "incorrect keyset name format" + "Invalid keyset name format" (enforce-keyset "")) (expect-failure "keyset name format is not permissive post-pact-4.4 - empty keyset - enforce-guard - keyset ref" - "incorrect keyset name format" + "Invalid keyset name format" (enforce-guard (keyset-ref-guard ""))) ;; admin/user guard differentiation in keyset diff --git a/pact-tests/pact-tests/marmalade/pact/marmalade.repl b/pact-tests/pact-tests/marmalade/pact/marmalade.repl index 223b3ccc5..c35ce071b 100644 --- a/pact-tests/pact-tests/marmalade/pact/marmalade.repl +++ b/pact-tests/pact-tests/marmalade/pact/marmalade.repl @@ -262,7 +262,7 @@ }) (expect-failure "offer fails when called directly" - "pact-id: not in pact execution" + "Attempted to fetch defpact data, but currently not within defpact execution" (offer (read-string "token-id") (read-string "account") 1.0) ) diff --git a/pact-tests/pact-tests/nested-defpacts.repl b/pact-tests/pact-tests/nested-defpacts.repl index f21b84103..15afdcfcd 100644 --- a/pact-tests/pact-tests/nested-defpacts.repl +++ b/pact-tests/pact-tests/nested-defpacts.repl @@ -369,29 +369,29 @@ ; Case 2 test (begin-tx) (expect "Case 2: step 0" ["hello1-nested" "hello1-nested" "hello1"] (parent.bad-parent)) -(expect-failure "Case 2: step 1" "Nested defpacts were not all advanced in prior step for pact" (continue-pact 1)) +(expect-failure "Case 2: step 1" "Nested defpact not advanced DldRwCblQ7Loqy6wYJnaodHl30d3j3eH-qtFzfEv46g" (continue-pact 1)) (commit-tx) ; Case 3 test (begin-tx) (expect "Case 3: step 0" ["hello1-nested" "hello1-nested" "hello1"] (parent.bad1)) -(expect-failure "Case 3: step 1" "Nested defpacts were not all advanced in prior step for pact" (continue-pact 1)) +(expect-failure "Case 3: step 1" "Nested defpact not advanced 183L52xV0ivDcgsehtAOwvlOXTeLYmG00yIySUiXBAY" (continue-pact 1)) (commit-tx) ; Case 4 test (begin-tx) -(expect-failure "Case 4: step 0" "applyNestedPact: invalid nested defpact length, must be equal to length of parent" (parent.bad2)) +(expect-failure "Case 4: step 0" "Nested defpact execution failed, parameter mismatch: PactId: DldRwCblQ7Loqy6wYJnaodHl30d3j3eH-qtFzfEv46g step count: 2 Parent step count: 3" (parent.bad2)) (commit-tx) ; Case 5 test (begin-tx) -(expect-failure "Case 5: step 0" "applyNestedPact: invalid nested defpact length, must be equal to length of parent" (parent.bad3)) +(expect-failure "Case 5: step 0" "Nested defpact execution failed, parameter mismatch: PactId: 5I6koMWVHo7UQhUSpFjWBqxHKW5erkEzT9CuAmjbZ6U step count: 2 Parent step count: 3" (parent.bad3)) (commit-tx) ; Case 6 test (begin-tx) (expect "Case 6: step 0" ["hello1-nested" "hello1-nested" "hello1"] (parent.bad4)) -(expect-failure "Case 6: step 1" "Attempting to continue a pact that was not nested" (continue-pact 1)) +(expect-failure "Case 6: step 1" "Requested nested defpact double execution: defpact id: z7mCQuEV5PzQ0A0jfOD9UV0W7InCocHSoJqno8xZNWw" (continue-pact 1)) (commit-tx) ; Nested yields diff --git a/pact-tests/pact-tests/strings.repl b/pact-tests/pact-tests/strings.repl index 99c14da51..917288237 100644 --- a/pact-tests/pact-tests/strings.repl +++ b/pact-tests/pact-tests/strings.repl @@ -4,12 +4,12 @@ (expect "str-to-list on str returns a list of single char strings" ["a" "b" "c"] (str-to-list "abc")) (expect "str-to-list on empty string" [] (str-to-list "")) -(expect-failure "str-to-list fails on list" "Invalid arguments" (str-to-list [])) +(expect-failure "str-to-list fails on list" "Type error" (str-to-list [])) "===== concat" (expect "concat works on empty list" "" (concat [])) (expect "concat works on list of str" "abc" (concat ["a" "b" "c"])) (expect "concat works on list of multi char strings" "aabbcc" (concat ["aa" "bb" "cc"])) (expect "concat works singleton list" "abc" (concat ["abc"])) -(expect-failure "concat fails when not all elems are strings" "concat: expecting list of strings" (concat ["a" "b" 2])) -(expect-failure "concat fails on non list" "Invalid arguments" (concat "hello")) +(expect-failure "concat fails when not all elems are strings" "Type error" (concat ["a" "b" 2])) +(expect-failure "concat fails on non list" "Type error" (concat "hello")) diff --git a/pact/Pact/Core/Errors.hs b/pact/Pact/Core/Errors.hs index 782e92369..c2c7e4317 100644 --- a/pact/Pact/Core/Errors.hs +++ b/pact/Pact/Core/Errors.hs @@ -440,6 +440,7 @@ pactValueToArgTypeError = \case PGuard _ -> ATEPrim PrimGuard PModRef _ -> ATEModRef PCapToken _ -> ATEClosure + PTable _ -> ATETable typeToArgTypeError :: Type -> ArgTypeError typeToArgTypeError = \case @@ -781,7 +782,7 @@ instance Pretty EvalError where NestedDefPactParentStepCountMismatch pid stepCount parentStepCount -> Pretty.hsep [ "Nested defpact execution failed, parameter mismatch:" - , "PacId: " <> pretty pid + , "PactId: " <> pretty pid , "step count: " <> pretty stepCount , "Parent step count: " <> pretty parentStepCount ] @@ -825,7 +826,7 @@ instance Pretty EvalError where InvalidManagedCap fqn -> "Install capability error: capability is not managed and cannot be installed:" <+> pretty (fqnToQualName fqn) CapNotInstalled cap -> - "Capability not installed:" <+> pretty cap + "Managed capability not installed:" <+> pretty cap CapAlreadyInstalled cap -> "Capability already installed:" <+> pretty cap ModuleMemberDoesNotExist fqn -> @@ -856,7 +857,7 @@ instance Pretty EvalError where NativeIsTopLevelOnly b -> "Top-level call used in module" <+> pretty b EventDoesNotMatchModule mn -> - "Emitted event does not match module" <+> pretty mn + "Emitted event does not match module:" <+> pretty mn InvalidEventCap fqn -> "Invalid event capability" <+> pretty fqn NestedDefpactsNotAdvanced dpid -> @@ -1434,7 +1435,7 @@ evalErrorToBoundedText = mkBoundedText . \case thsep ["Install capability failed. Capability is not declared as a managed capability and cannot be installed.", tFqn fqn] CapNotInstalled cap -> thsep - ["Capability" + ["Managed capability" , renderQualName (_ctName cap) , "was not installed." , "Check the sigs field or the arguments to verify that the capability is specified correctly."] diff --git a/pact/Pact/Core/IR/Eval/CEK/CoreBuiltin.hs b/pact/Pact/Core/IR/Eval/CEK/CoreBuiltin.hs index be9cd5472..537a4f821 100644 --- a/pact/Pact/Core/IR/Eval/CEK/CoreBuiltin.hs +++ b/pact/Pact/Core/IR/Eval/CEK/CoreBuiltin.hs @@ -805,7 +805,7 @@ coreEnforceGuard info b cont handler env = \case chargeGasArgs info $ GStrOp $ StrOpParse $ T.length s case parseAnyKeysetName s of Left {} -> - throwNativeExecutionError info b "incorrect keyset name format" + throwExecutionError info (InvalidKeysetNameFormat s) Right ksn -> isKeysetNameInSigs info cont handler env ksn args -> argsError info b args @@ -814,7 +814,7 @@ keysetRefGuard info b cont handler env = \case [VString g] -> do chargeGasArgs info $ GStrOp $ StrOpParse $ T.length g case parseAnyKeysetName g of - Left {} -> throwNativeExecutionError info b "incorrect keyset name format" + Left {} -> throwExecutionError info (InvalidKeysetNameFormat g) Right ksn -> do let pdb = view cePactDb env liftGasM info (_pdbRead pdb DKeySets ksn) >>= \case @@ -829,7 +829,6 @@ coreTypeOf info b cont handler _env = \case VPactValue pv -> returnCEKValue cont handler $ VString $ renderType $ synthesizePvType pv VClosure _ -> returnCEKValue cont handler $ VString "<>" - VTable tv -> returnCEKValue cont handler $ VString (renderType (TyTable (_tvSchema tv))) args -> argsError info b args coreDec :: (IsBuiltin b) => NativeFunction e b i @@ -1603,7 +1602,7 @@ dbDescribeKeySet info b cont handler env = \case Nothing -> throwExecutionError info (NoSuchKeySet ksn) Left{} -> - throwNativeExecutionError info b "incorrect keyset name format" + throwExecutionError info (InvalidKeysetNameFormat s) args -> argsError info b args coreCompose :: (IsBuiltin b) => NativeFunction e b i diff --git a/pact/Pact/Core/IR/Eval/CEK/Types.hs b/pact/Pact/Core/IR/Eval/CEK/Types.hs index 12cb65553..43774fa86 100644 --- a/pact/Pact/Core/IR/Eval/CEK/Types.hs +++ b/pact/Pact/Core/IR/Eval/CEK/Types.hs @@ -59,6 +59,7 @@ module Pact.Core.IR.Eval.CEK.Types , pattern VPartialNative , pattern VCapToken , pattern VTime + , pattern VTable , CapCont(..) , CapState(..) , csSlots, csManaged @@ -242,8 +243,6 @@ instance (NFData b, NFData i) => NFData (CanApply e b i) data CEKValue (e :: RuntimeMode) (b :: K.Type) (i :: K.Type) = VPactValue !PactValue -- ^ PactValue(s), which contain no terms - | VTable !TableValue - -- ^ Table references, which despite being a syntactic -- value with | VClosure !(CanApply e b i) -- ^ Closures, which may contain terms @@ -254,12 +253,14 @@ instance (NFData b, NFData i) => NFData (CEKValue e b i) instance Show (CEKValue e b i) where show = \case VPactValue pv -> show pv - VTable vt -> "table" <> show (_tvName vt) VClosure _ -> "closure<>" pattern VLiteral :: Literal -> CEKValue e b i pattern VLiteral lit = VPactValue (PLiteral lit) +pattern VTable :: TableValue -> CEKValue e b i +pattern VTable tv = VPactValue (PTable tv) + pattern VString :: Text -> CEKValue e b i pattern VString txt = VLiteral (LString txt) @@ -575,7 +576,6 @@ instance (Pretty b, Show i, Show b) => Pretty (NativeFn e b i) where instance (Show i, Show b, Pretty b) => Pretty (CEKValue e b i) where pretty = \case VPactValue pv -> pretty pv - VTable tv -> "table" <> P.braces (pretty (_tvName tv)) VClosure{} -> P.angles "closure#" diff --git a/pact/Pact/Core/IR/Eval/CEK/Utils.hs b/pact/Pact/Core/IR/Eval/CEK/Utils.hs index 502968a3f..af1bfa2a8 100644 --- a/pact/Pact/Core/IR/Eval/CEK/Utils.hs +++ b/pact/Pact/Core/IR/Eval/CEK/Utils.hs @@ -50,7 +50,7 @@ toArgTypeError = \case PGuard _ -> ATEPrim PrimGuard PModRef _ -> ATEModRef PCapToken _ -> ATEClosure - VTable{} -> ATETable + PTable _ -> ATETable VClosure{} -> ATEClosure -------------------------- diff --git a/pact/Pact/Core/IR/Eval/Direct/CoreBuiltin.hs b/pact/Pact/Core/IR/Eval/Direct/CoreBuiltin.hs index fd5de887c..47640655f 100644 --- a/pact/Pact/Core/IR/Eval/Direct/CoreBuiltin.hs +++ b/pact/Pact/Core/IR/Eval/Direct/CoreBuiltin.hs @@ -55,7 +55,6 @@ import qualified Pact.Time as PactTime import Pact.Core.IR.Eval.Runtime.Utils -import Pact.Core.IR.Eval.Runtime.Types import Pact.Core.IR.Term import Pact.Core.Names import Pact.Core.Environment @@ -797,7 +796,7 @@ coreEnforceGuard info b env = \case [VString s] -> do chargeGasArgs info $ GStrOp $ StrOpParse $ T.length s case parseAnyKeysetName s of - Left {} -> throwNativeExecutionError info b "incorrect keyset name format" + Left {} -> throwExecutionError info (InvalidKeysetNameFormat s) Right ksn -> VBool <$> isKeysetNameInSigs info env ksn args -> argsError info b args @@ -807,7 +806,7 @@ keysetRefGuard info b env = \case [VString g] -> do chargeGasArgs info $ GStrOp $ StrOpParse $ T.length g case parseAnyKeysetName g of - Left {} -> throwNativeExecutionError info b "incorrect keyset name format" + Left {} -> throwExecutionError info (InvalidKeysetNameFormat g) Right ksn -> do let pdb = view cePactDb env liftGasM info (_pdbRead pdb DKeySets ksn) >>= \case @@ -821,7 +820,6 @@ coreTypeOf info b _env = \case VPactValue pv -> return $ VString $ renderType $ synthesizePvType pv VClosure _ -> return $ VString "<>" - VTable tv -> return $ VString (renderType (TyTable (_tvSchema tv))) args -> argsError info b args coreDec :: (IsBuiltin b) => NativeFunction e b i @@ -1596,7 +1594,7 @@ dbDescribeKeySet info b env = \case Nothing -> throwExecutionError info (NoSuchKeySet ksn) Left{} -> - throwNativeExecutionError info b "incorrect keyset name format" + throwExecutionError info (InvalidKeysetNameFormat s) args -> argsError info b args coreCompose :: (IsBuiltin b) => NativeFunction e b i diff --git a/pact/Pact/Core/IR/Eval/Direct/ReplBuiltin.hs b/pact/Pact/Core/IR/Eval/Direct/ReplBuiltin.hs index 53c0ce2b2..260d717cd 100644 --- a/pact/Pact/Core/IR/Eval/Direct/ReplBuiltin.hs +++ b/pact/Pact/Core/IR/Eval/Direct/ReplBuiltin.hs @@ -116,16 +116,13 @@ coreExpectFailure info b _env = \case [VString desc, VString toMatch, VClosure vclo] -> do es <- get tryError (applyLamUnsafe vclo []) >>= \case - Left (PEUserRecoverableError userErr _ _) -> do + Left userErr -> do put es let err = renderCompactText userErr if toMatch `T.isInfixOf` err then return $ VLiteral $ LString $ "Expect failure: Success: " <> desc else return $ VLiteral $ LString $ "FAILURE: " <> desc <> ": expected error message '" <> toMatch <> "', got '" <> err <> "'" - Left _err -> do - put es - return $ VLiteral $ LString $ "Expect failure: Success: " <> desc Right v -> return $ VLiteral $ LString $ "FAILURE: " <> toMatch <> ": expected failure, got result: " <> prettyShowValue v args -> argsError info b args diff --git a/pact/Pact/Core/IR/Eval/Direct/Types.hs b/pact/Pact/Core/IR/Eval/Direct/Types.hs index 04d818e96..576ef7be4 100644 --- a/pact/Pact/Core/IR/Eval/Direct/Types.hs +++ b/pact/Pact/Core/IR/Eval/Direct/Types.hs @@ -46,6 +46,7 @@ module Pact.Core.IR.Eval.Direct.Types , pattern VLamClosure , pattern VPartialClosure , pattern VDefPactClosure + , pattern VTable , CapPopState(..) , NativeFunction , BuiltinEnv @@ -81,7 +82,6 @@ import Pact.Core.DefPacts.Types import Pact.Core.Literal import Pact.Core.ModRefs import Pact.Core.Builtin -import Pact.Core.IR.Eval.Runtime.Types data ClosureType i = NullaryClosure @@ -200,8 +200,6 @@ instance (Show i, Show b) => Show (PartialNativeFn e b i) where data EvalValue e b i = VPactValue !PactValue -- ^ PactValue(s), which contain no terms - | VTable !TableValue - -- ^ Table references, which despite being a syntactic -- value with | VClosure !(CanApply e b i) -- ^ Closures, which may contain terms @@ -212,7 +210,6 @@ instance (NFData b, NFData i) => NFData (EvalValue e b i) instance Show (EvalValue e b i) where show = \case VPactValue pv -> show pv - VTable vt -> "table" <> show (_tvName vt) VClosure _ -> "closure<>" -- | Locally bound variables and @@ -302,6 +299,9 @@ pattern VPartialClosure clo = VClosure (PC clo) pattern VDefPactClosure :: DefPactClosure e b i -> EvalValue e b i pattern VDefPactClosure clo = VClosure (DPC clo) +pattern VTable :: TableValue -> EvalValue e b i +pattern VTable tv = VPactValue (PTable tv) + -- | What to do post-cap evaluation: do we pop the cap from the stack, -- or compose it within the capset data CapPopState @@ -326,7 +326,7 @@ toArgTypeError = \case PGuard _ -> ATEPrim PrimGuard PModRef _ -> ATEModRef PCapToken _ -> ATEClosure - VTable{} -> ATETable + PTable _ -> ATETable VClosure{} -> ATEClosure argsError diff --git a/pact/Pact/Core/IR/Eval/Runtime/Types.hs b/pact/Pact/Core/IR/Eval/Runtime/Types.hs index fdd9fbc35..5a2a0819a 100644 --- a/pact/Pact/Core/IR/Eval/Runtime/Types.hs +++ b/pact/Pact/Core/IR/Eval/Runtime/Types.hs @@ -13,8 +13,7 @@ {-# LANGUAGE InstanceSigs #-} module Pact.Core.IR.Eval.Runtime.Types - ( TableValue(..) - , ErrorState(..) + (ErrorState(..) , EvalCapType(..)) where @@ -26,21 +25,10 @@ import Control.DeepSeq import Pact.Core.Names import Pact.Core.PactValue -import Pact.Core.Hash -import Pact.Core.Type import Pact.Core.Capabilities import Pact.Core.Environment -data TableValue - = TableValue - { _tvName :: !TableName - , _tvHash :: !ModuleHash - , _tvSchema :: !Schema - } deriving (Show, Generic) - -instance NFData TableValue - -- | State to preserve in the error handler data ErrorState i = ErrorState (CapState QualifiedName PactValue) [StackFrame i] (NonEmpty RecursionCheck) diff --git a/pact/Pact/Core/IR/Eval/Runtime/Utils.hs b/pact/Pact/Core/IR/Eval/Runtime/Utils.hs index 4764901cf..35fab5524 100644 --- a/pact/Pact/Core/IR/Eval/Runtime/Utils.hs +++ b/pact/Pact/Core/IR/Eval/Runtime/Utils.hs @@ -259,6 +259,36 @@ gassedRuntimeTypecheck i ty = \case TyAnyObject -> pure True TyObject sc -> gassedTypecheckObj i o sc _ -> pure False + PTable tv -> case ty of + TyTable sc -> + gassedTypecheckSchemas i (_tvSchema tv) sc + _ -> pure False + +gassedTypecheckSchemas :: forall e b i. i -> Schema -> Schema -> EvalM e b i Bool +gassedTypecheckSchemas i (Schema _ fs) (Schema _ fs') + | M.size fs == M.size fs' = do + chargeConstantScalarMulFromConfig _gcMachineTickCost (fromIntegral (M.size fs)) i + tcFields (M.toList fs) (M.toList fs') + | otherwise = pure False + where + tcFields ((f, t):xs) ((f', t'):ys) + | f == f' = do + c <- gassedTypeEq t t' + if c then tcFields xs ys + else pure c + | otherwise = pure False + tcFields _ _ = pure True + gassedTypeEq :: Type -> Type -> EvalM e b i Bool + gassedTypeEq (TyPrim t) (TyPrim t') = pure $ t == t' + gassedTypeEq TyCapToken TyCapToken = pure True + gassedTypeEq (TyList t) (TyList t') = gassedTypeEq t t' + gassedTypeEq (TyObject sc) (TyObject sc') = gassedTypecheckSchemas i sc sc' + gassedTypeEq (TyTable sc) (TyTable sc') = gassedTypecheckSchemas i sc sc' + gassedTypeEq (TyModRef mrs) (TyModRef mrs') = pure $ mrs == mrs' + gassedTypeEq TyAny TyAny = pure True + gassedTypeEq TyAnyList TyAnyList = pure True + gassedTypeEq TyAnyObject TyAnyObject = pure True + gassedTypeEq _ _ = pure False -- | Typecheck an object against a schema, charge gas gassedTypecheckObj :: i -> M.Map Field PactValue -> Schema -> EvalM e b i Bool @@ -292,6 +322,7 @@ pvToArgTypeError = \case PGuard _ -> ATEPrim PrimGuard PModRef _ -> ATEModRef PCapToken _ -> ATEClosure + PTable _ -> ATETable findCallingModule :: EvalM e b i (Maybe ModuleName) findCallingModule = do diff --git a/pact/Pact/Core/IR/ModuleHashing.hs b/pact/Pact/Core/IR/ModuleHashing.hs index 5164ffe80..0eccde8a2 100644 --- a/pact/Pact/Core/IR/ModuleHashing.hs +++ b/pact/Pact/Core/IR/ModuleHashing.hs @@ -113,6 +113,9 @@ updatePactValueHash mname mhash = \case PCapToken (CapToken ct pvs) -> PCapToken $ CapToken (updateFqNameHash mname mhash ct) (updatePactValueHash mname mhash <$> pvs) PTime t -> PTime t + PTable tv@(TableValue tn _ sc) + | _tableModuleName tn == mname -> PTable (TableValue tn mhash sc) + | otherwise -> PTable tv encodeModule :: (Serialise (SerialiseV1 b)) => Module Name Type b () -> B.ByteString encodeModule (Module mname mgov defs mblessed imports mimps _mh _txh _mcode _i) = diff --git a/pact/Pact/Core/Names.hs b/pact/Pact/Core/Names.hs index feb1316c8..b664c3f68 100644 --- a/pact/Pact/Core/Names.hs +++ b/pact/Pact/Core/Names.hs @@ -66,6 +66,8 @@ module Pact.Core.Names , parseFullyQualifiedName , VerifierName(..) , renderTableName + , jsonSafeRenderTableName + , parseJsonSafeTableName , HashedModuleName(..) , renderHashedModuleName , parseHashedModuleName @@ -454,6 +456,13 @@ moduleNameParser = do p1 <- identParser pure (ModuleName p1 (Just (NamespaceName ns))) +jsonSafeTableNameParser :: Parser TableName +jsonSafeTableNameParser = do + p <- moduleNameParser + _ <- MP.char ':' + ident <- identParser + pure (TableName ident p) + hashedModuleNameParser :: Parser HashedModuleName hashedModuleNameParser = do mn <- moduleNameParser @@ -557,3 +566,11 @@ renderTableName (TableName tbl mn) = renderModuleName mn <> "_" <> tbl renderHashedModuleName :: HashedModuleName -> Text renderHashedModuleName (HashedModuleName mn mh) = renderModuleName mn <> "{" <> moduleHashToText mh <> "}" + +-- | Map the user's table name into a set of names suitable for +-- storage in the persistence backend. +jsonSafeRenderTableName :: TableName -> Text +jsonSafeRenderTableName (TableName tbl mn) = renderModuleName mn <> ":" <> tbl + +parseJsonSafeTableName :: Text -> Maybe TableName +parseJsonSafeTableName = MP.parseMaybe (jsonSafeTableNameParser <* MP.eof) diff --git a/pact/Pact/Core/PactValue.hs b/pact/Pact/Core/PactValue.hs index a2c281b0b..2dbe4e29c 100644 --- a/pact/Pact/Core/PactValue.hs +++ b/pact/Pact/Core/PactValue.hs @@ -30,6 +30,7 @@ module Pact.Core.PactValue , _PUnit , synthesizePvType , pactValueToText + , TableValue(..) ) where import Control.Lens @@ -54,8 +55,21 @@ import Pact.Core.Literal import Pact.Core.Pretty import Pact.Core.ModRefs import Pact.Core.Capabilities +import Pact.Core.Hash +data TableValue + = TableValue + { _tvName :: !TableName + , _tvHash :: !ModuleHash + , _tvSchema :: !Schema + } deriving (Show, Eq, Ord, Generic) + +instance Pretty TableValue where + pretty (TableValue n _ _) = pretty (renderTableName n) + +instance NFData TableValue + data PactValue = PLiteral !Literal | PList !(Vector PactValue) @@ -63,6 +77,7 @@ data PactValue | PObject !(Map Field PactValue) | PModRef !ModRef | PCapToken !(CapToken FullyQualifiedName PactValue) + | PTable !TableValue | PTime !PactTime.UTCTime -- Note: -- This ord instance is dangerous. Be careful of comparisons with it @@ -117,6 +132,7 @@ instance Pretty PactValue where PCapToken (CapToken fqn args) -> "CapToken" <> pretty (CapToken (fqnToQualName fqn) args) PTime t -> dquotes $ pretty (formatLTime t) + PTable t -> pretty t pactValueToText :: PactValue -> Text @@ -161,6 +177,8 @@ pactValueToText = \case qualName = fqnToQualName qn in T.concat ["CapToken(", renderQualName qualName, args',")"] -- Todo: check PTime t -> tdquotes $ formatLTime t + PTable (TableValue tn _ _) -> + renderTableName tn where tdquotes x = T.concat ["\"",x,"\""] tshow :: Show a => a -> Text @@ -185,6 +203,7 @@ instance Pretty (AbbrevPretty PactValue) where PTime t -> pretty (PactTime.formatTime "%Y-%m-%d %H:%M:%S%Q %Z" t) PList l -> brackets (prettyAbbrevText' 15 (hsep (pretty . AbbrevPretty <$> V.toList (V.take 10 l)))) + PTable t -> prettyAbbrevText' 20 (pretty t) synthesizePvType :: PactValue -> Type synthesizePvType = \case @@ -195,6 +214,7 @@ synthesizePvType = \case PObject _ -> TyAnyObject PCapToken {} -> TyCapToken PTime _ -> TyTime + PTable sc -> TyTable (_tvSchema sc) diff --git a/pact/Pact/Core/Persistence/SQLite.hs b/pact/Pact/Core/Persistence/SQLite.hs index dd76ae9c7..d651abf23 100644 --- a/pact/Pact/Core/Persistence/SQLite.hs +++ b/pact/Pact/Core/Persistence/SQLite.hs @@ -128,12 +128,15 @@ createSysTables db = do where mkTbl tbl = do SQL.exec db (cStmt tbl) + SQL.exec db (indexStmt tbl) mkTblStatement db tbl cStmt tbl = "CREATE TABLE IF NOT EXISTS \"" <> tbl <> "\" \ - \ (txid UNSIGNED BIG INT, \ - \ rowkey TEXT, \ - \ rowdata BLOB, \ - \ UNIQUE (txid, rowkey))" + \ (rowkey TEXT, \ + \ txid UNSIGNED BIGINT NOT NULL, \ + \ rowdata BLOB NOT NULL, \ + \ UNIQUE (txid, rowkey));" + indexStmt tbl = + "CREATE INDEX IF NOT EXISTS \"" <> tbl <> "_ix\"" <> " ON \"" <> tbl <> "\" (txid DESC);" data TblStatements = TblStatements diff --git a/pact/Pact/Core/Repl.hs b/pact/Pact/Core/Repl.hs index 91520ac33..35eb27b3f 100644 --- a/pact/Pact/Core/Repl.hs +++ b/pact/Pact/Core/Repl.hs @@ -63,7 +63,6 @@ runRepl = do putStrLn $ T.unpack $ replError (SourceCode "(interactive)" "") err _ -> pure () where - replSettings = Settings (replCompletion replCoreBuiltinNames) (Just ".pc-history") True displayOutput :: (Pretty a, MonadIO m) => a -> InputT m () displayOutput = outputStrLn . show . pretty diff --git a/pact/Pact/Core/Repl/Runtime/ReplBuiltin.hs b/pact/Pact/Core/Repl/Runtime/ReplBuiltin.hs index a36f56c93..53578934f 100644 --- a/pact/Pact/Core/Repl/Runtime/ReplBuiltin.hs +++ b/pact/Pact/Core/Repl/Runtime/ReplBuiltin.hs @@ -125,9 +125,13 @@ coreExpectFailure info b cont handler _env = \case then returnCEKValue cont handler $ VLiteral $ LString $ "Expect failure: Success: " <> desc else returnCEKValue cont handler $ VLiteral $ LString $ "FAILURE: " <> desc <> ": expected error message '" <> toMatch <> "', got '" <> err <> "'" - Left _err -> do + Left errMsg -> do put es - returnCEKValue cont handler $ VLiteral $ LString $ "Expect failure: Success: " <> desc + let err = renderCompactText errMsg + if toMatch `T.isInfixOf` err + then returnCEKValue cont handler $ VLiteral $ LString $ "Expect failure: Success: " <> desc + else returnCEKValue cont handler $ VLiteral $ LString $ + "FAILURE: " <> desc <> ": expected error message '" <> toMatch <> "', got '" <> err <> "'" Right (EvalValue v) -> returnCEKValue cont handler $ VLiteral $ LString $ "FAILURE: " <> toMatch <> ": expected failure, got result: " <> prettyShowValue v args -> argsError info b args diff --git a/pact/Pact/Core/Serialise/CBOR_V1.hs b/pact/Pact/Core/Serialise/CBOR_V1.hs index b729e353c..93400eefc 100644 --- a/pact/Pact/Core/Serialise/CBOR_V1.hs +++ b/pact/Pact/Core/Serialise/CBOR_V1.hs @@ -980,6 +980,24 @@ instance Serialise (SerialiseV1 name) => Serialise (SerialiseV1 (CapToken name P SerialiseV1 <$> (CapToken <$> decodeS <*> decodeS) {-# INLINE decode #-} +instance Serialise (SerialiseV1 TableName) where + encode (SerialiseV1 (TableName tn mn)) = + encodeListLen 2 <> encode tn <> encodeS mn + {-# INLINE encode #-} + decode = do + safeDecodeListLen 2 "TableName" + SerialiseV1 <$> (TableName <$> decode <*> decodeS) + {-# INLINE decode #-} + +instance Serialise (SerialiseV1 TableValue) where + encode (SerialiseV1 (TableValue tn k v)) = + encodeListLen 3 <> encodeS tn <> encodeS k <> encodeS v + {-# INLINE encode #-} + decode = do + safeDecodeListLen 3 "TableValue" + SerialiseV1 <$> (TableValue <$> decodeS <*> decodeS <*> decodeS) + {-# INLINE decode #-} + instance Serialise (SerialiseV1 PactValue) where encode (SerialiseV1 pv) = encodeListLen 2 <> @@ -991,6 +1009,7 @@ instance Serialise (SerialiseV1 PactValue) where PModRef mr -> encodeWord 4 <> encodeS mr PCapToken ct -> encodeWord 5 <> encodeS ct PTime (UTCTime (NominalDiffTime pt)) -> encodeWord 6 <> encode pt + PTable t -> encodeWord 7 <> encodeS t {-# INLINE encode #-} decode = do safeDecodeListLen 2 "PactValue" @@ -1002,6 +1021,7 @@ instance Serialise (SerialiseV1 PactValue) where 4 -> PModRef <$> decodeS 5 -> PCapToken <$> decodeS 6 -> PTime . UTCTime . NominalDiffTime <$> decode + 7 -> PTable <$> decodeS _ -> fail "unexpected decoding" {-# INLINE decode #-} diff --git a/pact/Pact/Core/SizeOf.hs b/pact/Pact/Core/SizeOf.hs index 6e51c28ac..645f1aa88 100644 --- a/pact/Pact/Core/SizeOf.hs +++ b/pact/Pact/Core/SizeOf.hs @@ -366,6 +366,8 @@ makeSizeOf ''DefManagedMeta makeSizeOf ''DefCapMeta makeSizeOf ''Governance makeSizeOf ''ModRef +makeSizeOf ''TableName +makeSizeOf ''TableValue makeSizeOf ''PactValue makeSizeOf ''DefPactContinuation makeSizeOf ''Provenance diff --git a/pact/Pact/Core/StableEncoding.hs b/pact/Pact/Core/StableEncoding.hs index 45185b1a9..adbabd5be 100644 --- a/pact/Pact/Core/StableEncoding.hs +++ b/pact/Pact/Core/StableEncoding.hs @@ -49,6 +49,7 @@ import Pact.Core.PactValue import Pact.Time import Data.Maybe (fromMaybe) import Pact.Core.Namespace +import Pact.Core.Type -- | JSON serialization for 'readInteger' and public meta info; -- accepts both a String version (parsed as a Pact integer), @@ -396,6 +397,12 @@ instance J.Encode (StableEncoding v) => J.Encode (StableEncoding (Map Field v)) c = coerce {-# INLINABLE build #-} +instance JD.FromJSON (StableEncoding v) => JD.FromJSON (StableEncoding (Map Field v)) where + parseJSON = fmap (StableEncoding . c) . JD.parseJSON + where + c :: Map Text (StableEncoding v) -> Map Field v + c = unsafeCoerce + -- | Stable encoding of `KSPredicate FullyQualifiedName` instance J.Encode (StableEncoding KSPredicate) where build (StableEncoding ksp) = case ksp of @@ -486,6 +493,103 @@ instance J.Encode (StableEncoding UTCTime) where build (StableEncoding utc) = encoder timeCodec utc {-# INLINABLE build #-} +instance J.Encode (StableEncoding PrimType) where + build (StableEncoding pt) = J.build (renderPrimType pt) + {-# INLINABLE build #-} + +instance JD.FromJSON (StableEncoding PrimType) where + parseJSON = JD.withText "PrimType" $ \t -> fmap StableEncoding $ case t of + "integer" -> pure PrimInt + "decimal" -> pure PrimDecimal + "string" -> pure PrimString + "bool" -> pure PrimBool + "time" -> pure PrimTime + "guard" -> pure PrimGuard + "unit" -> pure PrimUnit + _ -> fail "could not parse prim type" + +instance J.Encode (StableEncoding Type) where + build (StableEncoding t) = case t of + TyPrim p -> J.build (StableEncoding p) + TyAnyList -> J.build ("list" :: T.Text) + TyAnyObject -> J.build ("object" :: T.Text) + TyAny -> J.build ("*" :: T.Text) + TyCapToken -> J.build ("cap-token" :: T.Text) + TyList ty -> J.object [ "tylist" J..= StableEncoding ty ] + TyObject ty -> J.object [ "tyobject" J..= StableEncoding ty ] + TyModRef mr -> J.object [ "tymodref" J..= J.Array (StableEncoding <$> S.toList mr) ] + TyTable tn -> J.object [ "tytable" J..= StableEncoding tn ] + +instance JD.FromJSON (StableEncoding Type) where + parseJSON v = + parsePrim v + <|> parseSimple v + <|> parseTyList v + <|> parseTyObject v + <|> parseTyModRef v + <|> parseTyTable v + where + parsePrim = fmap (StableEncoding . TyPrim . _stableEncoding) . JD.parseJSON + parseSimple = JD.withText "Type" $ \t -> case t of + "list" -> pure $ StableEncoding TyAnyList + "object" -> pure $ StableEncoding TyAnyObject + "*" -> pure $ StableEncoding TyAny + "cap-token" -> pure $ StableEncoding TyCapToken + _ -> fail "could not parse simple type" + parseTyList = JD.withObject "TyList" $ \o -> do + ty <- o JD..: "tylist" + pure $ StableEncoding (TyList (_stableEncoding ty)) + parseTyObject = JD.withObject "TyObject" $ \o -> do + ty <- o JD..: "tyobject" + pure $ StableEncoding (TyObject (_stableEncoding ty)) + parseTyTable = JD.withObject "TyTable" $ \o -> do + tn <- o JD..: "tytable" + pure $ StableEncoding (TyTable (_stableEncoding tn)) + parseTyModRef = JD.withObject "TyModRef" $ \o -> do + mr <- o JD..: "tymodref" + pure $ StableEncoding (TyModRef (S.fromList (fmap _stableEncoding mr))) + +instance J.Encode (StableEncoding Schema) where + build (StableEncoding (Schema qn fieldMap)) = J.object + [ "sc_name" J..= StableEncoding qn + , "sc_fields" J..= J.build (StableEncoding fieldMap) + ] + {-# INLINABLE build #-} + +instance JD.FromJSON (StableEncoding Schema) where + parseJSON = JD.withObject "Schema" $ \o -> do + qn <- o JD..: "sc_name" + fieldMap <- o JD..: "sc_fields" + pure $ StableEncoding (Schema (_stableEncoding qn) (_stableEncoding fieldMap)) + + +instance J.Encode (StableEncoding TableName) where + build (StableEncoding tn) = + J.build $ jsonSafeRenderTableName tn + {-# INLINABLE build #-} + +instance JD.FromJSON (StableEncoding TableName) where + parseJSON = JD.withText "TableName" $ \t -> case parseJsonSafeTableName t of + Just tn -> pure $ StableEncoding tn + _ -> fail "could not parse table name" + +instance J.Encode (StableEncoding TableValue) where + build (StableEncoding (TableValue tn mh sc)) = J.object + -- The colon here prevents this from being a valid field name + -- for a schema. + [ ":tableName" J..= StableEncoding tn + , ":moduleHash" J..= StableEncoding mh + , ":schema" J..= StableEncoding sc + ] + {-# INLINABLE build #-} + +instance JD.FromJSON (StableEncoding TableValue) where + parseJSON = JD.withObject "TableValue" $ \o -> do + tn <- o JD..: ":tableName" + mh <- o JD..: ":moduleHash" + sc <- o JD..: ":schema" + pure $ StableEncoding (TableValue (_stableEncoding tn) (_stableEncoding mh) (_stableEncoding sc)) + -- | Stable encoding of `PactValue` instance J.Encode (StableEncoding PactValue) where build (StableEncoding pv) = case pv of @@ -496,6 +600,7 @@ instance J.Encode (StableEncoding PactValue) where PModRef mr -> J.build (StableEncoding mr) PCapToken ct -> J.build (StableEncoding ct) PTime pt -> J.build (StableEncoding pt) + PTable t -> J.build (StableEncoding t) {-# INLINABLE build #-} instance JD.FromJSON (StableEncoding PactValue) where @@ -506,6 +611,7 @@ instance JD.FromJSON (StableEncoding PactValue) where (PModRef . _stableEncoding <$> JD.parseJSON v) <|> (PTime <$> decoder timeCodec v) <|> (PCapToken . _stableEncoding <$> JD.parseJSON v) <|> + (PTable . _stableEncoding <$> JD.parseJSON v) <|> (PObject . fmap _stableEncoding <$> JD.parseJSON v) {-# INLINABLE parseJSON #-} @@ -698,6 +804,7 @@ instance J.Encode (StableEncoding RowDataValue) where PModRef mr -> buildTagged "m" $ J.build (StableEncoding mr) PCapToken ct -> buildTagged "ct" $ J.build (StableEncoding (RowDataValue <$> ct)) PObject o -> buildTagged "o" $ J.build (StableEncoding (RowDataValue <$> o)) + PTable t -> buildTagged "tv" $ J.build (StableEncoding t) where buildTagged :: Text -> J.Builder -> J.Builder buildTagged tag o = J.object @@ -722,6 +829,7 @@ instance JD.FromJSON (StableEncoding RowDataValue) where "o" -> StableEncoding . RowDataValue . PObject . fmap (_unRowDataValue . _stableEncoding) <$> JD.parseJSON val "g" -> StableEncoding . RowDataValue . PGuard . fmap (_unRowDataValue) . _stableEncoding <$> JD.parseJSON val "ct" -> StableEncoding . RowDataValue . PCapToken . fmap (_unRowDataValue) . _stableEncoding <$> JD.parseJSON val + "tv" -> StableEncoding . RowDataValue . PTable . _stableEncoding <$> JD.parseJSON val "m" -> StableEncoding . RowDataValue . PModRef <$> parseMR val _ -> fail "tagged RowData" parseMR = JD.withObject "tagged ModRef" $ \o -> ModRef diff --git a/pact/Pact/Core/Type.hs b/pact/Pact/Core/Type.hs index 88a32c467..e75e09fb9 100644 --- a/pact/Pact/Core/Type.hs +++ b/pact/Pact/Core/Type.hs @@ -120,7 +120,9 @@ data Type instance NFData Type data Schema - = Schema QualifiedName (Map Field Type) + = Schema + { _scName :: QualifiedName + , _scFields :: Map Field Type } deriving (Eq, Show, Ord, Generic) instance NFData Schema @@ -255,7 +257,7 @@ instance Pretty Type where "object" <> Pretty.braces (pretty n) TyTable (Schema n _sc) -> "table" <> Pretty.braces (pretty n) - TyCapToken -> "CAPTOKEN" + TyCapToken -> "cap-token" TyAnyList -> "list" TyAnyObject -> "object" TyAny -> "*" diff --git a/test-utils/Pact/Core/Gen.hs b/test-utils/Pact/Core/Gen.hs index 8049e05a3..2e8e13694 100644 --- a/test-utils/Pact/Core/Gen.hs +++ b/test-utils/Pact/Core/Gen.hs @@ -125,6 +125,9 @@ parsedTyNameGen = Gen.choice hashGen :: Gen Hash hashGen = pactHash . encodeUtf8 <$> identGen +hashedModuleNameGen :: Gen HashedModuleName +hashedModuleNameGen = do + HashedModuleName <$> moduleNameGen <*> moduleHashGen -- | Generate a keyset, polymorphic over the custom -- predicate function `a`. This particular variant is @@ -193,7 +196,7 @@ fieldGen = Field <$> identGen schemaGen :: Gen Schema schemaGen = do qual <- qualifiedNameGen - elems <- Gen.list (Range.linear 0 10) $ (,) <$> fieldGen <*> typeGen + elems <- Gen.list (Range.linear 1 10) $ (,) <$> fieldGen <*> typeGen pure (Schema qual (fromList elems)) typeGen :: Gen Type @@ -500,11 +503,24 @@ modRefGen = <$> moduleNameGen <*> (Set.fromList <$> Gen.list (Range.constant 0 5) moduleNameGen) +tableNameGen :: Gen TableName +tableNameGen = do + tname <- identGen + mname <- moduleNameGen + pure $ TableName tname mname + +tableValueGen :: Gen TableValue +tableValueGen = do + tn <- tableNameGen + sc <- schemaGen + mh <- moduleHashGen + pure $ TableValue tn mh sc pactValueGen :: Gen PactValue pactValueGen = Gen.recursive Gen.choice [ PLiteral <$> literalGen , PTime <$> timeGen + , PTable <$> tableValueGen ] [ PList . Vec.fromList <$> Gen.list (Range.linear 1 5) pactValueGen , PObject <$> (Gen.map (Range.linear 1 5) ((,) <$> fieldGen <*> pactValueGen))