Skip to content

Commit 6707cc4

Browse files
committed
add mechanism for separate change utxo
1 parent d8e66ac commit 6707cc4

File tree

2 files changed

+94
-73
lines changed

2 files changed

+94
-73
lines changed

src/BotPlutusInterface/Balance.hs

Lines changed: 76 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
{-# LANGUAGE NamedFieldPuns #-}
33

44
module BotPlutusInterface.Balance (
5-
BalanceTxConstraint(..),
5+
BalanceTxConstraint (TxWithScript, TxWithoutScript, TxWithSeparateChange),
66
balanceTxStep,
77
balanceTxIO,
88
balanceTxIO',
@@ -32,11 +32,12 @@ import Data.Bifunctor (bimap)
3232
import Data.Coerce (coerce)
3333
import Data.Either.Combinators (rightToMaybe)
3434
import Data.Kind (Type)
35-
import Data.List (uncons, (\\))
35+
import Data.List ((\\))
36+
import Data.List qualified as List
3637
import Data.Map (Map)
3738
import Data.Map qualified as Map
3839
import Data.Maybe (fromMaybe, mapMaybe)
39-
import Data.Proxy (Proxy(Proxy))
40+
import Data.Proxy (Proxy (Proxy))
4041
import Data.Set (Set)
4142
import Data.Set qualified as Set
4243
import Data.Text (Text)
@@ -81,8 +82,8 @@ data TxWithScript
8182
data TxWithoutScript
8283

8384
data BalanceTxConstraint a where
84-
TxWithScript :: BalanceTxConstraint TxWithScript
85-
TxWithoutScript :: BalanceTxConstraint TxWithoutScript
85+
TxWithScript :: BalanceTxConstraint TxWithScript
86+
TxWithoutScript :: BalanceTxConstraint TxWithoutScript
8687
TxWithSeparateChange :: BalanceTxConstraint a
8788

8889
instance 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+
-}
103109
balanceTxIO ::
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
-}
115121
balanceTxIO' ::
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.
220223
utxosAndCollateralAtAddress ::
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

242247
hasChangeUTxO :: 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

424437
addValueToTxOut :: Value -> TxOut -> TxOut
425438
addValueToTxOut val txOut = txOut {txOutValue = txOutValue txOut <> val}
@@ -501,6 +514,9 @@ isValueNat :: Value -> Bool
501514
isValueNat =
502515
all (\(_, _, a) -> a >= 0) . Value.flattenValue
503516

517+
justLovelace :: Value -> Bool
518+
justLovelace value = length (Value.flattenValue value) == 1 && lovelaceValue value /= 0
519+
504520
consJust :: forall (a :: Type). Maybe a -> [a] -> [a]
505521
consJust (Just x) = (x :)
506522
consJust _ = id

src/BotPlutusInterface/Contract.hs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,6 @@ balanceTx ::
287287
Eff effs BalanceTxResponse
288288
balanceTx contractEnv unbalancedTx = do
289289
let pabConf = contractEnv.cePABConfig
290-
-- balanceTxconstaints =
291-
-- if PreBalance.txUsesScripts (unBalancedTxTx unbalancedTx)
292-
-- then [PreBalance.TxWithScript]
293-
-- else [PreBalance.TxWithoutScript]
294-
295290

296291
result <- handleCollateral @w contractEnv
297292

@@ -300,10 +295,18 @@ balanceTx contractEnv unbalancedTx = do
300295
_ -> do
301296
uploadDir @w pabConf.pcSigningKeyFileDir
302297
eitherPreBalancedTx <-
303-
PreBalance.balanceTxIO @w
304-
pabConf
305-
pabConf.pcOwnPubKeyHash
306-
unbalancedTx
298+
if PreBalance.txUsesScripts (unBalancedTxTx unbalancedTx)
299+
then
300+
PreBalance.balanceTxIO' @w
301+
[PreBalance.TxWithScript]
302+
pabConf
303+
pabConf.pcOwnPubKeyHash
304+
unbalancedTx
305+
else
306+
PreBalance.balanceTxIO @w
307+
pabConf
308+
pabConf.pcOwnPubKeyHash
309+
unbalancedTx
307310

308311
pure $ either (BalanceTxFailed . InsufficientFunds) (BalanceTxSuccess . Right) eitherPreBalancedTx
309312

@@ -502,10 +505,12 @@ makeCollateral cEnv = runEitherT $ do
502505
firstEitherT (T.pack . show) $
503506
hoistEither $ Collateral.mkCollateralTx pabConf
504507

505-
balancedTx <- newEitherT
506-
$ PreBalance.balanceTxIO' @w
507-
[PreBalance.TxWithoutScript, PreBalance.TxWithSeparateChange]
508-
pabConf pabConf.pcOwnPubKeyHash unbalancedTx
508+
balancedTx <-
509+
newEitherT $
510+
PreBalance.balanceTxIO' @w
511+
[PreBalance.TxWithoutScript, PreBalance.TxWithSeparateChange]
512+
pabConf
513+
pabConf.pcOwnPubKeyHash unbalancedTx
509514

510515
wbr <- lift $ writeBalancedTx cEnv (Right balancedTx)
511516
case wbr of

0 commit comments

Comments
 (0)