@@ -11,18 +11,22 @@ module Wst.Offchain.BuildTx.ProgrammableLogic
1111where
1212
1313import Cardano.Api qualified as C
14+ import Cardano.Api.Internal.Tx.Body qualified as C
1415import Cardano.Api.Shelley qualified as C
1516import Control.Lens ((^.) )
17+ import Control.Lens qualified as L
18+ import Control.Monad (forM_ )
1619import Control.Monad.Reader (MonadReader , asks )
17- import Convex.BuildTx (MonadBuildTx , addReference , addWithdrawalWithTxBody ,
18- buildScriptWitness , findIndexReference ,
19- findIndexSpending , prependTxOut , spendPlutusInlineDatum )
20+ import Convex.BuildTx (MonadBuildTx , addOutput , addReference ,
21+ addWithdrawalWithTxBody , buildScriptWitness ,
22+ findIndexReference , findIndexSpending ,
23+ spendPlutusInlineDatum )
2024import Convex.CardanoApi.Lenses as L
2125import Convex.Class (MonadBlockchain (queryNetworkId ))
2226import Convex.PlutusLedger.V1 (transPolicyId )
2327import Convex.Utils qualified as Utils
2428import Data.Foldable (find )
25- import Data.List (partition )
29+ import Data.List (findIndex , partition )
2630import Data.Maybe (fromJust )
2731import GHC.Exts (IsList (.. ))
2832import PlutusLedgerApi.V3 (CurrencySymbol (.. ))
@@ -44,65 +48,84 @@ import Wst.Offchain.Query (UTxODat (..))
4448 NOTE: Seems the issuer is only able to seize 1 UTxO at a time.
4549 In the future we should allow multiple UTxOs in 1 Tx.
4650-}
47- seizeProgrammableToken :: forall a env era m . (MonadReader env m , Env. HasDirectoryEnv env , C. IsBabbageBasedEra era , MonadBlockchain era m , C. HasScriptLanguageInEra C. PlutusScriptV3 era , MonadBuildTx era m ) => UTxODat era ProgrammableLogicGlobalParams -> UTxODat era a -> C. PolicyId -> [UTxODat era DirectorySetNode ] -> m ()
48- seizeProgrammableToken UTxODat {uIn = paramsTxIn} UTxODat {uIn = seizingTxIn, uOut = seizingTxOut} seizingTokenPolicyId directoryList = Utils. inBabbage @ era $ do
51+ seizeProgrammableToken ::
52+ forall a env era m .
53+ ( MonadReader env m ,
54+ Env. HasDirectoryEnv env ,
55+ C. IsBabbageBasedEra era ,
56+ MonadBlockchain era m ,
57+ C. HasScriptLanguageInEra C. PlutusScriptV3 era ,
58+ MonadBuildTx era m
59+ ) =>
60+ UTxODat era ProgrammableLogicGlobalParams ->
61+ [UTxODat era a ] ->
62+ C. PolicyId ->
63+ [UTxODat era DirectorySetNode ] ->
64+ m ()
65+ seizeProgrammableToken UTxODat {uIn = paramsTxIn} seizingUTxOs seizingTokenPolicyId directoryList = Utils. inBabbage @ era $ do
4966 nid <- queryNetworkId
5067 globalStakeScript <- asks (Env. dsProgrammableLogicGlobalScript . Env. directoryEnv)
5168 baseSpendingScript <- asks (Env. dsProgrammableLogicBaseScript . Env. directoryEnv)
5269
5370 let globalStakeCred = C. StakeCredentialByScript $ C. hashScript $ C. PlutusScript C. PlutusScriptV3 globalStakeScript
71+ programmableLogicBaseCredential = C. PaymentCredentialByScript $ C. hashScript $ C. PlutusScript C. PlutusScriptV3 baseSpendingScript
5472
5573 -- Finds the directory node entry that references the programmable token symbol
5674 dirNodeRef <-
5775 maybe (error " Cannot seize non-programmable token. Entry does not exist in directoryList" ) (pure . uIn) $
5876 find (isNodeWithProgrammableSymbol (transPolicyId seizingTokenPolicyId)) directoryList
5977
6078 -- destStakeCred <- either (error . ("Could not unTrans credential: " <>) . show) pure $ unTransStakeCredential $ transCredential seizeDestinationCred
61- let
62- -- issuerDestinationAddress = C.makeShelleyAddressInEra C.shelleyBasedEra nid progLogicBaseCred (C.StakeAddressByValue destStakeCred)
63-
64- (seizedAddr, remainingValue) = case seizingTxOut of
65- (C. TxOut a v _ _) ->
66- let (_seized, other) =
67- partition
68- ( \ case
69- (C. AdaAssetId , _q) -> False
70- (C. AssetId a_ _, _q) -> a_ == seizingTokenPolicyId
71- )
72- $ toList $ C. txOutValueToValue v
73- in (a, fromList other)
74-
75- remainingTxOutValue = C. TxOutValueShelleyBased C. shelleyBasedEra $ C. toLedgerValue @ era C. maryBasedEra remainingValue
7679
77- seizedOutput = C. TxOut seizedAddr remainingTxOutValue C. TxOutDatumNone C. ReferenceScriptNone
80+ forM_ seizingUTxOs $ \ UTxODat {uIn = seizingTxIn, uOut = seizingTxOut} -> do
81+ spendPlutusInlineDatum seizingTxIn baseSpendingScript ()
82+ let (seizedAddr, remainingValue, seizedDatum, referenceScript) = case seizingTxOut of
83+ (C. TxOut a v dat refScript) ->
84+ let (_seized, other) =
85+ partition
86+ ( \ case
87+ (C. AdaAssetId , _q) -> False
88+ (C. AssetId a_ _, _q) -> a_ == seizingTokenPolicyId
89+ )
90+ $ toList $ C. txOutValueToValue v
91+ in (a, fromList other, dat, refScript)
92+ remainingTxOutValue = C. TxOutValueShelleyBased C. shelleyBasedEra $ C. toLedgerValue @ era C. maryBasedEra remainingValue
93+ seizedOutput = C. TxOut seizedAddr remainingTxOutValue seizedDatum referenceScript
94+ addOutput (C. fromCtxUTxOTxOut seizedOutput)
7895
96+ let
7997 -- Finds the index of the directory node reference in the transaction ref
8098 -- inputs
8199 directoryNodeReferenceIndex txBody =
82100 fromIntegral @ Int @ Integer $ findIndexReference dirNodeRef txBody
83101
84102 -- Finds the index of the issuer input in the transaction body
85103 seizingInputIndex txBody =
86- fromIntegral @ Int @ Integer $ findIndexSpending seizingTxIn txBody
87-
88- -- Finds the index of the issuer seized output in the transaction body
89- seizingOutputIndex txBody =
90- fromIntegral @ Int @ Integer $ fst $ fromJust (find ((== seizedOutput) . snd ) $ zip [0 .. ] $ txBody ^. L. txOuts)
104+ map (\ UTxODat {uIn = seizingTxIn} -> fromIntegral @ Int @ Integer $ findIndexSpending seizingTxIn txBody) seizingUTxOs
105+
106+ -- Finds the index of the first output to the programmable logic base credential
107+ firstSeizeContinuationOutputIndex txBody =
108+ fromIntegral @ Int @ Integer $
109+ fromJust $
110+ findIndex
111+ ( maybe False ((== programmableLogicBaseCredential) . C. fromShelleyPaymentCredential)
112+ . L. preview (L. _TxOut . L. _1 . L. _AddressInEra . L. _Address . L. _2)
113+ )
114+ (txBody ^. L. txOuts)
91115
92116 -- The seizing redeemer for the global script
93117 programmableLogicGlobalRedeemer txBody =
94118 SeizeAct
95- { plgrSeizeInputIdx = seizingInputIndex txBody,
96- plgrSeizeOutputIdx = seizingOutputIndex txBody,
97- plgrDirectoryNodeIdx = directoryNodeReferenceIndex txBody
119+ { plgrDirectoryNodeIdx = directoryNodeReferenceIndex txBody,
120+ plgrInputIdxs = seizingInputIndex txBody,
121+ plgrOutputsStartIdx = firstSeizeContinuationOutputIndex txBody,
122+ plgrLengthInputIdxs = fromIntegral @ Int @ Integer $ length seizingUTxOs
98123 }
99124
100125 programmableGlobalWitness txBody = buildScriptWitness globalStakeScript C. NoScriptDatumForStake (programmableLogicGlobalRedeemer txBody)
101126
102- prependTxOut seizedOutput
103127 addReference paramsTxIn -- Protocol Params TxIn
104128 addReference dirNodeRef -- Directory Node TxIn
105- spendPlutusInlineDatum seizingTxIn baseSpendingScript () -- Redeemer is ignored in programmableLogicBase
106129 addWithdrawalWithTxBody -- Add the global script witness to the transaction
107130 (C. makeStakeAddress nid globalStakeCred)
108131 (C. Quantity 0 )
0 commit comments