Skip to content

Commit 566436f

Browse files
committed
make suggested changes and add docs
1 parent fcca512 commit 566436f

File tree

3 files changed

+67
-55
lines changed

3 files changed

+67
-55
lines changed

src/BotPlutusInterface/CoinSelection.hs

Lines changed: 62 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import Control.Monad.Except (foldM, throwError, unless)
2424
import Control.Monad.Freer (Eff, Member)
2525
import Control.Monad.Trans.Class (lift)
2626
import Control.Monad.Trans.Either (hoistEither, newEitherT, runEitherT)
27-
import Data.Default (Default (def))
2827
import Data.Either.Combinators (isRight, maybeToRight)
2928
import Data.Kind (Type)
3029
import Data.List qualified as List
@@ -37,7 +36,7 @@ import Data.Vector (Vector)
3736
import Data.Vector qualified as Vec
3837
import Ledger qualified
3938
import Ledger.Tx (
40-
TxIn (..),
39+
TxIn (txInRef),
4140
TxOut (..),
4241
TxOutRef (..),
4342
)
@@ -157,7 +156,7 @@ Now, let's see how the coin selection works by looking at an example:
157156
158157
-}
159158

160-
-- 'searchStrategy' represents the possible search strategy.
159+
-- | 'searchStrategy' represents the possible search strategy.
161160
data SearchStrategy
162161
= -- | This is a greedy search that searches for nearest utxo using l2norm.
163162
Greedy
@@ -166,17 +165,19 @@ data SearchStrategy
166165
GreedyApprox
167166
deriving stock (Eq, Show)
168167

169-
instance Default SearchStrategy where
170-
def = GreedyApprox
168+
defaultSearchStrategy :: SearchStrategy
169+
defaultSearchStrategy = GreedyApprox
170+
171+
type ValueVector = Vector Integer
171172

172173
-- 'selectTxIns' selects utxos using default search strategy, it also preprocesses
173174
-- the utxos values in to normalized vectors. So that distances between utxos can be calculated.
174175
selectTxIns ::
175176
forall (w :: Type) (effs :: [Type -> Type]).
176177
Member (PABEffect w) effs =>
177-
Set TxIn ->
178-
Map TxOutRef TxOut ->
179-
Value ->
178+
Set TxIn -> -- Inputs `TxIn` of the transaction.
179+
Map TxOutRef TxOut -> -- Map of utxos that can be spent
180+
Value -> -- total output value of the Tx.
180181
Eff effs (Either Text (Set TxIn))
181182
selectTxIns originalTxIns utxosIndex outValue =
182183
runEitherT $ do
@@ -219,15 +220,22 @@ selectTxIns originalTxIns utxosIndex outValue =
219220

220221
-- we use the default search strategy to get indexes of optimal utxos, these indexes are for the
221222
-- remainingUtxos, as we are sampling utxos from that set.
222-
selectedUtxosIdxs <- newEitherT $ searchTxIns @w def (isSufficient outVec) outVec txInsVec remainingUtxosVec
223+
selectedUtxosIdxs <-
224+
newEitherT $
225+
searchTxIns @w
226+
defaultSearchStrategy
227+
(isSufficient outVec)
228+
outVec
229+
txInsVec
230+
remainingUtxosVec
223231

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

226234
let -- These are the selected utxos that we get using `selectedUtxosIdxs`.
227235
selectedUtxos :: [(TxOutRef, TxOut)]
228236
selectedUtxos = selectedUtxosIdxs ^.. folded . to (\idx -> remainingUtxos ^? ix idx) . folded
229237

230-
selectedVectors :: [Vector Integer]
238+
selectedVectors :: [ValueVector]
231239
selectedVectors = selectedUtxosIdxs ^.. folded . to (\idx -> remainingUtxosVec ^? ix idx) . folded
232240

233241
finalTxInputVector <- hoistEither $ foldM addVec txInsVec selectedVectors
@@ -238,14 +246,14 @@ selectTxIns originalTxIns utxosIndex outValue =
238246
lift $ printBpiLog @w (Debug [CoinSelectionLog]) $ "Selected TxIns: " <+> pretty selectedTxIns
239247

240248
-- Now we add the selected utxos to originalTxIns present in the transaction previously.
241-
return $ originalTxIns <> Set.fromList selectedTxIns
249+
pure $ originalTxIns <> Set.fromList selectedTxIns
242250
where
243251
-- This represents the condition when we can stop searching for utxos.
244252
-- First condition is that the input vector must not be zero vector, i.e.
245253
-- There must be atleast some input to the transaction.
246254
-- Second condition is that all the values of input vector must be greater than
247255
-- or equal to the output vector.
248-
isSufficient :: Vector Integer -> Vector Integer -> Bool
256+
isSufficient :: ValueVector -> ValueVector -> Bool
249257
isSufficient outVec txInsVec =
250258
Vec.all (== True) (Vec.zipWith (<=) outVec txInsVec)
251259
&& txInsVec /= zeroVec (length txInsVec)
@@ -255,42 +263,40 @@ selectTxIns originalTxIns utxosIndex outValue =
255263
searchTxIns ::
256264
forall (w :: Type) (effs :: [Type -> Type]).
257265
Member (PABEffect w) effs =>
258-
SearchStrategy ->
259-
(Vector Integer -> Bool) ->
260-
Vector Integer ->
261-
Vector Integer ->
262-
[Vector Integer] ->
266+
SearchStrategy -> -- search strategy to use for selecting utxos
267+
(ValueVector -> Bool) -> -- condition on when to stop the search
268+
ValueVector -> -- output value vector of the Tx.
269+
ValueVector -> -- input value vector of the Tx.
270+
[ValueVector] -> -- all the value vectors of the utxos that can be spent.
263271
Eff effs (Either Text [Int])
264-
searchTxIns searchStrategy stopSearch outVec txInsVec utxosVec
265-
| searchStrategy == Greedy =
266-
printBpiLog @w (Debug [CoinSelectionLog]) "Selecting UTxOs via greedy search"
267-
>> greedySearch @w stopSearch outVec txInsVec utxosVec
268-
| searchStrategy == GreedyApprox =
269-
printBpiLog @w (Debug [CoinSelectionLog]) "Selecting UTxOs via greedy pruning search"
270-
>> greedyApprox @w stopSearch outVec txInsVec utxosVec
271-
| otherwise = return $ throwError "Not a valid search strategy."
272+
searchTxIns Greedy stopSearch outVec txInsVec utxosVec =
273+
printBpiLog @w (Debug [CoinSelectionLog]) "Selecting UTxOs via greedy search"
274+
>> greedySearch @w stopSearch outVec txInsVec utxosVec
275+
searchTxIns GreedyApprox stopSearch outVec txInsVec utxosVec =
276+
printBpiLog @w (Debug [CoinSelectionLog]) "Selecting UTxOs via greedy approx search"
277+
>> greedyApprox @w stopSearch outVec txInsVec utxosVec
272278

273279
-- `greedySearch` searches for utxos vectors for input to a transaction,
274280
-- this is achieved by selecting the utxo vector that have closest euclidean distance
275281
-- from output vector.
276282
greedySearch ::
277283
forall (w :: Type) (effs :: [Type -> Type]).
278284
Member (PABEffect w) effs =>
279-
(Vector Integer -> Bool) ->
280-
Vector Integer ->
281-
Vector Integer ->
282-
[Vector Integer] ->
285+
(ValueVector -> Bool) -> -- condition on when to stop the search
286+
ValueVector -> -- output value vector of the Tx.
287+
ValueVector -> -- input value vector of the Tx.
288+
[ValueVector] -> -- all the value vectors of the utxos that can be spent.
283289
Eff effs (Either Text [Int])
284290
greedySearch stopSearch outVec txInsVec utxosVec
285291
-- we stop the search if there are no utxos vectors left, as we will not be able to
286292
-- select any further utxos as input to a transaction.
287293
| null utxosVec =
288294
printBpiLog @w (Debug [CoinSelectionLog]) "Greedy: The list of remanining UTxO vectors in null."
289-
>> return (Right mempty)
295+
>> pure (Right mempty)
290296
-- we stop the search is the predicate `stopSearch` is true.
291297
| stopSearch txInsVec =
292298
printBpiLog @w (Debug [CoinSelectionLog]) "Greedy: Stopping search early."
293-
>> return (Right mempty)
299+
>> pure (Right mempty)
294300
| otherwise =
295301
runEitherT $ do
296302
-- Here, we calculate the euclidean distance of the following vectors:
@@ -307,11 +313,11 @@ greedySearch stopSearch outVec txInsVec utxosVec
307313

308314
newEitherT $ loop sortedDist txInsVec
309315
where
310-
loop :: [(Int, Float)] -> Vector Integer -> Eff effs (Either Text [Int])
311-
loop [] _ = return $ Right mempty
316+
loop :: [(Int, Float)] -> ValueVector -> Eff effs (Either Text [Int])
317+
loop [] _ = pure $ Right mempty
312318
loop ((idx, _) : remSortedDist) newTxInsVec =
313319
if stopSearch newTxInsVec -- we check if we should stop the search.
314-
then return $ Right mempty
320+
then pure $ Right mempty
315321
else runEitherT $ do
316322
-- Get the selected utxo vector given the current idx.
317323
selectedUtxoVec <-
@@ -346,21 +352,21 @@ greedySearch stopSearch outVec txInsVec utxosVec
346352
greedyApprox ::
347353
forall (w :: Type) (effs :: [Type -> Type]).
348354
Member (PABEffect w) effs =>
349-
(Vector Integer -> Bool) ->
350-
Vector Integer ->
351-
Vector Integer ->
352-
[Vector Integer] ->
355+
(ValueVector -> Bool) -> -- condition on when to stop the search
356+
ValueVector -> -- output value vector of the Tx.
357+
ValueVector -> -- input value vector of the Tx.
358+
[ValueVector] -> -- all the value vectors of the utxos that can be spent.
353359
Eff effs (Either Text [Int])
354360
greedyApprox stopSearch outVec txInsVec utxosVec
355361
-- we stop the search if there are no utxos vectors left, as we will not be able to
356362
-- select any further utxos as input to a transaction.
357363
| null utxosVec =
358364
printBpiLog @w (Debug [CoinSelectionLog]) "Greedy Pruning: The list of remanining UTxO vectors in null."
359-
>> return (Right mempty)
365+
>> pure (Right mempty)
360366
-- we stop the search is the predicate `stopSearch` is true.
361367
| stopSearch txInsVec =
362368
printBpiLog @w (Debug [CoinSelectionLog]) "Greedy Pruning: Stopping search early."
363-
>> return (Right mempty)
369+
>> pure (Right mempty)
364370
| otherwise =
365371
runEitherT $ do
366372
-- Here, we get the selected indexes of utxo vectors using greedy search.
@@ -371,7 +377,7 @@ greedyApprox stopSearch outVec txInsVec utxosVec
371377
-- last will have greater distance from the output vector.
372378
-- Hence, they may contain all the values that's required for
373379
-- the output vector.
374-
revSelectedUtxosVec :: [Vector Integer]
380+
revSelectedUtxosVec :: [ValueVector]
375381
revSelectedUtxosVec =
376382
List.reverse $ selectedUtxosIdx ^.. folded . to (\idx -> utxosVec ^? ix idx) . folded
377383

@@ -380,7 +386,7 @@ greedyApprox stopSearch outVec txInsVec utxosVec
380386

381387
hoistEither $ loop txInsVec revSelectedUtxosIdx revSelectedUtxosVec
382388
where
383-
loop :: Vector Integer -> [Int] -> [Vector Integer] -> Either Text [Int]
389+
loop :: ValueVector -> [Int] -> [ValueVector] -> Either Text [Int]
384390
loop newTxInsVec (idx : idxs) (vec : vecs) = do
385391
-- Add the selected utxo vector to the current tx input vector.
386392
newTxInsVec' <- addVec newTxInsVec vec
@@ -400,12 +406,12 @@ greedyApprox stopSearch outVec txInsVec utxosVec
400406
-- Else we check if we should stop the search here.
401407
False | stopSearch newTxInsVec -> Right mempty
402408
-- We add the current utxo vector.
403-
False -> (idx :) <$> loop newTxInsVec' idxs vecs
409+
_ -> (idx :) <$> loop newTxInsVec' idxs vecs
404410
loop _newTxInsVec [] [] = pure mempty
405411
loop _newTxInsVec _idxs _vecs = Left "Length of idxs and list of vecs are not same."
406412

407413
-- calculate euclidean distance of two vectors, of same length/dimension.
408-
l2norm :: Vector Integer -> Vector Integer -> Either Text Float
414+
l2norm :: ValueVector -> ValueVector -> Either Text Float
409415
l2norm v1 v2
410416
| length v1 == length v2 = Right $ sqrt $ fromInteger $ sum $ Vec.zipWith formula v1 v2
411417
| otherwise =
@@ -423,25 +429,25 @@ l2norm v1 v2
423429
formula n1 n2 = (n1 - n2) ^ (2 :: Integer)
424430

425431
-- Add two vectors of same length.
426-
addVec :: Num n => Vector n -> Vector n -> Either Text (Vector n)
432+
addVec :: forall (n :: Type). Num n => Vector n -> Vector n -> Either Text (Vector n)
427433
addVec = opVec (+)
428434

429435
-- Substract two vectors of same length.
430-
subVec :: Num n => Vector n -> Vector n -> Either Text (Vector n)
436+
subVec :: forall (n :: Type). Num n => Vector n -> Vector n -> Either Text (Vector n)
431437
subVec = opVec (-)
432438

433439
-- create zero vector of specified length.
434440
zeroVec :: Int -> Vector Integer
435-
zeroVec n = Vec.fromList $ replicate n 0
441+
zeroVec n = Vec.replicate n 0
436442

437443
-- convert a value to a vector.
438-
valueToVec :: Set AssetClass -> Value -> Either Text (Vector Integer)
444+
valueToVec :: Set AssetClass -> Value -> Either Text ValueVector
439445
valueToVec allAssetClasses v =
440446
maybeToRight "Error: Not able to uncons from empty vector." $
441447
(over _Just fst . uncons) $ valuesToVecs allAssetClasses [v]
442448

443449
-- convert values to a list of vectors.
444-
valuesToVecs :: Set AssetClass -> [Value] -> Vector (Vector Integer)
450+
valuesToVecs :: Set AssetClass -> [Value] -> Vector ValueVector
445451
valuesToVecs allAssetClasses values = Vec.fromList $ map toVec values
446452
where
447453
toVec :: Value -> Vector Integer
@@ -464,7 +470,13 @@ txOutToTxIn (txOutRef, txOut) =
464470
ScriptCredential _ -> Left "Cannot covert a script output to TxIn"
465471

466472
-- Apply a binary operation on two vectors of same length.
467-
opVec :: Num n => (forall a. Num a => a -> a -> a) -> Vector n -> Vector n -> Either Text (Vector n)
473+
opVec ::
474+
forall (n :: Type).
475+
Num n =>
476+
(forall a. Num a => a -> a -> a) ->
477+
Vector n ->
478+
Vector n ->
479+
Either Text (Vector n)
468480
opVec f v1 v2
469481
| length v1 == length v2 = Right $ Vec.zipWith f v1 v2
470482
| otherwise =

test/Spec/BotPlutusInterface/CoinSelection.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ assertUniqueAssetClasses = withMaxSuccess 1000 (forAll uniqueAssetClassesGen val
134134

135135
utxos <- randomTxOuts numUTxOs assetClassSampleSize allAssetClasses
136136

137-
return (allAssetClasses, utxos)
137+
pure (allAssetClasses, utxos)
138138

139139
validValueVectors :: Property
140140
validValueVectors = withMaxSuccess 1000 (forAll txOutsGen validate)
@@ -226,4 +226,4 @@ validateBalancing = withMaxSuccess 10000 (forAll balanceGen validate)
226226
utxos :: Map TxOutRef TxOut
227227
utxos = Map.fromList $ zip (tail rTxOutRefs) (tail rTxOuts)
228228

229-
return (txOutput, utxos)
229+
pure (txOutput, utxos)

test/Spec/RandomLedger.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ randomValue samplesize assetclasses =
3939

4040
amounts <- replicateM (length selectedAc) (toInteger <$> arbitrary @Natural)
4141

42-
return $
42+
pure $
4343
mconcatMap (uncurry Value.assetClassValue) $
4444
zip (Set.toList selectedAc) amounts
4545

@@ -50,7 +50,7 @@ randomTxOut samplesize assetclasses =
5050
value <- randomValue samplesize assetclasses
5151
datumhash <- arbitrary
5252

53-
return (TxOut addr value datumhash)
53+
pure (TxOut addr value datumhash)
5454

5555
randomTxOuts :: Int -> Int -> Set AssetClass -> Gen [TxOut]
5656
randomTxOuts numTxOuts samplesize =
@@ -61,7 +61,7 @@ randomTxOutRef =
6161
do
6262
txId <- arbitrary
6363
txIdx <- toInteger <$> arbitrary @Natural
64-
return (TxOutRef txId txIdx)
64+
pure (TxOutRef txId txIdx)
6565

6666
randomTxOutRefs :: Int -> Gen [TxOutRef]
6767
randomTxOutRefs n = replicateM n randomTxOutRef

0 commit comments

Comments
 (0)