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

Wallet Primitive Types #25

Merged
merged 3 commits into from
Mar 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 1 addition & 3 deletions app/server/Main.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
module Main where

import Cardano.Wallet
( sayHello )
import Prelude

main :: IO ()
main = sayHello
main = return ()
16 changes: 14 additions & 2 deletions cardano-wallet.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,15 @@ library
build-depends:
base

-- Hackage Dependencies
, bytestring
, containers
, deepseq

hs-source-dirs:
src
exposed-modules:
Cardano.Wallet
Cardano.Wallet.Primitive
other-modules:
Paths_cardano_wallet

Expand All @@ -49,7 +54,6 @@ executable cardano-wallet-server

build-depends:
base
, cardano-wallet

hs-source-dirs:
app/server
Expand All @@ -70,9 +74,17 @@ test-suite unit

build-depends:
base
, cardano-wallet

, bytestring
, containers
, hspec
, QuickCheck
type:
exitcode-stdio-1.0
hs-source-dirs:
test/unit
main-is:
Main.hs
other-modules:
Cardano.Wallet.PrimitiveSpec
8 changes: 0 additions & 8 deletions src/Cardano/Wallet.hs

This file was deleted.

220 changes: 220 additions & 0 deletions src/Cardano/Wallet/Primitive.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeFamilies #-}

{-
This module contains the core primitive of a Wallet. This is roughly a
Haskell translation of the 'Formal Specification for a Cardano Wallet'.

It doesn't contain any particular business-logic code, but define a few
primitive operations on Wallet core types as well.
-}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

I knew you would like it 😉
That's actually a good habit we should take. Thanks for pointing that out. Maybe it should be part of our coding standard actually 🤔 ?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe it should be part of our coding standard actually

I'd like that! 😍

Copy link
Member Author

Choose a reason for hiding this comment

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

Then make a proposal 😶

Copy link
Collaborator

Choose a reason for hiding this comment

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

I thought you might say that 👍

module Cardano.Wallet.Primitive
(
-- * Block
Block(..)
, BlockHeader(..)

-- * Tx
, Tx(..)
, TxIn(..)
, TxOut(..)

-- * Address
, Address (..)

-- * Coin
, Coin (..)

-- * UTxO
, UTxO (..)
, excluding
, isSubsetOf
, restrictedBy
, restrictedTo
, Dom(..)

-- * Generic
, Hash (..)
) where

import Prelude

import Control.DeepSeq
( NFData (..) )
import Data.ByteString
( ByteString )
import Data.Map.Strict
( Map )
import Data.Set
( Set )
import Data.Word
( Word16, Word32, Word64 )
import GHC.Generics
( Generic )
import GHC.TypeLits
( Symbol )

import qualified Data.Map.Strict as Map
import qualified Data.Set as Set


-- * Block

data Block = Block
{ header
:: !BlockHeader
, transactions
:: !(Set Tx)
} deriving (Show, Generic)
Copy link
Contributor

Choose a reason for hiding this comment

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

Eq would be handy for testing.


instance NFData Block


data BlockHeader = BlockHeader
{ epochIndex
:: !Word64
, slotNumber
:: !Word16
, prevBlockHash
:: !(Hash "BlockHeader")
} deriving (Show, Generic)

instance NFData BlockHeader


-- * Tx

data Tx = Tx
{ inputs
:: ![TxIn]
-- ^ Order of inputs matters in the transaction representation. The
-- transaction id is computed from the binary representation of a tx,
-- for which inputs are serialized in a specific order.
, outputs
:: ![TxOut]
-- ^ Order of outputs matter in the transaction representations. Outputs
-- are used as inputs for next transactions which refer to them using
-- their indexes. It matters also for serialization.
} deriving (Show, Generic)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ord would be needed to insert transactions into a Set.


instance NFData Tx


data TxIn = TxIn
{ txId
:: !(Hash "Tx")
, txIx
:: !Word32
} deriving (Show, Generic, Eq, Ord)

instance NFData TxIn


data TxOut = TxOut
{ address
:: !Address
, coin
:: !Coin
} deriving (Show, Generic, Eq, Ord)

instance NFData TxOut


-- * Address

newtype Address = Address
{ getAddress :: ByteString
} deriving (Show, Generic, Eq, Ord)

instance NFData Address


-- * Coin

-- | Coins are stored as Lovelace (reminder: 1 Lovelace = 1e6 ADA)
newtype Coin = Coin
{ getCoin :: Word64
} deriving stock (Show, Ord, Eq, Generic)
deriving newtype (Enum, Num, Real, Integral)

instance NFData Coin

instance Bounded Coin where
minBound = Coin 0
maxBound = Coin 45000000000000000
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this is in Lovelace it would be nice to mention. Either here or for the Coin type.

Copy link
Member Author

Choose a reason for hiding this comment

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

💯


instance Semigroup Coin where
(Coin a) <> (Coin b) =
invariant
( mconcat
[ "Cardano.Wallet.Primitive.Coin (<>), sum out of bounds: "
, show a
, " + "
, show b
]
)
(Coin (a + b))
(<= maxBound)

instance Monoid Coin where
mempty = minBound
mconcat = foldr (<>) mempty


-- * UTxO

newtype UTxO = UTxO { getUTxO :: Map TxIn TxOut }
deriving stock (Show, Generic, Eq, Ord)
deriving newtype (Semigroup, Monoid)

instance NFData UTxO

instance Dom UTxO where
type DomElem UTxO = TxIn
dom (UTxO utxo) = Map.keysSet utxo

-- ins⋪ u
excluding :: UTxO -> Set TxIn -> UTxO
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shoot me, but have we considered using the unicode symbols for matching the spec?

Copy link
Member Author

Choose a reason for hiding this comment

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

Haha. I thought of it yes. And, that's code we will probably not use much so, that could be "nice and pretty". In practice, we probably don't want that because it's a pain in the ass to write such symbols 😂 ...

excluding (UTxO utxo) =
UTxO . Map.withoutKeys utxo

-- a ⊆ b
isSubsetOf :: UTxO -> UTxO -> Bool
isSubsetOf (UTxO a) (UTxO b) =
a `Map.isSubmapOf` b

-- ins⊲ u
restrictedBy :: UTxO -> Set TxIn -> UTxO
restrictedBy (UTxO utxo) =
UTxO . Map.restrictKeys utxo

-- u ⊳ outs
restrictedTo :: UTxO -> Set TxOut -> UTxO
restrictedTo (UTxO utxo) outs =
UTxO $ Map.filter (`Set.member` outs) utxo


class Dom a where
type DomElem a :: *
dom :: a -> Set (DomElem a)


-- * Helpers

newtype Hash (tag :: Symbol) = Hash
{ getHash :: ByteString
} deriving (Show, Generic, Eq, Ord)

instance NFData (Hash tag)


invariant
:: String
-> a
-> (a -> Bool)
-> a
invariant msg a predicate =
if predicate a then a else error msg
Loading