Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add describe-guard primitive #851

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions src/Pact/Native/Db.hs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ dbDefs =
(funType tTyObjectAny [("table",tableTy)])
[LitExample "(describe-table accounts)"]
"Get metadata for TABLE. Returns an object with 'name', 'hash', 'blessed', 'code', and 'keyset' fields."
,setTopLevelOnly $ defGasRNative "describe-keyset" descKeySet
(funType tTyObjectAny [("keyset",tTyString)]) [] "Get metadata for KEYSET."
,setTopLevelOnly $ defRNative "describe-module" descModule
(funType tTyObjectAny [("module",tTyString)])
[LitExample "(describe-module 'my-module)"]
Expand All @@ -159,15 +157,6 @@ descTable _ [TTable {..}] = return $ toTObject TyAny def [
("type", toTerm $ pack $ showPretty _tTableType)]
descTable i as = argsError i as

descKeySet :: GasRNativeFun e
descKeySet g i [TLitString t] = do
r <- readRow (_faInfo i) KeySets (KeySetName t)
case r of
Just v -> computeGas' g i (GPostRead (ReadKeySet (KeySetName t) v)) $
return $ toTerm v
Nothing -> evalError' i $ "Keyset not found: " <> pretty t
descKeySet _ i as = argsError i as

descModule :: RNativeFun e
descModule i [TLitString t] = do
mods <- lookupModule i (ModuleName t Nothing)
Expand Down
44 changes: 44 additions & 0 deletions src/Pact/Native/Keysets.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- |
-- Module : Pact.Native.Keysets
Expand All @@ -13,10 +14,12 @@ module Pact.Native.Keysets where

import Control.Lens

import Data.Default
import Data.Text (Text)

import Pact.Eval
import Pact.Native.Internal
import Pact.Types.Pretty
import Pact.Types.Purity
import Pact.Types.Runtime

Expand Down Expand Up @@ -49,6 +52,10 @@ keyDefs =
"Define keyset as NAME with KEYSET, or if unspecified, read NAME from message payload as keyset, \
\similarly to 'read-keyset'. \
\If keyset NAME already exists, keyset will be enforced before updating to new value."
,setTopLevelOnly $ defGasRNative "describe-keyset" descKeySet
(funType tTyObjectAny [("keyset",tTyString)]) [] "Get metadata for KEYSET."
mightybyte marked this conversation as resolved.
Show resolved Hide resolved
,setTopLevelOnly $ defGasRNative "describe-guard" descGuard
(funType tTyObjectAny [("keyset",tTyString)]) [] "Get metadata for KEYSET."
mightybyte marked this conversation as resolved.
Show resolved Hide resolved
,enforceGuardDef "enforce-keyset"
,defKeyPred KeysAll (==)
["(keys-all 3 3)"] "Keyset predicate function to match all keys in keyset."
Expand Down Expand Up @@ -82,6 +89,43 @@ defineKeyset g0 fi as = case as of
runSysOnly $ enforceKeySet i (Just ksn) oldKs
writeRow i Write KeySets ksn ks & success "Keyset defined"

descKeySet :: GasRNativeFun e
descKeySet g i [TLitString t] = do
r <- readRow (_faInfo i) KeySets (KeySetName t)
case r of
Just v -> computeGas' g i (GPostRead (ReadKeySet (KeySetName t) v)) $
return $ toTerm v
Nothing -> evalError' i $ "Keyset not found: " <> pretty t
descKeySet _ i as = argsError i as

descGuard :: GasRNativeFun e
descGuard gas i [TGuard g _] = do
case g of
GKeySet ks ->
computeGas' gas i (GUserApp Defun) $
return $ toTObject TyAny def
[ ("type", tStr "KeySet")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How should we handle type-specific metadata? I'm guessing we should only have type be common and everything else varies, but for some that's JSON no-no. What about a details field which is a varying object? Or is that overthinking it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main motivating purpose for this was providing information to apps / wallets / etc about what keys they should sign with. So the big thing my immediate use case wants from this is public keys. Beyond that I really don't know. Probably the most important criteria is that any changes we make down the road can be done in a backwards-compatible manner.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess one other high level question is whether we should put the case-specific data at the same level or if we should make some kind of generic object wrapper to make it easier to decode.

Copy link
Contributor

@sirlensalot sirlensalot Feb 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

case-specific data at the same level or if we should make some kind of generic object wrapper to make it easier to decode

That was what I was trying to suggest with details, let's do that.

, ("keyset", toTerm ks)
]
GKeySetRef (KeySetName t) -> do
r <- readRow (_faInfo i) KeySets (KeySetName t)
case r of
Just v -> computeGas' gas i (GPostRead (ReadKeySet (KeySetName t) v)) $
return $ toTObject TyAny def
[ ("type", tStr "KeySetRef")
, ("keyset", toTerm v)
mightybyte marked this conversation as resolved.
Show resolved Hide resolved
]
Nothing -> evalError' i $ "Keyset not found: " <> pretty t

--descKeySet gas i [TLiteral (LString t) info]
GPact _ -> computeGas' gas i (GUserApp Defun) $
return $ toTObject TyAny def [ ("type", tStr "Pact") ]
mightybyte marked this conversation as resolved.
Show resolved Hide resolved
GModule _ -> computeGas' gas i (GUserApp Defun) $
return $ toTObject TyAny def [ ("type", tStr "Module") ]
mightybyte marked this conversation as resolved.
Show resolved Hide resolved
GUser _ -> computeGas' gas i (GUserApp Defun) $
return $ toTObject TyAny def [ ("type", tStr "User") ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK this is where it gets interesting.

First, it should have fun and args from UserGuard.

(Note that all of these (including other types) should probably try to stay consistent with JSON field names. This is maybe why a details field could be good, to make it really clear that we're enumerating the constructor JSON-style)

Second: wasn't it a motivation to show all keysets? This is where we might be able to traverse the guard structure. Let's look at the JSON of a gas station with a key:

{ "fun": "util.guards1.enforce-guard-any"
  "args": [
    [ { "pred": "keys-all",
        "keys": [ "9a4849687cbcfeb1f7c6510539638da576289508aedcc75f4d6ad3ed2623635c" ]
      },
      { "fun": "util.guards1.enforce-guard-all",
        "args": [
          [ { "fun": "coin.gas-only",
              "args": []
            },
            { "fun": "util.guards1.enforce-below-or-at-gas-price",
              "args": [ 1e-08 ]
            },
            { "fun": "util.guards1.enforce-below-or-at-gas-limit",
              "args": [ { "int": 10000 } ]
            }
          ] ]
      }
    ] ]
}

So, first, it might be nice to try to pretty-print in a desc field. More importantly, we can walk the args in that any time we find a TGuard argument, we can find keysets! Then we dump them in a keysets field. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds promising! I'm also thinking about the scenario where maybe you have a smart wallet that allows different keysets in different situations. Is there a way the smart wallet module could somehow expose what keys it's accepting? I'm imagining situations where it's not necessarily static. For instance, maybe you have a smart wallet designed to make funds available to beneficiaries on the event of death or loss of owner's keys. In this situation you might have a time delay. If it's been less than a year since the last transaction, only allow the owner's key. Otherwise allow some multi-sig of social recovery with maybe a family member plus an attorney or something.

descGuard _ i as = argsError i as

keyPred :: (Integer -> Integer -> Bool) -> RNativeFun e
keyPred predfun _ [TLitInteger count,TLitInteger matched] =
return $ toTerm (predfun count matched)
Expand Down