22{-# LANGUAGE NamedFieldPuns #-}
33
44module BotPlutusInterface.Balance (
5+ BalanceTxConstraint (.. ),
56 balanceTxStep ,
67 balanceTxIO ,
8+ balanceTxIO' ,
79 txUsesScripts ,
810 withFee ,
911) where
@@ -18,7 +20,7 @@ import BotPlutusInterface.Effects (
1820 )
1921import BotPlutusInterface.Files (DummyPrivKey , unDummyPrivateKey )
2022import BotPlutusInterface.Files qualified as Files
21- import BotPlutusInterface.Types (CollateralUtxo , LogLevel (Debug ), PABConfig , collateralTxOutRef , collateralValue )
23+ import BotPlutusInterface.Types (CollateralUtxo , LogLevel (Debug ), PABConfig , collateralTxOutRef )
2224import Cardano.Api (ExecutionUnitPrices (ExecutionUnitPrices ))
2325import Cardano.Api.Shelley (ProtocolParameters (protocolParamPrices ))
2426import Control.Monad (foldM , void , zipWithM )
@@ -34,6 +36,7 @@ import Data.List (uncons, (\\))
3436import Data.Map (Map )
3537import Data.Map qualified as Map
3638import Data.Maybe (fromMaybe , mapMaybe )
39+ import Data.Proxy (Proxy (Proxy ))
3740import Data.Set (Set )
3841import Data.Set qualified as Set
3942import Data.Text (Text )
@@ -74,57 +77,53 @@ import BotPlutusInterface.Collateral (removeCollateralFromMap)
7477import Prettyprinter (pretty , viaShow , (<+>) )
7578import Prelude
7679
77- -- `BalanceTx` is for checking which type of `Tx` we are balancing.
78- data BalanceTx
79- = -- | `BalanceTxCollateral` denotes that we are balancing the `Tx` that creates collateral.
80- BalanceTxCollateral
81- | -- | `BalanceTxWithScripts` denotes that we are balancing the `Tx` that uses scripts.
82- BalanceTxWithScripts
83- | -- | `BalanceTxWithoutScripts` denotes that we are balancing the `Tx` that doesn't uses any scripts.
84- BalanceTxWithoutScripts
85- deriving stock (Eq )
86-
87- -- `balanceTxIO` checks the type of the current `UnbalancedTx` and then calls `balanceTxIO'`.
80+ data TxWithScript
81+ data TxWithoutScript
82+
83+ data BalanceTxConstraint a where
84+ TxWithScript :: BalanceTxConstraint TxWithScript
85+ TxWithoutScript :: BalanceTxConstraint TxWithoutScript
86+ TxWithSeparateChange :: BalanceTxConstraint a
87+
88+ instance Eq (BalanceTxConstraint a ) where
89+ TxWithScript == TxWithScript = True
90+ TxWithoutScript == TxWithoutScript = True
91+ TxWithSeparateChange == TxWithSeparateChange = True
92+ _ == _ = False
93+
94+ class KnowBalanceConstraint (a :: Type ) where
95+ constraint :: proxy a -> BalanceTxConstraint a
96+
97+ instance KnowBalanceConstraint TxWithScript where
98+ constraint _ = TxWithScript
99+
100+ instance KnowBalanceConstraint TxWithoutScript where
101+ constraint _ = TxWithoutScript
102+
88103balanceTxIO ::
89104 forall (w :: Type ) (effs :: [Type -> Type ]).
90105 (Member (PABEffect w ) effs ) =>
91106 PABConfig ->
92107 PubKeyHash ->
93108 UnbalancedTx ->
94109 Eff effs (Either Text Tx )
95- balanceTxIO pabConf ownPkh unbalancedTx@ (UnbalancedTx tx' _ _ _)
96- | validCollateralTx tx' = balanceTxIO' @ w pabConf ownPkh unbalancedTx BalanceTxCollateral
97- | txUsesScripts tx' = balanceTxIO' @ w pabConf ownPkh unbalancedTx BalanceTxWithScripts
98- | otherwise = balanceTxIO' @ w pabConf ownPkh unbalancedTx BalanceTxWithoutScripts
99- where
100- validCollateralTx :: Tx -> Bool
101- validCollateralTx tx
102- | [out] <- txOutputs tx
103- , Map. size (txData tx) == 0
104- , txMint tx == mempty
105- , txOutValue out == collateralValue pabConf
106- , txMintScripts tx == mempty
107- , txCollateral tx == mempty
108- , not (txUsesScripts tx) =
109- True
110- | otherwise =
111- False
110+ balanceTxIO = balanceTxIO' @ w [TxWithScript ]
112111
113112{- | Collect necessary tx inputs and collaterals, add minimum lovelace values and balance non ada
114113 assets
115114-}
116115balanceTxIO' ::
117- forall (w :: Type ) (effs :: [Type -> Type ]).
118- (Member (PABEffect w ) effs ) =>
116+ forall (w :: Type ) (effs :: [Type -> Type ]) (a :: Type ) .
117+ (Member (PABEffect w ) effs , KnowBalanceConstraint a ) =>
118+ [BalanceTxConstraint a ] ->
119119 PABConfig ->
120120 PubKeyHash ->
121121 UnbalancedTx ->
122- BalanceTx ->
123122 Eff effs (Either Text Tx )
124- balanceTxIO' pabConf ownPkh unbalancedTx balanceTxType =
123+ balanceTxIO' balanceTxconstraints pabConf ownPkh unbalancedTx =
125124 runEitherT $
126125 do
127- (utxos, mcollateral) <- newEitherT $ utxosAndCollateralAtAddress @ w balanceTxType pabConf changeAddr
126+ (utxos, mcollateral) <- newEitherT $ utxosAndCollateralAtAddress @ w balanceTxconstraints pabConf changeAddr
128127 privKeys <- newEitherT $ Files. readPrivateKeys @ w pabConf
129128
130129 let utxoIndex = fmap Tx. toTxOut utxos <> unBalancedTxUtxoIndex unbalancedTx
@@ -143,14 +142,13 @@ balanceTxIO' pabConf ownPkh unbalancedTx balanceTxType =
143142
144143 -- Adds required collaterals in the `Tx`, if the `Tx` is of type `BalanceTxWithScripts`.
145144 -- Also adds signatures for fee calculation
146- preBalancedTx <-
147- case balanceTxType of
148- BalanceTxWithScripts ->
149- maybe
150- (throwE " Tx uses script but no collateral was provided." )
151- (hoistEither . addSignatories ownPkh privKeys requiredSigs . flip addTxCollaterals tx)
152- mcollateral
153- _ -> hoistEither $ addSignatories ownPkh privKeys requiredSigs tx
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
154152
155153 -- Balance the tx
156154 (balancedTx, minUtxos) <- balanceTxLoop utxoIndex privKeys [] preBalancedTx
@@ -159,30 +157,30 @@ balanceTxIO' pabConf ownPkh unbalancedTx balanceTxType =
159157 let adaChange = getAdaChange utxoIndex balancedTx
160158 -- This represents the collateral TxOut, in cases of `BalanceTxWithoutScripts` & `BalanceTxWithScripts`
161159 -- we don't create any collateral TxOut, hence the result is Nothing.
162- collateralTxOut = case balanceTxType of
163- BalanceTxCollateral -> fmap fst . uncons . txOutputs $ unBalancedTxTx unbalancedTx
164- BalanceTxWithoutScripts -> Nothing
165- BalanceTxWithScripts -> Nothing
160+ ownTxOut = if TxWithSeparateChange `elem` balanceTxconstraints
161+ then fmap fst . uncons $ filter ((== changeAddr) . txOutAddress) $ txOutputs $ unBalancedTxTx unbalancedTx
162+ else Nothing
166163 bTx = fst <$> balanceTxLoop utxoIndex privKeys minUtxos (addOutput changeAddr balancedTx)
167164
168165 -- If we have change but no change UTxO, we need to add an output for it
169166 -- We'll add a minimal output, run the loop again so it gets minUTxO, then update change
170167
171168 balancedTxWithChange <-
172- case balanceTxType of
173- BalanceTxCollateral | adaChange /= 0 -> bTx
174- _ | adaChange /= 0 && not (hasChangeUTxO changeAddr balancedTx) -> bTx
175- _ -> pure balancedTx
176-
169+ case adaChange /= 0 of
170+ True | TxWithSeparateChange `elem` balanceTxconstraints -> bTx
171+ True | not (hasChangeUTxO changeAddr balancedTx) -> bTx
172+ True -> pure balancedTx
173+ False -> pure balancedTx
174+
177175 -- Get the updated change, add it to the tx
178176 let finalAdaChange = getAdaChange utxoIndex balancedTxWithChange
179- fullyBalancedTx = addAdaChange changeAddr finalAdaChange balancedTxWithChange collateralTxOut
177+ fullyBalancedTx = addAdaChange changeAddr finalAdaChange balancedTxWithChange ownTxOut
180178
181179 -- finally, we must update the signatories
182180 hoistEither $ addSignatories ownPkh privKeys requiredSigs fullyBalancedTx
183181 where
184182 changeAddr :: Address
185- changeAddr = Ledger. pubKeyHashAddress (Ledger. PaymentPubKeyHash ownPkh) ( pabConf. pcOwnStakePubKeyHash)
183+ changeAddr = Ledger. pubKeyHashAddress (Ledger. PaymentPubKeyHash ownPkh) pabConf. pcOwnStakePubKeyHash
186184
187185 balanceTxLoop ::
188186 Map TxOutRef TxOut ->
@@ -219,29 +217,27 @@ balanceTxIO' pabConf ownPkh unbalancedTx balanceTxType =
219217 then pure (balancedTx, minUtxos)
220218 else balanceTxLoop utxoIndex privKeys minUtxos balancedTx
221219
222- -- `utxosAndCollateralAtAddress` returns all the utxos that can be used as input of a `Tx`,
223- -- i.e. we filter out `CollateralUtxo` present at the user's address, so it can't be used as input of a `Tx`.
224- -- This function throws error if the `Tx` type is of `BalanceTxWithScripts` but there's not `CollateralUtxo`
225- -- in the environment.
226220utxosAndCollateralAtAddress ::
227- forall (w :: Type ) (effs :: [Type -> Type ]).
228- Member (PABEffect w ) effs =>
229- BalanceTx ->
221+ forall (w :: Type ) (effs :: [Type -> Type ]) ( a :: Type ) .
222+ ( Member (PABEffect w ) effs , KnowBalanceConstraint a ) =>
223+ [ BalanceTxConstraint a ] ->
230224 PABConfig ->
231225 Address ->
232226 Eff effs (Either Text (Map TxOutRef Tx. ChainIndexTxOut , Maybe CollateralUtxo ))
233- utxosAndCollateralAtAddress balanceTxType pabConf changeAddr =
227+ utxosAndCollateralAtAddress _ pabConf changeAddr =
234228 runEitherT $ do
235229 utxos <- newEitherT $ CardanoCLI. utxosAt @ w pabConf changeAddr
236230 inMemCollateral <- lift $ getInMemCollateral @ w
237231
238- case balanceTxType of
239- BalanceTxCollateral -> pure (utxos, Nothing )
240- BalanceTxWithoutScripts -> pure (removeCollateralFromMap inMemCollateral utxos, Nothing )
241- BalanceTxWithScripts ->
232+ case constraint @ a Proxy of
233+ TxWithScript ->
242234 case inMemCollateral of
243235 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."
244236 Just _ -> pure (removeCollateralFromMap inMemCollateral utxos, inMemCollateral)
237+
238+ TxWithoutScript -> pure (removeCollateralFromMap inMemCollateral utxos, Nothing )
239+
240+ _ -> pure (utxos, Nothing )
245241
246242hasChangeUTxO :: Address -> Tx -> Bool
247243hasChangeUTxO changeAddr tx =
0 commit comments