11{-# LANGUAGE AllowAmbiguousTypes #-}
22
3- module BotPlutusInterface.PreBalance (
4- preBalanceTx ,
5- preBalanceTxIO ,
3+ module BotPlutusInterface.Balance (
4+ balanceTxStep ,
5+ balanceTxIO ,
66) where
77
88import BotPlutusInterface.CardanoCLI qualified as CardanoCLI
99import BotPlutusInterface.Effects (PABEffect , createDirectoryIfMissing , printLog )
1010import BotPlutusInterface.Files (DummyPrivKey , unDummyPrivateKey )
1111import BotPlutusInterface.Files qualified as Files
1212import BotPlutusInterface.Types (LogLevel (Debug ), PABConfig )
13- import Cardano.Api.Shelley (Lovelace (Lovelace ), ProtocolParameters (protocolParamUTxOCostPerWord ))
1413import Control.Monad (foldM , void , zipWithM )
1514import Control.Monad.Freer (Eff , Member )
1615import Control.Monad.Trans.Class (lift )
1716import Control.Monad.Trans.Either (EitherT , hoistEither , newEitherT , runEitherT )
18- import Data.Either.Combinators (maybeToRight , rightToMaybe )
17+ import Data.Either.Combinators (rightToMaybe )
1918import Data.Kind (Type )
2019import Data.List (partition , (\\) )
2120import Data.Map (Map )
@@ -47,27 +46,26 @@ import Ledger.Tx (
4746 TxOutRef (.. ),
4847 )
4948import Ledger.Tx qualified as Tx
50- import Ledger.Value (Value ( Value ), getValue )
49+ import Ledger.Value (Value )
5150import Ledger.Value qualified as Value
5251import Plutus.V1.Ledger.Api (
5352 Credential (PubKeyCredential , ScriptCredential ),
5453 CurrencySymbol (.. ),
5554 TokenName (.. ),
5655 )
57- import PlutusTx.AssocMap qualified as AssocMap
5856import Prelude
5957
6058{- | Collect necessary tx inputs and collaterals, add minimum lovelace values and balance non ada
6159 assets
6260-}
63- preBalanceTxIO ::
61+ balanceTxIO ::
6462 forall (w :: Type ) (effs :: [Type -> Type ]).
6563 Member (PABEffect w ) effs =>
6664 PABConfig ->
6765 PubKeyHash ->
6866 UnbalancedTx ->
6967 Eff effs (Either Text Tx )
70- preBalanceTxIO pabConf ownPkh unbalancedTx =
68+ balanceTxIO pabConf ownPkh unbalancedTx =
7169 runEitherT $
7270 do
7371 utxos <- newEitherT $ CardanoCLI. utxosAt @ w pabConf $ Ledger. pubKeyHashAddress (Ledger. PaymentPubKeyHash ownPkh) Nothing
@@ -83,16 +81,23 @@ preBalanceTxIO pabConf ownPkh unbalancedTx =
8381
8482 lift $ printLog @ w Debug $ show utxoIndex
8583
86- loop utxoIndex privKeys requiredSigs [] tx
84+ -- Adds required collaterals, only needs to happen once
85+ -- Also adds signatures for fee calculation
86+ preBalancedTx <- hoistEither $ addTxCollaterals utxoIndex tx >>= addSignatories ownPkh privKeys requiredSigs
87+
88+ -- Balance the tx
89+ balancedTx <- loop utxoIndex privKeys [] preBalancedTx
90+
91+ -- finally, we must update the signatories
92+ hoistEither $ addSignatories ownPkh privKeys requiredSigs balancedTx
8793 where
8894 loop ::
8995 Map TxOutRef TxOut ->
9096 Map PubKeyHash DummyPrivKey ->
91- [PubKeyHash ] ->
9297 [(TxOut , Integer )] ->
9398 Tx ->
9499 EitherT Text (Eff effs ) Tx
95- loop utxoIndex privKeys requiredSigs prevMinUtxos tx = do
100+ loop utxoIndex privKeys prevMinUtxos tx = do
96101 void $ lift $ Files. writeAll @ w pabConf tx
97102 nextMinUtxos <-
98103 newEitherT $
@@ -102,20 +107,24 @@ preBalanceTxIO pabConf ownPkh unbalancedTx =
102107
103108 lift $ printLog @ w Debug $ " Min utxos: " ++ show minUtxos
104109
110+ -- Calculate fees by pre-balancing the tx, building it, and running the CLI on result
105111 txWithoutFees <-
106- hoistEither $ preBalanceTx pabConf . pcProtocolParams minUtxos 0 utxoIndex ownPkh privKeys requiredSigs tx
112+ hoistEither $ balanceTxStep minUtxos 0 utxoIndex ownPkh tx
107113
108114 lift $ createDirectoryIfMissing @ w False (Text. unpack pabConf. pcTxFileDir)
109- newEitherT $ CardanoCLI. buildTx @ w pabConf privKeys ownPkh ( CardanoCLI. BuildRaw 0 ) txWithoutFees
115+ newEitherT $ CardanoCLI. buildTx @ w pabConf privKeys txWithoutFees
110116 fees <- newEitherT $ CardanoCLI. calculateMinFee @ w pabConf txWithoutFees
111117
112118 lift $ printLog @ w Debug $ " Fees: " ++ show fees
113119
114- balancedTx <- hoistEither $ preBalanceTx pabConf. pcProtocolParams minUtxos fees utxoIndex ownPkh privKeys requiredSigs tx
120+ -- Rebalance the initial tx with the above fees
121+ balancedTx <- hoistEither $ balanceTxStep minUtxos fees utxoIndex ownPkh tx
122+
123+ let balanceTxWithFees = balancedTx {txFee = Ada. lovelaceValueOf fees}
115124
116- if balancedTx == tx
117- then pure balancedTx
118- else loop utxoIndex privKeys requiredSigs minUtxos balancedTx
125+ if balanceTxWithFees == tx
126+ then pure balanceTxWithFees
127+ else loop utxoIndex privKeys minUtxos balanceTxWithFees
119128
120129calculateMinUtxos ::
121130 forall (w :: Type ) (effs :: [Type -> Type ]).
@@ -127,24 +136,17 @@ calculateMinUtxos ::
127136calculateMinUtxos pabConf datums txOuts =
128137 zipWithM (fmap . (,)) txOuts <$> mapM (CardanoCLI. calculateMinUtxo @ w pabConf datums) txOuts
129138
130- preBalanceTx ::
131- ProtocolParameters ->
139+ balanceTxStep ::
132140 [(TxOut , Integer )] ->
133141 Integer ->
134142 Map TxOutRef TxOut ->
135143 PubKeyHash ->
136- Map PubKeyHash DummyPrivKey ->
137- [PubKeyHash ] ->
138144 Tx ->
139145 Either Text Tx
140- preBalanceTx pparams minUtxos fees utxos ownPkh privKeys requiredSigs tx =
141- addTxCollaterals utxos tx
142- >>= balanceTxIns pparams utxos fees
143- >>= balanceNonAdaOuts ownPkh utxos
144- >>= Right . addLovelaces minUtxos
145- >>= balanceTxIns pparams utxos fees -- Adding more inputs if required
146- >>= balanceNonAdaOuts ownPkh utxos
147- >>= addSignatories ownPkh privKeys requiredSigs
146+ balanceTxStep minUtxos fees utxos ownPkh tx =
147+ Right (addLovelaces minUtxos tx)
148+ >>= balanceTxIns utxos fees
149+ >>= handleChange ownPkh utxos fees
148150
149151-- | Getting the necessary utxos to cover the fees for the transaction
150152collectTxIns :: Set TxIn -> Map TxOutRef TxOut -> Value -> Either Text (Set TxIn )
@@ -202,18 +204,13 @@ addLovelaces minLovelaces tx =
202204 $ txOutputs tx
203205 in tx {txOutputs = lovelacesAdded}
204206
205- balanceTxIns :: ProtocolParameters -> Map TxOutRef TxOut -> Integer -> Tx -> Either Text Tx
206- balanceTxIns pparams utxos fees tx = do
207- Lovelace utxoCost <-
208- maybeToRight " UTxOCostPerWord parameter not found" $ protocolParamUTxOCostPerWord pparams
207+ balanceTxIns :: Map TxOutRef TxOut -> Integer -> Tx -> Either Text Tx
208+ balanceTxIns utxos fees tx = do
209209 let txOuts = Tx. txOutputs tx
210210 nonMintedValue = mconcat (map Tx. txOutValue txOuts) `minus` txMint tx
211- -- An ada-only UTxO entry is 29 words. More details about min utxo calculation can be found here:
212- -- https://github.com/cardano-foundation/CIPs/tree/master/CIP-0028#rationale-for-parameter-choices
213- changeMinUtxo = 29 * utxoCost
214211 minSpending =
215212 mconcat
216- [ Ada. lovelaceValueOf ( fees + changeMinUtxo)
213+ [ Ada. lovelaceValueOf fees
217214 , nonMintedValue
218215 ]
219216 txIns <- collectTxIns (txInputs tx) utxos minSpending
@@ -235,28 +232,28 @@ addTxCollaterals utxos tx = do
235232 _ -> Left " There are no utxos to be used as collateral"
236233 filterAdaOnly = Map. filter (isAdaOnly . txOutValue)
237234
238- -- | We need to balance non ada values, as the cardano-cli is unable to balance them (as of 2021/09/24)
239- balanceNonAdaOuts :: PubKeyHash -> Map TxOutRef TxOut -> Tx -> Either Text Tx
240- balanceNonAdaOuts ownPkh utxos tx =
235+ -- | Ensures all change goes back to user
236+ handleChange :: PubKeyHash -> Map TxOutRef TxOut -> Integer -> Tx -> Either Text Tx
237+ handleChange ownPkh utxos fees tx =
241238 let changeAddr = Ledger. pubKeyHashAddress (Ledger. PaymentPubKeyHash ownPkh) Nothing
242239 txInRefs = map Tx. txInRef $ Set. toList $ txInputs tx
243240 inputValue = mconcat $ map Tx. txOutValue $ mapMaybe (`Map.lookup` utxos) txInRefs
244241 outputValue = mconcat $ map Tx. txOutValue $ txOutputs tx
245242 nonMintedOutputValue = outputValue `minus` txMint tx
246- nonAdaChange = filterNonAda inputValue `minus` filterNonAda nonMintedOutputValue
243+ change = ( inputValue `minus` nonMintedOutputValue) `minus` Ada. lovelaceValueOf fees
247244 outputs =
248245 case partition ((==) changeAddr . Tx. txOutAddress) $ txOutputs tx of
249246 ([] , txOuts) ->
250247 TxOut
251248 { txOutAddress = changeAddr
252- , txOutValue = nonAdaChange
249+ , txOutValue = change
253250 , txOutDatumHash = Nothing
254251 } :
255252 txOuts
256253 (txOut@ TxOut {txOutValue = v} : txOuts, txOuts') ->
257- txOut {txOutValue = v <> nonAdaChange } : (txOuts <> txOuts')
258- in if isValueNat nonAdaChange
259- then Right $ if Value. isZero nonAdaChange then tx else tx {txOutputs = outputs}
254+ txOut {txOutValue = v <> change } : (txOuts <> txOuts')
255+ in if isValueNat change
256+ then Right $ if Value. isZero change then tx else tx {txOutputs = outputs}
260257 else Left " Not enough inputs to balance tokens."
261258
262259{- | Add the required signatorioes to the transaction. Be aware the the signature itself is invalid,
@@ -289,14 +286,6 @@ validateRange _ = True
289286showText :: forall (a :: Type ). Show a => a -> Text
290287showText = Text. pack . show
291288
292- -- | Filter by key for Associated maps (why doesn't this exist?)
293- filterKey :: (k -> Bool ) -> AssocMap. Map k v -> AssocMap. Map k v
294- filterKey f = AssocMap. mapMaybeWithKey $ \ k v -> if f k then Just v else Nothing
295-
296- -- | Filter a value to contain only non ada assets
297- filterNonAda :: Value -> Value
298- filterNonAda = Value . filterKey (/= Ada. adaSymbol) . getValue
299-
300289minus :: Value -> Value -> Value
301290minus x y =
302291 let negativeValues = map (\ (c, t, a) -> (c, t, - a)) $ Value. flattenValue y
0 commit comments