diff --git a/pact-core-tests/pact-tests/fqns.repl b/pact-core-tests/pact-tests/fqns.repl new file mode 100644 index 000000000..f47e34808 --- /dev/null +++ b/pact-core-tests/pact-tests/fqns.repl @@ -0,0 +1,120 @@ +(env-data {"keyset": { "keys": ["bob"], "pred": "keys-any" }}) +(env-keys ["bob"]) +(begin-tx) +(define-namespace 'free (read-keyset 'keyset) (read-keyset 'keyset)) +(commit-tx) + +(begin-tx) +(namespace 'free) +(module modA G + (defcap G () true) + (defun func (x) (+ 1 x)) + (defconst test:string "hi") + ) +(module modB G + (defcap G () true) + (defun chain () (modA.func 10)) + (defconst test:string "hello") + (defun get-test() test) + ) + +(expect "ns-scoped module call works fully qualified" (free.modB.chain) 11) + +(namespace 'free) + +(expect "ns-scoped module call works within namespace scope" (modB.chain) 11) + +(expect "selects correct test" (modB.get-test) "hello") +(commit-tx) + +; works across different txs +(begin-tx) +(namespace 'free) +(module modA G + (defcap G () true) + (defun func (x) (+ 1 x)) + (defconst test:string "hi") + ) +(commit-tx) +(begin-tx) +(namespace 'free) +(module modB G + (defcap G () true) + (defun chain () (modA.func 10)) + (defconst test:string "hello") + (defun get-test() test) + ) + +(expect "ns-scoped module call works fully qualified" (free.modB.chain) 11) + +(namespace 'free) + +(expect "ns-scoped module call works within namespace scope" (modB.chain) 11) + +(expect "selects correct test" (modB.get-test) "hello") +(commit-tx) + +;; +;; Module redeploy name resolution +;; + +; In the following tests, we define a module `test-mod-redeploy-ref`, and then +; redeploy the same module with the change to one capability: `test`. +; In the old version, the `test` capability fails, in the new one it passes. + +(begin-tx) +; First, demonstrate the behavior prior to pact-4.8. +; (env-exec-config ["DisablePact48"]) + +(namespace 'free) +(module test-mod-redeploy-ref g + (defcap g () true) + + (defcap test () + (enforce false "boom")) + + (defun f () + (with-capability (test) + 1)) + ) +; Before pact-4.8, the updated capability will be ignored, and calls to a function +; requiring that capability will fail. +(expect-failure "Demonstrate defcap resolution." (f)) + +(commit-tx) + +(begin-tx) +(namespace 'free) +(module test-mod-redeploy-ref g + (defcap g () true) + + (defcap test () + (enforce false "boom")) + + (defun f () + (with-capability (test) + 1)) + ) +(commit-tx) + +(begin-tx) +(namespace 'free) +(env-exec-config []) ; reset + +(module test-mod-redeploy-ref g + (defcap g () true) + (defcap test () + true) + (defun f () + (with-capability (free.test-mod-redeploy-ref.test) + 1)) + + (defun f1 () + (with-capability (test-mod-redeploy-ref.test) + 1)) + ) +; These tests show that f now references the updated version of the capability. +(expect "Demonstrate correct resolution with fully-qualified reference." 1 (f)) +(expect "Demonstrate correct resolution with non-namespace-qualified reference." 1 (f1)) + +(commit-tx) diff --git a/pact-core-tests/pact-tests/namespaces.repl b/pact-core-tests/pact-tests/namespaces.repl new file mode 100644 index 000000000..9fe04de19 --- /dev/null +++ b/pact-core-tests/pact-tests/namespaces.repl @@ -0,0 +1,314 @@ +;; This is an example of using namespaces for simple modules, +;; showcasing how to declare them, as well as use qualified +;; names to access declared constructs. +(begin-tx) + +(env-data + { "alice-keys" : ["alice"] + , "bob-keys" : ["bob"] + }) + +(env-keys ["alice", "bob"]) + +; Define namespace using a keyset guard +(define-namespace 'alice (read-keyset 'alice-keys) (read-keyset 'alice-keys)) +(define-namespace 'bob (read-keyset 'bob-keys) (read-keyset 'bob-keys)) + +(expect + "describe-namespace describes namespaces correctly - alice" + { "admin-guard": (read-keyset 'alice-keys) + , "namespace-name": "alice" + , "user-guard": (read-keyset 'alice-keys) + } + (describe-namespace 'alice)) + +(expect + "describe-namespace describes namespaces correctly - bob" + { "admin-guard": (read-keyset 'bob-keys) + , "namespace-name": "bob" + , "user-guard": (read-keyset 'bob-keys) + } + (describe-namespace 'bob)) + +; Set tx namespace to 'alice' +(namespace 'alice) + +(env-data + { "alice.alice-keys" : ["alice"] + , "alice-keys" : ["alice"] + , "bob.bob-keys" : ["bob"] + , "bob-keys" : ["bob"] + }) +(env-keys ["alice", "bob"]) + +(define-keyset "alice.alice-keys") + +; (expect-failure +; "keyset namespace mismatch" +; (define-keyset "alice-keys" (read-keyset 'alice-keys))) + +(interface alice-contract + + @doc "this contract is for alice, and occurs in the \ + \namespace 'alice'" + + (defun f:bool ()) + (defun g:string (x:bool)) + + ; simple constants + (defconst C1 true) + (defconst C2 "bar") +) + +; now bob gets his own namespace in the tx +(namespace 'bob) +(define-keyset "bob.bob-keys") + +(interface bob-contract + @doc "this contract is for bob, and occurs in the \ + \namespace 'bob'" + + (defun h:decimal (x:bool z:decimal)) + (defun i:string ()) +) + +(commit-tx) +(begin-tx) + +(env-data + { "carl-keys" : ["carl"] + , "carl.carl-keys": ["carl"] + }) + +(env-keys ["carl"]) + +(define-namespace 'carl (read-keyset 'carl-keys) (read-keyset 'carl-keys)) +(namespace 'carl) +(define-keyset "carl.carl-keys") + +(module carl-module "carl.carl-keys" + @doc "lets implement alice's and bob's contracts" + + (implements alice.alice-contract) + (implements bob.bob-contract) + + (defschema s foo:string) + (deftable t:{s}) + + (defun f:bool () + @doc "Alice's flag" + alice.alice-contract.C1) + + (defun g:string (x:bool) + @doc "do something simple or default to alice's constant" + + (if x + "something simple" + i)) + + (defun h:decimal (x:bool z:decimal) + @doc "Bob's decision" + (if x z (- z))) + + (defun i:string () + alice.alice-contract.C2) +) + +(commit-tx) +(begin-tx) + +;; Used as part of NamespaceSpec test making sure +;; namespaces propagate down the term tree +(interface test-sig + @doc "test for biplate" + + (defun f:bool ()) + (defconst TBOOL true) +) + +; Todo: namespaced keysets here +; (env-exec-config ['DisablePact44]) +(env-data { "test-keys" : ["test"] }) +(define-keyset 'test-keys) +(env-keys ["test"]) + +(module global-module 'test-keys + @doc "biplate test impl" + + (defun global:bool () + true) +) + +(commit-tx) +(begin-tx) + +(define-namespace 'test (read-keyset 'test-keys) (read-keyset 'test-keys)) +(namespace 'test) + +(module test-module 'test-keys + @doc "biplate test impl" + + (implements test-sig) + (use global-module) + + (defschema test s:string) + (deftable test-table:{test}) + + (defun f:bool () + true) + + (defun g:bool () + test-sig.TBOOL) +) + +(interface test-iface + (defun tif ())) + + +(commit-tx) + +(use carl.carl-module) +(expect "alice's triple reference" true alice.alice-contract.C1) +(expect "qualified module name in table" "carl.carl-module" (at 'module (describe-table carl.carl-module.t))) + +(namespace 'test) +(module m2 'test-keys + (use test-module) + (implements test-iface) + (defun tif () 1) + ) + +;;;;; +;; Don't check ns-user guard for upgrade: OLD BEHAVIOR +;;;; + +(begin-tx) +; Todo: namespaced keysets here +(env-data { 'user: ["user"], 'admin: ["admin"] }) +(define-namespace 'ns-user-behavior (read-keyset 'user) (read-keyset 'admin)) +(namespace 'ns-user-behavior) +(env-keys ["user"]) + +(module user-module-1 G + (defcap G () true) + (defun f () 1)) + +(commit-tx) + +;;;;; +;; Don't check ns-user guard for upgrade: NEW BEHAVIOR +;;;; + +(begin-tx) +;; TODO need `describe-namespace` #1009 +(env-data { 'user: ["user"], 'admin: ["admin"] }) +(env-keys []) +(env-exec-config []) +(expect-failure + "ensure user keyset does not pass" + (enforce-keyset (read-keyset 'user))) + +(expect-that + "entering ns does not enforce user guard" + (constantly true) + (namespace 'ns-user-behavior)) + +;; Upgrading SUCCESS with new behavior +(module user-module-1 G + (defcap G () false) + (defun f () 2)) + +(expect "upgrade succeeds" 2 (f)) + +;; FAILURE on module install: bad-module-enforce-ns-user.repl +;; FAILURE on interface install: bad-iface-enforce-ns-user.repl + +(rollback-tx) + + + + +;;;;; +;; test managed namespaces +;;;;; + +(begin-tx) +(module test-mgd-ns GOV + (defcap GOV () true) + (defun the-guard () (create-module-guard "test")) + (defun manage (ns guard) + (if (= ns "approve") true + (if (= ns "check-guard") + (enforce (= guard (the-guard)) "Invalid guard") + false)))) +(commit-tx) + +(begin-tx) +(use test-mgd-ns) +(expect + "ns policy install succeeds" + "Installed namespace policy" + (env-namespace-policy true (manage))) + +(env-data { "k1": ["k1"], "k2": ["k2"] }) +(expect + "define 'approve succeeds" + "Namespace defined: approve" + (define-namespace 'approve (read-keyset "k1") (read-keyset "k2"))) + +(expect-failure + "define 'check-guard with wrong admin guard fails" + (define-namespace "check-guard" (read-keyset "k1") (read-keyset "k2"))) + +(expect + "define 'check-guard with right admin guard succeeds" + "Namespace defined: check-guard" + (define-namespace "check-guard" (read-keyset "k1") (the-guard))) + +;; the following should succeed per ns policy above +(module my-root-module G + (defcap G () true) + (defun foo () 1)) + +;; testing allow-root in bad-root-namespace.repl, plus legacy (-44.repl) + +;; testing root upgrade failure in bad-root-namespace-upgrade.repl + +;; upgrade succeeds post-44 with allow root false + +(env-namespace-policy false (manage)) + +(module my-root-module G + (defcap G () true) + (defun foo () 2)) + + + +(rollback-tx) + +(begin-tx) + +; (env-exec-config ['DisablePact47]) +; (namespace 'carl) + +; (expect-failure +; "Setting namespace back to root namespace pre-Pact 4.7 fork fails" +; (namespace "")) + +; (expect +; "Carl contract fqn not required for member access - no ns" +; true +; (carl-module.f)) + +(env-exec-config []) + +(expect + "Setting namespace back to root namespace post-Pact 4.7 fork succeeds" + "Namespace reset to root" + (namespace "")) + +(expect + "Carl contract fqn required for member access - with ns" + true + (carl.carl-module.f)) +(commit-tx) diff --git a/pact-core/Pact/Core/Builtin.hs b/pact-core/Pact/Core/Builtin.hs index e713184b6..43ce05a44 100644 --- a/pact-core/Pact/Core/Builtin.hs +++ b/pact-core/Pact/Core/Builtin.hs @@ -14,12 +14,7 @@ module Pact.Core.Builtin , replRawBuiltinNames , replRawBuiltinMap , IsBuiltin(..) --- , CapabilityOp(..) --- , CapType(..) --- , DefType(..) - , CoreBuiltin(..) , ReplRawBuiltin - , ReplCoreBuiltin , BuiltinForm(..) , ReplBuiltins(..) , HasObjectOps(..) @@ -34,7 +29,6 @@ import Pact.Core.Names(NativeName(..)) import Pact.Core.Pretty type ReplRawBuiltin = ReplBuiltin RawBuiltin -type ReplCoreBuiltin = ReplBuiltin CoreBuiltin data BuiltinForm o = CAnd o o @@ -56,12 +50,6 @@ instance Pretty o => Pretty (BuiltinForm o) where parens ("enforce-one" <+> pretty o <+> brackets (hsep (punctuate comma (pretty <$> li)))) CEnforce o o' -> parens ("enforce" <+> pretty o <+> pretty o') - -- CFold e1 e2 e3 -> - -- parens ("fold" <+> pretty e1 <+> pretty e2 <+> pretty e3) - -- CMap e1 e2 -> - -- parens ("map" <+> pretty e1 <+> pretty e2) - -- CZip e1 e2 e3 -> - -- parens ("zip" <+> pretty e1 <+> pretty e2 <+> pretty e3) class HasObjectOps b where objectAt :: b @@ -73,82 +61,7 @@ data DefType deriving Show {- - [Typeclasses and Instances] - Builtin operator overloads, grouped by the current type class hierarchy: - class Add a where - (+) :: a -> a -> a - - instance Add integer - instance Add decimal - instance Add string - instance Add (list a) - - class Eq a where - (==) :: a -> a -> bool - (/=) :: a -> a -> bool - - instance Eq integer - instance Eq decimal - instance Eq string - instance Eq time - instance Eq unit - instance Eq bool - instance (Eq a) => Eq (list a) - -- todo: rows - - class Ord a where - (>=) :: a -> a -> bool - (>) :: a -> a -> bool - (<) :: a -> a -> bool - (<=) :: a -> a -> bool - - instance Ord integer - instance Ord decimal - instance Ord string - instance Ord time - instance Ord unit - instance Ord a => Ord (list a) - - class Show a where - show :: a -> string - - instance Show integer - instance Show decimal - instance Show string - instance Show time - instance Show unit - instance Show bool - instance (Show a) => Show (list a) - - class Num a where - (-) :: a -> a -> a - (*) :: a -> a -> a - (/) :: a -> a -> a - abs :: a -> a - negate :: a -> a - - instance Num integer - instance Num decimal - - class Fractional a where - ln :: a -> decimal - exp :: a -> decimal - sqrt :: a -> decimal - log-base :: a -> a -> a - - instance Fractional integer - instance Fractional decimal - - class ListLike a where - take :: integer -> a -> a - drop :: integer -> a -> a - concat :: [a] -> a - reverse :: a -> a - length :: a -> integer - - instance ListList string - instance ListLike (list a) -} data RawBuiltin -- Operators @@ -277,6 +190,10 @@ data RawBuiltin | RawMinutes | RawDays | RawCompose + -- Namespaces + | RawNamespace + | RawDefineNamespace + | RawDescribeNamespace deriving (Eq, Show, Ord, Bounded, Enum) instance HasObjectOps RawBuiltin where @@ -293,9 +210,7 @@ rawBuiltinToText = \case RawNegate -> "negate" RawAbs -> "abs" RawPow -> "^" - -- Bolean ops - -- RawAnd -> "and" - -- RawOr -> "or" + -- Boolean ops RawNot -> "not" -- Eq RawEq -> "=" @@ -342,8 +257,6 @@ rawBuiltinToText = \case RawZip -> "zip" RawDistinct -> "distinct" RawFormat -> "format" - -- RawEnforce -> "enforce" - -- RawEnforceOne -> "enforce-one" RawEnumerate -> "enumerate" RawEnumerateStepN -> "enumerate-step" RawShow -> "show" @@ -406,6 +319,9 @@ rawBuiltinToText = \case RawMinutes -> "minutes" RawDays -> "days" RawCompose -> "compose" + RawNamespace -> "namespace" + RawDefineNamespace -> "define-namespace" + RawDescribeNamespace -> "describe-namespace" instance IsBuiltin RawBuiltin where builtinName = NativeName . rawBuiltinToText @@ -532,6 +448,9 @@ instance IsBuiltin RawBuiltin where RawMinutes -> 1 RawDays -> 1 RawCompose -> 3 + RawNamespace -> 1 + RawDefineNamespace -> 3 + RawDescribeNamespace -> 1 rawBuiltinNames :: [Text] @@ -562,6 +481,7 @@ data ReplBuiltins | RSigKeyset | RTestCapability | REnvExecConfig + | REnvNamespacePolicy -- | REnvGas -- | REnvGasLimit -- | REnvGasLog @@ -605,6 +525,7 @@ instance IsBuiltin ReplBuiltins where RContinuePactRollback -> 2 RContinuePactRollbackYield -> 3 REnvExecConfig -> 1 + REnvNamespacePolicy -> 2 -- RLoad -> 1 -- RLoadWithEnv -> 2 -- Note: commented out natives are @@ -672,6 +593,7 @@ replBuiltinsToText = \case RContinuePactRollback -> "continue-pact-with-rollback" RContinuePactRollbackYield -> "continue-pact-rollback-yield" REnvExecConfig -> "env-exec-config" + REnvNamespacePolicy -> "env-namespace-policy" replBuiltinToText :: (t -> Text) -> ReplBuiltin t -> Text replBuiltinToText f = \case @@ -699,402 +621,3 @@ instance (Pretty b) => Pretty (ReplBuiltin b) where pretty = \case RBuiltinWrap b -> pretty b t -> pretty (replBuiltinToText (const "") t) - --- monomorphised builtin operations -data CoreBuiltin - -- IntOps - -- Integer Add - = AddInt - -- Int Num functions - | SubInt - | DivInt - | MulInt - | NegateInt - | AbsInt - | PowInt - -- Int fractional - | ExpInt - | LnInt - | SqrtInt - | LogBaseInt - -- General int ops - | ModInt - | BitAndInt - | BitOrInt - | BitXorInt - | BitShiftInt - | BitComplementInt - -- Int show instance - | ShowInt - -- Int Equality - | EqInt - | NeqInt - | GTInt - | GEQInt - | LTInt - | LEQInt - -- If - -- | IfElse - -- Decimal ops - -- Decimal add - | AddDec - -- Decimal num - | SubDec - | DivDec - | MulDec - | NegateDec - | AbsDec - | PowDec - -- Decimal rounding ops - | RoundDec - | CeilingDec - | FloorDec - -- Decimal rounding ops - | ExpDec - | LnDec - | LogBaseDec - | SqrtDec - -- Decimal Show - | ShowDec - -- Decimal Equality - | EqDec - | NeqDec - -- Decimal ord - | GTDec - | GEQDec - | LTDec - | LEQDec - -- Bool Comparisons - -- | AndBool - -- | OrBool - | NotBool - -- other bool ops - | EqBool - | NeqBool - | ShowBool - -- String Equality - | EqStr - | NeqStr - -- String Ord - | GTStr - | GEQStr - | LTStr - | LEQStr - -- String Add - | AddStr - -- String ListLike - | ConcatStr - | DropStr - | TakeStr - | LengthStr - | ReverseStr - -- String Show - | ShowStr - -- Object equality - -- | EqObj - -- | NeqObj - -- List Equality - | EqList - | NeqList - -- List Ord - | GTList - | GEQList - | LTList - | LEQList - -- List Show - | ShowList - -- List Add - | AddList - -- ListLike List - | TakeList - | DropList - | LengthList - | ConcatList - | ReverseList - -- Misc list ops - | FilterList - | DistinctList - | MapList - | ZipList - | FoldList - -- Unit ops - | EqUnit - | NeqUnit - | ShowUnit - -- Module references - | EqModRef - | NeqModRef - -- Others - | Enforce - | EnforceOne - | Enumerate - | EnumerateStepN - | ReadInteger - | ReadDecimal - | ReadString - | ReadKeyset - | EnforceGuard - | KeysetRefGuard - -- List ops - | ListAccess - | MakeList - | B64Encode - | B64Decode - | StrToList - deriving (Eq, Show, Ord, Bounded, Enum) - - -instance Pretty CoreBuiltin where - pretty = pretty . coreBuiltinToText - -instance IsBuiltin CoreBuiltin where - builtinName = NativeName . coreBuiltinToText - builtinArity = \case - AddInt -> 2 - SubInt -> 2 - DivInt -> 2 - MulInt -> 2 - PowInt -> 2 - NegateInt -> 1 - AbsInt -> 1 - ExpInt -> 1 - LnInt -> 1 - SqrtInt -> 1 - LogBaseInt -> 2 - ModInt -> 2 - BitAndInt -> 2 - BitOrInt -> 2 - BitXorInt -> 2 - BitShiftInt -> 2 - BitComplementInt -> 1 - ShowInt -> 1 - EqInt -> 2 - NeqInt -> 2 - GTInt -> 2 - GEQInt -> 2 - LTInt -> 2 - LEQInt -> 2 - -- IfElse -> 3 - AddDec -> 2 - SubDec -> 2 - DivDec -> 2 - MulDec -> 2 - PowDec -> 2 - NegateDec -> 1 - AbsDec -> 1 - RoundDec -> 1 - CeilingDec -> 1 - FloorDec -> 1 - ExpDec -> 1 - LnDec -> 1 - LogBaseDec -> 2 - SqrtDec -> 1 - ShowDec -> 1 - EqDec -> 2 - NeqDec -> 2 - GTDec -> 2 - GEQDec -> 2 - LTDec -> 2 - LEQDec -> 2 - -- AndBool -> 2 - -- OrBool -> 2 - NotBool -> 1 - EqBool -> 2 - NeqBool -> 2 - ShowBool -> 1 - EqStr -> 2 - NeqStr -> 2 - GTStr -> 2 - GEQStr -> 2 - LTStr -> 2 - LEQStr -> 2 - AddStr -> 2 - ConcatStr -> 1 - DropStr -> 2 - TakeStr -> 2 - LengthStr -> 1 - ReverseStr -> 1 - ShowStr -> 1 - -- EqObj -> 2 - -- NeqObj -> 2 - EqList -> 3 - NeqList -> 3 - GTList -> 2 - GEQList -> 2 - LTList -> 2 - LEQList -> 2 - ShowList -> 2 - AddList -> 2 - TakeList -> 2 - DropList -> 2 - LengthList -> 1 - ConcatList -> 1 - ReverseList -> 1 - FilterList -> 2 - DistinctList -> 1 - MapList -> 2 - ZipList -> 3 - FoldList -> 3 - EqUnit -> 2 - NeqUnit -> 2 - ShowUnit -> 1 - -- Module references - EqModRef -> 2 - NeqModRef -> 2 - Enforce -> 2 - EnforceOne -> 2 - Enumerate -> 2 - EnumerateStepN -> 3 - ReadInteger -> 1 - ReadDecimal -> 1 - ReadString -> 1 - ReadKeyset -> 1 - EnforceGuard -> 1 - KeysetRefGuard -> 1 - -- CreateUserGuard -> 1 - ListAccess -> 2 - MakeList -> 2 - B64Encode -> 1 - B64Decode -> 1 - StrToList -> 1 - -coreBuiltinToText :: CoreBuiltin -> Text -coreBuiltinToText = \case --- IntOps - AddInt -> "addInt" - -- Int Num functions - SubInt -> "subInt" - DivInt -> "divInt" - MulInt -> "mulInt" - NegateInt -> "negateInt" - AbsInt -> "absInt" - PowInt -> "powInt" - -- Int fractional - ExpInt -> "expInt" - LnInt -> "lnInt" - SqrtInt -> "sqrtInt" - LogBaseInt -> "logBaseInt" - -- General int ops - ModInt -> "modInt" - BitAndInt -> "bitAndInt" - BitOrInt -> "bitOrInt" - BitXorInt -> "bitXorInt" - BitShiftInt -> "bitShiftInt" - BitComplementInt -> "bitComplementInt" - -- Int show instance - ShowInt -> "showInt" - -- Int Equality - EqInt -> "eqInt" - NeqInt -> "neqInt" - GTInt -> "gtInt" - GEQInt -> "geqInt" - LTInt -> "ltInt" - LEQInt -> "leqInt" - -- If - -- IfElse -> "ifElse" - -- Decimal ops - -- Decimal add - AddDec -> "addDec" - -- Decimal num - SubDec -> "subDec" - DivDec -> "divDec" - MulDec -> "mulDec" - NegateDec -> "negateDec" - AbsDec -> "absDec" - PowDec -> "powDec" - -- Decimal rounding ops - RoundDec -> "roundDec" - CeilingDec -> "ceilingDec" - FloorDec -> "floorDec" - -- Decimal rounding ops - ExpDec -> "expDec" - LnDec -> "lnDec" - LogBaseDec -> "logBaseDec" - SqrtDec -> "sqrtDec" - -- Decimal Show - ShowDec -> "showDec" - -- Decimal Equality - EqDec -> "eqDec" - NeqDec -> "neqDec" - -- Decimal ord - GTDec -> "gtDec" - GEQDec -> "geqDec" - LTDec -> "ltDec" - LEQDec -> "leqDec" - -- Bool Comparisons - -- AndBool -> "andBool" - -- OrBool -> "orBool" - NotBool -> "notBool" - -- other bool ops - EqBool -> "eqBool" - NeqBool -> "neqBool" - ShowBool -> "showBool" - -- String Equality - EqStr -> "eqStr" - NeqStr -> "neqStr" - -- String Ord - GTStr -> "gtStr" - GEQStr -> "gtStr" - LTStr -> "gtStr" - LEQStr -> "gtStr" - -- String Add - AddStr -> "addStr" - -- String ListLike - ConcatStr -> "concatStr" - DropStr -> "dropStr" - TakeStr -> "takeStr" - LengthStr -> "lengthStr" - ReverseStr -> "reverseStr" - -- String Show - ShowStr -> "showStr" - -- Object equality - -- EqObj -> "eqObj" - -- NeqObj -> "neqObj" - -- List Equality - EqList -> "eqList" - NeqList -> "neqList" - -- List Ord - GTList -> "gtList" - GEQList -> "geqList" - LTList -> "ltList" - LEQList -> "leqList" - -- List Show - ShowList -> "showList" - -- List Add - AddList -> "addList" - -- ListLike List - TakeList -> "takeList" - DropList -> "dropList" - LengthList -> "lengthList" - ConcatList -> "concatList" - ReverseList -> "reverseList" - -- Misc list ops - FilterList -> "filterList" - DistinctList -> "distinctList" - MapList -> "mapList" - ZipList -> "zipList" - FoldList -> "foldList" - -- Unit ops - EqUnit -> "eqUnit" - NeqUnit -> "neqUnit" - ShowUnit -> "showUnit" - -- Module references - EqModRef -> "eqModRef" - NeqModRef -> "neqModRef" - -- Others - Enforce -> "enforce" - EnforceOne -> "enforceOn" - Enumerate -> "enumerate" - EnumerateStepN -> "enumerateStep" - ReadInteger -> "read-integer" - ReadDecimal -> "read-decimal" - ReadString -> "read-string" - ReadKeyset -> "read-keyset" - EnforceGuard -> "enforce-guard" - KeysetRefGuard -> "keyset-ref-guard" - -- CreateUserGuard -> "create-user-guard" - ListAccess -> "at" - MakeList -> "make-list" - B64Encode -> "base64-encode" - B64Decode -> "base64-decode" - StrToList -> "str-to-list" diff --git a/pact-core/Pact/Core/Compile.hs b/pact-core/Pact/Core/Compile.hs index e4828b822..a7865ee01 100644 --- a/pact-core/Pact/Core/Compile.hs +++ b/pact-core/Pact/Core/Compile.hs @@ -37,10 +37,12 @@ import Pact.Core.Environment import Pact.Core.Capabilities import Pact.Core.Literal import Pact.Core.Imports +import Pact.Core.Namespace import qualified Pact.Core.Syntax.Lexer as Lisp import qualified Pact.Core.Syntax.Parser as Lisp import qualified Pact.Core.Syntax.ParseTree as Lisp +import qualified Pact.Core.IR.Eval.CEK as Eval type HasCompileEnv b i m = ( MonadEval b i m @@ -64,6 +66,26 @@ data CompileValue b | InterpretValue InterpretValue deriving Show + +enforceNamespaceInstall + :: (HasCompileEnv b i m) + => i + -> Interpreter b i m + -> m () +enforceNamespaceInstall info interp = + useEvalState (esLoaded . loNamespace) >>= \case + Just ns -> + void (_interpretGuard interp info (_nsUser ns)) + Nothing -> + enforceRootNamespacePolicy + where + enforceRootNamespacePolicy = do + policy <- viewEvalEnv eeNamespacePolicy + unless (allowRoot policy) $ + throwExecutionError info (NamespaceInstallError "cannot install in root namespace") + allowRoot SimpleNamespacePolicy = True + allowRoot (SmartNamespacePolicy ar _) = ar + -- | Evaluate module governance evalModuleGovernance :: (HasCompileEnv b i m) @@ -75,34 +97,34 @@ evalModuleGovernance interp tl = do pdb <- viewEvalEnv eePactDb case tl of Lisp.TLModule m -> do + let info = Lisp._mInfo m let unmangled = Lisp._mName m mname <- mangleNamespace unmangled lookupModule (Lisp._mInfo m) pdb mname >>= \case Just targetModule -> do term <- case _mGovernance targetModule of KeyGov (KeySetName ksn) -> do - let info = Lisp._mInfo m - ksnTerm = Constant (LString ksn) info + let ksnTerm = Constant (LString ksn) info ksrg = App (Builtin (liftRaw RawKeysetRefGuard) info) (pure ksnTerm) info term = App (Builtin (liftRaw RawEnforceGuard) info) (pure ksrg) info pure term CapGov (ResolvedGov fqn) -> do - let info = Lisp._mInfo m - cgBody = Constant LUnit info + let cgBody = Constant LUnit info term = CapabilityForm (WithCapability (fqnToName fqn) [] cgBody) info pure term void (_interpret interp term) esCaps . csModuleAdmin %== S.insert (Lisp._mName m) -- | Restore the state to pre-module admin acquisition esLoaded .== lo - Nothing -> pure () + Nothing -> enforceNamespaceInstall info interp Lisp.TLInterface iface -> do + let info = Lisp._ifInfo iface let unmangled = Lisp._ifName iface ifn <- mangleNamespace unmangled - lookupModuleData (Lisp._ifInfo iface) pdb ifn >>= \case - Nothing -> pure () + lookupModuleData info pdb ifn >>= \case + Nothing -> enforceNamespaceInstall info interp Just _ -> - throwExecutionError (Lisp._ifInfo iface) (CannotUpgradeInterface (Lisp._ifName iface)) + throwExecutionError info (CannotUpgradeInterface ifn) _ -> pure () interpretTopLevel diff --git a/pact-core/Pact/Core/Environment/Utils.hs b/pact-core/Pact/Core/Environment/Utils.hs index a46dcc5b8..67b3db1ff 100644 --- a/pact-core/Pact/Core/Environment/Utils.hs +++ b/pact-core/Pact/Core/Environment/Utils.hs @@ -19,6 +19,7 @@ module Pact.Core.Environment.Utils , viewsEvalEnv , getModuleData , getModule + , getModuleMember , lookupModule , lookupModuleData , throwExecutionError @@ -154,6 +155,15 @@ getModuleData info pdb mn = Nothing -> throwExecutionError info (ModuleDoesNotExist mn) +-- | getModuleData, but only for modules, no interfaces +getModuleMember :: (MonadEval b i m) => i -> PactDb b i -> QualifiedName -> m (EvalDef b i) +getModuleMember info pdb (QualifiedName qn mn) = do + md <- getModule info pdb mn + case findDefInModule qn md of + Just d -> pure d + Nothing -> throwExecutionError info (InvariantFailure ("no such module member: " <> qn)) + + mangleNamespace :: (MonadEvalState b i m) => ModuleName -> m ModuleName mangleNamespace mn@(ModuleName mnraw ns) = useEvalState (esLoaded . loNamespace) >>= \case diff --git a/pact-core/Pact/Core/Errors.hs b/pact-core/Pact/Core/Errors.hs index e8a3d84e8..f1a786b98 100644 --- a/pact-core/Pact/Core/Errors.hs +++ b/pact-core/Pact/Core/Errors.hs @@ -333,6 +333,8 @@ data EvalError | ExpectedPactValue | NotInPactExecution | GuardEnforceError Text + | NamespaceInstallError Text + | DefineNamespaceError Text -- ^ Non-recoverable guard enforces. deriving Show diff --git a/pact-core/Pact/Core/IR/Desugar.hs b/pact-core/Pact/Core/IR/Desugar.hs index 1bcbcb9c6..6243e9591 100644 --- a/pact-core/Pact/Core/IR/Desugar.hs +++ b/pact-core/Pact/Core/IR/Desugar.hs @@ -754,22 +754,6 @@ resolveModuleData mn@(ModuleName name mNs) i = do Nothing -> throwDesugarError (NoSuchModule mn) i Just (Namespace ns _ _) -> lift (getModuleData i pdb (ModuleName name (Just ns))) - -- useEvalState (esLoaded . loModules . at mn) >>= \case - -- Just md -> pure md - -- Nothing -> - -- viewEvalEnv eePactDb >>= RenamerT . lift . lift . liftDbFunction i . (`readModule` mn) >>= \case - -- Just md -> case md of - -- ModuleData module_ depmap -> do - -- md <$ loadModule module_ depmap - -- InterfaceData in' depmap -> - -- md <$ loadInterface in' depmap - -- -- We didn't find the module data, therefore - -- -- we will check whether a namespace was supplied. - -- Nothing -> case mNs of - -- Just _ -> throwDesugarError (NoSuchModule mn) i - -- Nothing -> useEvalState (esLoaded . loNamespace) >>= \case - -- Nothing -> throwDesugarError (NoSuchModule mn) i - -- Just (Namespace ns _ _) -> resolveModuleData (ModuleName name (Just ns)) i -- lookupModuleData -- :: (MonadEval b i m) @@ -1230,11 +1214,11 @@ resolveQualified (QualifiedName qn qmn@(ModuleName modName mns)) i = do ModuleData module' _ -> do d <- hoistMaybe (findDefInModule defnName module' ) lift $ rsDependencies %= S.insert moduleName - pure (Name qn (NTopLevel qmn (_mHash module')), Just (defKind d)) + pure (Name qn (NTopLevel moduleName (_mHash module')), Just (defKind d)) InterfaceData iface _ -> do d <- hoistMaybe (findDefInInterface defnName iface) lift $ rsDependencies %= S.insert moduleName - pure (Name qn (NTopLevel qmn (_ifHash iface)), Just (defKind d)) + pure (Name qn (NTopLevel moduleName (_ifHash iface)), Just (defKind d)) modRefLookup pdb = case mns of -- Fail eagerly: the previous lookup was fully qualified Just _ -> MaybeT (throwDesugarError (NoSuchModuleMember qmn qn) i) @@ -1324,8 +1308,7 @@ checkImplements -> ModuleName -> RenamerT b i m () checkImplements i defs moduleName ifaceName = do - pdb <- viewEvalEnv eePactDb - lift (getModuleData i pdb ifaceName) >>= \case + resolveModuleData ifaceName i >>= \case InterfaceData iface _deps -> traverse_ checkImplementedMember (_ifDefns iface) _ -> throwDesugarError (NoSuchInterface ifaceName) i @@ -1365,15 +1348,10 @@ renameInterface :: (MonadEval b i m, DesugarBuiltin b) => Interface ParsedName DesugarType b i -> RenamerT b i m (Interface Name Type b i) -renameInterface (Interface ifn defs ih info) = local (set reCurrModule (Just (ifn,[]))) $ do - -- defMap = M.fromList $ (, (NTopLevel ifn ih, DKDefConst)) <$> rawDefNames - -- fqns = M.fromList $ (\n -> (n, (FullyQualifiedName ifn n ih, DKDefConst))) <$> rawDefNames - -- `maybe all of this next section should be in a block laid out by the - -- `locally reBinds` - -- rsModuleBinds %= M.insert ifn defMap - -- rsLoaded . loToplevel %= M.union fqns +renameInterface (Interface unmangled defs ih info) = do + ifn <- mangleNamespace unmangled let defNames = ifDefName <$> defs - let scc = mkScc (S.fromList defNames) <$> defs + let scc = mkScc ifn (S.fromList defNames) <$> defs defs' <- forM (stronglyConnComp scc) \case AcyclicSCC d -> pure d CyclicSCC d -> @@ -1382,17 +1360,24 @@ renameInterface (Interface ifn defs ih info) = local (set reCurrModule (Just (if throwDesugarError (RecursionDetected ifn (ifDefName <$> d)) (ifDefInfo (head d)) -- defs' <- locally reBinds (M.union (over _2 Just <$> defMap)) $ traverse renameIfDef defs binds <- view reBinds - (defs'', _, _) <- over _1 reverse <$> foldlM go ([], S.empty, binds) defs' - + (defs'', _, _) <- over _1 reverse <$> foldlM (go ifn) ([], S.empty, binds) defs' pure (Interface ifn defs'' ih info) where - mkScc dns def = (def, ifDefName def, S.toList (ifDefSCC ifn dns def)) - go (ds, s, m) d = do - when (S.member (ifDefName d) s) $ error "duplicate defn name in interface" + mkScc ifn dns def = (def, ifDefName def, S.toList (ifDefSCC ifn dns def)) + go ifn (ds, s, m) d = do let dn = ifDefName d - d' <- local (set reBinds m) $ renameIfDef d - let m' = maybe m (\dk -> M.insert dn (NTopLevel ifn ih, Just dk) m) (ifDefKind d') - -- rsModuleBinds . ix ifn %= maybe id (M.insert dn . (NTopLevel ifn ih,)) (ifDefKind d') + when (S.member dn s) $ + throwDesugarError (DuplicateDefinition dn) info + d' <- local (set reBinds m) $ + local (set reCurrModule (Just (ifn, []))) $ renameIfDef d + -- let m' = maybe m (\dk -> M.insert dn (NTopLevel ifn ih, Just dk) m) (ifDefKind d') + m' <- case ifDefToDef d' of + Just defn -> do + let fqn = FullyQualifiedName ifn dn ih + dk = defKind defn + esLoaded . loToplevel . ix dn .== (fqn, dk) + pure (M.insert dn (NTopLevel ifn ih, Just dk) m) + Nothing -> pure m pure (d':ds, S.insert dn s, m') runRenamerT diff --git a/pact-core/Pact/Core/IR/Eval/CEK.hs b/pact-core/Pact/Core/IR/Eval/CEK.hs index 54476f1ea..2a7056d8d 100644 --- a/pact-core/Pact/Core/IR/Eval/CEK.hs +++ b/pact-core/Pact/Core/IR/Eval/CEK.hs @@ -1089,7 +1089,7 @@ applyLam -> Cont b i m -> CEKErrorHandler b i m -> m (EvalResult b i m) -applyLam (C (Closure fn mn ca arity term mty env cloi)) args cont handler +applyLam vc@(C (Closure fn mn ca arity term mty env cloi)) args cont handler | arity == argLen = case ca of ArgClosure cloargs -> do args' <- traverse (enforcePactValue cloi) args @@ -1106,8 +1106,11 @@ applyLam (C (Closure fn mn ca arity term mty env cloi)) args cont handler | argLen > arity = throwExecutionError cloi ClosureAppliedToTooManyArgs | otherwise = case ca of NullaryClosure -> throwExecutionError cloi ClosureAppliedToTooManyArgs - ArgClosure cloargs -> - apply' mempty (NE.toList cloargs) args + ArgClosure cloargs + | null args -> + returnCEKValue cont handler (VClosure vc) + | otherwise -> + apply' mempty (NE.toList cloargs) args where argLen = length args -- Here we enforce an argument to a user fn is a diff --git a/pact-core/Pact/Core/IR/Eval/RawBuiltin.hs b/pact-core/Pact/Core/IR/Eval/RawBuiltin.hs index e1f1a9487..33bfafb86 100644 --- a/pact-core/Pact/Core/IR/Eval/RawBuiltin.hs +++ b/pact-core/Pact/Core/IR/Eval/RawBuiltin.hs @@ -55,6 +55,7 @@ import Pact.Core.Persistence import Pact.Core.Pacts.Types import Pact.Core.Environment import Pact.Core.Capabilities +import Pact.Core.Namespace import Pact.Core.IR.Term import Pact.Core.IR.Eval.Runtime @@ -1061,15 +1062,6 @@ coreEmitEvent = \info b cont handler env -> \case where enforceMeta Unmanaged = throwExecutionError info (InvalidEventCap fqn) enforceMeta _ = pure () - -- Just mn -> do - -- let fqn = _ctName ct - -- let ctModule = _fqModule fqn - -- if ctModule == mn then do - -- let pactEvent = PactEvent ct (_fqModule fqn) (_fqHash fqn) - -- esEvents %== (++ [pactEvent]) - -- returnCEKValue cont handler (VBool True) - -- else returnCEK cont handler (VError "Event does not match emitting module" info) - -- Nothing -> returnCEK cont handler (VError "emit-event called outside of module code" info) args -> argsError info b args createCapGuard :: (IsBuiltin b, MonadEval b i m) => NativeFunction b i m @@ -1403,6 +1395,100 @@ coreCompose = \info b cont handler _env -> \case err -> returnCEK cont handler err args -> argsError info b args +-------------------------------------------------- +-- Namespace functions +-------------------------------------------------- +coreNamespace :: (IsBuiltin b, MonadEval b i m) => NativeFunction b i m +coreNamespace = \info b cont handler env -> \case + [VString n] -> do + enforceTopLevelOnly info b + let pdb = view cePactDb env + if T.null n then do + (esLoaded . loNamespace) .== Nothing + returnCEKValue cont handler (VString "Namespace reset to root") + else + liftDbFunction info (_pdbRead pdb DNamespaces (NamespaceName n)) >>= \case + Just ns -> do + (esLoaded . loNamespace) .== Just ns + let msg = "Namespace set to " <> n + returnCEKValue cont handler (VString msg) + Nothing -> + returnCEK cont handler $ VError ("Namespace " <> n <> " not defined") info + args -> argsError info b args + + + + +coreDefineNamespace :: (IsBuiltin b, MonadEval b i m) => NativeFunction b i m +coreDefineNamespace info b cont handler env = \case + [VString n, VGuard usrG, VGuard adminG] -> do + enforceTopLevelOnly info b + unless (isValidNsFormat n) $ throwExecutionError info (DefineNamespaceError "invalid namespace format") + let pdb = view cePactDb env + liftDbFunction info (_pdbRead pdb DNamespaces (NamespaceName n)) >>= \case + -- G! + -- https://static.wikia.nocookie.net/onepiece/images/5/52/Lao_G_Manga_Infobox.png/revision/latest?cb=20150405020446 + -- Enforce the old guard + Just (Namespace _ _ laoG) -> do + coreEnforceGuard info b Mt CEKNoHandler env [VGuard laoG] >>= \case + -- Enforce guard returns an error and never a different kind of value, so + -- this pattern match is fine + EvalValue _ -> do + let nsn = NamespaceName n + ns = Namespace nsn usrG adminG + liftDbFunction info (_pdbWrite pdb Write DNamespaces nsn ns) + returnCEKValue cont handler $ VString $ "Namespace defined: " <> n + VError e i -> returnCEK cont handler (VError e i) + Nothing -> do + enforcePolicy pdb n adminG + let nsn = NamespaceName n + ns = Namespace nsn usrG adminG + liftDbFunction info (_pdbWrite pdb Write DNamespaces nsn ns) + returnCEKValue cont handler $ VString $ "Namespace defined: " <> n + args -> argsError info b args + where + enforcePolicy pdb nsn adminG = viewEvalEnv eeNamespacePolicy >>= \case + SimpleNamespacePolicy -> pure () + SmartNamespacePolicy _ fun -> getModuleMember info pdb fun >>= \case + Dfun d -> do + clo <- mkDefunClosure d (_qnModName fun) env + -- Todo: nested exec? + applyLam (C clo) [VString nsn, VGuard adminG] Mt CEKNoHandler >>= \case + EvalValue (VBool allow) -> + unless allow $ throwExecutionError info $ DefineNamespaceError "Namespace definition not permitted" + EvalValue _ -> + throwExecutionError info $ DefineNamespaceError "Namespace manager function returned an invalid value" + VError e _ -> + throwExecutionError info $ DefineNamespaceError e + _ -> failInvariant info "Namespace manager function is not a defun" + isValidNsFormat nsn = case T.uncons nsn of + Just (h, tl) -> + isValidNsHead h && T.all isValidNsChar tl + Nothing -> False + -- not (T.null nsn) && isValidNsHead (T.head nsn) && T.all isValidNsChar (T.tail ) + isValidNsHead c = + Char.isLatin1 c && Char.isAlpha c + isValidNsChar c = + Char.isLatin1 c && (Char.isAlphaNum c || T.elem c validSpecialChars) + validSpecialChars :: T.Text + validSpecialChars = + "%#+-_&$@<>=^?*!|/~" + +coreDescribeNamespace :: (IsBuiltin b, MonadEval b i m) => NativeFunction b i m +coreDescribeNamespace = \info b cont handler _env -> \case + [VString n] -> do + pdb <- viewEvalEnv eePactDb + liftDbFunction info (_pdbRead pdb DNamespaces (NamespaceName n)) >>= \case + Just (Namespace _ usrG laoG) -> do + let obj = M.fromList + [ (Field "user-guard", PGuard usrG) + , (Field "admin-guard", PGuard laoG) + , (Field "namespace-name", PString n)] + returnCEKValue cont handler (VObject obj) + Nothing -> + returnCEK cont handler (VError ("Namespace not defined " <> n) info) + args -> argsError info b args + ----------------------------------- -- Core definitions ----------------------------------- @@ -1526,3 +1612,6 @@ rawBuiltinRuntime = \case RawDays -> days RawCompose -> coreCompose RawSelectWithFields -> dbSelect + RawNamespace -> coreNamespace + RawDefineNamespace -> coreDefineNamespace + RawDescribeNamespace -> coreDescribeNamespace diff --git a/pact-core/Pact/Core/IR/Eval/Runtime/Utils.hs b/pact-core/Pact/Core/IR/Eval/Runtime/Utils.hs index 5a1938cab..8b9ddb44c 100644 --- a/pact-core/Pact/Core/IR/Eval/Runtime/Utils.hs +++ b/pact-core/Pact/Core/IR/Eval/Runtime/Utils.hs @@ -24,13 +24,11 @@ module Pact.Core.IR.Eval.Runtime.Utils , throwExecutionError' , argsError , findCallingModule --- , getModule , getCallingModule , readOnlyEnv , sysOnlyEnv , calledByModule , failInvariant --- , getModuleData , isExecutionFlagSet , checkNonLocalAllowed , evalStateToErrorState diff --git a/pact-core/Pact/Core/Repl/Runtime/ReplBuiltin.hs b/pact-core/Pact/Core/Repl/Runtime/ReplBuiltin.hs index 4541526bb..a1770926f 100644 --- a/pact-core/Pact/Core/Repl/Runtime/ReplBuiltin.hs +++ b/pact-core/Pact/Core/Repl/Runtime/ReplBuiltin.hs @@ -35,6 +35,7 @@ import Pact.Core.Errors import Pact.Core.Persistence import Pact.Core.IR.Term import Pact.Core.Info +import Pact.Core.Namespace import Pact.Core.Repl.Utils @@ -341,6 +342,20 @@ envExecConfig = \info b cont handler _env -> \case Nothing -> failInvariant info $ "Invalid flag, allowed: " <> T.pack (show (M.keys flagReps)) args -> argsError info b args +envNamespacePolicy :: (IsBuiltin b) => NativeFunction b SpanInfo (ReplM b) +envNamespacePolicy info b cont handler _env = \case + [VBool allowRoot, VClosure (C clo)] -> do + pdb <- viewEvalEnv eePactDb + let qn = QualifiedName (_cloFnName clo) (_cloModName clo) + when (_cloArity clo /= 2) $ failInvariant info "Namespace manager function has invalid argument length" + getModuleMember info pdb qn >>= \case + Dfun _ -> do + let nsp = SmartNamespacePolicy allowRoot qn + replEvalEnv . eeNamespacePolicy .= nsp + returnCEKValue cont handler (VString "Installed namespace policy") + _ -> returnCEK cont handler (VError "invalid namespace manager function type" info) + args -> argsError info b args + replBuiltinEnv :: BuiltinEnv (ReplBuiltin RawBuiltin) SpanInfo (ReplM (ReplBuiltin RawBuiltin)) replBuiltinEnv i b env = @@ -377,3 +392,4 @@ replRawBuiltinRuntime = \case RContinuePactRollback -> continuePact RContinuePactRollbackYield -> continuePact REnvExecConfig -> envExecConfig + REnvNamespacePolicy -> envNamespacePolicy diff --git a/pact-core/Pact/Core/Syntax/Lexer.x b/pact-core/Pact/Core/Syntax/Lexer.x index a4a438164..02701ad08 100644 --- a/pact-core/Pact/Core/Syntax/Lexer.x +++ b/pact-core/Pact/Core/Syntax/Lexer.x @@ -29,7 +29,6 @@ $lower = [ a-z ] $digit = [ 0-9 ] $alpha = [a-zA-Z] $psymbol = [\%\#\+\-\_\&\$\@\<\>\=\^\?\*\!\|\/\~] -$special = [\.\;\,\$\|\*\+\?\#\~\-\{\}\(\)\[\]\^\/] @ident = [$alpha $psymbol][$alpha $digit $psymbol]* @integer = [\-]?[$digit]+ @singletick = [\'][$alpha][$alpha $digit \- \_]*