Skip to content

Commit

Permalink
Merge #3217
Browse files Browse the repository at this point in the history
3217: Make `UTxOIndex` agnostic to the distinction between ada and non-ada assets. r=jonathanknowles a=jonathanknowles

## Issue Number

ADP-1419

## Summary

This PR makes the `UTxOIndex` type **_agnostic_** to the distinction between **_ada_** and **_non-ada_** assets.

## Motivation

We want to evolve the coin selection algorithm so that:
- the concept of "value" (provided by inputs and consumed by outputs) is **_generalized_**.
- all asset types are treated equally, with no special treatment for **_ada_** versus **_non-ada_** assets.

Currently, there are a few obstacles that prevent such a generalization:
1. The `UTxOIndex` type indexes ada quantities differently from other assets.
2. The `computeMinimumCost` function is ada-specific.
3. The `computeMinimumAdaQuantity` function is ada-specific.

This PR tackles the **_first_** of these obstacles.

## Details

We make the following change to `SelectionFilter`:

```patch
- data SelectionFilter
-     = WithAdaOnly
-     | WithAssetOnly AssetId
-     | WithAsset AssetId
-     | Any
+ data SelectionFilter asset
+     = SelectSingleton asset
+     | SelectPairWith asset
+     | SelectAnyWith asset
+     | SelectAny
```

We also (temporarily) introduce a type called `Asset`, which can represent both ada and non-ada assets:
```hs
data Asset
    = AssetLovelace
    | Asset AssetId
```

This allows us to generalize the way we perform random selection for ada and non-ada assets in the `Balance` module:
```patch
 selectAdaQuantity =
-    selectMatchingQuantity (WithAdaOnly :| [Any])
+    selectMatchingQuantity
+        [ SelectSingleton AssetLovelace
+        , SelectPairWith AssetLovelace
+        , SelectAnyWith AssetLovelace
+        ]

 selectNonAdaAssetQuantity asset =
-    selectMatchingQuantity [WithAssetOnly asset, WithAsset asset]
+    selectMatchingQuantity
+        [ SelectSingleton (Asset asset)
+        , SelectPairWith (Asset asset)
+        , SelectAnyWith (Asset asset)
+        ]
```
In the above functions, selection filters are processed in the following order of priority:
1. `SelectSingleton`
    matches UTxOs that contain **_just_** the given asset and **_no other_** assets
2. `SelectPairWith`
    matches UTxOs that contain the given asset and **_one other_** asset.
2. `SelectAnyWith`
    matches UTxOs that contain the given asset and **_any number_** of other assets.

Because both selection functions now share **_exactly the same priority order_**, we can simplify these definitions even further by extracting out the shared priority order and parameterizing over the type of asset.

Co-authored-by: Jonathan Knowles <[email protected]>
  • Loading branch information
iohk-bors[bot] and jonathanknowles authored Apr 5, 2022
2 parents d3a5ad4 + c6bf8fa commit 73223bb
Show file tree
Hide file tree
Showing 6 changed files with 419 additions and 247 deletions.
41 changes: 17 additions & 24 deletions lib/core/src/Cardano/Wallet/CoinSelection/Internal/Balance.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
Expand Down Expand Up @@ -141,7 +142,7 @@ import Cardano.Wallet.Primitive.Types.TokenQuantity
import Cardano.Wallet.Primitive.Types.Tx
( TokenBundleSizeAssessment (..), TokenBundleSizeAssessor (..) )
import Cardano.Wallet.Primitive.Types.UTxOIndex
( SelectionFilter (..), UTxOIndex (..) )
( Asset (..), SelectionFilter (..), UTxOIndex (..) )
import Cardano.Wallet.Primitive.Types.UTxOSelection
( IsUTxOSelection, UTxOSelection, UTxOSelectionNonEmpty )
import Control.Monad.Extra
Expand Down Expand Up @@ -757,7 +758,7 @@ deriving instance SelectionContext ctx =>

instance SelectionContext ctx => Buildable (InsufficientMinCoinValueError ctx)
where
build (InsufficientMinCoinValueError (a, b) c) = unlinesF
build (InsufficientMinCoinValueError (a, b) c) = unlinesF @[]
[ nameF "Expected min coin value" (build c)
, nameF "Address" (build a)
, nameF "Token bundle" (build (Flat b))
Expand Down Expand Up @@ -1102,7 +1103,7 @@ performSelectionNonEmpty constraints params
, assetsToBurn
}

selectOneEntry = selectCoinQuantity selectionLimit
selectOneEntry = selectQuantityOf AssetLovelace selectionLimit

requiredCost = computeMinimumCost SelectionSkeleton
{ skeletonInputCount = UTxOSelection.selectedSize s
Expand Down Expand Up @@ -1149,7 +1150,9 @@ runSelectionNonEmpty
=> RunSelectionParams u
-> m (Maybe (UTxOSelectionNonEmpty u))
runSelectionNonEmpty = (=<<)
<$> runSelectionNonEmptyWith . selectCoinQuantity . view #selectionLimit
<$> runSelectionNonEmptyWith
. selectQuantityOf AssetLovelace
. view #selectionLimit
<*> runSelection

runSelectionNonEmptyWith
Expand Down Expand Up @@ -1203,7 +1206,7 @@ assetSelectionLens limit strategy (asset, minimumAssetQuantity) = SelectionLens
{ currentQuantity = selectedAssetQuantity asset
, updatedQuantity = selectedAssetQuantity asset
, minimumQuantity = unTokenQuantity minimumAssetQuantity
, selectQuantity = selectAssetQuantity asset limit
, selectQuantity = selectQuantityOf (Asset asset) limit
, selectionStrategy = strategy
}

Expand All @@ -1218,32 +1221,22 @@ coinSelectionLens limit strategy minimumCoinQuantity = SelectionLens
{ currentQuantity = selectedCoinQuantity
, updatedQuantity = selectedCoinQuantity
, minimumQuantity = intCast $ unCoin minimumCoinQuantity
, selectQuantity = selectCoinQuantity limit
, selectQuantity = selectQuantityOf AssetLovelace limit
, selectionStrategy = strategy
}

-- | Specializes 'selectMatchingQuantity' to a particular asset.
--
selectAssetQuantity
selectQuantityOf
:: (MonadRandom m, Ord u)
=> IsUTxOSelection utxoSelection u
=> AssetId
=> Asset
-> SelectionLimit
-> utxoSelection u
-> m (Maybe (UTxOSelectionNonEmpty u))
selectAssetQuantity asset =
selectMatchingQuantity (WithAssetOnly asset :| [WithAsset asset])

-- | Specializes 'selectMatchingQuantity' to ada.
--
selectCoinQuantity
:: (MonadRandom m, Ord u)
=> IsUTxOSelection utxoSelection u
=> SelectionLimit
-> utxoSelection u
-> m (Maybe (UTxOSelectionNonEmpty u))
selectCoinQuantity =
selectMatchingQuantity (WithAdaOnly :| [Any])
selectQuantityOf a = selectMatchingQuantity
[ SelectSingleton a
, SelectPairWith a
, SelectAnyWith a
]

-- | Selects a UTxO entry that matches one of the specified filters.
--
Expand All @@ -1264,7 +1257,7 @@ selectCoinQuantity =
selectMatchingQuantity
:: forall m utxoSelection u. (MonadRandom m, Ord u)
=> IsUTxOSelection utxoSelection u
=> NonEmpty SelectionFilter
=> NonEmpty (SelectionFilter Asset)
-- ^ A list of selection filters to be traversed from left-to-right,
-- in descending order of priority.
-> SelectionLimit
Expand Down
1 change: 1 addition & 0 deletions lib/core/src/Cardano/Wallet/Primitive/Types/UTxOIndex.hs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ module Cardano.Wallet.Primitive.Types.UTxOIndex
, disjoint

-- * Selection
, Asset (..)
, SelectionFilter (..)
, selectRandom
, selectRandomWithPriority
Expand Down
Loading

0 comments on commit 73223bb

Please sign in to comment.