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 20 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
72 changes: 44 additions & 28 deletions cardano-ledger/src/Cardano/Chain/Common/Lovelace.hs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ module Cardano.Chain.Common.Lovelace
(
-- * Lovelace
Lovelace
, LovelaceError(..)
, maxLovelaceVal

-- Only export the error cases that are still possible:
, LovelaceError(LovelaceTooSmall, LovelaceUnderflow)

-- * Constructors
, mkLovelace
Expand All @@ -34,15 +35,18 @@ module Cardano.Chain.Common.Lovelace
, lovelaceF

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

-- * Arithmetic operations
, sumLovelace
, addLovelace
, subLovelace
, scaleLovelace
, scaleLovelaceRational
, divLovelace
, modLovelace
)
Expand All @@ -51,6 +55,7 @@ where
import Cardano.Prelude

import Data.Data (Data)
import Data.Monoid (Monoid(..))
import Formatting (Format, bprint, build, int, sformat)
import qualified Formatting.Buildable as B
import GHC.TypeLits (type (<=))
Expand All @@ -70,19 +75,21 @@ import Cardano.Binary

-- | Lovelace is the least possible unit of currency
newtype Lovelace = Lovelace
{ getLovelace :: Word64
{ getLovelace :: 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)
encodedSizeExpr size _pxy = encodedSizeExpr size (Proxy :: Proxy Word64)

instance FromCBOR Lovelace where
fromCBOR = do
Expand All @@ -95,7 +102,17 @@ instance Monad m => Canonical.ToJSON m Lovelace where
toJSON = Canonical.toJSON . unsafeGetLovelace

instance Canonical.ReportSchemaErrors m => Canonical.FromJSON m Lovelace where
fromJSON = fmap Lovelace . Canonical.fromJSON
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

data LovelaceError
= LovelaceOverflow Word64
Expand All @@ -116,7 +133,7 @@ instance B.Buildable LovelaceError where
LovelaceTooSmall c -> bprint
("Lovelace value, " . build . ", is less than minimum, " . build)
c
(minBound :: Lovelace)
(Lovelace 0)
LovelaceUnderflow c c' -> bprint
("Lovelace underflow when subtracting " . build . " from " . build)
c'
Expand Down Expand Up @@ -152,9 +169,7 @@ 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))
mkLovelace c = Right (Lovelace (fromIntegral c))
{-# INLINE mkLovelace #-}

-- | Construct a 'Lovelace' from a 'KnownNat', known to be less than
Expand All @@ -169,7 +184,7 @@ 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
unsafeGetLovelace = fromIntegral . getLovelace
{-# INLINE unsafeGetLovelace #-}

-- | Compute sum of all lovelace in container. Result is 'Integer' as a
Expand All @@ -178,30 +193,32 @@ sumLovelace
:: (Foldable t, Functor t) => t Lovelace -> Either LovelaceError Lovelace
sumLovelace = integerToLovelace . sum . map lovelaceToInteger

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
-- | Addition of lovelace.
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
addLovelace (Lovelace a) (Lovelace b) = Right (Lovelace (a + b))
{-# INLINE addLovelace #-}

-- | Subtraction of lovelace, returning 'LovelaceError' on underflow
subLovelace :: Lovelace -> Lovelace -> Either LovelaceError Lovelace
subLovelace (Lovelace a) (Lovelace b)
| a >= b = Right (Lovelace (a - b))
| otherwise = Left (LovelaceUnderflow a b)
| otherwise = Left (LovelaceUnderflow (fromIntegral a) (fromIntegral 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 #-}

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

-- | 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
Expand All @@ -215,6 +232,5 @@ modLovelace (Lovelace a) b = integerToLovelace $ toInteger a `mod` toInteger b
integerToLovelace :: Integer -> Either LovelaceError Lovelace
integerToLovelace n
| n < 0 = Left (LovelaceTooSmall n)
| n <= lovelaceToInteger (maxBound :: Lovelace) = Right
$ Lovelace (fromInteger n)
| otherwise = Left (LovelaceTooLarge n)
| otherwise = Right (Lovelace (fromInteger n))

176 changes: 50 additions & 126 deletions cardano-ledger/src/Cardano/Chain/Common/LovelacePortion.hs
Original file line number Diff line number Diff line change
@@ -1,176 +1,100 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE ExplicitNamespaces #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NumDecimals #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}

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

module Cardano.Chain.Common.LovelacePortion
( LovelacePortion(..)
, LovelacePortionError
, mkLovelacePortion
, mkKnownLovelacePortion
, lovelacePortionDenominator
, lovelacePortionFromDouble
, lovelacePortionToDouble
, applyLovelacePortionDown
, applyLovelacePortionUp
( LovelacePortion
, rationalToLovelacePortion
, lovelacePortionToRational
)
where

import Cardano.Prelude

import Control.Monad (fail)
import Control.Monad.Except (MonadError(..))
import Formatting (bprint, build, float, int, sformat)
import Formatting (sformat, build, bprint, float, int)
import qualified Formatting.Buildable as B
import GHC.TypeLits (type (<=))
import Text.JSON.Canonical (FromJSON(..), ToJSON(..))

import Cardano.Binary (FromCBOR(..), ToCBOR(..))
import Cardano.Chain.Common.Lovelace


-- | LovelacePortion is some portion of Lovelace; it is interpreted as a fraction with
-- denominator of 'lovelacePortionDenominator'. The numerator must be in the
-- interval of [0, lovelacePortionDenominator].
-- | 'LovelacePortion' is a legacy Byron type that we keep only for
-- compatibility. It was originally intended to represent a fraction of stake
-- in the system. It is used only for the thresholds used in the update system
-- rules, most of which are now themselves unused. The remaining case is no
-- longer interpreted as a fraction of all stake, but as a fraction of the
-- number of genesis keys.
--
-- It has enormous precision, due to the fact that it was originally intended
-- to represent a fraction of all stake and can cover the precision of all the
-- Lovelace in the system.
--
-- It is represented as a rational nominator with a fixed implicit denominator
-- of 1e15. So the nominator must be in the range @[0..1e15]@. This is also the
-- representation used on-chain (in update proposals) and in the JSON
-- genesis file.
--
-- Usually 'LovelacePortion' is used to determine some threshold expressed as
-- portion of total stake.
-- It is interpreted as a 'Rational' via the provided conversion functions.
--
-- To multiply a lovelace portion by 'Lovelace', use 'applyLovelacePortionDown' (when
-- calculating number of lovelace) or 'applyLovelacePortionUp' (when calculating a
-- threshold).
newtype LovelacePortion = LovelacePortion
{ getLovelacePortion :: Word64
} deriving (Show, Ord, Eq, Generic, HeapWords, NFData, NoUnexpectedThunks)

instance B.Buildable LovelacePortion where
build cp@(getLovelacePortion -> x) = bprint
build cp@(LovelacePortion x) = bprint
(int . "/" . int . " (approx. " . float . ")")
x
lovelacePortionDenominator
(lovelacePortionToDouble cp)
(fromRational (lovelacePortionToRational cp) :: Double)

instance ToCBOR LovelacePortion where
toCBOR = toCBOR . getLovelacePortion

instance FromCBOR LovelacePortion where
fromCBOR = LovelacePortion <$> fromCBOR

-- The Canonical and Aeson instances for LovelacePortion are inconsistent -
-- Canonical reads/writes an integer, but Aeson reads/write a Real in range [0,1]
fromCBOR = do
nominator <- fromCBOR
when (nominator > lovelacePortionDenominator) $
fail "LovelacePortion: value out of bounds [0..1e15]"
return (LovelacePortion nominator)

-- The canonical JSON instance for LovelacePortion uses only the nominator in
-- the external representation, rather than a real in the range [0,1].
-- This is because 'canonical-json' only supports numbers of type @Int54@.
instance Monad m => ToJSON m LovelacePortion where
toJSON = toJSON . getLovelacePortion

instance MonadError SchemaError m => FromJSON m LovelacePortion where
fromJSON val = do
number <- fromJSON val
pure $ LovelacePortion number
nominator <- fromJSON val
when (nominator > lovelacePortionDenominator) $
throwError SchemaError {
seExpected = "LovelacePortion integer in bounds [0..1e15]",
seActual = Just (sformat build nominator)
}
pure (LovelacePortion nominator)

-- | Denominator used by 'LovelacePortion'.
lovelacePortionDenominator :: Word64
lovelacePortionDenominator = 1e15

instance Bounded LovelacePortion where
minBound = LovelacePortion 0
maxBound = LovelacePortion lovelacePortionDenominator

data LovelacePortionError
= LovelacePortionDoubleOutOfRange Double
| LovelacePortionTooLarge Word64
deriving Show

instance B.Buildable LovelacePortionError where
build = \case
LovelacePortionDoubleOutOfRange d -> bprint
( "Double, "
. build
. " , out of range [0, 1] when constructing LovelacePortion"
)
d
LovelacePortionTooLarge c -> bprint
("LovelacePortion, " . build . ", exceeds maximum, " . build)
c
lovelacePortionDenominator

-- | Constructor for 'LovelacePortion', returning 'LovelacePortionError' when @c@
-- exceeds 'lovelacePortionDenominator'
mkLovelacePortion :: Word64 -> Either LovelacePortionError LovelacePortion
mkLovelacePortion c
| c <= lovelacePortionDenominator = Right (LovelacePortion c)
| otherwise = Left (LovelacePortionTooLarge c)

-- | Construct a 'LovelacePortion' from a 'KnownNat', known to be less than
-- 'lovelacePortionDenominator'
mkKnownLovelacePortion
:: forall n . (KnownNat n, n <= 1000000000000000) => LovelacePortion
mkKnownLovelacePortion = LovelacePortion . fromIntegral . natVal $ Proxy @n

-- | Make LovelacePortion from Double. Caller must ensure that value is in [0..1].
-- Internally 'LovelacePortion' stores 'Word64' which is divided by
-- 'lovelacePortionDenominator' to get actual value. So some rounding may take
-- place.
lovelacePortionFromDouble
:: Double -> Either LovelacePortionError LovelacePortion
lovelacePortionFromDouble x
| 0 <= x && x <= 1 = Right (LovelacePortion v)
| otherwise = Left (LovelacePortionDoubleOutOfRange x)
where
v :: Word64
v = round $ realToFrac lovelacePortionDenominator * x
{-# INLINE lovelacePortionFromDouble #-}

lovelacePortionToDouble :: LovelacePortion -> Double
lovelacePortionToDouble (getLovelacePortion -> x) =
realToFrac x / realToFrac lovelacePortionDenominator
{-# INLINE lovelacePortionToDouble #-}

-- | Apply LovelacePortion to Lovelace (with rounding down)
-- | Make a 'LovelacePortion' from a 'Rational'
-- which must be in the range @[0..1]@.
--
-- Use it for calculating lovelace amounts.
applyLovelacePortionDown :: LovelacePortion -> Lovelace -> Lovelace
applyLovelacePortionDown (getLovelacePortion -> p) (unsafeGetLovelace -> c) =
case c' of
Right lovelace -> lovelace
Left err -> panic $ sformat
("The impossible happened in applyLovelacePortionDown: " . build)
err
where
c' =
mkLovelace
. fromInteger
$ toInteger p
* toInteger c
`div` toInteger lovelacePortionDenominator
rationalToLovelacePortion :: Rational -> LovelacePortion
rationalToLovelacePortion r
| r >= 0 && r <= 1 = LovelacePortion
(ceiling (r * toRational lovelacePortionDenominator))
| otherwise = panic "rationalToLovelacePortion: out of range [0..1]"

-- | Apply LovelacePortion to Lovelace (with rounding up)
-- | Turn a 'LovelacePortion' into a 'Rational' in the range @[0..1]@.
--
-- Use it for calculating thresholds.
applyLovelacePortionUp :: LovelacePortion -> Lovelace -> Lovelace
applyLovelacePortionUp (getLovelacePortion -> p) (unsafeGetLovelace -> c) =
case mkLovelace c' of
Right lovelace -> lovelace
Left err -> panic $ sformat
("The impossible happened in applyLovelacePortionUp: " . build)
err
where
(d, m) =
divMod (toInteger p * toInteger c) (toInteger lovelacePortionDenominator)
c' :: Word64
c' = if m > 0 then fromInteger (d + 1) else fromInteger d
lovelacePortionToRational :: LovelacePortion -> Rational
lovelacePortionToRational (LovelacePortion n) =
toInteger n % toInteger lovelacePortionDenominator

Loading