Skip to content

Commit 08e2ca6

Browse files
committed
add docs
1 parent 1302148 commit 08e2ca6

File tree

4 files changed

+79
-35
lines changed

4 files changed

+79
-35
lines changed

.envrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
nix-user-chroot ~/.nix zsh
1+
nix-user-chroot ~/.nix zsh
22
use flake

src/BotPlutusInterface/CoinSelection.hs

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,28 @@
33

44
module BotPlutusInterface.CoinSelection (valueToVec, valuesToVecs, selectTxIns, uniqueAssetClasses) where
55

6-
import Control.Lens (foldOf, folded, ix, over, to, uncons, (^..), (^?), _Just)
6+
import BotPlutusInterface.Effects (PABEffect, printBpiLog)
7+
import BotPlutusInterface.Types (LogLevel (Debug), LogType (CoinSelectionLog))
8+
import Control.Lens (
9+
foldOf,
10+
folded,
11+
ifolded,
12+
ix,
13+
over,
14+
to,
15+
uncons,
16+
withIndex,
17+
(%~),
18+
(&),
19+
(^..),
20+
(^?),
21+
_Just,
22+
)
23+
import Control.Monad.Except (throwError)
724
import Control.Monad.Freer (Eff, Member)
825
import Control.Monad.Trans.Class (lift)
926
import Control.Monad.Trans.Either (hoistEither, newEitherT, runEitherT)
27+
import Data.Default (Default (def))
1028
import Data.Either.Combinators (isRight, maybeToRight)
1129
import Data.Kind (Type)
1230
import Data.List qualified as List
@@ -25,20 +43,26 @@ import Ledger.Tx (
2543
)
2644
import Ledger.Value (AssetClass, Value)
2745
import Ledger.Value qualified as Value
28-
2946
import Plutus.V1.Ledger.Api (
3047
Credential (PubKeyCredential, ScriptCredential),
3148
)
32-
import BotPlutusInterface.Effects (PABEffect, printBpiLog)
33-
import BotPlutusInterface.Types (LogLevel (Debug), LogType (CoinSelectionLog))
3449
import Prettyprinter (pretty, (<+>))
3550
import Prelude
3651

52+
-- 'Search' represents the possible search strategy.
3753
data Search
38-
= Greedy
39-
| GreedyPruning
40-
deriving stock (Show)
41-
54+
= -- | This is a greedy search that searches for nearest utxo using l2norm.
55+
Greedy
56+
| -- | This is like greedy search, but here there's
57+
-- additonal goal that the change utxo should be equal to the output utxo.
58+
GreedyApprox
59+
deriving stock (Eq, Show)
60+
61+
instance Default Search where
62+
def = GreedyApprox
63+
64+
-- 'selectTxIns' selects utxos using default search strategy, it also preprocesses
65+
-- the utxos values in to normalized vectors. So that distances between utxos can be calculated.
4266
selectTxIns ::
4367
forall (w :: Type) (effs :: [Type -> Type]).
4468
Member (PABEffect w) effs =>
@@ -48,17 +72,20 @@ selectTxIns ::
4872
Eff effs (Either Text (Set TxIn))
4973
selectTxIns originalTxIns utxosIndex outValue =
5074
runEitherT $ do
51-
let txInsValue :: Value
75+
let -- This represents the input value.
76+
txInsValue :: Value
5277
txInsValue =
5378
foldOf (folded . to ((`Map.lookup` utxosIndex) . txInRef) . folded . to txOutValue) originalTxIns
5479

80+
-- This is set of all the asset classes present in outValue, inputValue and all the utxos combined
5581
allAssetClasses :: Set AssetClass
5682
allAssetClasses =
5783
uniqueAssetClasses $ txInsValue : outValue : utxosIndex ^.. folded . to txOutValue
5884

5985
txInRefs :: [TxOutRef]
6086
txInRefs = originalTxIns ^.. folded . to txInRef
6187

88+
-- All the remainingUtxos that has not been used as an input to the transaction yet.
6289
remainingUtxos :: [(TxOutRef, TxOut)]
6390
remainingUtxos =
6491
Map.toList $
@@ -68,29 +95,42 @@ selectTxIns originalTxIns utxosIndex outValue =
6895

6996
lift $ printBpiLog @w (Debug [CoinSelectionLog]) $ "Remaining UTxOs: " <+> pretty remainingUtxos
7097

98+
-- the input vector for the current transaction, this can be a zero vector when there are no
99+
-- inputs the transaction.
71100
txInsVec <-
72101
hoistEither $
73102
if Value.isZero txInsValue
74103
then Right $ zeroVec (length allAssetClasses)
75104
else valueToVec allAssetClasses txInsValue
76105

106+
-- the output vector of the current transaction, this is all the values of TxOut combined.
77107
outVec <- hoistEither $ valueToVec allAssetClasses outValue
78108

109+
-- all the remainingUtxos converted to the vectors.
79110
remainingUtxosVec <- hoistEither $ mapM (valueToVec allAssetClasses . txOutValue . snd) remainingUtxos
80111

81-
selectedUtxosIdxs <- newEitherT $ selectTxIns' @w GreedyPruning (isSufficient outVec) outVec txInsVec remainingUtxosVec
112+
-- we use the default search strategy to get indexes of optimal utxos, these indexes are for the
113+
-- remainingUtxos, as we are sampling utxos from that set.
114+
selectedUtxosIdxs <- newEitherT $ selectTxIns' @w def (isSufficient outVec) outVec txInsVec remainingUtxosVec
82115

83116
lift $ printBpiLog @w (Debug [CoinSelectionLog]) $ "" <+> "Selected UTxOs Index: " <+> pretty selectedUtxosIdxs
84117

85-
let selectedUtxos :: [(TxOutRef, TxOut)]
118+
let -- These are the selected utxos that we get using `selectedUtxosIdxs`.
119+
selectedUtxos :: [(TxOutRef, TxOut)]
86120
selectedUtxos = selectedUtxosIdxs ^.. folded . to (\idx -> remainingUtxos ^? ix idx) . folded
87121

88122
selectedTxIns <- hoistEither $ mapM txOutToTxIn selectedUtxos
89123

90124
lift $ printBpiLog @w (Debug [CoinSelectionLog]) $ "Selected TxIns: " <+> pretty selectedTxIns
91125

126+
-- Now we add the selected utxos to originalTxIns present in the transaction previously.
92127
return $ originalTxIns <> Set.fromList selectedTxIns
93128
where
129+
-- This represents the condition when we can stop searching for utxos.
130+
-- First condition is that the input vector must not be zero vector, i.e.
131+
-- There must be atleast some input to the transaction.
132+
-- Second condition is that all the values of input vector must be greater than
133+
-- or equal to the output vector.
94134
isSufficient :: Vector Integer -> Vector Integer -> Bool
95135
isSufficient outVec txInsVec =
96136
Vec.all (== True) (Vec.zipWith (<=) outVec txInsVec)
@@ -105,8 +145,14 @@ selectTxIns' ::
105145
Vector Integer ->
106146
[Vector Integer] ->
107147
Eff effs (Either Text [Int])
108-
selectTxIns' Greedy = greedySearch @w
109-
selectTxIns' GreedyPruning = greedyPruning @w
148+
selectTxIns' searchStrategy stopSearch outVec txInsVec utxosVec
149+
| searchStrategy == Greedy =
150+
printBpiLog @w (Debug [CoinSelectionLog]) "Selecting UTxOs via greedy search"
151+
>> greedySearch @w stopSearch outVec txInsVec utxosVec
152+
| searchStrategy == GreedyApprox =
153+
printBpiLog @w (Debug [CoinSelectionLog]) "Selecting UTxOs via greedy pruning search"
154+
>> greedyApprox @w stopSearch outVec txInsVec utxosVec
155+
| otherwise = return $ throwError "Not a valid search strategy."
110156

111157
greedySearch ::
112158
forall (w :: Type) (effs :: [Type -> Type]).
@@ -125,13 +171,12 @@ greedySearch stopSearch outVec txInsVec utxosVec
125171
>> return (Right mempty)
126172
| otherwise =
127173
runEitherT $ do
128-
x <- hoistEither $ mapM (addVec txInsVec) utxosVec
129-
utxosDist <- hoistEither $ mapM (l2norm outVec) x
174+
utxosDist <- hoistEither $ mapM (addVec txInsVec) utxosVec >>= mapM (l2norm outVec)
130175

131176
let sortedDist :: [(Int, Float)]
132177
sortedDist =
133-
List.sortBy (\a b -> compare (snd a) (snd b)) $
134-
zip [0 .. length utxosVec - 1] utxosDist
178+
utxosDist ^.. ifolded . withIndex
179+
& id %~ List.sortBy (\a b -> compare (snd a) (snd b))
135180

136181
newEitherT $ loop sortedDist txInsVec
137182
where
@@ -153,15 +198,15 @@ greedySearch stopSearch outVec txInsVec utxosVec
153198

154199
(idx :) <$> newEitherT (loop remSortedDist newTxInsVec')
155200

156-
greedyPruning ::
201+
greedyApprox ::
157202
forall (w :: Type) (effs :: [Type -> Type]).
158203
Member (PABEffect w) effs =>
159204
(Vector Integer -> Bool) ->
160205
Vector Integer ->
161206
Vector Integer ->
162207
[Vector Integer] ->
163208
Eff effs (Either Text [Int])
164-
greedyPruning stopSearch outVec txInsVec utxosVec
209+
greedyApprox stopSearch outVec txInsVec utxosVec
165210
| null utxosVec =
166211
printBpiLog @w (Debug [CoinSelectionLog]) "Greedy Pruning: The list of remanining UTxO vectors in null."
167212
>> return (Right mempty)
@@ -244,8 +289,8 @@ valuesToVecs allAssetClasses values = Vec.fromList $ map toVec values
244289
where
245290
toVec :: Value -> Vector Integer
246291
toVec v =
247-
Vec.map (Value.assetClassValueOf v) $
248-
Vec.fromList $ Set.toList allAssetClasses
292+
fmap (Value.assetClassValueOf v) $
293+
allAssetClasses & id %~ (Vec.fromList . Set.toList)
249294

250295
uniqueAssetClasses :: [Value] -> Set AssetClass
251296
uniqueAssetClasses = Set.fromList . concatMap valueToAssetClass

src/BotPlutusInterface/Types.hs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ module BotPlutusInterface.Types (
2525
ContractStats (..),
2626
TxStatusPolling (..),
2727
LogsList (..),
28-
LogType (..),
2928
CollateralUtxo (..),
3029
CollateralVar (..),
3130
addBudget,
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
module Spec.BotPlutusInterface.CoinSelection (tests) where
22

33
import BotPlutusInterface.CoinSelection (uniqueAssetClasses)
4-
import Control.Lens (folded, (^..), to)
4+
import Control.Lens (folded, to, (^..))
55
import Data.Set (Set)
66
import Data.Set qualified as Set
77
import Ledger qualified
@@ -16,38 +16,38 @@ import Test.Tasty (TestTree, testGroup)
1616
import Test.Tasty.HUnit (Assertion, assertFailure, testCase, (@?=))
1717
import Prelude
1818

19-
2019
tests :: TestTree
2120
tests =
2221
testGroup
2322
"BotPlutusInterface.CoinSelection"
2423
[ testCase "validAssetClasses" validAssetClasses
2524
]
2625

27-
28-
pkh1, pkh2 :: PubKeyHash
26+
pkh1 :: PubKeyHash
2927
pkh1 = Address.unPaymentPubKeyHash . Wallet.paymentPubKeyHash $ Wallet.knownMockWallet 1
30-
pkh2 = Address.unPaymentPubKeyHash . Wallet.paymentPubKeyHash $ Wallet.knownMockWallet 2
3128

32-
addr1, addr2 :: Address
29+
addr1 :: Address
3330
addr1 = Ledger.pubKeyHashAddress (PaymentPubKeyHash pkh1) Nothing
34-
addr2 = Ledger.pubKeyHashAddress (PaymentPubKeyHash pkh2) Nothing
3531

36-
txOutRef1, txOutRef2, txOutRef3, txOutRef4 :: TxOutRef
32+
txOutRef1, txOutRef2, txOutRef3, txOutRef4, txOutRef5 :: TxOutRef
3733
txOutRef1 = TxOutRef "384de3f29396fdf687551e3f9e05bd400adcd277720c71f1d2b61f17f5183e51" 0
3834
txOutRef2 = TxOutRef "52a003b3f4956433429631afe4002f82a924a5a7a891db7ae1f6434797a57dff" 1
3935
txOutRef3 = TxOutRef "d8a5630a9d7e913f9d186c95e5138a239a4e79ece3414ac894dbf37280944de3" 0
4036
txOutRef4 = TxOutRef "d8a5630a9d7e913f9d186c95e5138a239a4e79ece3414ac894dbf37280944de3" 2
4137
txOutRef5 = TxOutRef "34d491e0596b3a04be6e3442ebf115b33900f26e5e415e5151f820778ba576ed" 0
4238

43-
utxo1, utxo2, utxo3, utxo4 :: (TxOutRef, TxOut)
39+
utxo1, utxo2, utxo3, utxo4, utxo5 :: (TxOutRef, TxOut)
4440
utxo1 = (txOutRef1, TxOut addr1 (Ada.lovelaceValueOf 1_100_000) Nothing)
4541
utxo2 = (txOutRef2, TxOut addr1 (Ada.lovelaceValueOf 1_000_000) Nothing)
4642
utxo3 = (txOutRef3, TxOut addr1 (Ada.lovelaceValueOf 900_000) Nothing)
4743
utxo4 = (txOutRef4, TxOut addr1 (Ada.lovelaceValueOf 800_000 <> Value.singleton "11223344" "Token1" 500) Nothing)
4844
utxo5 = (txOutRef4, TxOut addr1 (Ada.lovelaceValueOf 800_000 <> Value.singleton "44332211" "Token2" 500) Nothing)
4945

50-
5146
validAssetClasses :: Assertion
52-
validAssetClasses = (uniqueAssetClasses $ map (txOutValue . snd) [utxo1,utxo2,utxo3,utxo4,utxo5]) @?= Set.fromList
53-
[Value.assetClass Ada.adaSymbol Ada.adaToken, Value.assetClass "44332211" "Token2", Value.assetClass "11223344" "Token1"]
47+
validAssetClasses =
48+
uniqueAssetClasses (map (txOutValue . snd) [utxo1, utxo2, utxo3, utxo4, utxo5])
49+
@?= Set.fromList
50+
[ Value.assetClass Ada.adaSymbol Ada.adaToken
51+
, Value.assetClass "44332211" "Token2"
52+
, Value.assetClass "11223344" "Token1"
53+
]

0 commit comments

Comments
 (0)