Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
review fee calculation for addresses
Browse files Browse the repository at this point in the history
KtorZ committed Apr 26, 2019

Verified

This commit was signed with the committer’s verified signature.
KtorZ Matthias Benkort
1 parent cbd317d commit 6c87d3a
Showing 2 changed files with 83 additions and 52 deletions.
48 changes: 25 additions & 23 deletions src/Cardano/Wallet/CoinSelection/Fee.hs
Original file line number Diff line number Diff line change
@@ -21,7 +21,6 @@ module Cardano.Wallet.CoinSelection.Fee
, FeeOptions (..)
, FeeError(..)
, TxSizeLinear (..)
, AddressScheme (..)
, Network (..)
, adjustForFees
, cardanoPolicy
@@ -33,7 +32,8 @@ import Prelude
import Cardano.Wallet.CoinSelection
( CoinSelection (..) )
import Cardano.Wallet.Primitive.Types
( Coin (..)
( Address (..)
, Coin (..)
, TxIn
, TxOut (..)
, UTxO (..)
@@ -62,6 +62,7 @@ import GHC.Generics

import qualified Codec.CBOR.Encoding as CBOR
import qualified Codec.CBOR.Write as CBOR
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import qualified Data.List as L

@@ -358,11 +359,8 @@ data TxSizeLinear =
TxSizeLinear (Quantity "lovelace" Double) (Quantity "lovelace/byte" Double)
deriving (Eq, Show)

data AddressScheme = Sequential | Random
deriving (Eq, Show)

data Network = Mainnet | Testnet
deriving (Eq, Show)
deriving (Generic, Eq, Show)

cardanoPolicy :: TxSizeLinear
cardanoPolicy = TxSizeLinear (Quantity 155381) (Quantity 43.946)
@@ -374,13 +372,14 @@ cardanoPolicy = TxSizeLinear (Quantity 155381) (Quantity 43.946)
-- the transaction attributes @Attributes ()@ are both hard-coded
estimateCardanoFee
:: TxSizeLinear
-> AddressScheme
-> Network
-> CoinSelection
-> Fee
estimateCardanoFee (TxSizeLinear (Quantity a) (Quantity b)) scheme net (CoinSelection inps outs chngs) =
estimateCardanoFee fee network (CoinSelection inps outs chngs) =
Fee $ ceiling (a + b*totalPayload)
where
TxSizeLinear (Quantity a) (Quantity b) = fee

-- With `n` the number of inputs
-- signed tx ----------------------------------- 9 + n * 42 + n * 139 + Σ sizeOf(output)
-- | list len 2 -- 1
@@ -437,14 +436,20 @@ estimateCardanoFee (TxSizeLinear (Quantity a) (Quantity b)) scheme net (CoinSele
-- | | attributes -- 12-19
-- | | word8 -- 1
-- | word32 -- 1-4
sizeOfTxOut :: Word64 -> Int
sizeOfTxOut =
let toAdd = case (scheme, net) of
(Sequential, Mainnet) -> 48
(Sequential, Testnet) -> 56
(Random, Mainnet) -> 69
(Random, Testnet) -> 77
in (+ toAdd) . sizeOf . CBOR.encodeWord64
sizeOfTxOut :: TxOut -> Int
sizeOfTxOut (TxOut (Address bytes) c) =
5 + BS.length bytes + sizeOfCoin c

-- Compute the size of a coin
sizeOfCoin :: Coin -> Int
sizeOfCoin = sizeOf . CBOR.encodeWord64 . getCoin

-- Compute the size of the change, we assume that change is necessarily
-- using a sequential scheme. For the rest, cf 'sizeOfTxOut'
sizeOfChange :: Coin -> Int
sizeOfChange c = case network of
Mainnet -> 5 + 1 + sizeOfCoin c
Testnet -> 5 + 8 + sizeOfCoin c

-- tx ---------------------------------- 6 + (n * 42) + Σ sizeOf(output)
-- | list len 3 -- 1
@@ -456,13 +461,10 @@ estimateCardanoFee (TxSizeLinear (Quantity a) (Quantity b)) scheme net (CoinSele
-- | break -- 1
-- | empty attributes -- 1
sizeOfTx :: Int
sizeOfTx =
let n = length inps
coinOuts = map (getCoin . coin) outs
coinChns = map getCoin chngs
coins = coinOuts ++ coinChns
cborOverhead = 6
in cborOverhead + sum (map sizeOfTxIn [0..(n-1)]) + sum (map sizeOfTxOut coins)
sizeOfTx = 6
+ sum (map sizeOfTxIn [0..(length inps-1)])
+ sum (map sizeOfTxOut outs)
+ sum (map sizeOfChange chngs)

-- witness ---------------------------------- 139
-- | list len 2 -- 1
87 changes: 58 additions & 29 deletions test/unit/Cardano/Wallet/CoinSelection/FeeSpec.hs
Original file line number Diff line number Diff line change
@@ -15,8 +15,7 @@ import Cardano.Wallet.Binary
import Cardano.Wallet.CoinSelection
( CoinSelection (..), CoinSelectionOptions (..) )
import Cardano.Wallet.CoinSelection.Fee
( AddressScheme (..)
, Fee (..)
( Fee (..)
, FeeError (..)
, FeeOptions (..)
, Network (..)
@@ -78,6 +77,9 @@ import Test.QuickCheck
, (===)
, (==>)
)
import Test.QuickCheck.Arbitrary.Generic
( genericArbitrary, genericShrink )


import qualified Cardano.Wallet.CoinSelection as CS
import qualified Data.ByteString.Lazy as BL
@@ -361,9 +363,9 @@ propReducedChanges drg (ShowFmt (FeeProp (CoinSelProp utxo txOuts) utxo' (fee, d
propFeeEstimation
:: FeeEstimation
-> Property
propFeeEstimation (FeeEstimation (ShowFmt sel)) =
propFeeEstimation (FeeEstimation network (ShowFmt sel)) =
let
(Fee estFee) = estimateCardanoFee cardanoPolicy Random Testnet sel
(Fee estFee) = estimateCardanoFee cardanoPolicy network sel
(TxSizeLinear (Quantity a) (Quantity b)) = cardanoPolicy
tx = fromCoinSelection sel
size = BL.length $ toLazyByteString $ encodeSignedTx tx
@@ -374,12 +376,32 @@ propFeeEstimation (FeeEstimation (ShowFmt sel)) =
fromCoinSelection :: CoinSelection -> (Tx, [TxWitness])
fromCoinSelection (CoinSelection inps outs chngs) =
let
txIns = zipWith TxIn (replicate (length inps) inputId0) [0..]
coins = map coin outs ++ chngs
txOuts = zipWith TxOut (replicate (length coins) address0) coins
wits = replicate (length inps) (PublicKeyWitness pkWitness)
dummyRndAddr = case network of
Mainnet ->
addr58
"DdzFFzCqrhsw6TjWD5zcSo1wzYJPRnweuAt1DPJccYxaBudJpNMG\
\qoQtvhFhiRrRM9L2qBEkjRgFtSTg84rUUytaSCwQ48nLmZEJBiLy"
Testnet ->
addr58
"DdzFFzCqrhsug8jKBMV5Cr94hKY4DrbJtkUpqptoGEkovR2QSkcA\
\cRgjnUyegE689qBX6b2kyxyNvCL6mfqiarzRB9TRq8zwJphR31pr"

dummySeqAddr = case network of
Mainnet ->
addr58
"Ae2tdPwUPEZLLeQYaBmNXiwrv9Mf13eauCxgHxZYF1EhDKMTKR5t\
\1dFrSCU"
Testnet ->
addr58
"2cWKMJemoBaiH2NVgNh9aPDt5wr7i1YWZ8ySbFkpx7nbbs3x38gB\
\ec4NJc8vSgEVRV2G7"

txIns = zipWith TxIn (replicate (length inps) dummyInput) [0..]
txChngs = zipWith TxOut (replicate (length chngs) dummySeqAddr) chngs
coins = map coin outs
wits = replicate (length inps) (PublicKeyWitness dummyWitness)
in
(Tx txIns txOuts, wits)
(Tx txIns (outs <> txChngs), wits)

-- | Make a Hash from a Base16 encoded string, without error handling.
hash16 :: Text -> Hash "Tx"
@@ -395,9 +417,8 @@ propFeeEstimation (FeeEstimation (ShowFmt sel)) =
error ("addr58: Could not decode because " <> err)
Right res -> res

inputId0 = hash16 "60dbb2679ee920540c18195a3d92ee9be50aee6ed5f891d92d51db8a76b02cd2"
address0 = addr58 "DdzFFzCqrhsug8jKBMV5Cr94hKY4DrbJtkUpqptoGEkovR2QSkcAcRgjnUyegE689qBX6b2kyxyNvCL6mfqiarzRB9TRq8zwJphR31pr"
pkWitness = "\130X@\226E\220\252\DLE\170\216\210\164\155\182mm$ePG\252\186\195\225_\b=\v\241=\255 \208\147[\239\RS\170|\214\202\247\169\229\205\187O_)\221\175\155?e\198\248\170\157-K\155\169z\144\174\ENQhX@\193\151*,\NULz\205\234\&1tL@\211\&2\165\129S\STXP\164C\176 Xvf\160|;\CANs{\SYN\204<N\207\154\130\225\229\t\172mbC\139\US\159\246\168x\163Mq\248\145)\160|\139\207-\SI"
dummyInput = hash16 "60dbb2679ee920540c18195a3d92ee9be50aee6ed5f891d92d51db8a76b02cd2"
dummyWitness = "\130X@\226E\220\252\DLE\170\216\210\164\155\182mm$ePG\252\186\195\225_\b=\v\241=\255 \208\147[\239\RS\170|\214\202\247\169\229\205\187O_)\221\175\155?e\198\248\170\157-K\155\169z\144\174\ENQhX@\193\151*,\NULz\205\234\&1tL@\211\&2\165\129S\STXP\164C\176 Xvf\160|;\CANs{\SYN\204<N\207\154\130\225\229\t\172mbC\139\US\159\246\168x\163Mq\248\145)\160|\139\207-\SI"


{-------------------------------------------------------------------------------
@@ -478,11 +499,24 @@ data FeeOutput = FeeOutput
Arbitrary Instances
-------------------------------------------------------------------------------}

newtype FeeEstimation = FeeEstimation (ShowFmt CoinSelection)
deriving (Eq, Show)
instance Arbitrary FeeProp where
shrink (FeeProp cc utxo opts) =
(\(cc', utxo') -> FeeProp cc' utxo' opts)
<$> zip (shrink cc) (shrink utxo)
arbitrary = do
cc <- arbitrary
utxo <- arbitrary
fee <- choose (100000, 500000)
dust <- choose (0, 10000)
return $ FeeProp cc utxo (fee, dust)

data FeeEstimation = FeeEstimation
{ fsNetwork :: Network
, fsCoinSelection :: ShowFmt CoinSelection
} deriving (Eq, Show)

instance Arbitrary FeeEstimation where
shrink (FeeEstimation (ShowFmt (CoinSelection inps outs chgs))) =
shrink (FeeEstimation net (ShowFmt sel@(CoinSelection inps outs chgs))) =
case (inps, outs, chgs) of
([_], [_], []) ->
[]
@@ -496,28 +530,23 @@ instance Arbitrary FeeEstimation where
outs'' = if length outs > 1 then drop 1 outs else outs
chgs'' = drop 1 chgs
in
FeeEstimation . ShowFmt <$>
FeeEstimation net . ShowFmt <$> filter (/= sel)
[ CoinSelection inps' outs' chgs'
, CoinSelection inps'' outs'' chgs''
]
arbitrary = do
chgs <- choose (0, 10)
>>= \n -> vectorOf n arbitrary
inps <- choose (0, 100)
inps <- choose (1, 100)
>>= \n -> vectorOf n arbitrary
>>= fmap (Map.toList . getUTxO) . genUTxO
outs <- choose (0, 100)
outs <- choose (1, 100)
>>= \n -> vectorOf n arbitrary
>>= genTxOut
return $ FeeEstimation $ ShowFmt $ CoinSelection inps outs chgs
FeeEstimation
<$> arbitrary
<*> pure (ShowFmt $ CoinSelection inps outs chgs)

instance Arbitrary FeeProp where
shrink (FeeProp cc utxo opts) =
(\(cc', utxo') -> FeeProp cc' utxo' opts)
<$> zip (shrink cc) (shrink utxo)
arbitrary = do
cc <- arbitrary
utxo <- arbitrary
fee <- choose (100000, 500000)
dust <- choose (0, 10000)
return $ FeeProp cc utxo (fee, dust)
instance Arbitrary Network where
shrink = genericShrink
arbitrary = genericArbitrary

0 comments on commit 6c87d3a

Please sign in to comment.