Skip to content

Commit

Permalink
move prop_rebalanceSelection from byron package to core package
Browse files Browse the repository at this point in the history
It was originally in `-byron` because it was using the fee policy and
fee estimation from Byron, so, while moving it, I also changed the way
the transaction size is estimated to make it mimics the way it's done on
shelley and byron, with rather realistic fee values.
  • Loading branch information
KtorZ committed Jul 3, 2020
1 parent eebc981 commit 08a6cbe
Show file tree
Hide file tree
Showing 2 changed files with 66 additions and 50 deletions.
45 changes: 1 addition & 44 deletions lib/byron/test/unit/Cardano/Wallet/Byron/TransactionSpec.hs
Expand Up @@ -48,12 +48,7 @@ import Cardano.Wallet.Primitive.AddressDerivation.Byron
import Cardano.Wallet.Primitive.AddressDerivation.Icarus
( IcarusKey (..) )
import Cardano.Wallet.Primitive.CoinSelection
( CoinSelection (..)
, CoinSelectionOptions (..)
, changeBalance
, inputBalance
, outputBalance
)
( CoinSelection (..), CoinSelectionOptions (..) )
import Cardano.Wallet.Primitive.CoinSelection.LargestFirst
( largestFirst )
import Cardano.Wallet.Primitive.Fee
Expand All @@ -62,7 +57,6 @@ import Cardano.Wallet.Primitive.Fee
, FeePolicy (..)
, OnDanglingChange (..)
, adjustForFee
, rebalanceSelection
)
import Cardano.Wallet.Primitive.Types
( Address (..)
Expand Down Expand Up @@ -165,11 +159,6 @@ spec = do
Left e -> expectationFailure $ "failed with: " <> show e
Right{}-> pure ()

it "1561 - The fee balancing algorithm converges for any coin selection."
$ property
$ withMaxSuccess 10000
$ forAllBlind (genSelection @'Mainnet @ByronKey) prop_rebalanceSelection

describe "Fee estimation calculation" $ do
it "Byron / Mainnet" $ property $
propSizeEstimation @'Mainnet @ByronKey mainnetMagic
Expand Down Expand Up @@ -285,38 +274,6 @@ spec = do
Properties
-------------------------------------------------------------------------------}

prop_rebalanceSelection
:: CoinSelection
-> OnDanglingChange
-> Property
prop_rebalanceSelection sel onDangling = do
let (sel', fee') = rebalanceSelection opts sel
let prop = case onDangling of
PayAndBalance ->
fee' /= Fee 0 || Fee (delta sel') == estimateFee opts sel'
SaveMoney ->
fee' /= Fee 0 || Fee (delta sel') >= estimateFee opts sel'
property prop
& counterexample (unlines
[ "selection (before):", pretty sel
, "selection (after):", pretty sel'
, "delta (before): " <> show (delta sel)
, "delta (after): " <> show (delta sel')
, "remaining fee: " <> show (getFee fee')
])
where
delta s = inputBalance s - (outputBalance s + changeBalance s)
opts = FeeOptions
{ estimateFee = minimumFee tlayer feePolicy []
, dustThreshold = minBound
, onDanglingChange = onDangling
}
where
tlayer =
newTransactionLayer @'Mainnet @ByronKey Proxy mainnetMagic
feePolicy =
LinearFee (Quantity 155381) (Quantity 43) (Quantity 0)

propSizeEstimation
:: forall n k.
( WalletKey k
Expand Down
71 changes: 65 additions & 6 deletions lib/core/test/unit/Cardano/Wallet/Primitive/FeeSpec.hs
Expand Up @@ -13,7 +13,7 @@ module Cardano.Wallet.Primitive.FeeSpec
import Prelude

import Cardano.Wallet.Primitive.CoinSelection
( CoinSelection (..) )
( CoinSelection (..), changeBalance, inputBalance, outputBalance )
import Cardano.Wallet.Primitive.CoinSelection.LargestFirst
( largestFirst )
import Cardano.Wallet.Primitive.Fee
Expand All @@ -23,6 +23,7 @@ import Cardano.Wallet.Primitive.Fee
, OnDanglingChange (..)
, adjustForFee
, divvyFee
, rebalanceSelection
)
import Cardano.Wallet.Primitive.Types
( Address (..)
Expand All @@ -45,14 +46,16 @@ import Crypto.Random.Types
( withDRG )
import Data.Either
( isRight )
import Data.Function
( (&) )
import Data.Functor.Identity
( Identity (runIdentity) )
import Data.List.NonEmpty
( NonEmpty )
import Data.Word
( Word64 )
import Fmt
( Buildable (..), nameF, tupleF )
( Buildable (..), nameF, pretty, tupleF )
import Test.Hspec
( Spec, SpecWith, before, describe, it, shouldBe, shouldSatisfy )
import Test.QuickCheck
Expand All @@ -62,10 +65,12 @@ import Test.QuickCheck
, Property
, checkCoverage
, choose
, counterexample
, coverTable
, disjoin
, elements
, expectFailure
, forAllBlind
, generate
, property
, scale
Expand Down Expand Up @@ -349,6 +354,13 @@ spec = do
it "expectFailure: empty list"
(expectFailure propDivvyFeeInvariantEmptyList)

describe "prop_rebalanceSelection" $ do
it "The fee balancing algorithm converges for any coin selection."
$ property
$ withMaxSuccess 10000
$ forAllBlind genSelection prop_rebalanceSelection


{-------------------------------------------------------------------------------
Fee Adjustment - Properties
-------------------------------------------------------------------------------}
Expand Down Expand Up @@ -473,6 +485,44 @@ propDivvyFeeInvariantEmptyList (fee, outs) =
where
prop = divvyFee fee outs `seq` True

{-------------------------------------------------------------------------------
Fee Adjustment - properties
-------------------------------------------------------------------------------}

prop_rebalanceSelection
:: CoinSelection
-> OnDanglingChange
-> Property
prop_rebalanceSelection sel onDangling = do
let (sel', fee') = rebalanceSelection opts sel
let prop = case onDangling of
PayAndBalance ->
fee' /= Fee 0 || Fee (delta sel') == estimateFee opts sel'
SaveMoney ->
fee' /= Fee 0 || Fee (delta sel') >= estimateFee opts sel'
property prop
& counterexample (unlines
[ "selection (before):", pretty sel
, "selection (after):", pretty sel'
, "delta (before): " <> show (delta sel)
, "delta (after): " <> show (delta sel')
, "remaining fee: " <> show (getFee fee')
])
where
delta s = inputBalance s - (outputBalance s + changeBalance s)
opts = FeeOptions
-- NOTE
-- Dummy fee policy but, following a similar rule as the fee policy on
-- Byron / Shelley (bigger transaction cost more) with sensible values.
{ estimateFee = \cs ->
let
size = fromIntegral $ length $ show cs
in
Fee (100000 + 100 * size)
, dustThreshold = minBound
, onDanglingChange = onDangling
}

{-------------------------------------------------------------------------------
Fee Adjustment - Unit Tests
-------------------------------------------------------------------------------}
Expand Down Expand Up @@ -568,12 +618,17 @@ genTxOut coins = do
outs <- vector n
return $ zipWith TxOut outs coins

genSelection :: NonEmpty TxOut -> Gen CoinSelection
genSelection outs = do
genSelection :: Gen CoinSelection
genSelection = do
outs <- choose (1, 10) >>= vector >>= genTxOut
genSelectionFor (NE.fromList outs)

genSelectionFor :: NonEmpty TxOut -> Gen CoinSelection
genSelectionFor outs = do
let opts = CS.CoinSelectionOptions (const 100) (const $ pure ())
utxo <- vector (NE.length outs * 3) >>= genUTxO
case runIdentity $ runExceptT $ largestFirst opts outs utxo of
Left _ -> genSelection outs
Left _ -> genSelectionFor outs
Right (s,_) -> return s

instance Arbitrary TxIn where
Expand Down Expand Up @@ -645,7 +700,11 @@ instance Arbitrary CoinSelection where
outs <- choose (1, 10)
>>= vector
>>= genTxOut
genSelection (NE.fromList outs)
genSelectionFor (NE.fromList outs)

instance Arbitrary OnDanglingChange
where
arbitrary = elements [ PayAndBalance, SaveMoney ]

instance Arbitrary FeeOptions where
arbitrary = do
Expand Down

0 comments on commit 08a6cbe

Please sign in to comment.