Skip to content

Commit

Permalink
refine types
Browse files Browse the repository at this point in the history
  • Loading branch information
paweljakubas committed Oct 28, 2020
1 parent 13880fb commit a67d55f
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 23 deletions.
3 changes: 2 additions & 1 deletion lib/core/cardano-wallet-core.cabal
Expand Up @@ -49,6 +49,7 @@ library
, deepseq
, digest
, directory
, either
, exceptions
, extra
, fast-logger
Expand Down Expand Up @@ -197,7 +198,6 @@ test-suite unit
, cardano-api
, cardano-crypto
, cardano-wallet-core
, ouroboros-consensus
, cardano-wallet-launcher
, cardano-wallet-test-utils
, cardano-slotting
Expand Down Expand Up @@ -229,6 +229,7 @@ test-suite unit
, network-uri
, persistent
, OddWord
, ouroboros-consensus
, QuickCheck
, quickcheck-state-machine >= 0.6.0
, random
Expand Down
70 changes: 58 additions & 12 deletions lib/core/src/Cardano/Wallet/Api/Types.hs
Expand Up @@ -45,8 +45,9 @@ module Cardano.Wallet.Api.Types
-- * API Types
, ApiAddress (..)
, ApiScript (..)
, ApiCredentials (..)
, ApiPubKey (..)
, Credential (..)
, ApiCredentials (..)
, ApiCertificate (..)
, ApiEpochInfo (..)
, ApiSelectCoinsData (..)
Expand Down Expand Up @@ -219,6 +220,8 @@ import Codec.Binary.Bech32
( dataPartFromBytes, dataPartToBytes )
import Codec.Binary.Bech32.TH
( humanReadablePart )
import Codec.Binary.Encoding
( AbstractEncoding (..), detectEncoding, encode )
import Control.Applicative
( optional, (<|>) )
import Control.Arrow
Expand Down Expand Up @@ -250,6 +253,8 @@ import Data.ByteArray.Encoding
( Base (Base16), convertFromBase, convertToBase )
import Data.ByteString
( ByteString )
import Data.Either.Combinators
( maybeToRight )
import Data.Either.Extra
( maybeToEither )
import Data.Function
Expand Down Expand Up @@ -303,8 +308,10 @@ import qualified Cardano.Crypto.Wallet as CC
import qualified Cardano.Wallet.Primitive.AddressDerivation as AD
import qualified Cardano.Wallet.Primitive.Types as W
import qualified Codec.Binary.Bech32 as Bech32
import qualified Codec.Binary.Bech32.TH as Bech32
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Types as Aeson
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as HM
import qualified Data.Map as Map
Expand Down Expand Up @@ -393,10 +400,13 @@ newtype ApiScript = ApiScript
{ script :: ApiT Script
} deriving (Eq, Generic, Show)

data Credential =
FromKey Text
| FromScript ApiScript
deriving (Eq, Generic, Show)
newtype ApiPubKey = ApiPubKey
{ pubKey :: ByteString
} deriving (Eq, Generic, Show)

data Credential = Credential
{ credential :: Either ApiPubKey ApiScript
} deriving (Eq, Generic, Show)

data ApiCredentials = ApiCredentials
{ spending :: !(Maybe Credential)
Expand Down Expand Up @@ -1336,15 +1346,29 @@ instance ToJSON ApiScript where
toJSON (ApiScript (ApiT script')) =
object ["script" .= toJSON script']

instance FromJSON ApiPubKey where
parseJSON = parseJSON >=> eitherToParser . first ShowFmt . fromText
instance ToJSON ApiPubKey where
toJSON (ApiPubKey key') = do
let hrp = [Bech32.humanReadablePart|addr_vk|]
String $ T.decodeUtf8 $ encode (EBech32 hrp) key'

instance FromJSON Credential where
parseJSON val = flip (withObject "DelegationAction") val $ \o -> do
k <- o .:? "key"
case k of
Just txt -> pure $ FromKey txt
Nothing -> FromScript <$> parseJSON val
parseJSON obj = do
key' <-
(withObject "PubKey" $
\o -> o .:? "pub_key" :: Aeson.Parser (Maybe Text)) obj
case key' of
Nothing ->
Credential . Right <$> parseJSON obj
Just pubKeyTxt -> case fromText pubKeyTxt of
Right bytes ->
pure $ Credential $ Left bytes
Left (TextDecodingError err) -> fail err

instance ToJSON Credential where
toJSON (FromKey key') = object [ "key" .= toJSON key']
toJSON (FromScript script') = toJSON script'
toJSON (Credential (Left c))= object ["pub_key" .= toJSON c]
toJSON (Credential (Right c))= toJSON c

instance FromJSON ApiCredentials where
parseJSON = withObject "ApiCredentials" $ \obj -> do
Expand Down Expand Up @@ -1796,6 +1820,28 @@ instance FromText PostExternalTransactionData where
Right load ->
pure $ PostExternalTransactionData load

instance FromText ApiPubKey where
fromText txt = case detectEncoding (T.unpack txt) of
Just EBech32{} -> do
(hrp, dp) <- either
(const $ Left $ TextDecodingError "ApiPubKey's Bech32 has invalid text.")
Right (Bech32.decodeLenient txt)
let err1 = TextDecodingError "ApiPubKey has invalid Bech32 datapart."
let err2 = TextDecodingError "ApiPubKey must be 32 bytes."
let pubKeyFromBytes bytes
| BS.length bytes /= 32 = Nothing
| otherwise = Just $ ApiPubKey bytes
let checkPayload bytes =
maybeToRight err2 (pubKeyFromBytes bytes)
let proceedWhenHrpCorrect = do
bytes <- maybeToRight err1 (Bech32.dataPartToBytes dp)
checkPayload bytes
case Bech32.humanReadablePartToText hrp of
"stake_vk" -> proceedWhenHrpCorrect
"addr_vk" -> proceedWhenHrpCorrect
_ -> Left $ TextDecodingError "ApiPubKey must have either 'addr_vk' or 'stake_vk' prefix."
_ -> Left $ TextDecodingError "ApiPubKey must be must be encoded as Bech32."

{-------------------------------------------------------------------------------
HTTPApiData instances
-------------------------------------------------------------------------------}
Expand Down
58 changes: 58 additions & 0 deletions lib/core/test/data/Cardano/Wallet/Api/ApiCredentials.json
@@ -0,0 +1,58 @@
{
"seed": -1365317730990572323,
"samples": [
{
"staking": {
"script": "script_vkh1tkpt7t9ds7gvz7xnpw06zzvmmpzmsxayy9pw9jtrpfdwcek92z5"
}
},
{
"staking": {
"pub_key": "addr_vk1f22l33e90jsrwf8zn56sevn8rk26u4ke2hltpe3vc5y2nexzacyqk5vw3k"
}
},
{
"spending": {
"pub_key": "addr_vk1w94lccag57dktlvg6877yjwau6t9c6qlzlwuundxvh4te4mznz8sjqxwgf"
}
},
{
"spending": {
"pub_key": "addr_vk1dhv4hra3hzlskvl58zlzxlrq8chcnmwspe2umpml6eqgccnfch9smnxsxh"
},
"staking": {
"pub_key": "addr_vk1uplve822dzqkxgcw39sxx8d7jjhjzf49r5w6395qqeslyp8958ws6fse74"
}
},
{
"staking": {
"pub_key": "addr_vk1798f2rxru58s9fxqgv3q253gafcm79ax4wqsf88tmmzrmtgjmkgqllmuf9"
}
},
{
"spending": {
"pub_key": "addr_vk1pyrl6mw32fy72q49xssremd2tpghhrq23v363zlrskquhexhe9vs725v4y"
}
},
{
"spending": {
"script": "script_vkh17gnygava5ejc3pqalgxle4n4ld96hy7xvlfsvtfkgpyt66j9akd"
}
},
{
"staking": {
"pub_key": "addr_vk1ejgas033hvtgc36jex8wd2wnrz5563y8dlxycxhrw5aukkus6d3sf66843"
}
},
{
"staking": {
"script": "script_vkh1dz5ex8shske46jngrwwcjcp5hxj9e2fpmv67esxcg505w4prev8"
}
},
{
"spending": {
"script": "script_vkh10ruyvwrnzjtnhsfgphwfnylfkyjgyjenmchhlwa3y0cvywlkfxm"
}
}
]
}
15 changes: 15 additions & 0 deletions lib/core/test/data/Cardano/Wallet/Api/ApiPubKey.json
@@ -0,0 +1,15 @@
{
"seed": 6302548750665082884,
"samples": [
"addr_vk14hqpszg9shsfzean5u6wmqk502pf44pjpqpr5kj4eyv83z66ansqu40d20",
"addr_vk1d2xr7jqdk7wc0d6x7vuque88scff223x0fwkvxds4s6c5ur0yqrqxgwdy3",
"addr_vk1vnxlrfrjw2udctw9z20j9vtmpf9lreshcl5wuzhd8yqu7z80p2wqz4yq2j",
"addr_vk1pesy703p2vjlv5mgnanqdksg0luj0deevdhx49t5j6gpgg3ct3sqeqz9cg",
"addr_vk1ypvfnarsvmartq0v27zsae566962yzw5cf4se36nf4mce428ux6q0m7rwj",
"addr_vk1z4s9p5756w9zkg7f52g9nfr9vwzyxqpa3angxm4xd7mkpf0jpeusqtlf3t",
"addr_vk10vtapeka8zxne95w2x6kldth76asv2cajh0ctwl50rsmfzk0tpgspnm9jw",
"addr_vk122lmd8dp5x6ftswn262mexst0z4y7h6sx2vqrh2xvm9wrmxlrlrqw4w6r2",
"addr_vk1e7d456p3vgje5y0zk2zcmvtvmnz64q9jkuk88zwc58gk3yj5p0aqslmzff",
"addr_vk1ge6aj0y2urpsht288lypyds3vtwc37njx9d6vmhxc77hvkqvpjcs52wvj3"
]
}
49 changes: 49 additions & 0 deletions lib/core/test/data/Cardano/Wallet/Api/Credential.json
@@ -0,0 +1,49 @@
{
"seed": 3593536623839180826,
"samples": [
{
"script": "script_vkh1g0rzml9gezsa2easzhpe6pze5rzr2u6k4phylawxs336z2rh5jv"
},
{
"pub_key": "addr_vk1jzlx626gay2mep7nkgjsqaztpjdwws2lrldw90835xwsnp9cva9st2y0jg"
},
{
"pub_key": "addr_vk1l7lg4j4exyyg99d6xlagfz6w7gxrddgmgslsvkpc8pnmwf2mfyjs8npeu8"
},
{
"script": "script_vkh1vayxa6sdef0dyghnwutvxdl4tmtx5g8wjzpnz90nss3jgkqtx4s"
},
{
"pub_key": "addr_vk18r20gwmj0522nw32a3ld72mth8c05depujwr9f33t3rgwfry0pxq2v6eyj"
},
{
"script": "script_vkh1sg8hj4q8xjcw9cy6eqhrlmz8g9j8qwkhng6uyu8cf7pdxshwqkm"
},
{
"pub_key": "addr_vk1ve7fcxcxtqtwsd5jn0hggceqjg2vm86rxa25heu99k4t2v83r6ds24ds0g"
},
{
"script": {
"all": [
{
"any": [
"script_vkh17getjqyt2fv5qlpl9rvu6ne554u0606t3avw92rze6ksj0l83aj",
"script_vkh10pt47v62wsklft3zlcp20s7xhe59u33425myten0h89cvmk59ju",
"script_vkh1ejd2uxatx2x87cpfv6ygx55ry7yge2hy9t20rw6v5302g9fzvf6",
"script_vkh1qygn5erlxwtstnf463m2kpjn9z5snr5dceg94w80alpkkc9ggjt"
]
},
"script_vkh1q4fv9tltcw89nczw5he3d2dez20ucvf0s6ejgeffrrss2wdwxyc",
"script_vkh1ku4fhgkfcg5khex3vmuk7e5aqd8dl65ags7cj5kwxse5symr540",
"script_vkh1tr975zqadt4r4kxrunql8p7r0kpcah6qm6myumgr4797yfhcd33"
]
}
},
{
"script": "script_vkh1pw42ehkuytm7e3ef58lsxt26w8uds6cq9e83csjytezgs6l63u6"
},
{
"pub_key": "addr_vk1fmzgzuqjk5ll237872d63yxrrl3ujvzu9sfdkdj8y9vu223ueqgqjk4anr"
}
]
}
33 changes: 33 additions & 0 deletions lib/core/test/unit/Cardano/Wallet/Api/Malformed.hs
Expand Up @@ -51,6 +51,7 @@ import Prelude

import Cardano.Wallet.Api.Types
( ApiAddressInspectData
, ApiCredentials
, ApiPoolId
, ApiPostRandomAddressData
, ApiPutAddressesData
Expand Down Expand Up @@ -289,6 +290,38 @@ instance Malformed (BodyParam ApiScript) where
)
]

instance Malformed (BodyParam ApiCredentials) where
malformed = first BodyParam <$>
[ ( Aeson.encode [aesonQQ|
{}|]
, "Error in $: ApiCredentials must have at least one credential."
)
, ( Aeson.encode [aesonQQ|
{ "script": {}
}|]
, "Error in $: ApiCredentials must have at least one credential."
)
, ( Aeson.encode [aesonQQ|
{ "staking": {}
}|]
, "Error in $: key 'script' not found"
)
, ( Aeson.encode [aesonQQ|
{ "spending": {}
}|]
, "Error in $: key 'script' not found"
)
, ( Aeson.encode [aesonQQ|
{ "staking": 2
}|]
, "Error in $: parsing PubKey failed, expected Object, but encountered Number"
)
, ( Aeson.encode [aesonQQ|
{ "spending": 2
}|]
, "Error in $: parsing PubKey failed, expected Object, but encountered Number"
)
]
instance Malformed (BodyParam SomeByronWalletPostData) where
malformed = jsonValid ++ jsonInvalid
where
Expand Down

0 comments on commit a67d55f

Please sign in to comment.