@@ -24,7 +24,6 @@ import Control.Monad.Except (foldM, throwError, unless)
2424import Control.Monad.Freer (Eff , Member )
2525import Control.Monad.Trans.Class (lift )
2626import Control.Monad.Trans.Either (hoistEither , newEitherT , runEitherT )
27- import Data.Default (Default (def ))
2827import Data.Either.Combinators (isRight , maybeToRight )
2928import Data.Kind (Type )
3029import Data.List qualified as List
@@ -37,7 +36,7 @@ import Data.Vector (Vector)
3736import Data.Vector qualified as Vec
3837import Ledger qualified
3938import 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.
161160data 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.
174175selectTxIns ::
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 ))
181182selectTxIns 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 =
255263searchTxIns ::
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.
276282greedySearch ::
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 ])
284290greedySearch 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
346352greedyApprox ::
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 ])
354360greedyApprox 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
409415l2norm 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 )
427433addVec = 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 )
431437subVec = opVec (-)
432438
433439-- create zero vector of specified length.
434440zeroVec :: 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
439445valueToVec 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
445451valuesToVecs 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 )
468480opVec f v1 v2
469481 | length v1 == length v2 = Right $ Vec. zipWith f v1 v2
470482 | otherwise =
0 commit comments