Automation with Daml Triggers - Interactive Tutorial Help

Hello! I’m receiving an error when I attempt to run daml start in the interactive Terminal for the exercise (page 5) of the Automation with Daml Triggers tutorial.

Here’s the error:
$ daml start
Compiling market to a DAR.
File: daml/Market.daml
Hidden: no
Range: 109:30-109:33
Source: typecheck
Severity: DsError
Message: daml/Market.daml:109:30: error:Not in scope: type constructor or class ‘ACS’
ERROR: Creation of DAR file failed.
daml-helper: Received ExitFailure 1 when running
Shell command: /root/.daml/bin/daml build

I’ve tried messing with the indentation but that triggers another error. What am I missing?
Here’s the code that I copied into the browser IDE:

module Market where

import DA.Date
import Daml.Trigger
import DA.Next.Map
import DA.Foldable(forA_)

template User
  with
    party : Party
  where
    signatory party
    key party : Party
    maintainer key

    nonconsuming choice NewSellOffer : ()
      with
        observers : [Party]
        title : Text
        description : Text
        price : Int
      controller party
        do
          now <- getTime
          create $ SellOffer {seller = party, date = toDateUTC now, ..}
          pure ()

    nonconsuming choice TakeSellOffer : ()
      with
        offer : ContractId SellOffer
      controller party
        do
          exercise offer DoTrade with tradePartner = party
          pure ()

    nonconsuming choice ConfirmPayment : ()
      with
        invoice : ContractId Invoice
      controller party
        do
          Invoice{..} <- fetch invoice
          assert $ owner == party
          create $ PaymentConfirmation
                      with
                        invoice = invoice
                        party = party
                        obligor = obligor
          pure ()

template SellOffer
  with
    observers : [Party]
    title : Text
    description : Text
    price : Int
    seller : Party
    date : Date
  where
    signatory seller
    observer observers

    nonconsuming choice DoTrade : ()
      with
        tradePartner : Party
      controller tradePartner
        do
          assert $ tradePartner `elem` observers
          archive self
          create $ Invoice {owner = seller, obligor = tradePartner, amount = price, description = title}
          pure ()

template Invoice
  with
    owner : Party
    obligor : Party
    amount : Int
    description : Text
  where
    signatory obligor
    observer owner

template PaymentConfirmation
  with
    invoice : ContractId Invoice
    party : Party
    obligor : Party
  where
    signatory party
    observer obligor

    nonconsuming choice ArchiveInvoice : ()
      controller obligor
      do
        archive invoice
        archive self

deleteInvoiceTrigger : Trigger ()
deleteInvoiceTrigger = Trigger
  { initialize = const ()
  , updateState = \_acs _message () -> ()
  , rule = deleteInvoiceRule
  , registeredTemplates = RegisteredTemplates
      [ registeredTemplate @Invoice
      , registeredTemplate @PaymentConfirmation
      ]
  , heartbeat = None
  }

deleteInvoiceRule : Party -> ACS -> Time -> Map CommandId [Command] -> () -> TriggerA ()
deleteInvoiceRule party acs _t commandsInFlight () = do
  let invoices = getContracts @Invoice acs
  let confirmations = getContracts @PaymentConfirmation acs
  let ready = do
              (confirmationCid, PaymentConfirmation{invoice, obligor}) <- confirmations
              (invoiceCid, Invoice{}) <- invoices
              guard $ invoiceCid == invoice
              guard $ party == obligor
              pure confirmationCid
  
  forA_ ready $ \confirmationCid -> dedupExercise confirmationCid ArchiveInvoice
1 Like

Hi @kirksudduth,

This is not an indentation issue. The compiler is complaining about this line:

deleteInvoiceRule : Party -> ACS -> Time -> Map CommandId [Command] -> () -> TriggerA ()

Specifically, the fact that you do not have a type ACS defined.

I’m not familiar with the tutorial you mentioned, so I can’t comment where that type is supposed to be coming from. Happy to take a deeper look if you can share a link.

2 Likes

Rules in Daml Triggers no longer take so many arguments; ACS is not defined because there is no longer an ACS type exported by the triggers library, as it is not needed. You can see the current signature in the Daml Triggers documentation.

I assume you got the sample source code from another part of the tutorial; I am unsure of the provenance of this sample.

3 Likes

Looking at your code more closely (and cross-referencing with the Daml.Trigger module documentation) it looks like you have a handful of types and names wrong here (at least as of SDK 1.12.0). Changing the last two blocks of code to:

deleteInvoiceTrigger : Trigger ()
deleteInvoiceTrigger = Trigger
  { initialize = return ()
  , updateState = \ _message -> return ()
  , rule = deleteInvoiceRule
  , registeredTemplates = RegisteredTemplates
      [ registeredTemplate @Invoice
      , registeredTemplate @PaymentConfirmation
      ]
  , heartbeat = None
  }

deleteInvoiceRule : Party -> TriggerA () ()
deleteInvoiceRule party = do
  invoices <- query @Invoice
  confirmations <- query @PaymentConfirmation
  let ready = do
              (confirmationCid, PaymentConfirmation{invoice, obligor}) <- confirmations
              (invoiceCid, Invoice{}) <- invoices
              guard $ invoiceCid == invoice
              guard $ party == obligor
              pure confirmationCid

  forA_ ready $ \confirmationCid -> dedupExercise confirmationCid ArchiveInvoice

gets your file to compile for me. Specifically:

  1. The initialize field expects a type TriggerInitializeA s, whereas you are providing a function a -> (). If you don’t care about carrying your own state (of type s), you can create a value of type TriggerInitializeA () (i.e. set s to ()) by writing return () (or pure () if that makes more sense to you; pure is literally defined by pure = return).
  2. The updateState field requires a Message -> TriggerUpdateA s () while you’re giving it a a -> b -> () -> (). To construct a no-op Message -> TriggerUpdateA s () (which seems to be what you want) you can write \_ -> return () or, alternatively, const (return ()).
  3. The type of a rule should be Party -> TriggerA s (), whereas you re trying to give it a Party -> ACS -> Time -> Map CommandId [Command] -> () -> TriggerA (). So the type of deleteInvoiceRule needs to change to Party -> TriggerA () () (that s must be the same s as in initialize and updateState, so in our case ()).

This last point means deleteInvoiceRule is a function of a single argument, which raises the question of where you can get all the other things from (ACS, current time, commands in flight). On that front, it seems plausible you may be a little bit confused about do notation, so I’ll try to explain it here (though very briefly). A do block represents what we call an Action in Daml (hence the A at the end of the corresponding types). You can think of the do block as wrapping a computation for execution somewhere else, in some other context. Often in Daml that other context is the ledger, but here it is the trigger engine.

What does it mean that “the computation will be run in a context”? It means you can ask questions of the context, for example in this case “get me all the contracts in the current ACS” or “get me the current time”. Those questions are a bit special because they are asked of the context: they are not explicit parameters to the function. So they have a slightly different syntax, using the <- notation. For example, this line:

  invoices <- query @Invoice

can be understood as "when this code runs in a context, query the context for all the contracts of type invoice, and store the result in the variable invoices". This is in contrast with a line like:

  let a = 4 + 5

which represents a “pure” computation (of which the result is stored in a), i.e. one that is independent of the context.

Hope that helped. Don’t hesitate to ask further questions if anything is still unclear. Also, please do point us to wherever you got your code from, as it seems we may need to update it to match the latest version.

2 Likes

Thank you for the reference @Stephen!
I guess the tutorial is using an outdated process.

1 Like

Thank you so much for the detailed response, @Gary_Verhaegen!
I’m cutting my teeth with a lot of these concepts so this is very helpful!

Link to the tutorial

1 Like

Would you mind sharing a link to the tutorial you’re following, so we can make sure to update it?

2 Likes

If you’re looking for an up2date tutorial, the one in our documentation should always be updated to the latest SDK version.

2 Likes

Thanks, @cocreature!

Appreciate all the responses and enjoying learning more about Daml

1 Like

Thanks for pointing this out, we’ll make sure to update it!

2 Likes

Hi @kirksudduth :wave:

Sorry for experiencing the issue with the tutorial, my bad really (I was away for some time). We’re in the process of updating it.

@Gary_Verhaegen @Stephen @cocreature @anthony Thanks for all the help and pointing out that the tutorial needs to be fixed :slight_smile:

2 Likes

Hi @kirksudduth :wave:

We’ve updated the tutorial with the working code. Thnx for your feedback!

3 Likes

Thank you @nemanja!
Learning more every day :+1:

1 Like