Skip to content

Commit

Permalink
SCP-2018: Waiting vs non-waiting actions
Browse files Browse the repository at this point in the history
* Add proper type for BlockchainActions
* Roll all 'Effects' into 'Contract.Request'
* Change types in Plutus.Contract.Types

[plutus-contract compiles]
[plutus-contract compiles and tests pass]
  • Loading branch information
j-mueller committed Jun 12, 2021
1 parent d4e1493 commit a6baa64
Show file tree
Hide file tree
Showing 33 changed files with 1,037 additions and 1,487 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions plutus-contract/plutus-contract.cabal
Expand Up @@ -42,6 +42,7 @@ library
Plutus.Contract.Effects.UtxoAt
Plutus.Contract.Effects.WatchAddress
Plutus.Contract.Effects.WriteTx
Plutus.Contract.Effects
Plutus.Contract.Request
Plutus.Contract.Checkpoint
Plutus.Contract.Constraints
Expand Down
129 changes: 42 additions & 87 deletions plutus-contract/src/Plutus/Contract.hs
Expand Up @@ -6,8 +6,6 @@ module Plutus.Contract(
Contract(..)
, ContractError(..)
, AsContractError(..)
, HasBlockchainActions
, BlockchainActions
, both
, selectEither
, select
Expand All @@ -17,62 +15,46 @@ module Plutus.Contract(
, mapError
, runError
-- * Dealing with time
, HasAwaitSlot
, AwaitSlot
, awaitSlot
, currentSlot
, waitNSlots
, until
, when
, timeout
, between
, collectUntil
, Request.awaitSlot
, Request.currentSlot
, Request.waitNSlots
-- * Endpoints
, HasEndpoint
, EndpointDescription(..)
, Endpoint
, endpoint
, endpointWithMeta
, Request.HasEndpoint
, Request.EndpointDescription(..)
, Request.Endpoint
, Request.endpoint
, Request.endpointWithMeta
, Schema.EmptySchema
-- * Blockchain events
, HasWatchAddress
, WatchAddress
, AddressChangeRequest(..)
, AddressChangeResponse(..)
, addressChangeRequest
, nextTransactionsAt
, watchAddressUntil
, fundsAtAddressGt
, fundsAtAddressGeq
, Wallet.Types.AddressChangeRequest(..)
, Wallet.Types.AddressChangeResponse(..)
, Request.addressChangeRequest
, Request.nextTransactionsAt
, Request.watchAddressUntil
, Request.fundsAtAddressGt
, Request.fundsAtAddressGeq
-- * UTXO set
, HasUtxoAt
, UtxoAt
, utxoAt
, UtxoMap
, Request.utxoAt
-- * Wallet's own public key
, HasOwnPubKey
, OwnPubKey
, ownPubKey
, Request.ownPubKey
-- * Contract instance Id
, HasOwnId
, ContractInstanceId
, ownInstanceId
, Wallet.Types.ContractInstanceId
, Request.ownInstanceId
-- * Notifications
, tell
-- * Transactions
, HasWriteTx
, WriteTx
, WalletAPIError
, submitTx
, submitTxConfirmed
, submitTxConstraints
, submitTxConstraintsSpending
, submitTxConstraintsWith
, submitUnbalancedTx
, Request.submitTx
, Request.submitTxConfirmed
, Request.submitTxConstraints
, Request.submitTxConstraintsSpending
, Request.submitTxConstraintsWith
, Request.submitUnbalancedTx
-- ** Creating transactions
, module Tx
-- ** Tx confirmation
, HasTxConfirmation
, TxConfirmation
, awaitTxConfirmed
, Request.awaitTxConfirmed
-- * Checkpoints
, checkpoint
, checkpointLoop
Expand All @@ -90,50 +72,23 @@ module Plutus.Contract(
, type Empty
) where

import Data.Aeson (ToJSON (toJSON))
import Data.Aeson (ToJSON (toJSON))
import Data.Row

import Plutus.Contract.Effects.AwaitSlot as AwaitSlot
import Plutus.Contract.Effects.AwaitTxConfirmed as AwaitTxConfirmed
import Plutus.Contract.Effects.ExposeEndpoint
import Plutus.Contract.Effects.Instance
import Plutus.Contract.Effects.OwnPubKey as OwnPubKey
import Plutus.Contract.Effects.UtxoAt as UtxoAt
import Plutus.Contract.Effects.WatchAddress as WatchAddress
import Plutus.Contract.Effects.WriteTx
import Plutus.Contract.Request (ContractRow)
import qualified Plutus.Contract.Request as Request
import qualified Plutus.Contract.Schema as Schema
import Plutus.Contract.Typed.Tx as Tx
import Plutus.Contract.Types (AsCheckpointError (..), AsContractError (..), CheckpointError (..),
Contract (..), ContractError (..), checkpoint, checkpointLoop,
handleError, mapError, runError, select, selectEither, throwError)

import Plutus.Contract.Request (ContractRow)
import Plutus.Contract.Typed.Tx as Tx
import Plutus.Contract.Types (AsCheckpointError (..), AsContractError (..),
CheckpointError (..), Contract (..), ContractError (..),
checkpoint, checkpointLoop, handleError, mapError, runError,
select, selectEither, throwError)

import qualified Control.Monad.Freer.Extras.Log as L
import qualified Control.Monad.Freer.Writer as W
import Prelude hiding (until)
import Wallet.API (WalletAPIError)

-- | Schema for contracts that can interact with the blockchain (via a node
-- client & signing process)
type BlockchainActions =
AwaitSlot
.\/ WatchAddress
.\/ WriteTx
.\/ UtxoAt
.\/ OwnPubKey
.\/ TxConfirmation
.\/ OwnId

type HasBlockchainActions s =
( HasAwaitSlot s
, HasWatchAddress s
, HasWriteTx s
, HasUtxoAt s
, HasOwnPubKey s
, HasTxConfirmation s
, HasOwnId s
)
import qualified Control.Monad.Freer.Extras.Log as L
import qualified Control.Monad.Freer.Writer as W
import Ledger.AddressMap (UtxoMap)
import Prelude hiding (until)
import Wallet.API (WalletAPIError)
import qualified Wallet.Types

-- | Execute both contracts in any order
both :: Contract w s e a -> Contract w s e b -> Contract w s e (a, b)
Expand Down
181 changes: 181 additions & 0 deletions plutus-contract/src/Plutus/Contract/Effects.hs
@@ -0,0 +1,181 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module Plutus.Contract.Effects( -- TODO: Move to Requests.Internal
PABReq(..),
_AwaitSlotReq,
_CurrentSlotReq,
_AwaitTxConfirmedReq,
_OwnContractInstanceIdReq,
_SendNotificationReq,
_OwnPublicKeyReq,
_UtxoAtReq,
_AddressChangeReq,
_WriteTxReq,
_ExposeEndpointReq,
PABResp(..),
_AwaitSlotResp,
_CurrentSlotResp,
_AwaitTxConfirmedResp,
_OwnContractInstanceIdResp,
_SendNotificationResp,
_OwnPublicKeyResp,
_UtxoAtResp,
_AddressChangeResp,
_WriteTxResp,
_ExposeEndpointResp,
matches,

-- * Etc.
UtxoAtAddress(..),
WriteTxResponse(..),
writeTxResponse,
ActiveEndpoint(..),
TxConfirmed(..)
) where

import Control.Lens (Iso', iso, makePrisms)
import Data.Aeson (FromJSON, ToJSON)
import qualified Data.Aeson as JSON
import qualified Data.Map as Map
import Data.Text.Prettyprint.Doc (Pretty (..), colon, indent, viaShow, vsep, (<+>))
import GHC.Generics (Generic)
import Ledger (Address, PubKey, Tx, TxId, TxOutTx (..), txId)
import Ledger.AddressMap (UtxoMap)
import Ledger.Constraints.OffChain (UnbalancedTx)
import Ledger.Slot (Slot (..))
import Wallet.API (WalletAPIError)
import Wallet.Types (AddressChangeRequest, AddressChangeResponse, ContractInstanceId,
EndpointDescription, EndpointValue, Notification, NotificationError)

-- | Requests that 'Contract's can make
data PABReq =
AwaitSlotReq Slot
| CurrentSlotReq
| AwaitTxConfirmedReq TxId
| OwnContractInstanceIdReq
| SendNotificationReq Notification -- TODO: Delete
| OwnPublicKeyReq
| UtxoAtReq Address
| AddressChangeReq AddressChangeRequest
| WriteTxReq UnbalancedTx
| ExposeEndpointReq ActiveEndpoint
deriving stock (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)

instance Pretty PABReq where
pretty = \case
AwaitSlotReq s -> "Await slot:" <+> pretty s
CurrentSlotReq -> "Current slot"
AwaitTxConfirmedReq txid -> "Await tx confirmed:" <+> pretty txid
OwnContractInstanceIdReq -> "Own contract instance ID"
SendNotificationReq noti -> "Send notification:" <+> pretty noti
OwnPublicKeyReq -> "Own public key"
UtxoAtReq addr -> "Utxo at:" <+> pretty addr
AddressChangeReq req -> "Address change:" <+> pretty req
WriteTxReq utx -> "Write unbalanced tx:" <+> pretty utx
ExposeEndpointReq ep -> "Expose endpoint:" <+> pretty ep

-- | Responses that 'Contract's receive
data PABResp =
AwaitSlotResp Slot
| CurrentSlotResp Slot
| AwaitTxConfirmedResp TxId
| OwnContractInstanceIdResp ContractInstanceId
| SendNotificationResp (Maybe NotificationError)
| OwnPublicKeyResp PubKey
| UtxoAtResp UtxoAtAddress
| AddressChangeResp AddressChangeResponse
| WriteTxResp WriteTxResponse
| ExposeEndpointResp EndpointDescription (EndpointValue JSON.Value)
deriving stock (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)


instance Pretty PABResp where
pretty = \case
AwaitSlotResp s -> "Slot:" <+> pretty s
CurrentSlotResp s -> "Current slot:" <+> pretty s
AwaitTxConfirmedResp txid -> "Tx confirmed:" <+> pretty txid
OwnContractInstanceIdResp i -> "Own contract instance ID:" <+> pretty i
SendNotificationResp e -> "Send notification:" <+> pretty e
OwnPublicKeyResp k -> "Own public key:" <+> pretty k
UtxoAtResp rsp -> "Utxo at:" <+> pretty rsp
AddressChangeResp rsp -> "Address change:" <+> pretty rsp
WriteTxResp r -> "Write unbalanced tx:" <+> pretty r
ExposeEndpointResp desc rsp -> "Call endpoint" <+> pretty desc <+> "with" <+> pretty rsp

matches :: PABReq -> PABResp -> Bool
matches a b = case (a, b) of
(AwaitSlotReq{}, AwaitSlotResp{}) -> True
(CurrentSlotReq, CurrentSlotResp{}) -> True
(AwaitTxConfirmedReq{}, AwaitTxConfirmedResp{}) -> True
(OwnContractInstanceIdReq, OwnContractInstanceIdResp{}) -> True
(SendNotificationReq{}, SendNotificationResp{}) -> True
(OwnPublicKeyReq, OwnPublicKeyResp{}) -> True
(UtxoAtReq{}, UtxoAtResp{}) -> True
(AddressChangeReq{}, AddressChangeResp{}) -> True
(WriteTxReq{}, WriteTxResp{}) -> True
(ExposeEndpointReq ActiveEndpoint{aeDescription}, ExposeEndpointResp desc _)
| aeDescription == desc -> True
_ -> False

data UtxoAtAddress =
UtxoAtAddress
{ address :: Address
, utxo :: UtxoMap
}
deriving stock (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)

instance Pretty UtxoAtAddress where
pretty UtxoAtAddress{address, utxo} =
let
prettyTxOutPair (txoutref, TxOutTx{txOutTxOut}) =
pretty txoutref <> colon <+> pretty txOutTxOut
utxos = vsep $ fmap prettyTxOutPair (Map.toList utxo)
in vsep ["Utxo at" <+> pretty address <+> "=", indent 2 utxos]

data WriteTxResponse =
WriteTxFailed WalletAPIError
| WriteTxSuccess Tx
deriving stock (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)

instance Pretty WriteTxResponse where
pretty = \case
WriteTxFailed e -> "WriteTxFailed:" <+> pretty e
WriteTxSuccess i -> "WriteTxSuccess:" <+> pretty (txId i)

writeTxResponse :: Iso' WriteTxResponse (Either WalletAPIError Tx)
writeTxResponse = iso f g where
f = \case { WriteTxFailed w -> Left w; WriteTxSuccess t -> Right t }
g = either WriteTxFailed WriteTxSuccess

data ActiveEndpoint = ActiveEndpoint
{ aeDescription :: EndpointDescription -- ^ The name of the endpoint
, aeMetadata :: Maybe JSON.Value -- ^ Data that should be shown to the user
}
deriving (Eq, Show, Generic)
deriving anyclass (ToJSON, FromJSON)

instance Pretty ActiveEndpoint where
pretty ActiveEndpoint{aeDescription, aeMetadata} =
indent 2 $ vsep
[ "Endpoint:" <+> pretty aeDescription
, "Metadata:" <+> viaShow aeMetadata
]

newtype TxConfirmed =
TxConfirmed { unTxConfirmed :: TxId }
deriving stock (Eq, Ord, Generic, Show)
deriving anyclass (ToJSON, FromJSON)
deriving Pretty via TxId

makePrisms ''PABReq

makePrisms ''PABResp

0 comments on commit a6baa64

Please sign in to comment.