Skip to content

Commit

Permalink
Merge pull request #106 from mlabs-haskell/euonymos/rip-transition-spine
Browse files Browse the repository at this point in the history
Rip transition spines from transactions
  • Loading branch information
euonymos authored Dec 9, 2024
2 parents baee6d0 + c08a64d commit a83073b
Show file tree
Hide file tree
Showing 28 changed files with 1,027 additions and 953 deletions.
2 changes: 1 addition & 1 deletion cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ source-repository-package
tag: d5b0e7ce07258482d53704ce19383013b1fa6610
--sha256: 6+Os/mQDzBOU+TkTD+n/T1MFcI+Mn0/tcBMJhLRfqyA=

-- FIXME: Cannot use new commit, because it requires `plutus-ledger-api==1.29`
-- Cannot use new commit, because it requires `plutus-ledger-api==1.29`
source-repository-package
type: git
location: https://github.com/Plutonomicon/plutarch-plutus
Expand Down
32 changes: 21 additions & 11 deletions cem-script.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version: 0.1.0
synopsis: CEM Script - a Cardano dApp SDK
homepage: https://github.com/mlabs-haskell/cem-script
author: MLabs
maintainer: gregory@mlabs.city
maintainer: ilia@mlabs.city
data-files: README.md
tested-with: GHC ==9.6.3

Expand All @@ -15,9 +15,11 @@ flag dev
default: True
manual: False

common common-lang
-- Options from MLabs styleguide
flag force-recomp
description: Compile with -fforce-recomp and -Wunused-packages
default: False

common common-lang
ghc-options:
-Wall -Wcompat -Wincomplete-record-updates
-Wincomplete-uni-patterns -Wredundant-constraints
Expand All @@ -26,8 +28,15 @@ common common-lang
if !flag(dev)
ghc-options: -Werror

if flag(dev)
default-extensions: PartialTypeSignatures

if flag(force-recomp)
ghc-options: -fforce-recomp -Wunused-packages

build-depends:
, base
, extra
, mtl
, transformers

Expand All @@ -52,9 +61,6 @@ common common-lang
UndecidableInstances
ViewPatterns

if flag(dev)
default-extensions: PartialTypeSignatures

default-language: GHC2021

common common-onchain
Expand Down Expand Up @@ -144,26 +150,32 @@ library
Cardano.CEM.Examples.Auction
Cardano.CEM.Examples.Compilation
Cardano.CEM.Examples.Voting
Cardano.CEM.Indexing
Cardano.CEM.Indexing.Event
Cardano.CEM.Indexing.Oura
Cardano.CEM.Indexing.Tx
Cardano.CEM.Monads
Cardano.CEM.Monads.CLB
Cardano.CEM.Monads.L1
Cardano.CEM.OffChain
Cardano.CEM.OnChain
Cardano.CEM.Testing.StateMachine
Cardano.CEM.TH

other-modules: Cardano.CEM.Monads.L1Commons
build-depends:
, base16
, base64
, cem-script:cardano-extras
, cem-script:data-spine
, clb
, dependent-map
, lens
, ouroboros-consensus
, QuickCheck
, quickcheck-dynamic
, safe
, singletons-th
, toml-parser
, vector

test-suite cem-sdk-test
import:
Expand Down Expand Up @@ -205,12 +217,10 @@ test-suite cem-sdk-test
Auction
Dynamic
OffChain
Oura
Oura.Communication
Oura.Config
OuraFilters
OuraFilters.Auction
OuraFilters.Mock
OuraFilters.Simple
TestNFT
Utils
Voting
Expand Down
1 change: 0 additions & 1 deletion src-lib/cardano-extras/Cardano/Extras.hs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,6 @@ mintedTokens ::
[(AssetName, Quantity)] ->
Cardano.TxMintValue BuildTx Era
mintedTokens script redeemer assets =
-- FIXME: is hardcoding era correct?
TxMintValue Cardano.MaryEraOnwardsBabbage mintedTokens' mintedWitnesses'
where
mintedTokens' = valueFromList (fmap (first (AssetId policyId)) assets)
Expand Down
8 changes: 3 additions & 5 deletions src-lib/data-spine/Data/Spine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import Language.Haskell.TH.Syntax

-- | Definitions

{- | Spine is datatype, which tags constructors of ADT.
| TH deriving utility generates Spines, which are Enums,
| but one could introduce more complex Spine datatypes manually.
{- | Spine is datatype, which tags only constructors of ADT skipping their content.
TH deriving utility generates Spines which are Enums but one could introduce
more complex Spine datatypes manually.
-}
class
( Ord (Spine sop)
Expand Down Expand Up @@ -88,8 +88,6 @@ deriveSpine name = do
suffix = "Spine"
spineName = addSuffix name suffix
spineDec <- deriveTags name suffix [''Eq, ''Ord, ''Enum, ''Show]
-- TODO: derive Sing
-- TODO: derive HasField (OfSpine ...)

decls <-
[d|
Expand Down
8 changes: 4 additions & 4 deletions src/Cardano/CEM.hs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ class
type EqShow datatype =
( Prelude.Eq datatype
, Prelude.Show datatype
-- TODO: add IsData here? (now it breaks Plutus compilation)
-- Shoul we add IsData here? (now it breaks Plutus compilation)
)

{- | All associated types for 'CEMScript' class defined separately to simplify
Expand Down Expand Up @@ -223,7 +223,7 @@ data TxFanKind
-- | Constraint on a single tx fan
data TxFanFilter script = MkTxFanFilter
{ address :: AddressSpec
, rest :: FilterDatum script -- TODO: not ideal naming
, datumFilter :: FilterDatum script
}
deriving stock (Show, Prelude.Eq)

Expand Down Expand Up @@ -252,8 +252,8 @@ bySameCEM = UnsafeBySameCEM . toBuiltinData

-- | How many tx fans should satify a 'TxFansConstraint'
data Quantifier
= ExactlyNFans Integer -- TODO: use natural numbers
| FansWithTotalValueOfAtLeast Value -- TODO: use natural numbers
= ExactlyNFans Integer -- Here we'd better use natural numbers
| FansWithTotalValueOfAtLeast Value
deriving stock (Show)

-- | A constraint on Tx inputs or Outputs.
Expand Down
20 changes: 15 additions & 5 deletions src/Cardano/CEM/Address.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Cardano.CEM.Address (
cardanoAddressBech32,
scriptCredential,
scriptCardanoAddress,
cardanoAddressBech32,
plutusAddressToShelleyAddress,
AddressBech32 (MkAddressBech32, unAddressBech32),
) where
Expand All @@ -14,7 +15,7 @@ import Cardano.Ledger.BaseTypes qualified as Ledger
import Cardano.Ledger.Credential qualified as Cred
import Cardano.Ledger.Hashes qualified
import Cardano.Ledger.Keys qualified as Ledger.Keys
import Data.Data (Proxy (Proxy))
import Data.Proxy (Proxy)
import Data.String (IsString)
import Data.Text qualified as T
import Plutus.Extras qualified
Expand All @@ -33,13 +34,22 @@ scriptCardanoAddress ::
Proxy script ->
Cardano.Api.Ledger.Network ->
Either String (Cardano.Api.Address Cardano.Api.ShelleyAddr)
scriptCardanoAddress _ network =
scriptCardanoAddress p network =
plutusAddressToShelleyAddress network
. flip PlutusLedgerApi.V1.Address Nothing
. PlutusLedgerApi.V1.ScriptCredential
. scriptCredential
$ p

scriptCredential ::
forall script.
(Compiled.CEMScriptCompiled script) =>
Proxy script ->
PlutusLedgerApi.V1.Credential
scriptCredential p =
PlutusLedgerApi.V1.ScriptCredential
. Plutus.Extras.scriptValidatorHash
. Compiled.cemScriptCompiled
$ Proxy @script
$ p

plutusAddressToShelleyAddress ::
Cardano.Api.Ledger.Network ->
Expand Down
Empty file removed src/Cardano/CEM/Examples.hs
Empty file.
2 changes: 2 additions & 0 deletions src/Cardano/CEM/Examples/Auction.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{-# LANGUAGE NoPolyKinds #-}

-- {-# OPTIONS_GHC -Wno-unrecognised-pragmas -ddump-splices #-}

module Cardano.CEM.Examples.Auction where

import PlutusTx.Prelude
Expand Down
146 changes: 146 additions & 0 deletions src/Cardano/CEM/Indexing/Event.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}

-- | Indexer events, i.e. indexer outputs.
module Cardano.CEM.Indexing.Event where

import Cardano.Api qualified as C
import Cardano.Api.ScriptData qualified as C
import Cardano.Api.SerialiseRaw qualified as SerialiseRaw
import Cardano.CEM (CEMScript, CEMScriptDatum, State, Transition, transitionStage)
import Cardano.CEM.Address qualified as Address
import Cardano.CEM.Indexing.Tx
import Cardano.CEM.OnChain (CEMScriptCompiled, CEMScriptIsData)
import Cardano.Ledger.BaseTypes qualified as Ledger
import Control.Lens (view, (^.))
import Data.Bifunctor (first)
import Data.ByteString.Base16 qualified as B16
import Data.Data (Proxy (Proxy))
import Data.Either.Extra (eitherToMaybe)
import Data.Function ((&))
import Data.List (find)
import Data.Map.Strict qualified as Map
import Data.Maybe (fromJust)
import Data.Spine (Spine, getSpine)
import Data.Text.Encoding (encodeUtf8)
import Data.Tuple (swap)
import PlutusLedgerApi.V1 (FromData)
import PlutusLedgerApi.V1 qualified
import Prelude

-- ---

{- | Indexer events.
We extract events from transactions, where we can encounter three situations:
(1) For the very first transition there is only target datum and no redeemer.
In that case we can only restore the name of the transition,
i.e. 'Spine Transition'
(2) For intermidiate transitions we have both datums that identify them and
additionally redeemer, that contains the whole transition. In that case
we can restore the whole transition.
(3) For the final transition the situation is like (2) except the target
datum is missing, which doesn't matter.
TODO: How we can improve this in the future:
* API is probably bad, as we always have some transition like Init state -
which you can decode, as you have State. If one changes data
`CEMAction script = MkCEMAction (Params script) (Transition script)` to
`... = Init (Params script) (State script)
| Transition (Params script) (Transition script)`
one could reuse this datatype in all situations.
-}
data IndexerEvent script
= Initial (Spine (Transition script))
| -- | TODO: Migrate from (Spine (Transition script)) to (Transition script)
-- once we have this done: https://github.com/utxorpc/spec/issues/132
Following (Spine (Transition script)) -- (Transition script)

deriving stock instance
(Show (Spine (Transition script))) =>
(Show (IndexerEvent script))
deriving stock instance
(Eq (Spine (Transition script))) =>
(Eq (IndexerEvent script))

{- | The core function, that extracts an Event out of a Oura transaction.
It might be a pure function, IO here was used mostly to simplify debugging
during its development.
-}
extractEvent ::
forall script.
( CEMScript script
, CEMScriptIsData script
, CEMScriptCompiled script
) =>
Ledger.Network ->
Tx ->
IO (Maybe (IndexerEvent script))
extractEvent network tx = do
-- Script payemnt credential based predicate
let ~(Right scriptAddr) = Address.scriptCardanoAddress (Proxy @script) network
let cPred = hasAddr scriptAddr

-- Source state
let mOwnInput :: Maybe TxInput = find (cPred . view as_output) (tx ^. inputs)
let mSourceState :: Maybe (State script) = (extractState . view as_output) =<< mOwnInput
let mSourceSpine :: Maybe (Spine (State script)) = getSpine <$> mSourceState

-- Target state
let mOwnOutput :: Maybe TxOutput = find cPred $ tx ^. outputs
let mTargetState :: Maybe (State script) = extractState =<< mOwnOutput
let mTargetSpine :: Maybe (Spine (State script)) = getSpine <$> mTargetState

-- Look up the transition
let transitions =
first
(\(_, b, c) -> (b, c))
. swap
<$> Map.toList (transitionStage $ Proxy @script)
let transSpine = lookup (mSourceSpine, mTargetSpine) transitions

-- Return
case mOwnInput of
Nothing -> pure $ Initial <$> transSpine
Just _ownInput -> do
-- TODO: fix once Oura has rawCbor for redeemer
-- rdm <- ownInput ^. redeemer
-- pure $ Following $ undefined (rdm ^. redeemerPayload)
pure $ Following <$> transSpine

extractState ::
forall script.
(FromData (CEMScriptDatum script)) =>
TxOutput ->
Maybe (State script)
extractState MkTxOutput {_datum = mDtm} =
case mDtm of
Nothing -> Nothing
Just dtm -> do
let MkDatum _ _ cbor = dtm
let datumAsData :: PlutusLedgerApi.V1.Data =
cbor
& C.toPlutusData
. C.getScriptData
. fromJust
. eitherToMaybe
. C.deserialiseFromCBOR C.AsHashableScriptData
. B16.decodeBase16Lenient -- use base64
. encodeUtf8
let ~(Just (_, _, state)) = PlutusLedgerApi.V1.fromData @(CEMScriptDatum script) datumAsData
pure state

hasAddr :: C.Address C.ShelleyAddr -> TxOutput -> Bool
hasAddr addr' output =
let addr = output ^. address
in fromOuraAddress addr == addr'

fromOuraAddress :: Address -> C.Address C.ShelleyAddr
fromOuraAddress (MkAddressAsBase64 addr) =
addr
& fromJust
. eitherToMaybe
. SerialiseRaw.deserialiseFromRawBytes (C.AsAddress C.AsShelleyAddr)
. B16.decodeBase16Lenient -- use base64
. encodeUtf8
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
module Cardano.CEM.Indexing (
{- | CEM provides the building blocks to build an indexer for your dApp.
Current implementation is based on Oura. This module provides tools to
run Oura.
-}
module Cardano.CEM.Indexing.Oura (
SourcePath (MkSourcePath, unSourcePath),
SinkPath (MkSinkPath, unSinkPath),
Filter (MkFilter, unFilter),
Expand Down
Loading

0 comments on commit a83073b

Please sign in to comment.