Skip to content
This repository has been archived by the owner on Feb 9, 2021. It is now read-only.

Change representation of Lovelace from Word64 to Natural #693

Open
wants to merge 35 commits into
base: dcoutts/lovelaceportion-simplification
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fd733d3
Remove instance Bounded LovelacePortion
dcoutts Dec 21, 2019
89b6381
Remove unused applyLovelacePortionUp
dcoutts Dec 20, 2019
fb8e99f
Remove unused lovelacePortionFromDouble
dcoutts Dec 20, 2019
9a24cd6
Add scaleLovelaceRational function
dcoutts Dec 21, 2019
c2fe730
Remove use of LovelacePortion in genesis generation
dcoutts Dec 21, 2019
474cf3b
Remove applyLovelacePortionDown
dcoutts Dec 21, 2019
eff662e
Add rationalToLovelacePortion and lovelacePortionToRational
dcoutts Dec 21, 2019
8f12a0a
Eliminate use of mkLovelacePortion
dcoutts Dec 21, 2019
3ac8916
Replace only real use of lovelacePortionToDouble
dcoutts Dec 21, 2019
26795a6
Remove last internal use of lovelacePortionToDouble
dcoutts Dec 21, 2019
f45ddf8
Remove last uses of mkKnownLovelacePortion in the tests
dcoutts Dec 21, 2019
d57e720
Remove 3 now-used LovelacePortion functions
dcoutts Dec 21, 2019
61e0051
Check the LovelacePortion is in range in the decoders
dcoutts Dec 21, 2019
46fabe1
Trim unnecessary language extensions
dcoutts Dec 21, 2019
5fb1f2a
Improve docs for LovelacePortion
dcoutts Dec 21, 2019
edc978b
Change Lovelace internal rep from Word64 to Natural
dcoutts Dec 21, 2019
10f9cbf
Eliminate reliance on Bounded instance for Lovelace
dcoutts Dec 21, 2019
7e1b1b4
Remove Lovelace overflow checks
dcoutts Dec 21, 2019
a2091b9
Add a Semigroup and Monoid instance for Lovelace
dcoutts Dec 21, 2019
c195c08
Add new Lovelace <-> Natural conversion functions
dcoutts Dec 21, 2019
cf2717f
Remove unused code from Cardano.Chain.Genesis.NonAvvmBalances
dcoutts Dec 20, 2019
2766a64
Remove uses of unsafeGetLovelace and integerToLovelace
dcoutts Dec 21, 2019
d8c949a
Remove uses of mkLovelace and mkKnownLovelace
dcoutts Dec 21, 2019
5a6c3c9
Use exponential notation for max supply constant.
dcoutts Dec 25, 2019
6165703
Make most Lovelace arithmetic operations pure
dcoutts Dec 21, 2019
884e1c0
Simplify types in genesis generator inputs
dcoutts Dec 21, 2019
5dbdb5f
Internal tidying in the Lovelace module
dcoutts Dec 21, 2019
d505188
Change subLovelace to use Maybe rather than LovelaceError
dcoutts Dec 23, 2019
b0e9fa0
Remove unnecessary language extensions in the Lovelace module
dcoutts Dec 25, 2019
fd32d0b
Eliminate the use of LovelaceError in genesis generation.
dcoutts Dec 25, 2019
c7eb096
Move LovelaceError to the last place it is used.
dcoutts Dec 25, 2019
60007b3
Remove unused LovelaceError constructors
dcoutts Dec 25, 2019
8af30e7
Specialise the TxValidationLovelaceError to its actual use
dcoutts Dec 25, 2019
1bc766e
Add CompactLovelace type to keep CompactTxOut compact
dcoutts Dec 27, 2019
4061b61
Add and use compactTxOut{Address,Value} projections
dcoutts Dec 27, 2019
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
215 changes: 62 additions & 153 deletions cardano-ledger/src/Cardano/Chain/Common/Lovelace.hs
Original file line number Diff line number Diff line change
@@ -1,220 +1,129 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NumDecimals #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}

-- This is for 'mkKnownLovelace''s @n <= 45000000000000000@ constraint, which is
-- considered redundant. TODO: investigate this.
{-# OPTIONS_GHC -fno-warn-redundant-constraints #-}

module Cardano.Chain.Common.Lovelace
(
-- * Lovelace
Lovelace
, LovelaceError(..)
, maxLovelaceVal

-- * Constructors
, mkLovelace
, mkKnownLovelace

-- * Formatting
, lovelaceF

-- * Conversions
, unsafeGetLovelace
, naturalToLovelace
, lovelaceToNatural
, lovelaceToInteger
, integerToLovelace

-- * Arithmetic operations
, sumLovelace
, addLovelace
, subLovelace
, scaleLovelace
, scaleLovelaceRational
, divLovelace
, modLovelace

-- * Formatting
, lovelaceF
)
where

import Cardano.Prelude

import Data.Data (Data)
import Formatting (Format, bprint, build, int, sformat)
import Data.Monoid (Monoid(..))
import Formatting (Format, bprint, build, int)
import qualified Formatting.Buildable as B
import GHC.TypeLits (type (<=))
import qualified Text.JSON.Canonical as Canonical
(FromJSON(..), ReportSchemaErrors, ToJSON(..))

import Cardano.Binary
( DecoderError(..)
, FromCBOR(..)
( FromCBOR(..)
, ToCBOR(..)
, decodeListLen
, decodeWord8
, encodeListLen
, matchSize
)


-- | Lovelace is the least possible unit of currency
newtype Lovelace = Lovelace
{ getLovelace :: Word64
} deriving (Show, Ord, Eq, Generic, Data, NFData, NoUnexpectedThunks)
newtype Lovelace = Lovelace { unLovelace :: Natural }
deriving (Show, Ord, Eq, Generic, Data, NFData, NoUnexpectedThunks)

instance Semigroup Lovelace where
Lovelace a <> Lovelace b = Lovelace (a + b)

instance Monoid Lovelace where
mempty = Lovelace 0

instance B.Buildable Lovelace where
build (Lovelace n) = bprint (int . " lovelace") n

instance Bounded Lovelace where
minBound = Lovelace 0
maxBound = Lovelace maxLovelaceVal

instance ToCBOR Lovelace where
toCBOR = toCBOR . unsafeGetLovelace
encodedSizeExpr size pxy = size (unsafeGetLovelace <$> pxy)
toCBOR = toCBOR . unLovelace
encodedSizeExpr size _pxy = encodedSizeExpr size (Proxy :: Proxy Word64)

instance FromCBOR Lovelace where
fromCBOR = do
l <- fromCBOR
toCborError
. first (DecoderErrorCustom "Lovelace" . sformat build)
$ mkLovelace l
return $! Lovelace (fromIntegral (l :: Word64))

instance Monad m => Canonical.ToJSON m Lovelace where
toJSON = Canonical.toJSON . unsafeGetLovelace
toJSON = Canonical.toJSON . unLovelace

instance Canonical.ReportSchemaErrors m => Canonical.FromJSON m Lovelace where
fromJSON = fmap Lovelace . Canonical.fromJSON

data LovelaceError
= LovelaceOverflow Word64
| LovelaceTooLarge Integer
| LovelaceTooSmall Integer
| LovelaceUnderflow Word64 Word64
deriving (Data, Eq, Show)

instance B.Buildable LovelaceError where
build = \case
LovelaceOverflow c -> bprint
("Lovelace value, " . build . ", overflowed")
c
LovelaceTooLarge c -> bprint
("Lovelace value, " . build . ", exceeds maximum, " . build)
c
maxLovelaceVal
LovelaceTooSmall c -> bprint
("Lovelace value, " . build . ", is less than minimum, " . build)
c
(minBound :: Lovelace)
LovelaceUnderflow c c' -> bprint
("Lovelace underflow when subtracting " . build . " from " . build)
c'
c

instance ToCBOR LovelaceError where
toCBOR = \case
LovelaceOverflow c ->
encodeListLen 2 <> toCBOR @Word8 0 <> toCBOR c
LovelaceTooLarge c ->
encodeListLen 2 <> toCBOR @Word8 1 <> toCBOR c
LovelaceTooSmall c ->
encodeListLen 2 <> toCBOR @Word8 2 <> toCBOR c
LovelaceUnderflow c c' ->
encodeListLen 3 <> toCBOR @Word8 3 <> toCBOR c <> toCBOR c'

instance FromCBOR LovelaceError where
fromCBOR = do
len <- decodeListLen
let checkSize size = matchSize "LovelaceError" size len
tag <- decodeWord8
case tag of
0 -> checkSize 2 >> LovelaceOverflow <$> fromCBOR
1 -> checkSize 2 >> LovelaceTooLarge <$> fromCBOR
2 -> checkSize 2 >> LovelaceTooSmall <$> fromCBOR
3 -> checkSize 3 >> LovelaceUnderflow <$> fromCBOR <*> fromCBOR
_ -> cborError $ DecoderErrorUnknownTag "TxValidationError" tag

-- | Maximal possible value of 'Lovelace'
maxLovelaceVal :: Word64
maxLovelaceVal = 45e15

-- | Constructor for 'Lovelace' returning 'LovelaceError' when @c@ exceeds
-- 'maxLovelaceVal'
mkLovelace :: Word64 -> Either LovelaceError Lovelace
mkLovelace c
| c <= maxLovelaceVal = Right (Lovelace c)
| otherwise = Left (LovelaceTooLarge (toInteger c))
{-# INLINE mkLovelace #-}

-- | Construct a 'Lovelace' from a 'KnownNat', known to be less than
-- 'maxLovelaceVal'
mkKnownLovelace :: forall n . (KnownNat n, n <= 45000000000000000) => Lovelace
mkKnownLovelace = Lovelace . fromIntegral . natVal $ Proxy @n
fromJSON = fmap (Lovelace . (fromIntegral :: Word64 -> Natural))
. Canonical.fromJSON

naturalToLovelace :: Natural -> Lovelace
naturalToLovelace = Lovelace

lovelaceToNatural :: Lovelace -> Natural
lovelaceToNatural (Lovelace n) = n

lovelaceToInteger :: Lovelace -> Integer
lovelaceToInteger = toInteger . lovelaceToNatural

-- | Lovelace formatter which restricts type.
lovelaceF :: Format r (Lovelace -> r)
lovelaceF = build

-- | Unwraps 'Lovelace'. It's called “unsafe” so that people wouldn't use it
-- willy-nilly if they want to sum lovelace or something. It's actually safe.
unsafeGetLovelace :: Lovelace -> Word64
unsafeGetLovelace = getLovelace
{-# INLINE unsafeGetLovelace #-}

-- | Compute sum of all lovelace in container. Result is 'Integer' as a
-- protection against possible overflow.
sumLovelace
:: (Foldable t, Functor t) => t Lovelace -> Either LovelaceError Lovelace
sumLovelace = integerToLovelace . sum . map lovelaceToInteger
sumLovelace :: Foldable t => t Lovelace -> Lovelace
sumLovelace = fold

lovelaceToInteger :: Lovelace -> Integer
lovelaceToInteger = toInteger . unsafeGetLovelace
{-# INLINE lovelaceToInteger #-}
kantp marked this conversation as resolved.
Show resolved Hide resolved

-- | Addition of lovelace, returning 'LovelaceError' in case of overflow
addLovelace :: Lovelace -> Lovelace -> Either LovelaceError Lovelace
addLovelace (Lovelace a) (Lovelace b)
| res >= a && res >= b && res <= maxLovelaceVal = Right (Lovelace res)
| otherwise = Left (LovelaceOverflow res)
where res = a + b
{-# INLINE addLovelace #-}
-- | Addition of lovelace.
addLovelace :: Lovelace -> Lovelace -> Lovelace
addLovelace (Lovelace a) (Lovelace b) = Lovelace (a + b)

-- | Subtraction of lovelace, returning 'LovelaceError' on underflow
subLovelace :: Lovelace -> Lovelace -> Either LovelaceError Lovelace
subLovelace :: Lovelace -> Lovelace -> Maybe Lovelace
subLovelace (Lovelace a) (Lovelace b)
| a >= b = Right (Lovelace (a - b))
| otherwise = Left (LovelaceUnderflow a b)

-- | Scale a 'Lovelace' by an 'Integral' factor, returning 'LovelaceError' when
-- the result is too large
scaleLovelace :: Integral b => Lovelace -> b -> Either LovelaceError Lovelace
scaleLovelace (Lovelace a) b = integerToLovelace $ toInteger a * toInteger b
{-# INLINE scaleLovelace #-}

-- | Integer division of a 'Lovelace' by an 'Integral' factor
divLovelace :: Integral b => Lovelace -> b -> Either LovelaceError Lovelace
divLovelace (Lovelace a) b = integerToLovelace $ toInteger a `div` toInteger b
{-# INLINE divLovelace #-}

-- | Integer modulus of a 'Lovelace' by an 'Integral' factor
modLovelace :: Integral b => Lovelace -> b -> Either LovelaceError Lovelace
modLovelace (Lovelace a) b = integerToLovelace $ toInteger a `mod` toInteger b
{-# INLINE modLovelace #-}

integerToLovelace :: Integer -> Either LovelaceError Lovelace
integerToLovelace n
| n < 0 = Left (LovelaceTooSmall n)
| n <= lovelaceToInteger (maxBound :: Lovelace) = Right
$ Lovelace (fromInteger n)
| otherwise = Left (LovelaceTooLarge n)
| a >= b = Just $! Lovelace (a - b)
| otherwise = Nothing

-- | Scale a 'Lovelace' by an 'Natural' factor.
scaleLovelace :: Lovelace -> Natural -> Lovelace
scaleLovelace (Lovelace a) b = Lovelace (a * b)

-- | Scale a 'Lovelace' by a 'Rational' factor between @0..1@, rounding down.
scaleLovelaceRational :: Lovelace -> Rational -> Lovelace
scaleLovelaceRational (Lovelace a) b =
Lovelace (a * n `div` d)
where
n, d :: Natural
n = fromInteger (numerator b)
d = fromInteger (denominator b)

-- | Integer division of a 'Lovelace' by an 'Natural' factor
divLovelace :: Lovelace -> Natural -> Lovelace
divLovelace (Lovelace a) b = Lovelace (a `div` b)

-- | Integer modulus of a 'Lovelace' by an 'Natural' factor
modLovelace :: Lovelace -> Natural -> Lovelace
modLovelace (Lovelace a) b = Lovelace (a `mod` b)

Loading