Skip to content

Commit f93927c

Browse files
authored
Merge pull request #128 from mlabs-haskell/issue-32-better-coin-selection
Issue 32 better coin selection
2 parents 8809969 + 08061f1 commit f93927c

File tree

11 files changed

+874
-83
lines changed

11 files changed

+874
-83
lines changed

.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,6 @@ geth-node/chaindata/history
6464
*.~undo-tree~
6565

6666
# debug configs
67-
examples/debug/ci-test.http
68-
examples/debug/run-test-lc.sh
69-
examples/debug/run-test-tn.sh
67+
examples/debug/
68+
69+
visualization/

bot-plutus-interface.cabal

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ library
8282
BotPlutusInterface.BodyBuilder
8383
BotPlutusInterface.CardanoCLI
8484
BotPlutusInterface.ChainIndex
85+
BotPlutusInterface.CoinSelection
8586
BotPlutusInterface.Collateral
8687
BotPlutusInterface.Config
8788
BotPlutusInterface.Contract
@@ -176,6 +177,7 @@ test-suite bot-plutus-interface-test
176177
ghc-options: -fplugin-opt PlutusTx.Plugin:defer-errors
177178
other-modules:
178179
Spec.BotPlutusInterface.Balance
180+
Spec.BotPlutusInterface.CoinSelection
179181
Spec.BotPlutusInterface.Collateral
180182
Spec.BotPlutusInterface.Config
181183
Spec.BotPlutusInterface.Contract
@@ -184,6 +186,7 @@ test-suite bot-plutus-interface-test
184186
Spec.BotPlutusInterface.TxStatusChange
185187
Spec.BotPlutusInterface.UtxoParser
186188
Spec.MockContract
189+
Spec.RandomLedger
187190

188191
build-depends:
189192
, aeson ^>=1.5.0.0
@@ -236,6 +239,7 @@ test-suite bot-plutus-interface-test
236239
, text ^>=1.2.4.0
237240
, utf8-string
238241
, uuid
242+
, vector
239243
, warp
240244

241245
hs-source-dirs: test

cabal.project

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ packages: ./bot-plutus-interface.cabal
77

88
tests: true
99
benchmarks: true
10+
test-show-details: direct

src/BotPlutusInterface/Balance.hs

Lines changed: 37 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ module BotPlutusInterface.Balance (
1313

1414
import BotPlutusInterface.BodyBuilder qualified as BodyBuilder
1515
import BotPlutusInterface.CardanoCLI qualified as CardanoCLI
16+
import BotPlutusInterface.CoinSelection (selectTxIns)
1617
import BotPlutusInterface.Collateral (removeCollateralFromMap)
1718
import BotPlutusInterface.Effects (
1819
PABEffect,
@@ -30,7 +31,6 @@ import BotPlutusInterface.Types (
3031
PABConfig,
3132
collateralTxOutRef,
3233
)
33-
3434
import Cardano.Api (ExecutionUnitPrices (ExecutionUnitPrices))
3535
import Cardano.Api.Shelley (ProtocolParameters (protocolParamPrices))
3636
import Control.Monad (foldM, void, zipWithM)
@@ -40,14 +40,12 @@ import Control.Monad.Trans.Either (EitherT, hoistEither, newEitherT, runEitherT)
4040
import Control.Monad.Trans.Except (throwE)
4141
import Data.Bifunctor (bimap)
4242
import Data.Coerce (coerce)
43-
import Data.Either.Combinators (rightToMaybe)
4443
import Data.Kind (Type)
4544
import Data.List ((\\))
4645
import Data.List qualified as List
4746
import Data.Map (Map)
4847
import Data.Map qualified as Map
4948
import Data.Maybe (fromMaybe, mapMaybe)
50-
import Data.Set (Set)
5149
import Data.Set qualified as Set
5250
import Data.Text (Text)
5351
import Data.Text qualified as Text
@@ -76,7 +74,6 @@ import Ledger.Tx qualified as Tx
7674
import Ledger.Value (Value)
7775
import Ledger.Value qualified as Value
7876
import Plutus.V1.Ledger.Api (
79-
Credential (PubKeyCredential, ScriptCredential),
8077
CurrencySymbol (..),
8178
TokenName (..),
8279
)
@@ -168,6 +165,18 @@ balanceTxIO' balanceCfg pabConf ownPkh unbalancedTx =
168165
-- Get the updated change, add it to the tx
169166
let finalAdaChange = getAdaChange utxoIndex balancedTxWithChange
170167
fullyBalancedTx = addAdaChange balanceCfg changeAddr finalAdaChange balancedTxWithChange
168+
txInfoLog =
169+
printBpiLog @w (Debug [TxBalancingLog]) $
170+
"UnbalancedTx TxInputs: "
171+
<+> pretty (length $ txInputs preBalancedTx)
172+
<+> "UnbalancedTx TxOutputs: "
173+
<+> pretty (length $ txOutputs preBalancedTx)
174+
<+> "TxInputs: "
175+
<+> pretty (length $ txInputs fullyBalancedTx)
176+
<+> "TxOutputs: "
177+
<+> pretty (length $ txOutputs fullyBalancedTx)
178+
179+
lift txInfoLog
171180

172181
-- finally, we must update the signatories
173182
hoistEither $ addSignatories ownPkh privKeys requiredSigs fullyBalancedTx
@@ -193,7 +202,7 @@ balanceTxIO' balanceCfg pabConf ownPkh unbalancedTx =
193202

194203
-- Calculate fees by pre-balancing the tx, building it, and running the CLI on result
195204
txWithoutFees <-
196-
hoistEither $ balanceTxStep balanceCfg minUtxos utxoIndex changeAddr $ tx `withFee` 0
205+
newEitherT $ balanceTxStep @w balanceCfg minUtxos utxoIndex changeAddr $ tx `withFee` 0
197206

198207
exBudget <- newEitherT $ BodyBuilder.buildAndEstimateBudget @w pabConf privKeys txWithoutFees
199208

@@ -204,7 +213,7 @@ balanceTxIO' balanceCfg pabConf ownPkh unbalancedTx =
204213
lift $ printBpiLog @w (Debug [TxBalancingLog]) $ "Fees:" <+> pretty fees
205214

206215
-- Rebalance the initial tx with the above fees
207-
balancedTx <- hoistEither $ balanceTxStep balanceCfg minUtxos utxoIndex changeAddr $ tx `withFee` fees
216+
balancedTx <- newEitherT $ balanceTxStep @w balanceCfg minUtxos utxoIndex changeAddr $ tx `withFee` fees
208217

209218
if balancedTx == tx
210219
then pure (balancedTx, minUtxos)
@@ -272,16 +281,18 @@ calculateMinUtxos pabConf datums txOuts =
272281
zipWithM (fmap . (,)) txOuts <$> mapM (CardanoCLI.calculateMinUtxo @w pabConf datums) txOuts
273282

274283
balanceTxStep ::
284+
forall (w :: Type) (effs :: [Type -> Type]).
285+
Member (PABEffect w) effs =>
275286
BalanceConfig ->
276287
[(TxOut, Integer)] ->
277288
Map TxOutRef TxOut ->
278289
Address ->
279290
Tx ->
280-
Either Text Tx
291+
Eff effs (Either Text Tx)
281292
balanceTxStep balanceCfg minUtxos utxos changeAddr tx =
282-
Right (addLovelaces minUtxos tx)
283-
>>= balanceTxIns utxos
284-
>>= handleNonAdaChange balanceCfg changeAddr utxos
293+
runEitherT $
294+
(newEitherT . balanceTxIns @w utxos) (addLovelaces minUtxos tx)
295+
>>= hoistEither . handleNonAdaChange balanceCfg changeAddr utxos
285296

286297
-- | Get change value of a transaction, taking inputs, outputs, mint and fees into account
287298
getChange :: Map TxOutRef TxOut -> Tx -> Value
@@ -303,45 +314,6 @@ getAdaChange utxos = lovelaceValue . getChange utxos
303314
getNonAdaChange :: Map TxOutRef TxOut -> Tx -> Value
304315
getNonAdaChange utxos = Ledger.noAdaValue . getChange utxos
305316

306-
-- | Getting the necessary utxos to cover the fees for the transaction
307-
collectTxIns :: Set TxIn -> Map TxOutRef TxOut -> Value -> Either Text (Set TxIn)
308-
collectTxIns originalTxIns utxos value =
309-
if isSufficient updatedInputs
310-
then Right updatedInputs
311-
else
312-
Left $
313-
Text.unlines
314-
[ "Insufficient tx inputs, needed: "
315-
, showText (Value.flattenValue value)
316-
, "got:"
317-
, showText (Value.flattenValue (txInsValue updatedInputs))
318-
]
319-
where
320-
updatedInputs =
321-
foldl
322-
( \acc txIn ->
323-
if isSufficient acc
324-
then acc
325-
else Set.insert txIn acc
326-
)
327-
originalTxIns
328-
$ mapMaybe (rightToMaybe . txOutToTxIn) $ Map.toList utxos
329-
330-
isSufficient :: Set TxIn -> Bool
331-
isSufficient txIns' =
332-
not (Set.null txIns') && txInsValue txIns' `Value.geq` value
333-
334-
txInsValue :: Set TxIn -> Value
335-
txInsValue txIns' =
336-
mconcat $ map Tx.txOutValue $ mapMaybe ((`Map.lookup` utxos) . Tx.txInRef) $ Set.toList txIns'
337-
338-
-- Converting a chain index transaction output to a transaction input type
339-
txOutToTxIn :: (TxOutRef, TxOut) -> Either Text TxIn
340-
txOutToTxIn (txOutRef, txOut) =
341-
case addressCredential (txOutAddress txOut) of
342-
PubKeyCredential _ -> Right $ Tx.pubKeyTxIn txOutRef
343-
ScriptCredential _ -> Left "Cannot covert a script output to TxIn"
344-
345317
-- | Add min lovelaces to each tx output
346318
addLovelaces :: [(TxOut, Integer)] -> Tx -> Tx
347319
addLovelaces minLovelaces tx =
@@ -359,17 +331,23 @@ addLovelaces minLovelaces tx =
359331
$ txOutputs tx
360332
in tx {txOutputs = lovelacesAdded}
361333

362-
balanceTxIns :: Map TxOutRef TxOut -> Tx -> Either Text Tx
334+
balanceTxIns ::
335+
forall (w :: Type) (effs :: [Type -> Type]).
336+
Member (PABEffect w) effs =>
337+
Map TxOutRef TxOut ->
338+
Tx ->
339+
Eff effs (Either Text Tx)
363340
balanceTxIns utxos tx = do
364-
let txOuts = Tx.txOutputs tx
365-
nonMintedValue = mconcat (map Tx.txOutValue txOuts) `minus` txMint tx
366-
minSpending =
367-
mconcat
368-
[ txFee tx
369-
, nonMintedValue
370-
]
371-
txIns <- collectTxIns (txInputs tx) utxos minSpending
372-
pure $ tx {txInputs = txIns <> txInputs tx}
341+
runEitherT $ do
342+
let txOuts = Tx.txOutputs tx
343+
nonMintedValue = mconcat (map Tx.txOutValue txOuts) `minus` txMint tx
344+
minSpending =
345+
mconcat
346+
[ txFee tx
347+
, nonMintedValue
348+
]
349+
txIns <- newEitherT $ selectTxIns @w (txInputs tx) utxos minSpending
350+
pure $ tx {txInputs = txIns <> txInputs tx}
373351

374352
-- | Set collateral or fail in case it's required but not available
375353
addTxCollaterals :: CollateralUtxo -> Tx -> Tx
@@ -500,9 +478,6 @@ modifyFirst ::
500478
modifyFirst _ m [] = m Nothing `consJust` []
501479
modifyFirst p m (x : xs) = if p x then m (Just x) `consJust` xs else x : modifyFirst p m xs
502480

503-
showText :: forall (a :: Type). Show a => a -> Text
504-
showText = Text.pack . show
505-
506481
minus :: Value -> Value -> Value
507482
minus x y =
508483
let negativeValues = map (\(c, t, a) -> (c, t, - a)) $ Value.flattenValue y

0 commit comments

Comments
 (0)