22{-# LANGUAGE NamedFieldPuns #-}
33
44module BotPlutusInterface.Balance (
5- BalanceTxConstraint ( .. ),
5+ BalanceTxConstraint ( TxWithScript , TxWithoutScript , TxWithSeparateChange ),
66 balanceTxStep ,
77 balanceTxIO ,
88 balanceTxIO' ,
@@ -32,11 +32,12 @@ import Data.Bifunctor (bimap)
3232import Data.Coerce (coerce )
3333import Data.Either.Combinators (rightToMaybe )
3434import Data.Kind (Type )
35- import Data.List (uncons , (\\) )
35+ import Data.List ((\\) )
36+ import Data.List qualified as List
3637import Data.Map (Map )
3738import Data.Map qualified as Map
3839import Data.Maybe (fromMaybe , mapMaybe )
39- import Data.Proxy (Proxy (Proxy ))
40+ import Data.Proxy (Proxy (Proxy ))
4041import Data.Set (Set )
4142import Data.Set qualified as Set
4243import Data.Text (Text )
@@ -81,8 +82,8 @@ data TxWithScript
8182data TxWithoutScript
8283
8384data BalanceTxConstraint a where
84- TxWithScript :: BalanceTxConstraint TxWithScript
85- TxWithoutScript :: BalanceTxConstraint TxWithoutScript
85+ TxWithScript :: BalanceTxConstraint TxWithScript
86+ TxWithoutScript :: BalanceTxConstraint TxWithoutScript
8687 TxWithSeparateChange :: BalanceTxConstraint a
8788
8889instance Eq (BalanceTxConstraint a ) where
@@ -91,30 +92,35 @@ instance Eq (BalanceTxConstraint a) where
9192 TxWithSeparateChange == TxWithSeparateChange = True
9293 _ == _ = False
9394
94- class KnowBalanceConstraint (a :: Type ) where
95- constraint :: proxy a -> BalanceTxConstraint a
95+ class KnownBalanceConstraint (a :: Type ) where
96+ knownConstraint :: proxy a -> BalanceTxConstraint a
9697
97- instance KnowBalanceConstraint TxWithScript where
98- constraint _ = TxWithScript
98+ instance KnownBalanceConstraint TxWithScript where
99+ knownConstraint _ = TxWithScript
99100
100- instance KnowBalanceConstraint TxWithoutScript where
101- constraint _ = TxWithoutScript
101+ instance KnownBalanceConstraint TxWithoutScript where
102+ knownConstraint _ = TxWithoutScript
102103
104+ {- | Collect necessary tx inputs and collaterals, add minimum lovelace values and balance non ada
105+ assets. `balanceTxIO` assumes that the `Tx` we are balancing is of type `TxWithoutScript`.
106+ If you want to add custom constraints while balancing the `Tx` like, saying that `Tx` contains
107+ scripts refer to `balanceTxIO'`.
108+ -}
103109balanceTxIO ::
104110 forall (w :: Type ) (effs :: [Type -> Type ]).
105111 (Member (PABEffect w ) effs ) =>
106112 PABConfig ->
107113 PubKeyHash ->
108114 UnbalancedTx ->
109115 Eff effs (Either Text Tx )
110- balanceTxIO = balanceTxIO' @ w [TxWithScript ]
116+ balanceTxIO = balanceTxIO' @ w [TxWithoutScript ]
111117
112- {- | Collect necessary tx inputs and collaterals, add minimum lovelace values and balance non ada
113- assets
118+ {- | This is just a more flexible version of `balanceTxIO` which let's specify the `BalanceTxConstraint`
119+ - for the `Tx` that we are balancing.
114120-}
115121balanceTxIO' ::
116- forall (w :: Type ) (effs :: [Type -> Type ]) (a :: Type ) .
117- (Member (PABEffect w ) effs , KnowBalanceConstraint a ) =>
122+ forall (w :: Type ) (effs :: [Type -> Type ]) (a :: Type ).
123+ (Member (PABEffect w ) effs , KnownBalanceConstraint a ) =>
118124 [BalanceTxConstraint a ] ->
119125 PABConfig ->
120126 PubKeyHash ->
@@ -128,6 +134,7 @@ balanceTxIO' balanceTxconstraints pabConf ownPkh unbalancedTx =
128134
129135 let utxoIndex = fmap Tx. toTxOut utxos <> unBalancedTxUtxoIndex unbalancedTx
130136 requiredSigs = map Ledger. unPaymentPubKeyHash $ Map. keys (unBalancedTxRequiredSignatories unbalancedTx)
137+ txType = knownConstraint @ a Proxy
131138
132139 lift $ printBpiLog @ w Debug $ viaShow utxoIndex
133140
@@ -140,41 +147,33 @@ balanceTxIO' balanceTxconstraints pabConf ownPkh unbalancedTx =
140147 (unBalancedTxValidityTimeRange unbalancedTx)
141148 (unBalancedTxTx unbalancedTx)
142149
143- -- Adds required collaterals in the `Tx`, if the `Tx` is of type `BalanceTxWithScripts `.
150+ -- Adds required collaterals in the `Tx`, if the `Tx` is of type `TxWithScript `.
144151 -- Also adds signatures for fee calculation
145-
146- preBalancedTx <- case constraint @ a Proxy of
147- TxWithScript -> maybe
148- (throwE " Tx uses script but no collateral was provided." )
149- (hoistEither . addSignatories ownPkh privKeys requiredSigs . flip addTxCollaterals tx)
150- mcollateral
151- _ -> hoistEither $ addSignatories ownPkh privKeys requiredSigs tx
152+ preBalancedTx <- case txType of
153+ TxWithScript ->
154+ maybe
155+ (throwE " Tx uses script but no collateral was provided." )
156+ (hoistEither . addSignatories ownPkh privKeys requiredSigs . flip addTxCollaterals tx)
157+ mcollateral
158+ _ -> hoistEither $ addSignatories ownPkh privKeys requiredSigs tx
152159
153160 -- Balance the tx
154161 (balancedTx, minUtxos) <- balanceTxLoop utxoIndex privKeys [] preBalancedTx
155162
156163 -- Get current Ada change
157164 let adaChange = getAdaChange utxoIndex balancedTx
158- -- This represents the collateral TxOut, in cases of `BalanceTxWithoutScripts` & `BalanceTxWithScripts`
159- -- we don't create any collateral TxOut, hence the result is Nothing.
160- ownTxOut = if TxWithSeparateChange `elem` balanceTxconstraints
161- then fmap fst . uncons $ filter ((== changeAddr) . txOutAddress) $ txOutputs $ unBalancedTxTx unbalancedTx
162- else Nothing
163165 bTx = fst <$> balanceTxLoop utxoIndex privKeys minUtxos (addOutput changeAddr balancedTx)
164166
165- -- If we have change but no change UTxO, we need to add an output for it
166- -- We'll add a minimal output, run the loop again so it gets minUTxO, then update change
167-
168167 balancedTxWithChange <-
169168 case adaChange /= 0 of
170169 True | TxWithSeparateChange `elem` balanceTxconstraints -> bTx
171170 True | not (hasChangeUTxO changeAddr balancedTx) -> bTx
172- True -> pure balancedTx
173- False -> pure balancedTx
174-
171+ True -> pure balancedTx
172+ False -> pure balancedTx
173+
175174 -- Get the updated change, add it to the tx
176175 let finalAdaChange = getAdaChange utxoIndex balancedTxWithChange
177- fullyBalancedTx = addAdaChange changeAddr finalAdaChange balancedTxWithChange ownTxOut
176+ fullyBalancedTx = addAdaChange balanceTxconstraints changeAddr finalAdaChange balancedTxWithChange
178177
179178 -- finally, we must update the signatories
180179 hoistEither $ addSignatories ownPkh privKeys requiredSigs fullyBalancedTx
@@ -217,9 +216,13 @@ balanceTxIO' balanceTxconstraints pabConf ownPkh unbalancedTx =
217216 then pure (balancedTx, minUtxos)
218217 else balanceTxLoop utxoIndex privKeys minUtxos balancedTx
219218
219+ -- `utxosAndCollateralAtAddress` returns all the utxos that can be used as input of a `Tx`,
220+ -- i.e. we filter out `CollateralUtxo` present at the user's address, so it can't be used as input of a `Tx`.
221+ -- This function throws error if the `Tx` type is of `BalanceTxWithScripts` but there's not `CollateralUtxo`
222+ -- in the environment.
220223utxosAndCollateralAtAddress ::
221224 forall (w :: Type ) (effs :: [Type -> Type ]) (a :: Type ).
222- (Member (PABEffect w ) effs , KnowBalanceConstraint a ) =>
225+ (Member (PABEffect w ) effs , KnownBalanceConstraint a ) =>
223226 [BalanceTxConstraint a ] ->
224227 PABConfig ->
225228 Address ->
@@ -229,14 +232,16 @@ utxosAndCollateralAtAddress _ pabConf changeAddr =
229232 utxos <- newEitherT $ CardanoCLI. utxosAt @ w pabConf changeAddr
230233 inMemCollateral <- lift $ getInMemCollateral @ w
231234
232- case constraint @ a Proxy of
233- TxWithScript ->
234- case inMemCollateral of
235- Nothing -> throwE " The given transaction uses script, but there's no collateral provided. This usually means that, we failed to create Tx and update our ContractEnvironment."
236- Just _ -> pure (removeCollateralFromMap inMemCollateral utxos, inMemCollateral)
237-
235+ case knownConstraint @ a Proxy of
236+ TxWithScript ->
237+ maybe
238+ ( throwE $
239+ " The given transaction uses script, but there's no collateral provided."
240+ <> " This usually means that, we failed to create Tx and update our ContractEnvironment."
241+ )
242+ (const $ pure (removeCollateralFromMap inMemCollateral utxos, inMemCollateral))
243+ inMemCollateral
238244 TxWithoutScript -> pure (removeCollateralFromMap inMemCollateral utxos, Nothing )
239-
240245 _ -> pure (utxos, Nothing )
241246
242247hasChangeUTxO :: Address -> Tx -> Bool
@@ -404,22 +409,30 @@ handleNonAdaChange changeAddr utxos tx =
404409 then Right $ if Value. isZero nonAdaChange then tx else tx {txOutputs = outputs}
405410 else Left " Not enough inputs to balance tokens."
406411
407- -- | Adds ada change to a transaction, assuming there is already an output going to ownPkh. Otherwise, this is identity
408- addAdaChange :: Address -> Integer -> Tx -> Maybe TxOut -> Tx
409- addAdaChange _ 0 tx _ = tx
410- addAdaChange changeAddr change tx collateralOut =
411- tx
412- { txOutputs =
413- modifyFirst
414- check
415- (fmap $ addValueToTxOut $ Ada. lovelaceValueOf change)
416- (txOutputs tx)
417- }
418- where
419- check :: TxOut -> Bool
420- check txOut =
421- Tx. txOutAddress txOut == changeAddr
422- && Just txOut /= collateralOut
412+ {- | `addAdaChange` checks if `TxWithSeparateChange` is the present in the provided balancing
413+ constraints, if it is then we add the ada change to seperate `TxOut`, else we add it to
414+ any `TxOut` present at changeAddr.
415+ -}
416+ addAdaChange :: forall (a :: Type ). [BalanceTxConstraint a ] -> Address -> Integer -> Tx -> Tx
417+ addAdaChange _ _ 0 tx = tx
418+ addAdaChange balanceTxconstraints changeAddr change tx
419+ | TxWithSeparateChange `elem` balanceTxconstraints =
420+ tx
421+ { txOutputs =
422+ List. reverse $
423+ modifyFirst
424+ (\ txout -> Tx. txOutAddress txout == changeAddr && justLovelace (txOutValue txout))
425+ (fmap $ addValueToTxOut $ Ada. lovelaceValueOf change)
426+ (List. reverse $ txOutputs tx)
427+ }
428+ | otherwise =
429+ tx
430+ { txOutputs =
431+ modifyFirst
432+ ((== changeAddr) . Tx. txOutAddress)
433+ (fmap $ addValueToTxOut $ Ada. lovelaceValueOf change)
434+ (txOutputs tx)
435+ }
423436
424437addValueToTxOut :: Value -> TxOut -> TxOut
425438addValueToTxOut val txOut = txOut {txOutValue = txOutValue txOut <> val}
@@ -501,6 +514,9 @@ isValueNat :: Value -> Bool
501514isValueNat =
502515 all (\ (_, _, a) -> a >= 0 ) . Value. flattenValue
503516
517+ justLovelace :: Value -> Bool
518+ justLovelace value = length (Value. flattenValue value) == 1 && lovelaceValue value /= 0
519+
504520consJust :: forall (a :: Type ). Maybe a -> [a ] -> [a ]
505521consJust (Just x) = (x : )
506522consJust _ = id
0 commit comments