1
1
{-# LANGUAGE DataKinds #-}
2
+ {-# LANGUAGE LambdaCase #-}
2
3
{-# LANGUAGE MultiParamTypeClasses #-}
4
+ {-# LANGUAGE NamedFieldPuns #-}
3
5
{-# LANGUAGE NoImplicitPrelude #-}
4
6
{-# LANGUAGE PatternSynonyms #-}
5
7
{-# LANGUAGE Strict #-}
6
8
{-# LANGUAGE TemplateHaskell #-}
7
9
{-# LANGUAGE ViewPatterns #-}
10
+ {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
8
11
{-# OPTIONS_GHC -fno-full-laziness #-}
9
12
{-# OPTIONS_GHC -fno-ignore-interface-pragmas #-}
10
13
{-# OPTIONS_GHC -fno-omit-interface-pragmas #-}
17
20
18
21
module PlutusBenchmark.LinearVesting where
19
22
23
+ import PlutusLedgerApi.V3
24
+ import PlutusTx
20
25
import PlutusTx.Prelude
21
-
22
- import PlutusLedgerApi.V1.Value (AssetClass )
23
- import PlutusLedgerApi.V3 (Address , Datum (.. ), ScriptContext , scriptContextScriptInfo )
24
- import PlutusLedgerApi.V3.Contexts (pattern SpendingScript )
25
- import PlutusTx (CompiledCode , compile , makeIsDataIndexed , makeLift )
26
26
import Prelude qualified as Haskell
27
27
28
+ import PlutusLedgerApi.V1.Value (AssetClass , assetClassValueOf )
29
+ import PlutusTx.Builtins.HasOpaque (stringToBuiltinByteString , stringToBuiltinString )
28
30
import PlutusTx.Builtins.Internal qualified as BI
31
+ import PlutusTx.List qualified as List
32
+
33
+ {-
34
+ data PAssetClassData (s :: S) = PAssetClassData (Term s (PDataRecord '["symbol" ':= PCurrencySymbol, "tokenName" ':= PTokenName]))
35
+ deriving stock (Generic)
36
+ deriving anyclass (PlutusType, PIsData, PDataFields)
37
+
38
+ ptoScottEncoding :: Term s (PAssetClassData :--> PAssetClass)
39
+ ptoScottEncoding = phoistAcyclic $ plam $ \x' -> P.do
40
+ x <- pletFields @["symbol", "tokenName"] x'
41
+ pcon $ PAssetClass x.symbol x.tokenName
42
+ -}
29
43
30
44
data VestingDatum = VestingDatum
31
45
{ beneficiary :: Address
@@ -51,6 +65,175 @@ $( PlutusTx.makeIsDataIndexed
51
65
[('PartialUnlock, 0 ), ('FullUnlock, 1 )]
52
66
)
53
67
68
+ countInputsAtScript :: ScriptHash -> [TxInInfo ] -> Integer
69
+ countInputsAtScript scriptHash inputs = 42
70
+
71
+ {-
72
+ pcountInputsAtScript :: Term s (PScriptHash :--> PBuiltinList PTxInInfo :--> PInteger)
73
+ pcountInputsAtScript =
74
+ phoistAcyclic $ plam $ \sHash ->
75
+ let go :: Term _ (PInteger :--> PBuiltinList PTxInInfo :--> PInteger)
76
+ go = pfix #$ plam $ \self n ->
77
+ pelimList
78
+ ( \x xs ->
79
+ let cred = pfield @"credential" # (pfield @"address" # (pfield @"resolved" # x))
80
+ in pmatch cred $ \case
81
+ PScriptCredential ((pfield @"_0" #) -> vh) -> pif (sHash #== vh) (self # (n + 1) # xs) (self # n # xs)
82
+ _ -> self # n # xs
83
+ )
84
+ n
85
+ in go # 0
86
+ -}
87
+
88
+ validateVestingPartialUnlock :: VestingDatum -> ScriptContext -> BuiltinUnit
89
+ validateVestingPartialUnlock vestingDatum ctx =
90
+ let
91
+ txInfo = scriptContextTxInfo ctx
92
+ SpendingScript ownRef (Just datum) = scriptContextScriptInfo ctx
93
+ outputs = txInfoOutputs txInfo
94
+ inputs = txInfoInputs txInfo
95
+ signatories = txInfoSignatories txInfo
96
+ validRange = txInfoValidRange txInfo
97
+ Just ownVestingInput = List. find ((== ownRef) . txInInfoOutRef) inputs
98
+ resolvedOut = txInInfoResolved ownVestingInput
99
+ inputAddress = txOutAddress resolvedOut
100
+ inputValue = txOutValue resolvedOut
101
+ inputDdatum = txOutDatum resolvedOut
102
+ ScriptCredential scriptHash = addressCredential inputAddress
103
+ Just ownVestingOutput = List. find ((== inputAddress) . txOutAddress) outputs
104
+ outputAddress = txOutAddress ownVestingOutput
105
+ outputValue = txOutValue ownVestingOutput
106
+ outputDatum = txOutDatum ownVestingOutput
107
+
108
+ asset = vestingAsset vestingDatum
109
+ currentTimeApproximation = getLowerInclusiveTimeRange validRange
110
+ oldRemainingQty = assetClassValueOf inputValue asset
111
+ newRemainingQty = assetClassValueOf outputValue asset
112
+ vestingPeriodLength = vestingPeriodEnd vestingDatum - vestingPeriodStart vestingDatum
113
+ vestingTimeRemaining = vestingPeriodEnd vestingDatum - currentTimeApproximation
114
+ timeBetweenTwoInstallments = ceiling (vestingPeriodLength `div` totalInstallments vestingDatum)
115
+ futureInstallments = ceiling (vestingTimeRemaining `div` timeBetweenTwoInstallments)
116
+ in
117
+ _
118
+
119
+ {-
120
+
121
+ pvalidateVestingPartialUnlock ::
122
+ Term
123
+ s
124
+ ( PVestingDatum
125
+ :--> PScriptContext
126
+ :--> PUnit
127
+ )
128
+ pvalidateVestingPartialUnlock = phoistAcyclic $ plam $ \datum ctx -> unTermCont $ do
129
+ ctxF <- pletFieldsC @'["txInfo", "purpose"] ctx
130
+ txInfoF <- pletFieldsC @'["outputs", "inputs", "signatories", "validRange"] ctxF.txInfo
131
+ PSpending ((pfield @"_0" #) -> ownRef) <- pmatchC ctxF.purpose
132
+
133
+ PJust ownVestingInput <- pmatchC $ pfindOwnInput # txInfoF.inputs # ownRef
134
+ ownVestingInputF <- pletFieldsC @'["address", "value", "datum"] (pfield @"resolved" # ownVestingInput)
135
+ PScriptCredential ((pfield @"_0" #) -> ownValHash) <- pmatchC (pfield @"credential" # ownVestingInputF.address)
136
+
137
+ ownVestingOutput <- pletC $ pheadSingleton #$ pfindOutputsToAddress # txInfoF.outputs # ownVestingInputF.address
138
+ ownVestingOutputF <- pletFieldsC @'["address", "value", "datum"] ownVestingOutput
139
+
140
+ datumF <-
141
+ pletFieldsC
142
+ @'[ "beneficiary"
143
+ , "vestingAsset"
144
+ , "totalVestingQty"
145
+ , "vestingPeriodStart"
146
+ , "vestingPeriodEnd"
147
+ , "firstUnlockPossibleAfter"
148
+ , "totalInstallments"
149
+ ]
150
+ datum
151
+
152
+ vestingAsset <- pletC $ ptoScottEncoding # datumF.vestingAsset
153
+ currentTimeApproximation <- pletC $ pfromData $ pto $ pgetLowerInclusiveTimeRange # txInfoF.validRange
154
+
155
+ oldRemainingQty <- pletC $ passetClassValueOf # vestingAsset # ownVestingInputF.value
156
+ newRemainingQty <- pletC $ passetClassValueOf # vestingAsset # ownVestingOutputF.value
157
+ vestingPeriodLength <- pletC $ (pfromData datumF.vestingPeriodEnd) - (pfromData datumF.vestingPeriodStart)
158
+ vestingTimeRemaining <- pletC $ (pfromData datumF.vestingPeriodEnd) - (currentTimeApproximation)
159
+ timeBetweenTwoInstallments <- pletC $ pdivCeil # vestingPeriodLength # datumF.totalInstallments
160
+ futureInstallments <- pletC $ pdivCeil # (vestingTimeRemaining) # (timeBetweenTwoInstallments)
161
+
162
+ let expectedRemainingQty = pdivCeil # (futureInstallments * datumF.totalVestingQty) #$ datumF.totalInstallments
163
+
164
+ PPubKeyCredential ((pfield @"_0" #) -> beneficiaryHash) <- pmatchC (pfield @"credential" # datumF.beneficiary)
165
+
166
+ pguardC "Missing beneficiary signature" (ptxSignedBy # txInfoF.signatories # beneficiaryHash)
167
+ pguardC "Unlock not permitted until firstUnlockPossibleAfter time" (datumF.firstUnlockPossibleAfter #< currentTimeApproximation)
168
+ pguardC "Zero remaining assets not allowed" (0 #< newRemainingQty)
169
+ pguardC "Remaining asset exceed old asset" (newRemainingQty #< oldRemainingQty)
170
+ pguardC "Mismatched remaining asset" (expectedRemainingQty #== newRemainingQty)
171
+ pguardC "Datum Modification Prohibited" (ownVestingInputF.datum #== ownVestingOutputF.datum)
172
+ pguardC "Double satisfaction" (pcountInputsAtScript # ownValHash # txInfoF.inputs #== 1)
173
+ pure $ pconstant ()
174
+
175
+ pvalidateVestingFullUnlock :: Term s (PVestingDatum :--> PScriptContext :--> PUnit)
176
+ pvalidateVestingFullUnlock = phoistAcyclic $ plam $ \datum context -> unTermCont $ do
177
+ datumF <- tcont $ pletFields @'["beneficiary", "vestingPeriodEnd"] datum
178
+ txInfoF <- tcont $ pletFields @'["signatories", "validRange"] $ pfield @"txInfo" # context
179
+ currentTimeApproximation <- pletC $ pfromData $ pto $ pgetLowerInclusiveTimeRange # txInfoF.validRange
180
+ PPubKeyCredential ((pfield @"_0" #) -> beneficiaryHash) <- pmatchC (pfield @"credential" # datumF.beneficiary)
181
+
182
+ pguardC "Missing beneficiary signature" (ptxSignedBy # txInfoF.signatories # beneficiaryHash)
183
+ pguardC "Unlock not permitted until vestingPeriodEnd time" (datumF.vestingPeriodEnd #< currentTimeApproximation)
184
+ pure $ pconstant ()
185
+
186
+ pvalidateVestingScript ::
187
+ Term
188
+ s
189
+ ( PVestingDatum
190
+ :--> PVestingRedeemer
191
+ :--> PScriptContext
192
+ :--> PUnit
193
+ )
194
+ pvalidateVestingScript =
195
+ phoistAcyclic $
196
+ plam $
197
+ \datum redeemer context -> unTermCont $ do
198
+ pure $
199
+ pmatch redeemer $ \case
200
+ PPartialUnlock _ -> pvalidateVestingPartialUnlock # datum # context
201
+ PFullUnlock _ -> pvalidateVestingFullUnlock # datum # context
202
+
203
+ pvalidateVestingScriptValidator :: Term s PValidator
204
+ pvalidateVestingScriptValidator = phoistAcyclic $
205
+ plam $ \dat red ctx -> unTermCont $ do
206
+ let datum = ptryFrom dat fst
207
+ let redeemer = punsafeCoerce red
208
+ return $
209
+ popaque $
210
+ pvalidateVestingScript # datum # redeemer # ctx
211
+
212
+ pgetLowerInclusiveTimeRange :: forall (s :: S). Term s ( PPOSIXTimeRange :--> (PAsData PPOSIXTime))
213
+ pgetLowerInclusiveTimeRange = phoistAcyclic $
214
+ plam $ \timeRange -> unTermCont $ do
215
+ PInterval ((pfield @"from" #) -> from) <- pmatchC timeRange
216
+ PLowerBound lb <- pmatchC from
217
+
218
+ let tryBound = phoistAcyclic $
219
+ plam $ \endpoint a ->
220
+ pmatch (pfield @"_0" # a) $ \case
221
+ PFinite ((pfield @"_0" #) -> posixTime) ->
222
+ pif
223
+ (pfield @"_1" # a)
224
+ (posixTime)
225
+ (pdata $ (pfromData posixTime) + endpoint)
226
+ _ -> ptraceError "Time range not Finite"
227
+ inclusiveLb <- pletC $ tryBound # (1) # lb
228
+ pure $ inclusiveLb
229
+ -}
230
+
231
+ getLowerInclusiveTimeRange :: POSIXTimeRange -> POSIXTime
232
+ getLowerInclusiveTimeRange = \ case
233
+ Interval (LowerBound (Finite posixTime) inclusive) _upperBound ->
234
+ if inclusive then posixTime else posixTime + 1
235
+ _ -> traceError (stringToBuiltinString " Time range not Finite" )
236
+
54
237
validatorCode :: CompiledCode (BuiltinData -> BuiltinUnit )
55
238
validatorCode = $$ (compile [|| untypedValidator|| ])
56
239
0 commit comments