-
Notifications
You must be signed in to change notification settings - Fork 476
/
Contexts.hs
254 lines (219 loc) · 10.5 KB
/
Contexts.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
-- editorconfig-checker-disable-file
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-}
{-# OPTIONS_GHC -fno-strictness #-}
{-# OPTIONS_GHC -fno-specialise #-}
{-# OPTIONS_GHC -fno-omit-interface-pragmas #-}
module PlutusLedgerApi.V2.Contexts
(
-- * Pending transactions and related types
TxInfo(..)
, ScriptContext(..)
, ScriptPurpose(..)
, TxId (..)
, TxOut(..)
, TxOutRef(..)
, TxInInfo(..)
, findOwnInput
, findDatum
, findDatumHash
, findTxInByTxOutRef
, findContinuingOutputs
, getContinuingOutputs
-- * Validator functions
, pubKeyOutput
, scriptOutputsAt
, pubKeyOutputsAt
, valueLockedBy
, valuePaidTo
, spendsOutput
, txSignedBy
, valueSpent
, valueProduced
, ownCurrencySymbol
, ownHashes
, ownHash
, fromSymbol
) where
import GHC.Generics (Generic)
import PlutusTx
import PlutusTx.AssocMap hiding (filter, mapMaybe)
import PlutusTx.Prelude hiding (toList)
import Prettyprinter (Pretty (..), nest, vsep, (<+>))
import PlutusLedgerApi.V1.Address (Address (..))
import PlutusLedgerApi.V1.Contexts (ScriptPurpose (..), fromSymbol, pubKeyOutput)
import PlutusLedgerApi.V1.Credential (Credential (..), StakingCredential)
import PlutusLedgerApi.V1.Crypto (PubKeyHash (..))
import PlutusLedgerApi.V1.DCert (DCert (..))
import PlutusLedgerApi.V1.Scripts
import PlutusLedgerApi.V1.Time (POSIXTimeRange)
import PlutusLedgerApi.V1.Value (CurrencySymbol, Value)
import PlutusLedgerApi.V2.Tx (OutputDatum (..), TxId (..), TxOut (..), TxOutRef (..))
import Prelude qualified as Haskell
-- | An input of a pending transaction.
data TxInInfo = TxInInfo
{ txInInfoOutRef :: TxOutRef
, txInInfoResolved :: TxOut
} deriving stock (Generic, Haskell.Show, Haskell.Eq)
instance Eq TxInInfo where
TxInInfo ref res == TxInInfo ref' res' = ref == ref' && res == res'
instance Pretty TxInInfo where
pretty TxInInfo{txInInfoOutRef, txInInfoResolved} =
pretty txInInfoOutRef <+> "->" <+> pretty txInInfoResolved
-- | A pending transaction. This is the view as seen by validator scripts, so some details are stripped out.
data TxInfo = TxInfo
{ txInfoInputs :: [TxInInfo] -- ^ Transaction inputs
, txInfoReferenceInputs :: [TxInInfo] -- ^ Transaction reference inputs
, txInfoOutputs :: [TxOut] -- ^ Transaction outputs
, txInfoFee :: Value -- ^ The fee paid by this transaction.
, txInfoMint :: Value -- ^ The 'Value' minted by this transaction.
, txInfoDCert :: [DCert] -- ^ Digests of certificates included in this transaction
, txInfoWdrl :: Map StakingCredential Integer -- ^ Withdrawals
, txInfoValidRange :: POSIXTimeRange -- ^ The valid range for the transaction.
, txInfoSignatories :: [PubKeyHash] -- ^ Signatures provided with the transaction, attested that they all signed the tx
, txInfoRedeemers :: Map ScriptPurpose Redeemer
, txInfoData :: Map DatumHash Datum
, txInfoId :: TxId
-- ^ Hash of the pending transaction (excluding witnesses)
} deriving stock (Generic, Haskell.Show, Haskell.Eq)
instance Eq TxInfo where
{-# INLINABLE (==) #-}
TxInfo i ri o f m c w r s rs d tid == TxInfo i' ri' o' f' m' c' w' r' s' rs' d' tid' =
i == i' && ri == ri' && o == o' && f == f' && m == m' && c == c' && w == w' && r == r' && s == s' && rs == rs' && d == d' && tid == tid'
instance Pretty TxInfo where
pretty TxInfo{txInfoInputs, txInfoReferenceInputs, txInfoOutputs, txInfoFee, txInfoMint, txInfoDCert, txInfoWdrl, txInfoValidRange, txInfoSignatories, txInfoRedeemers, txInfoData, txInfoId} =
vsep
[ "TxId:" <+> pretty txInfoId
, "Inputs:" <+> pretty txInfoInputs
, "Reference inputs:" <+> pretty txInfoReferenceInputs
, "Outputs:" <+> pretty txInfoOutputs
, "Fee:" <+> pretty txInfoFee
, "Value minted:" <+> pretty txInfoMint
, "DCerts:" <+> pretty txInfoDCert
, "Wdrl:" <+> pretty txInfoWdrl
, "Valid range:" <+> pretty txInfoValidRange
, "Signatories:" <+> pretty txInfoSignatories
, "Redeemers:" <+> pretty txInfoRedeemers
, "Datums:" <+> pretty txInfoData
]
data ScriptContext = ScriptContext{scriptContextTxInfo :: TxInfo, scriptContextPurpose :: ScriptPurpose }
deriving stock (Generic, Haskell.Eq, Haskell.Show)
instance Eq ScriptContext where
{-# INLINABLE (==) #-}
ScriptContext info purpose == ScriptContext info' purpose' = info == info' && purpose == purpose'
instance Pretty ScriptContext where
pretty ScriptContext{scriptContextTxInfo, scriptContextPurpose} =
vsep
[ "Purpose:" <+> pretty scriptContextPurpose
, nest 2 $ vsep ["TxInfo:", pretty scriptContextTxInfo]
]
{-# INLINABLE findOwnInput #-}
-- | Find the input currently being validated.
findOwnInput :: ScriptContext -> Maybe TxInInfo
findOwnInput ScriptContext{scriptContextTxInfo=TxInfo{txInfoInputs}, scriptContextPurpose=Spending txOutRef} =
find (\TxInInfo{txInInfoOutRef} -> txInInfoOutRef == txOutRef) txInfoInputs
findOwnInput _ = Nothing
{-# INLINABLE findDatum #-}
-- | Find the data corresponding to a data hash, if there is one
findDatum :: DatumHash -> TxInfo -> Maybe Datum
findDatum dsh TxInfo{txInfoData} = lookup dsh txInfoData
{-# INLINABLE findDatumHash #-}
-- | Find the hash of a datum, if it is part of the pending transaction's
-- hashes
findDatumHash :: Datum -> TxInfo -> Maybe DatumHash
findDatumHash ds TxInfo{txInfoData} = fst <$> find f (toList txInfoData)
where
f (_, ds') = ds' == ds
{-# INLINABLE findTxInByTxOutRef #-}
findTxInByTxOutRef :: TxOutRef -> TxInfo -> Maybe TxInInfo
findTxInByTxOutRef outRef TxInfo{txInfoInputs} =
find (\TxInInfo{txInInfoOutRef} -> txInInfoOutRef == outRef) txInfoInputs
{-# INLINABLE findContinuingOutputs #-}
-- | Find the indices of all the outputs that pay to the same script address we are currently spending from, if any.
findContinuingOutputs :: ScriptContext -> [Integer]
findContinuingOutputs ctx | Just TxInInfo{txInInfoResolved=TxOut{txOutAddress}} <- findOwnInput ctx = findIndices (f txOutAddress) (txInfoOutputs $ scriptContextTxInfo ctx)
where
f addr TxOut{txOutAddress=otherAddress} = addr == otherAddress
findContinuingOutputs _ = traceError "Le" -- "Can't find any continuing outputs"
{-# INLINABLE getContinuingOutputs #-}
-- | Get all the outputs that pay to the same script address we are currently spending from, if any.
getContinuingOutputs :: ScriptContext -> [TxOut]
getContinuingOutputs ctx | Just TxInInfo{txInInfoResolved=TxOut{txOutAddress}} <- findOwnInput ctx = filter (f txOutAddress) (txInfoOutputs $ scriptContextTxInfo ctx)
where
f addr TxOut{txOutAddress=otherAddress} = addr == otherAddress
getContinuingOutputs _ = traceError "Lf" -- "Can't get any continuing outputs"
{-# INLINABLE txSignedBy #-}
-- | Check if a transaction was signed by the given public key.
txSignedBy :: TxInfo -> PubKeyHash -> Bool
txSignedBy TxInfo{txInfoSignatories} k = case find ((==) k) txInfoSignatories of
Just _ -> True
Nothing -> False
{-# INLINABLE ownHashes #-}
-- | Get the validator and datum hashes of the output that is curently being validated
ownHashes :: ScriptContext -> (ValidatorHash, OutputDatum)
ownHashes (findOwnInput -> Just TxInInfo{txInInfoResolved=TxOut{txOutAddress=Address (ScriptCredential s) _, txOutDatum=d}}) = (s,d)
ownHashes _ = traceError "Lg" -- "Can't get validator and datum hashes"
{-# INLINABLE ownHash #-}
-- | Get the hash of the validator script that is currently being validated.
ownHash :: ScriptContext -> ValidatorHash
ownHash p = fst (ownHashes p)
{-# INLINABLE scriptOutputsAt #-}
-- | Get the list of 'TxOut' outputs of the pending transaction at
-- a given script address.
scriptOutputsAt :: ValidatorHash -> TxInfo -> [(OutputDatum, Value)]
scriptOutputsAt h p =
let flt TxOut{txOutDatum=d, txOutAddress=Address (ScriptCredential s) _, txOutValue} | s == h = Just (d, txOutValue)
flt _ = Nothing
in mapMaybe flt (txInfoOutputs p)
{-# INLINABLE valueLockedBy #-}
-- | Get the total value locked by the given validator in this transaction.
valueLockedBy :: TxInfo -> ValidatorHash -> Value
valueLockedBy ptx h =
let outputs = map snd (scriptOutputsAt h ptx)
in mconcat outputs
{-# INLINABLE pubKeyOutputsAt #-}
-- | Get the values paid to a public key address by a pending transaction.
pubKeyOutputsAt :: PubKeyHash -> TxInfo -> [Value]
pubKeyOutputsAt pk p =
let flt TxOut{txOutAddress = Address (PubKeyCredential pk') _, txOutValue} | pk == pk' = Just txOutValue
flt _ = Nothing
in mapMaybe flt (txInfoOutputs p)
{-# INLINABLE valuePaidTo #-}
-- | Get the total value paid to a public key address by a pending transaction.
valuePaidTo :: TxInfo -> PubKeyHash -> Value
valuePaidTo ptx pkh = mconcat (pubKeyOutputsAt pkh ptx)
{-# INLINABLE valueSpent #-}
-- | Get the total value of inputs spent by this transaction.
valueSpent :: TxInfo -> Value
valueSpent = foldMap (txOutValue . txInInfoResolved) . txInfoInputs
{-# INLINABLE valueProduced #-}
-- | Get the total value of outputs produced by this transaction.
valueProduced :: TxInfo -> Value
valueProduced = foldMap txOutValue . txInfoOutputs
{-# INLINABLE ownCurrencySymbol #-}
-- | The 'CurrencySymbol' of the current validator script.
ownCurrencySymbol :: ScriptContext -> CurrencySymbol
ownCurrencySymbol ScriptContext{scriptContextPurpose=Minting cs} = cs
ownCurrencySymbol _ = traceError "Lh" -- "Can't get currency symbol of the current validator script"
{-# INLINABLE spendsOutput #-}
-- | Check if the pending transaction spends a specific transaction output
-- (identified by the hash of a transaction and an index into that
-- transactions' outputs)
spendsOutput :: TxInfo -> TxId -> Integer -> Bool
spendsOutput p h i =
let spendsOutRef inp =
let outRef = txInInfoOutRef inp
in h == txOutRefId outRef
&& i == txOutRefIdx outRef
in any spendsOutRef (txInfoInputs p)
makeLift ''TxInInfo
makeIsDataIndexed ''TxInInfo [('TxInInfo,0)]
makeLift ''TxInfo
makeIsDataIndexed ''TxInfo [('TxInfo,0)]
makeLift ''ScriptContext
makeIsDataIndexed ''ScriptContext [('ScriptContext,0)]