Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit 499bb3e

Browse files
Merge pull request #3704 from input-output-hk/KtorZ/CBR-462/fix-0-fee-transaction
[CBR-462] Fix 0 fee transaction
2 parents 594204f + 527afc7 commit 499bb3e

File tree

2 files changed

+31
-9
lines changed

2 files changed

+31
-9
lines changed

wallet-new/src/Cardano/Wallet/Kernel/CoinSelection/Generic.hs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -590,16 +590,30 @@ nLargestFromListBy f n = \xs ->
590590
dropOne (Just [_]) = Nothing
591591
dropOne (Just (_:as)) = Just as
592592

593-
-- | Proportionally divide the fee over each output
593+
-- | Proportionally divide the fee over each output.
594+
--
595+
-- There's a special 'edge-case' when the given input is a singleton list
596+
-- with one 0 coin. This is artifically created during input selection when
597+
-- the transaction's amount matches exactly the source's balance.
598+
-- In such case, we can't really compute any ratio for fees and simply return
599+
-- the whole fee back with the given change value.
594600
divvyFee :: forall dom a. CoinSelDom dom
595601
=> (a -> Value dom) -> Fee dom -> [a] -> [(Fee dom, a)]
596-
divvyFee _ _ [] = error "divvyFee: empty list"
597-
divvyFee f fee as = map (\a -> (feeForOut a, a)) as
602+
divvyFee _ _ [] = error "divvyFee: empty list"
603+
divvyFee f fee [a] | f a == valueZero = [(fee, a)]
604+
divvyFee f fee as = map (\a -> (feeForOut a, a)) as
598605
where
599606
-- All outputs are selected from well-formed UTxO, so their sum cannot
600607
-- overflow
601608
totalOut :: Value dom
602-
totalOut = unsafeValueSum (map f as)
609+
totalOut =
610+
let
611+
total = unsafeValueSum (map f as)
612+
in
613+
if total == valueZero then
614+
error "divyyFee: invalid set of coins, total is 0"
615+
else
616+
total
603617

604618
-- The ratio will be between 0 and 1 so cannot overflow
605619
feeForOut :: a -> Fee dom

wallet-new/src/Cardano/Wallet/Kernel/CoinSelection/Generic/Fees.hs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,26 @@ senderPaysFee pickUtxo totalFee css = do
9090
let (css', remainingFee) = feeFromChange totalFee css
9191
(css', ) <$> coverRemainingFee pickUtxo remainingFee
9292

93-
coverRemainingFee :: forall utxo e m. (Monad m, CoinSelDom (Dom utxo))
94-
=> (Value (Dom utxo) -> CoinSelT utxo e m (UtxoEntry (Dom utxo)))
93+
coverRemainingFee :: forall utxo m. (Monad m, CoinSelDom (Dom utxo))
94+
=> (Value (Dom utxo) -> CoinSelT utxo CoinSelHardErr m (UtxoEntry (Dom utxo)))
9595
-> Fee (Dom utxo)
96-
-> CoinSelT utxo e m (SelectedUtxo (Dom utxo))
96+
-> CoinSelT utxo CoinSelHardErr m (SelectedUtxo (Dom utxo))
9797
coverRemainingFee pickUtxo fee = go emptySelection
9898
where
99+
-- | In this context, @CoinSelHardErrUtxoDepleted@ might be thrown by
100+
-- `pickUtxo` as we iterate which here means that we are running out of
101+
-- UTxOs to cover the fee, and therefore, remap the error accordingly.
102+
remapUtxoDepleted :: CoinSelHardErr -> CoinSelHardErr
103+
remapUtxoDepleted CoinSelHardErrUtxoDepleted = CoinSelHardErrCannotCoverFee
104+
remapUtxoDepleted err = err
105+
99106
go :: SelectedUtxo (Dom utxo)
100-
-> CoinSelT utxo e m (SelectedUtxo (Dom utxo))
107+
-> CoinSelT utxo CoinSelHardErr m (SelectedUtxo (Dom utxo))
101108
go !acc
102109
| selectedBalance acc >= getFee fee = return acc
103110
| otherwise = do
104-
io <- pickUtxo $ unsafeValueSub (getFee fee) (selectedBalance acc)
111+
io <- (pickUtxo $ unsafeValueSub (getFee fee) (selectedBalance acc))
112+
`catchError` (throwError . remapUtxoDepleted)
105113
go (select io acc)
106114

107115
-- | Attempt to pay the fee from change outputs, returning any fee remaining

0 commit comments

Comments
 (0)