-
Notifications
You must be signed in to change notification settings - Fork 46
/
Copy pathClient.hs
1396 lines (1238 loc) · 65.5 KB
/
Client.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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -Wno-name-shadowing #-}
{-# OPTIONS_GHC -Wno-simplifiable-class-constraints #-}
{-# OPTIONS_GHC -fno-ignore-interface-pragmas #-}
{-# OPTIONS_GHC -fno-omit-interface-pragmas #-}
{-# OPTIONS_GHC -fno-specialise #-}
{-# OPTIONS_GHC -Wno-unused-imports #-}
module Language.Marlowe.Client where
import Cardano.Api (AddressInEra (..), PaymentCredential (..), SerialiseAsRawBytes (serialiseToRawBytes), ShelleyEra,
StakeAddressReference (..))
import Cardano.Api.Shelley (StakeCredential (..))
import qualified Cardano.Api.Shelley as Shelley
import Control.Category ((<<<), (>>>))
import Control.Lens
import Control.Monad (forM_, guard, void, when)
import Control.Monad.Error.Lens (catching, handling, throwing, throwing_)
import Control.Monad.Extra (concatMapM)
import Control.Monad.Writer hiding (tell)
import Data.Aeson (FromJSON, ToJSON, parseJSON, toJSON)
import Data.Default (Default (def))
import Data.Foldable (for_)
import Data.Functor (($>))
import Data.List (intercalate)
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe, isJust, isNothing, listToMaybe, mapMaybe)
import Data.Monoid (Last)
import Data.Set (Set)
import qualified Data.Set as Set
import qualified Data.Text as T
import Data.UUID (UUID)
import Data.Void (Void)
import GHC.Generics (Generic)
import Language.Marlowe.Client.History (History (..), IncludePkhTxns (IncludePkhTxns), MarloweTxOutRef, RolePayout (..),
foldlHistory, foldrHistory, historyFrom, marloweHistory, marloweHistory',
marloweHistoryFrom, marloweUtxoStatesAt, toMarloweState, toRolePayout,
txRoleData)
import Language.Marlowe.Core.V1.Semantics
import qualified Language.Marlowe.Core.V1.Semantics as Marlowe
import Language.Marlowe.Core.V1.Semantics.Types hiding (Contract, getAction)
import qualified Language.Marlowe.Core.V1.Semantics.Types as Marlowe
import Language.Marlowe.Scripts
import Language.Marlowe.Util (extractNonMerkleizedContractRoles)
import Ledger (CurrencySymbol, Datum (..), POSIXTime (..), PaymentPubKeyHash (..), PubKeyHash (..), TokenName,
TxOut (..), TxOutRef (txOutRefId), dataHash, txOutValue)
import qualified Ledger
import Ledger.Ada (adaSymbol, adaToken, adaValueOf, lovelaceValueOf)
import Ledger.Address (Address, StakePubKeyHash (StakePubKeyHash), pubKeyHashAddress, scriptHashAddress)
import Ledger.Constraints hiding (ownPaymentPubKeyHash)
import qualified Ledger.Constraints as Constraints
import qualified Ledger.Interval as Interval
import Ledger.Scripts (datumHash, unitRedeemer)
import Ledger.TimeSlot (posixTimeRangeToContainedSlotRange, scSlotLength, slotToPOSIXTimeRange)
import Ledger.Tx (txId)
import qualified Ledger.Tx as Tx
import Ledger.Typed.Scripts
import qualified Ledger.Typed.Scripts as Typed
import Ledger.Typed.Tx (TypedScriptTxOutRef (tyTxOutRefRef))
import qualified Ledger.Typed.Tx as Typed
import qualified Ledger.Value as Val
import Numeric.Natural (Natural)
import Plutus.ChainIndex (ChainIndexTx (..), Page, PageQuery, _ValidTx, citxOutputs, citxTxId, nextPageQuery, pageItems)
import Plutus.ChainIndex.Api (paget)
import Plutus.Contract (AsCheckpointError, AsContractError, Contract, ContractError, EmptySchema, Endpoint, Promise,
_CheckpointError, _ConstraintResolutionContractError, _ContractError, awaitPromise, awaitTime,
awaitTxConfirmed, awaitUtxoProduced, balanceTx, checkpointLoop, currentSlot, currentTime,
endpoint, isTime, logDebug, logInfo, logWarn, mapError, never, ownPaymentPubKeyHash,
promiseBind, promiseMap, select, selectEither, selectList, submitBalancedTx, submitTxConfirmed,
tell, throwError, txOutFromRef, type (.\/), utxoIsProduced, utxoIsSpent, utxosAt,
utxosTxOutTxAt, waitNSlots)
import qualified Plutus.Contract as Contract
import Plutus.Contract.Request (getSlotConfig, txoRefsAt, txsFromTxIds)
import Plutus.Contract.Wallet (getUnspentOutput)
import qualified Plutus.Contracts.Currency as Currency
import Plutus.V1.Ledger.Api (toBuiltin)
import PlutusPrelude (foldMapM, (<|>))
import qualified PlutusTx
import qualified PlutusTx.AssocMap as AssocMap
import qualified PlutusTx.Prelude as P
import PlutusTx.Traversable (for)
data MarloweClientInput = ClientInput InputContent
| ClientMerkleizedInput InputContent Marlowe.Contract
deriving stock (Eq, Show, Generic)
instance FromJSON MarloweClientInput where
parseJSON json = uncurry ClientMerkleizedInput <$> parseJSON json <|> ClientInput <$> parseJSON json
instance ToJSON MarloweClientInput where
toJSON (ClientInput content) = toJSON content
toJSON (ClientMerkleizedInput content contract) = toJSON (content, contract)
type CreateEndpointSchema = (UUID, AssocMap.Map Val.TokenName (AddressInEra ShelleyEra), Marlowe.Contract)
type ApplyInputsEndpointSchema = (UUID, MarloweParams, Maybe TimeInterval, [MarloweClientInput])
type ApplyInputsNonMerkleizedEndpointSchema = (UUID, MarloweParams, Maybe TimeInterval, [InputContent])
type AutoEndpointSchema = (UUID, MarloweParams, Party, POSIXTime)
type RedeemEndpointSchema = (UUID, MarloweParams, TokenName, AddressInEra ShelleyEra)
type CloseEndpointSchema = UUID
type MarloweSchema =
Endpoint "create" CreateEndpointSchema
.\/ Endpoint "apply-inputs" ApplyInputsEndpointSchema
.\/ Endpoint "apply-inputs-nonmerkleized" ApplyInputsNonMerkleizedEndpointSchema
.\/ Endpoint "auto" AutoEndpointSchema
.\/ Endpoint "redeem" RedeemEndpointSchema
.\/ Endpoint "close" CloseEndpointSchema
data MarloweEndpointResult =
CreateResponse MarloweParams
| ApplyInputsResponse
| AutoResponse
| RedeemResponse
| CloseResponse
deriving (Show,Eq,Generic)
deriving anyclass (ToJSON, FromJSON)
type MarloweCompanionSchema = EmptySchema
type MarloweFollowSchema = Endpoint "follow" MarloweParams
data MarloweError =
TransitionError
| AmbiguousOnChainState
| UnableToExtractTransition
| OnChainStateNotFound
| MarloweEvaluationError TransactionError
| OtherContractError ContractError
deriving stock (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)
makeClassyPrisms ''MarloweError
instance AsContractError MarloweError where
_ContractError = _OtherContractError
instance AsCheckpointError MarloweError where
_CheckpointError = _OtherContractError . _CheckpointError
data PartyAction
= PayDeposit AccountId Party Token Integer
| WaitForTimeout POSIXTime
| WaitOtherActionUntil POSIXTime
| NotSure
| CloseContract
deriving (Show)
type RoleOwners = AssocMap.Map Val.TokenName (AddressInEra ShelleyEra)
-- FIXME: We should probably switch to the plain `FollowerContractState` here
-- (which is just `(Maybe MarloweHistory, UnspentPayouts)`)
-- so we can capture and report all the possible on chain states.
-- Now we are not able to notify about role payouts before the contract is on the chain.
data ContractHistory =
ContractHistory
{ chParams :: MarloweParams -- ^ The "instance id" of the contract
, chInitialData :: MarloweData -- ^ The initial Contract + State
, chHistory :: [TransactionInput] -- ^ All the transaction that affected the contract.
-- The current state and intermediate states can
-- be recalculated by using computeTransaction
-- of each TransactionInput to the initial state
, chAddress :: Address -- ^ The script address of the marlowe contract
, chUnspentPayouts :: UnspentPayouts -- ^ All UTxOs associated with our payout script.
-- Please note that in theory we include here outpus
-- which possible were created by an "external" transactions.
}
deriving stock (Show, Generic)
deriving anyclass (FromJSON, ToJSON)
-- We need a semigroup instance to be able to update the state of the FollowerContract via `tell`.
-- For most of the fields we just use the initial values as they are not expected to change,
-- and we only concatenate new TransactionInputs
instance Semigroup ContractHistory where
_ <> last = last
-- The FollowerContractNotification is a Maybe because the Contract monad requires the state
-- to have a Monoid instance. `Nothing` is the initial state of the contract, and then
-- with the first `tell` we have a valid initial ContractHistory
type FollowerContractNotification = Maybe ContractHistory
newtype Transition = Transition History -- ^ The state machine instance transitioned to a new state
deriving stock (Show, Generic)
deriving anyclass (ToJSON, FromJSON)
data PayoutsChange
= PayoutSpent ChainIndexTx
| PayoutProduced (NonEmpty ChainIndexTx)
deriving stock (Show, Generic)
-- We need full payouts set to better diff on chain changes
newtype Redeemed = Redeemed { redeemed :: Bool }
deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
newtype Payouts = Payouts [(RolePayout, Redeemed)]
deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
deriving newtype (Semigroup, Monoid)
-- We expose *unordered* (please interpret this list as such) payouts set
-- so we can compute this state from the chain easily and we can
-- avoid traversing the full history.
newtype UnspentPayouts = UnspentPayouts [RolePayout]
deriving stock (Show, Eq, Generic)
deriving anyclass (FromJSON, ToJSON)
deriving newtype (Semigroup, Monoid)
fromPayouts :: Payouts -> UnspentPayouts
fromPayouts (Payouts p) = UnspentPayouts <<< map fst <<< filter (not <<< redeemed <<< snd) $ p
data ContractProgress = InProgress | Finished
deriving stock (Show, Eq, Generic)
deriving anyclass (ToJSON, FromJSON)
instance Semigroup ContractProgress where
_ <> Finished = Finished
any <> InProgress = any
instance Monoid ContractProgress where
mempty = InProgress
type EndpointName = String
data EndpointResponse a err =
EndpointSuccess UUID a
-- TODO: The EndpointName should be a part of `err` if
-- the user decides to, but we need to refactor MarloweError and
-- the Marlowe Plutus App, so I leave this for a separate PR.
| EndpointException UUID EndpointName err
deriving (Show,Eq,Generic)
deriving anyclass (ToJSON, FromJSON)
-- The Semigroup instance is thought so that when we call `tell`, we inform
-- the FrontEnd of the last response. It is the responsability of the FE to
-- tie together a request and a response with the UUID.
instance Semigroup (EndpointResponse a err) where
_ <> last = last
type MarloweEndpointResponse = EndpointResponse MarloweEndpointResult MarloweError
type MarloweContractState = Maybe MarloweEndpointResponse
mkMarloweTypedValidator :: MarloweParams -> SmallTypedValidator
mkMarloweTypedValidator = smallUntypedValidator
minLovelaceDeposit :: Integer
minLovelaceDeposit = 2_000_000
debugMsg :: String -> String -> String
debugMsg fnName msg = "[DEBUG:" <> fnName <> "] " <> msg
-- TODO: Move to debug log.
debug :: forall st sc err. String -> String -> Contract st sc err ()
debug fnName msg = logDebug $ debugMsg fnName msg
-- | During first pass the counter equals to 0 - first pass is not a retry
newtype RetryCounter = RetryCounter Int
newtype MaxRetries = MaxRetries Int
retryTillJust :: Monad m => MaxRetries -> (RetryCounter -> m (Maybe a)) -> m (Maybe a)
retryTillJust (MaxRetries maxRetries) action = go 0
where
go cnt
| maxRetries <= cnt = pure Nothing
| otherwise = do
(action $ RetryCounter cnt) >>= \case
Nothing -> go (cnt + 1)
res -> pure res
-- | Our retries defaults
pollingInterval :: Natural
pollingInterval = 2
maxRetries :: MaxRetries
maxRetries = MaxRetries 4
newtype CallStackTrace = CallStackTrace [String]
deriving newtype (Semigroup, Monoid)
pushFnName :: String -> CallStackTrace -> CallStackTrace
pushFnName fn (CallStackTrace st) = CallStackTrace (fn : st)
printCallStackTrace :: CallStackTrace -> String
printCallStackTrace (CallStackTrace trace) = show $ intercalate ":" trace
debugTrace :: CallStackTrace -> String -> Contract st sc err ()
debugTrace trace =
debug (printCallStackTrace trace)
-- | The same as above but specializd to the PAB Contract monad with
-- | constant delay between retries.
-- |
-- | Used to do polling of the PAB because we have to
-- | wait till chain index catches up with
-- | the recent responses from the cardano-node (PAB STM)
retryRequestTillJust :: AsContractError err => CallStackTrace -> MaxRetries -> (RetryCounter -> Contract st sc err (Maybe a)) -> Contract st sc err (Maybe a)
retryRequestTillJust trace maxRetries query = do
retryTillJust maxRetries $ \cnt@(RetryCounter cntVal) -> do
when (cntVal > 0) $ do
debugTrace (pushFnName "retryRequestTillJust" trace) $ "Still waiting for desired change - iteration: " <> show cntVal
void $ waitNSlots pollingInterval
query cnt
retryRequestTillJust' :: AsContractError err => CallStackTrace -> Contract st sc err (Maybe a) -> Contract st sc err (Maybe a)
retryRequestTillJust' trace action = retryRequestTillJust trace maxRetries (const action)
retryTillDiffers :: Monad m => Eq a => MaxRetries -> a -> (RetryCounter -> m a) -> m (Maybe a)
retryTillDiffers maxRetries known action = do
retryTillJust maxRetries $ \cnt -> do
new <- action cnt
if new == known
then pure Nothing
else pure $ Just new
-- | The same as above but specializd to the PAB Contract monad with
-- | constant delay between retries.
retryTillResponseDiffers :: Eq a => AsContractError err => CallStackTrace -> MaxRetries -> a -> (RetryCounter -> Contract st sc err a) -> Contract st sc err (Maybe a)
retryTillResponseDiffers trace maxRetries known query = do
retryTillDiffers maxRetries known $ \cnt@(RetryCounter cntVal) -> do
when (cntVal > 0) $ do
debugTrace (pushFnName "retryTillResponseDiffers" trace) $ "Still waiting for desired change - iteration: " <> show cntVal
void $ waitNSlots pollingInterval
query cnt
retryTillResponseDiffers' :: Eq a => AsContractError err => CallStackTrace -> a -> Contract st sc err a -> Contract st sc err (Maybe a)
retryTillResponseDiffers' trace a query = retryTillResponseDiffers trace maxRetries a (const query)
-- | Queries which perform some extra polling to possibly sync the chain index
awaitTxConfirmed' :: AsContractError e => CallStackTrace -> MaxRetries -> Ledger.TxId -> Contract w s e Bool
awaitTxConfirmed' trace maxRetries txId = do
awaitTxConfirmed txId
fmap isJust $ retryRequestTillJust (pushFnName "awaitTxConfirmed'" trace) maxRetries $ const (listToMaybe <$> txsFromTxIds [txId])
awaitUtxoProduced' :: AsContractError e => CallStackTrace -> Address -> Contract w s e (NonEmpty ChainIndexTx)
awaitUtxoProduced' trace addr = do
awaitPromise (utxoIsProduced' trace addr)
utxoIsProduced' :: AsContractError e => CallStackTrace -> Address -> Promise w s e (NonEmpty ChainIndexTx)
utxoIsProduced' trace addr = do
promiseBind (utxoIsProduced addr) $ \txns -> do
void $ retryRequestTillJust' (pushFnName "utxoIsProduced'" trace) $ do
ctx <- txsFromTxIds $ map (view citxTxId) (NonEmpty.toList txns)
pure $ if length ctx == length txns
then Just txns
else Nothing
pure txns
utxoIsSpent' :: AsContractError e => CallStackTrace -> TxOutRef -> Promise w s e ChainIndexTx
utxoIsSpent' trace utxo = promiseBind (utxoIsSpent utxo) $ \tx -> do
void $ retryRequestTillJust' (pushFnName "utxoIsSpent'" trace) $ listToMaybe <$> txsFromTxIds [ tx ^. citxTxId ]
pure tx
-- | Trivial data type which helps fully embed contract into the `checkpointLoop`
data QueryResult a
= UnknownOnChainState
| LastResult a
deriving (Show,Eq,Generic)
deriving anyclass (ToJSON, FromJSON)
type FollowerM a = Contract FollowerContractNotification MarloweFollowSchema MarloweError a
type FollowerPromise a = Promise FollowerContractNotification MarloweFollowSchema MarloweError a
-- In theory we can have role payouts "outside" of the contract - our payout query
-- doesn't prevent that (we filter just on the payout script).
type FollowerContractState = (Maybe History, Payouts)
data FollowerContractUpdate = PayoutChange ChainIndexTx | HistoryChange ChainIndexTx
deriving (Show, Eq)
contractUpdateTransaction :: FollowerContractUpdate -> ChainIndexTx
contractUpdateTransaction (PayoutChange tx) = tx
contractUpdateTransaction (HistoryChange tx) = tx
-- Follower puts a single `null` to the websocket automatically.
-- You can expect another `null` (so two `null`s) if you start the follower
-- before the actual contract is on the chain.
marloweFollowContract :: FollowerM ()
marloweFollowContract = awaitPromise $ endpoint @"follow" $ \params ->
do
debug' $ "call parameters: " <> show params <> "."
slotConfig <- getSlotConfig
let
printHistory = show <<< (foldrHistory step [] :: History -> [String])
where
step Created {} acc = "Created" : acc
step InputApplied {} acc = "InputApplied " : acc
step Closed {} acc = "Closed " : acc
printState (Just history, payouts) = "{ inputs = Just" <> show (foldInputs history) <> ", " <> "payouts = " <> show payouts <> "}"
printState (Nothing, payouts) = "{ inputs = Nothing," <> "payouts = " <> show payouts <> "}"
fetchOnChainState :: FollowerM FollowerContractState
fetchOnChainState = (,) <$> marloweHistory' slotConfig params <*> payoutsAtCurrency (rolesCurrency params)
awaitNewState :: FollowerContractState -> FollowerM (FollowerContractUpdate, FollowerContractState)
awaitNewState (prevHistory, prevPayouts) = do
let
-- In both cases we bring back one of the transactions which have woken us up.
-- We do this only to perform the logging.
waitForContractChange :: FollowerPromise ChainIndexTx
waitForContractChange = case prevHistory >>= marloweUtxo of
Nothing -> NonEmpty.head <$> (utxoIsProduced' trace $ validatorAddress $ mkMarloweTypedValidator params)
Just utxo -> utxoIsSpent' trace utxo
where
trace = CallStackTrace ["waitForContractChange", "awaitNewState", "marloweFollowContract"]
waitForPayoutChange :: FollowerPromise ChainIndexTx
waitForPayoutChange =
let
payoutScriptAddress = scriptHashAddress $ mkRolePayoutValidatorHash $ rolesCurrency params
UnspentPayouts payouts = fromPayouts prevPayouts
trace = CallStackTrace ["waitForPayoutChange", "awaitNewState", "marloweFollowContract"]
waitTillPayoutIsSpent = fmap (utxoIsSpent' trace <<< rolePayoutTxOutRef) payouts
waitTillPayoutIsProduced = NonEmpty.head <$> utxoIsProduced' trace payoutScriptAddress
in
raceList $ waitTillPayoutIsProduced : waitTillPayoutIsSpent
-- We are here notified that there should be a new state on the chain...
changeNotification <- awaitPromise
(selectEither waitForPayoutChange waitForContractChange)
debug' $ either
(mappend "Payout change detected through txId =" <<< show <<< _citxTxId)
(mappend "Contract change detected through txId " <<< show <<< _citxTxId)
changeNotification
(either PayoutChange HistoryChange changeNotification,) <$> fetchOnChainState
-- Push a possible state update to the stream
notify :: FollowerContractState -> FollowerM ()
notify st@(Just history, payouts) = do
debug' $ "notifying new state = " <> printState st
debug' $ "history = " <> show history
debug' $ "history entries = " <> printHistory history
debug' $ "status = " <> show (status history)
case history of
Created {historyData} -> do
tell @FollowerContractNotification $
Just $ mkContractHistory params historyData (foldInputs history) (fromPayouts payouts)
pure ()
_ -> do
throwError $ OtherContractError $ Contract.OtherContractError $ "Invalid history trace head found: " <> T.pack (show history)
notify (Nothing, _) = do
debug' "notifying about empty state"
tell @FollowerContractNotification $ Nothing
-- In `checkpointLoop` we tail rec by returning `Right` ~ `whileRight` loop.
rec :: forall a err sc st w. st -> Contract w sc err (Either a st)
rec st = pure $ Right st
-- `follower` is a loop which iterates over the on chain updates:
-- * we use simple `QueryResult` wrapper so *every* query is wrapped in the `checkpointLoop`
-- * we pass in it the last known state and put it to the stream
-- * we try to use only previous state pieces when constrcuting async requests
-- * we wait for the changes on the chain
-- * we ask (up to `maxRetries * pollingInterval`) the chain index for the update
-- till it actually provides the new state by using local versions of queries
-- which perform active "sync check polling"
-- * we loop back (by returning `Right`) with the new state.
follow :: QueryResult FollowerContractState -> FollowerM (Either () (QueryResult FollowerContractState))
follow UnknownOnChainState = do
debug' "Staring follower loop..."
possibleOnChainState@(history, payouts) <- fetchOnChainState
marloweTxOutRef <- getMarloweTxOutRef $ mkMarloweTypedValidator params
-- As a last resort query also `pkhTxns` when contract exists but
-- was already closed.
currOnChainState <- case (marloweTxOutRef, history) of
(Just _, _) -> pure possibleOnChainState
(Nothing, Just Closed {}) -> pure possibleOnChainState
(Nothing, _) -> do
history' <- marloweHistory slotConfig params (IncludePkhTxns True)
unless (maybe True isClosed history') $
debug' "Follower was not able to reconstruct the full history even though the pkhTxns were included"
pure (history', payouts)
notify currOnChainState
rec $ LastResult currOnChainState
follow (LastResult prevState) = do
(update, newState@(newHistory, newPayouts)) <- awaitNewState prevState
if newState /= prevState
then do
debug' $ "New state change detected: newState = " <> printState newState
notify newState
rec $ LastResult newState
else do
debug'
$ "No state change detected by history module: "
<> "prevState =" <> printState prevState <> "; "
<> "update = " <> show update <> "; "
<> "history = " <> maybe "[]" printHistory newHistory <> "; "
onChainMarloweRef <- getMarloweTxOutRef $ mkMarloweTypedValidator params
let
closingEntry = case onChainMarloweRef of
Just _ -> Nothing
-- Contract was closed and we can try to extract the rest of the history
-- from the last tx which we have at hand.
-- This can also be done by `marloweHistory slotConfig params (IncludePkhTxns True)`
-- as above but it can stress chain index with a heavy query.
Nothing -> do
history <- newHistory
mUtxo <- marloweUtxo history
let
tx = contractUpdateTransaction update
historyFrom
slotConfig
(validatorAddress $ mkMarloweTypedValidator params)
[tx]
mUtxo
newHistory' = do
history <- newHistory
entry <- closingEntry
pure $ append history entry
newState' = (newHistory', newPayouts)
if newState' == prevState
then debug' $ "No state change detected by follower as well: closingEntry = " <> show closingEntry
else do
notify newState'
debug' $ "State change detected by follower: closingEntry = " <> show closingEntry
rec $ LastResult newState'
checkpointLoop follow UnknownOnChainState
where
debug' = debug "Language.Marlowe.Client.marloweFollowContract"
isClosed = last >>> \case
Closed {} -> True
_ -> False
status history = if isClosed history then Finished else InProgress
last h = foldlHistory step h h
where
step _ next = next
append h tail = foldrHistory step tail h
where
step (Created tx d _) acc = Created tx d (Just acc)
step (InputApplied i tx d _) acc = InputApplied i tx d (Just acc)
step c _ = c
marloweUtxo = last >>> \case
Created { historyTxOutRef } -> Just historyTxOutRef
InputApplied { historyTxOutRef } -> Just historyTxOutRef
_ -> Nothing
foldInputs = reverse <<< foldlHistory step []
where
step acc InputApplied {historyInput} = historyInput : acc
step acc Closed{historyInput} = historyInput : acc
step acc _ = acc
mkContractHistory params historyData inputs payouts = ContractHistory
{ chParams = params
, chInitialData = historyData
, chHistory = inputs
, chAddress = validatorAddress $ mkMarloweTypedValidator params
, chUnspentPayouts = payouts
}
newtype MaxPages = MaxPages Int
-- | `Left` means that there are more pages to grab...
txOutRefsAt ::
forall w s e.
( AsContractError e
)
=> Address
-> MaxPages
-> Contract w s e (Either ([TxOutRef], PageQuery TxOutRef) [TxOutRef])
txOutRefsAt _ (MaxPages maxPages) | maxPages <= 0 = pure $ Right []
txOutRefsAt addr (MaxPages maxPages) = go 1 [] (Just def)
where
go _ acc Nothing = pure $ Right acc
go pn acc (Just pq) = do
page <- paget <$> txoRefsAt pq addr
let
acc' = acc <> pageItems page
next = nextPageQuery page
case (pn == maxPages, next) of
(True, Just pq) -> pure $ Left (acc', pq)
_ -> go (pn + 1) acc' next
payoutsAtCurrency :: AsContractError e
=> CurrencySymbol
-> Contract w s e Payouts
payoutsAtCurrency rolesCurrency = do
let
address = scriptHashAddress $ mkRolePayoutValidatorHash rolesCurrency
utxosRefs <- Map.keys <$> utxosTxOutTxAt address
-- FIXME: We should notify through the API that we have possibly more payouts on the chain and not just
-- ignore our payouts buffer overflow here.
txoutRefs <- txOutRefsAt address (MaxPages 30) <&> \case
Left (items, _) -> items
Right items -> items
let
txids = map txOutRefId txoutRefs
txs <- txsFromTxIds txids
let
rolePayoutTxs = concatMap txRoleData txs
rolePayouts = map toRolePayout rolePayoutTxs
markRedeemed r@RolePayout { rolePayoutTxOutRef } =
(r, Redeemed (rolePayoutTxOutRef `notElem` utxosRefs))
pure $ Payouts <<< map markRedeemed $ rolePayouts
unspentPayoutsAtCurrency :: AsContractError e
=> CurrencySymbol
-> Contract w s e UnspentPayouts
unspentPayoutsAtCurrency rolesCurrency = do
let
address = scriptHashAddress $ mkRolePayoutValidatorHash rolesCurrency
txs <- do
utxosMap <- utxosTxOutTxAt address
pure $ fmap snd $ Map.elems utxosMap
let
rolePayoutTxs = concatMap txRoleData txs
pure $ UnspentPayouts . map toRolePayout $ rolePayoutTxs
{- This is a control contract.
It allows to create a contract, apply inputs, auto-execute a contract,
redeem role payouts, and close.
-}
marlowePlutusContract :: Contract MarloweContractState MarloweSchema MarloweError ()
marlowePlutusContract = selectList [create, apply, applyNonmerkleized, auto, redeem, close]
where
debug' endpoint msg = debug ("Language.Marlowe.Client.marlowePlutusContract:" <> endpoint) msg
catchError reqId endpointName handler = catching _MarloweError
(void $ mapError (review _MarloweError) handler)
(\err -> do
logWarn $ "Error " <> show err
tell $ Just $ EndpointException reqId endpointName err
marlowePlutusContract)
-- [UC-CONTRACT-1][1] Start a new marlowe contract
create = endpoint @"create" $ \(reqId, owners, contract) -> catchError reqId "create" $ do
let
debug'' = debug' "create"
slotConfig <- getSlotConfig
debug'' $ "slotConfig = " <> show slotConfig
-- Create a transaction with the role tokens and pay them to the contract creator
-- See Note [The contract is not ready]
ownPubKey <- unPaymentPubKeyHash <$> ownPaymentPubKeyHash
debug'' $ "ownPubKey = " <> show ownPubKey
let roles = extractNonMerkleizedContractRoles contract
debug'' $ "roles = " <> show roles
(params, distributeRoleTokens, lkps) <- setupMarloweParams owners roles
debug'' $ "params = " <> show params
time <- currentTime
debug'' $ "Marlowe contract created with parameters: " <> show params <> " at " <> show time
let marloweData = MarloweData {
marloweContract = contract,
marloweState = State
{ accounts = AssocMap.singleton (PK ownPubKey, Token adaSymbol adaToken) minLovelaceDeposit
, choices = AssocMap.empty
, boundValues = AssocMap.empty
, minTime = time } }
debug'' $ "marloweData = " <> show marloweData
let minAdaTxOut = lovelaceValueOf minLovelaceDeposit
let typedValidator = mkMarloweTypedValidator params
let tx = mustPayToTheScript marloweData minAdaTxOut <> distributeRoleTokens
debug'' $ "tx = " <> show tx
let lookups = Constraints.typedValidatorLookups typedValidator <> lkps
debug'' $ "lookups = " <> show lookups
-- Create the Marlowe contract and pay the role tokens to the owners
utx <- either (throwing _ConstraintResolutionContractError) pure (Constraints.mkTx lookups tx)
debug'' $ "utx = " <> show utx
btx <- balanceTx $ Constraints.adjustUnbalancedTx utx
debug'' $ "btx = " <> show btx
stx <- submitBalancedTx btx
debug'' $ "stx = " <> show stx
let txId = Tx.getCardanoTxId stx
debug'' $ "txId = " <> show txId
confirmed <- awaitTxConfirmed' (CallStackTrace ["create", "marlowePlutusContract"]) (MaxRetries 3) txId
if confirmed
then do
debug'' $ "MarloweApp contract creation confirmed for parameters " <> show params <> "."
tell $ Just $ EndpointSuccess reqId $ CreateResponse params
marlowePlutusContract
else do
debug'' $ "MarloweApp contract creation failed for parameters " <> show params <> "."
-- TODO: Introduce custom error value
throwError $ OtherContractError $ Contract.OtherContractError "MarloweApp contract creation failed"
apply = endpoint @"apply-inputs" $ \(reqId, params, timeInterval, inputs) -> catchError reqId "apply-inputs" $ do
let
debug'' = debug' "apply-inputs"
debug'' $ "MarloweApp contract input-application accepted for inputs " <> show inputs <> "."
let typedValidator = mkMarloweTypedValidator params
_ <- applyInputs params typedValidator timeInterval inputs
tell $ Just $ EndpointSuccess reqId ApplyInputsResponse
debug'' $ "MarloweApp contract input-application confirmed for inputs " <> show inputs <> "."
marlowePlutusContract
applyNonmerkleized = endpoint @"apply-inputs-nonmerkleized" $ \(reqId, params, timeInterval, inputs) -> catchError reqId "apply-inputs-nonmerkleized" $ do
let typedValidator = mkMarloweTypedValidator params
_ <- applyInputs params typedValidator timeInterval $ ClientInput <$> inputs
tell $ Just $ EndpointSuccess reqId ApplyInputsResponse
debug' "apply-inputs-nonmerkleized" $ "MarloweApp contract input-application confirmed for inputs " <> show inputs <> "."
marlowePlutusContract
redeem = promiseMap (mapError (review _MarloweError)) $ endpoint @"redeem" $ \(reqId, MarloweParams{rolesCurrency}, role, paymentAddress) -> catchError reqId "redeem" $ do
let
debug'' = debug' "redeem"
debug'' $ "rolesCurrency = " <> show rolesCurrency
let address = scriptHashAddress (mkRolePayoutValidatorHash rolesCurrency)
debug'' $ "address = " <> show address
utxos <- utxosAt address
let
spendable txout =
let
expectedDatumHash = datumHash (Datum $ PlutusTx.toBuiltinData role)
dh = either id Ledger.datumHash <$> preview Ledger.ciTxOutDatum txout
in
dh == Just expectedDatumHash
utxosToSpend = Map.filter spendable utxos
spendPayoutConstraints tx ref txout =
do
let amount = view Ledger.ciTxOutValue txout
previousConstraints <- tx
payOwner <- mustPayToShelleyAddress paymentAddress amount
pure
$ previousConstraints
<> payOwner -- pay to a token owner
<> Constraints.mustSpendScriptOutput ref unitRedeemer -- spend the rolePayoutScript address
spendPayouts <- Map.foldlWithKey spendPayoutConstraints (pure mempty) utxosToSpend
if spendPayouts == mempty
then do
debug'' $ "MarloweApp contract redemption empty for role " <> show role <> "."
tell $ Just $ EndpointSuccess reqId RedeemResponse
else do
let
constraints = spendPayouts
-- must spend a role token for authorization
<> Constraints.mustSpendAtLeast (Val.singleton rolesCurrency role 1)
-- lookup for payout validator and role payouts
validator = rolePayoutScript rolesCurrency
debug'' $ "constraints = " <> show constraints
ownAddressLookups <- ownShelleyAddress paymentAddress
let
lookups = Constraints.otherScript validator
<> Constraints.unspentOutputs utxosToSpend
<> ownAddressLookups
debug'' $ "lookups = " <> show lookups
tx <- either (throwing _ConstraintResolutionContractError) pure (Constraints.mkTx @Void lookups constraints)
debug'' $ "tx = " <> show tx
_ <- submitTxConfirmed $ Constraints.adjustUnbalancedTx tx
debug'' $ "MarloweApp contract redemption confirmed for role " <> show role <> "."
tell $ Just $ EndpointSuccess reqId RedeemResponse
marlowePlutusContract
auto = endpoint @"auto" $ \(reqId, params, party, untilTime) -> catchError reqId "auto" $ do
let typedValidator = mkMarloweTypedValidator params
let continueWith :: MarloweData -> Contract MarloweContractState MarloweSchema MarloweError ()
continueWith md@MarloweData{marloweContract} =
if canAutoExecuteContractForParty party marloweContract
then autoExecuteContract reqId params typedValidator party md
else do
tell $ Just $ EndpointSuccess reqId AutoResponse
marlowePlutusContract
getMarloweTxOutRef typedValidator >>= \case
Nothing ->
waitForTimeoutOrTransition typedValidator untilTime >>= \case
Left _ -> do
logInfo $ "Contract Timeout for party " <> show party
tell $ Just $ EndpointSuccess reqId AutoResponse
marlowePlutusContract
Right (Transition Closed{}) -> do
logInfo $ "Contract Ended for party " <> show party
tell $ Just $ EndpointSuccess reqId AutoResponse
marlowePlutusContract
Right (Transition InputApplied{historyData}) -> continueWith historyData
Right (Transition Created{historyData}) -> continueWith historyData
Just marloweTxOutRef -> do
let marloweData = toMarloweState marloweTxOutRef
continueWith marloweData
-- The MarloweApp contract is closed implicitly by not returning
-- itself (marlowePlutusContract) as a continuation
close = endpoint @"close" $ \reqId -> tell $ Just $ EndpointSuccess reqId CloseResponse
autoExecuteContract :: UUID
-> MarloweParams
-> SmallTypedValidator
-> Party
-> MarloweData
-> Contract MarloweContractState MarloweSchema MarloweError ()
autoExecuteContract reqId params typedValidator party marloweData = do
time <- currentTime
let timeRange = (time, time + defaultTxValidationRange)
let (warnings, action) = getAction timeRange party marloweData
forM_ warnings $ \w -> logWarn $ "Warning: " <> show w
case action of
PayDeposit acc p token amount -> do
logInfo $ "PayDeposit " <> show amount <> " at within time " <> show timeRange
let payDeposit = do
marloweData <- mkStep params typedValidator timeRange [ClientInput $ IDeposit acc p token amount]
continueWith marloweData
catching _MarloweError payDeposit $ \err -> do
logWarn $ "Error " <> show err
logInfo @String $ "Retry PayDeposit in 2 seconds"
_ <- awaitTime (time + 2_000)
continueWith marloweData
WaitForTimeout timeout -> do
logInfo $ "WaitForTimeout " <> show timeout
_ <- awaitTime timeout
continueWith marloweData
WaitOtherActionUntil timeout -> do
logInfo $ "WaitOtherActionUntil " <> show timeout
waitForTimeoutOrTransition typedValidator timeout >>= \case
Left _ -> do
logInfo @String $ "Contract Timeout"
continueWith marloweData
Right (Transition Closed{}) -> do
logInfo @String $ "Contract Ended"
tell $ Just $ EndpointSuccess reqId AutoResponse
marlowePlutusContract
Right (Transition InputApplied{historyData}) -> continueWith historyData
Right (Transition Created{historyData}) -> continueWith historyData
CloseContract -> do
logInfo @String $ "CloseContract"
let closeContract = do
_ <- mkStep params typedValidator timeRange []
tell $ Just $ EndpointSuccess reqId AutoResponse
marlowePlutusContract
catching _MarloweError closeContract $ \err -> do
logWarn $ "Error " <> show err
logInfo @String $ "Retry CloseContract in 2 seconds"
_ <- awaitTime (time + 2000)
continueWith marloweData
NotSure -> do
logInfo @String $ "NotSure"
tell $ Just $ EndpointSuccess reqId AutoResponse
marlowePlutusContract
where
continueWith = autoExecuteContract reqId params typedValidator party
setupMarloweParams
:: forall s e i o a.
(AsMarloweError e)
=> RoleOwners
-> Set Val.TokenName
-> Contract MarloweContractState s e
(MarloweParams, TxConstraints i o, ScriptLookups a)
setupMarloweParams owners roles = mapError (review _MarloweError) $
if Set.null roles
then do
let params = marloweParams adaSymbol
pure (params, mempty, mempty)
else if roles `Set.isSubsetOf` Set.fromList (AssocMap.keys owners)
then do
let tokens = (, 1) <$> Set.toList roles
txOutRef@(Ledger.TxOutRef h i) <- getUnspentOutput
-- TODO: Move to debug log.
debug "setupMarloweParams" $ "txOutRef = " <> show txOutRef
txOut <-
maybe
(throwing _ContractError . Contract.OtherContractError . T.pack $ show txOutRef <> " was not found on the chain index. Please verify that plutus-chain-index is 100% synced.")
pure
=<< txOutFromRef txOutRef
-- TODO: Move to debug log.
debug "setupMarloweParams" $ "txOut = " <> show txOut
let utxo = Map.singleton txOutRef txOut
let theCurrency = Currency.OneShotCurrency
{ curRefTransactionOutput = (h, i)
, curAmounts = AssocMap.fromList tokens
}
curVali = Currency.curPolicy theCurrency
lookups = Constraints.mintingPolicy curVali
<> Constraints.unspentOutputs utxo
mintTx = Constraints.mustSpendPubKeyOutput txOutRef
<> Constraints.mustMintValue (Currency.mintedValue theCurrency)
let rolesSymbol = Ledger.scriptCurrencySymbol curVali
let minAdaTxOut = adaValueOf 2
let giveToParty (role, addr) =
mustPayToShelleyAddress addr (Val.singleton rolesSymbol role 1 <> minAdaTxOut)
distributeRoleTokens <- foldMapM giveToParty $ AssocMap.toList owners
let params = marloweParams rolesSymbol
pure (params, mintTx <> distributeRoleTokens, lookups)
else do
let missingRoles = roles `Set.difference` Set.fromList (AssocMap.keys owners)
let message = T.pack $ "You didn't specify owners of these roles: " <> show missingRoles
throwing _ContractError $ Contract.OtherContractError message
ownShelleyAddress
:: AddressInEra ShelleyEra
-> Contract MarloweContractState s MarloweError (ScriptLookups Void)
ownShelleyAddress addr = Constraints.ownPaymentPubKeyHash . fst <$> shelleyAddressToKeys addr
mustPayToShelleyAddress
:: AddressInEra ShelleyEra
-> Val.Value
-> Contract MarloweContractState s MarloweError (TxConstraints i o)
mustPayToShelleyAddress addr value = do
(ppkh, skh) <- shelleyAddressToKeys addr
pure $ ($ value) $ maybe
(Constraints.mustPayToPubKey ppkh)
(Constraints.mustPayToPubKeyAddress ppkh)
skh
shelleyAddressToKeys
:: AddressInEra ShelleyEra
-> Contract MarloweContractState s MarloweError (PaymentPubKeyHash, Maybe StakePubKeyHash)
shelleyAddressToKeys (AddressInEra _ (Shelley.ShelleyAddress _ paymentCredential stakeRef)) =
case Shelley.fromShelleyPaymentCredential paymentCredential of
PaymentCredentialByScript _ -> throwError $ OtherContractError $ Contract.OtherContractError "Script payment addresses not supported"
PaymentCredentialByKey hash ->
let ppkh = PaymentPubKeyHash . PubKeyHash . toBuiltin $ serialiseToRawBytes hash
in
case Shelley.fromShelleyStakeReference stakeRef of
StakeAddressByValue (StakeCredentialByScript _) ->
throwError $ OtherContractError $ Contract.OtherContractError "Script stake addresses not supported"
StakeAddressByPointer _ ->
throwError $ OtherContractError $ Contract.OtherContractError "Pointer stake addresses not supported"
NoStakeAddress -> pure (ppkh, Nothing)
StakeAddressByValue (StakeCredentialByKey stakeHash) ->
pure (ppkh, Just . StakePubKeyHash . PubKeyHash . toBuiltin $ serialiseToRawBytes stakeHash)
shelleyAddressToKeys _ = throwError $ OtherContractError $ Contract.OtherContractError "Byron Addresses not supported"
getAction :: MarloweTimeRange -> Party -> MarloweData -> ([TransactionWarning], PartyAction)
getAction timeRange party MarloweData{marloweContract,marloweState} = let
env = Environment timeRange
in case reduceContractUntilQuiescent env marloweState marloweContract of
ContractQuiescent _reduced warnings _payments state contract -> (convertReduceWarnings warnings,
-- here the contract is either When or Close
case contract of
When [Case (Deposit acc depositParty tok value) _] _ _
| party == depositParty -> let
amount = Marlowe.evalValue env state value
in PayDeposit acc party tok amount
When [Case (Deposit _ depositParty _ _) _] timeout _
| party /= depositParty ->
WaitOtherActionUntil timeout
When [] timeout _ -> WaitForTimeout timeout
Close -> CloseContract
_ -> NotSure
)
-- When timeout is in the time range
RRAmbiguousTimeIntervalError ->
{- FIXME
Consider contract:
When [cases] (POSIXTime 100) (When [Case Deposit Close]] (POSIXTime 105) Close)
For a time range (95, 105) we get RRAmbiguousTimeIntervalError
because timeout 100 is inside the time range.
Now, we wait for time 105, and we miss the Deposit.
To avoid that we need to know what was the original timeout
that caused RRAmbiguousTimeIntervalError (i.e. POSIXTime 100).
Then we'd rather wait until time 100 instead and would make the Deposit.
I propose to modify RRAmbiguousTimeIntervalError to include the expected timeout.
-}
([], WaitForTimeout (snd timeRange))
canAutoExecuteContractForParty :: Party -> Marlowe.Contract -> Bool