Scenarios in different file

Mod note: As of SDK 1.5.0 Scenarios have been superseded by the more powerful Daml Script. We now recommend using that for all purposes. For more information, and to learn how to use Script please check out @Andreas’ post on our blog.

Hi,
I create a scenario in one file and exercise scenario in another. Create is running successfully but while running exercise is giving error: Variable not in scope: iouEurBankCid : ContractId t0typecheck
I have also imported Createfile. I want two separate files and don’t want to merge them. Any workarounds?

File1: Exercise file

import CreateFile
iouTransferAliceCid <- submit eurBank do
    exerciseCmd iouEurBankCid Iou_Transfer with newOwner = alice

File 2: Create File

iouEurBankCid <- submit eurBank do
    createCmd Iou with
      issuer = eurBank
      owner = eurBank
      currency = "EUR"
      amount = 100.0
      observers = [alice,bob,usBank]Preformatted text
3 Likes

Hi Nishant,

Are you sure these files compile? Someone should correct me if I’m wrong, but I don’t believe it’s valid DAML to use do variable binding syntax at the top level of a file line this

iouTransferAliceCid <- submit eurBank do
    ...

I believe you will have to do something like this:

myScenario : Scenario ()
myScenario = do
    iouEurBankCid <- submit eurBank do
        createCmd Iou with
          issuer = eurBank
          owner = eurBank
          currency = "EUR"
          amount = 100.0
          observers = [alice,bob,usBank]

    iouTransferAliceCid <- submit eurBank do
      exerciseCmd iouEurBankCid Iou_Transfer with newOwner = Alice

You can’t generate values in one scenario and reference them in another, but you can execute a scenario from one file within another scenario.

Edit: If your aim is to factor out some common code, then you could do:

-- *** CreateFile.daml ***

createDefaultIou : Scenario (ContractId Iou)
createDefaultIou = submit eurBank do
        createCmd Iou with
          issuer = eurBank
          owner = eurBank
          currency = "EUR"
          amount = 100.0
          observers = [alice,bob,usBank]

-- ***  ExerciseFile.daml ***

myScenario : Scenario ()
myScenario = do
    iouEurBankCid <- createDefaultIou
    iouTransferAliceCid <- submit eurBank do
      exerciseCmd iouEurBankCid Iou_Transfer with newOwner = Alice
1 Like

This is giving error. Not sure why

    module CreateFile where

    import Daml.Script
    import Iou
    import IouTrade()

    trade_exercise : Script ()
    trade_exercise = do
      -- allocate parties
      alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
      bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
      usBank <- allocatePartyWithHint "USD_Bank" (PartyIdHint "USD_Bank")
      eurBank <- allocatePartyWithHint "EUR_Bank`Preformatted text`" (PartyIdHint "EUR_Bank")


      createDefaultIou : Script (ContractId Iou)
      createDefaultIou = submit eurBank do
            createCmd Iou with
              issuer = eurBank
              owner = eurBank
              currency = "EUR"
              amount = 100.0
              observers = [alice,bob,usBank]

      pure ()
1 Like

The problem here is the definition of createDefaultIou. In a do block you have to options:

  1. Either you bind something using <- as you are doing with allocatePartyWithHint. You can do that to fix your example:
trade_exercise : Script ()
trade_exercise = do
  -- allocate parties
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  usBank <- allocatePartyWithHint "USD_Bank" (PartyIdHint "USD_Bank")
  eurBank <- allocatePartyWithHint "EUR_Bank`Preformatted text`" (PartyIdHint "EUR_Bank")
  createDefaultIou <- submit eurBank do
        createCmd Iou with
          issuer = eurBank
          owner = eurBank
          currency = "EUR"
          amount = 100.0
          observers = [alice,bob,usBank]
  pure ()
  1. Or you define a variable with let variableName = expression. Note that this will only evaluate expression but not execute any effects associated with it. So if you change your example, to use a letto definecreateDefaultIouthe command won’t actually be submitted. You can call createDefaultIou` afterwards to actually create it:
trade_exercise : Script ()
trade_exercise = do
  -- allocate parties
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  usBank <- allocatePartyWithHint "USD_Bank" (PartyIdHint "USD_Bank")
  eurBank <- allocatePartyWithHint "EUR_Bank`Preformatted text`" (PartyIdHint "EUR_Bank")
  let createDefaultIou : Script (ContractId Iou)
      createDefaultIou = submit eurBank do
        createCmd Iou with
          issuer = eurBank
          owner = eurBank
          currency = "EUR"
          amount = 100.0
          observers = [alice,bob,usBank]
  createDefaultIou
  pure ()
1 Like

In both mentioned above cases. I am not able to use createDefaultIou variable in ExerciseFile.daml.

Variable not in scope: createDefaultIou : Script (ContractId t0)typecheck

-- ***  ExerciseFile.daml ***

myScenario : Scenario ()
myScenario = do
    iouEurBankCid <- createDefaultIou
    iouTransferAliceCid <- submit eurBank do
      exerciseCmd iouEurBankCid Iou_Transfer with newOwner = Alice
1 Like

If you want to call it from another definition, you need to define it as a top-level definition, so something like:

data Parties = Parties with
  alice : Party
  bob : Party
  usBank : Party
  eurBank : Party

trade_exercise : Script ()
trade_exercise = do
  -- allocate parties
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  usBank <- allocatePartyWithHint "USD_Bank" (PartyIdHint "USD_Bank")
  eurBank <- allocatePartyWithHint "EUR_Bank`Preformatted text`" (PartyIdHint "EUR_Bank")
  createDefaultIou Parties{..}
  pure ()

createDefaultIou : Parties -> Script (ContractId Iou)
createDefaultIou Parties{..} = submit eurBank do
  createCmd Iou with
          issuer = eurBank
          owner = eurBank
          currency = "EUR"
          amount = 100.0
          observers = [alice,bob,usBank]

Note that createDefaultIou accepts the parties as an argument. I’ve chosen to define a datatype to make it easy to pass around all 4 of them instead of having to deal with 4 separate arguments.

1 Like

Same issue as above. I cannot use createDefaultIou in ExerciseFile,daml.

Is it possible to share both files in the current state you are using?

1 Like

File 1

module CreateFile where

import Daml.Script
import Iou
import IouTrade()

data Parties = Parties with
  alice : Party
  bob : Party
  usBank : Party
  eurBank : Party

trade_exercise : Script ()
trade_exercise = do
  -- allocate parties
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  usBank <- allocatePartyWithHint "USD_Bank" (PartyIdHint "USD_Bank")
  eurBank <- allocatePartyWithHint "EUR_Bank`Preformatted text`" (PartyIdHint "EUR_Bank")
  createDefaultIou Parties{..}
  pure ()

createDefaultIou : Parties -> Script (ContractId Iou)
createDefaultIou Parties{..} = submit eurBank do
  createCmd Iou with
          issuer = eurBank
          owner = eurBank
          currency = "EUR"
          amount = 100.0
          observers = [alice,bob,usBank]

File 2

module ExerciseFile where

import CreateFile
import Daml.Script
import Iou
import IouTrade()

trade_exercise : Script ()
trade_exercise = do
  -- allocate parties
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  usBank <- allocatePartyWithHint "USD_Bank" (PartyIdHint "USD_Bank")
  eurBank <- allocatePartyWithHint "EUR_Bank" (PartyIdHint "EUR_Bank")


  iouEurBankCid <- createDefaultIou
  iouTransferAliceCid <- submit eurBank do
    exerciseCmd iouEurBankCid Iou_Transfer with newOwner = alice



  pure ()

You forgot to pass in the parties, if you change ExerciseFile to

module ExerciseFile where

import CreateFile
import Daml.Script
import Iou
import IouTrade()

trade_exercise : Script ()
trade_exercise = do
  -- allocate parties
  alice <- allocatePartyWithHint "Alice" (PartyIdHint "Alice")
  bob <- allocatePartyWithHint "Bob" (PartyIdHint "Bob")
  usBank <- allocatePartyWithHint "USD_Bank" (PartyIdHint "USD_Bank")
  eurBank <- allocatePartyWithHint "EUR_Bank" (PartyIdHint "EUR_Bank")


  iouEurBankCid <- createDefaultIou Parties{..}
  iouTransferAliceCid <- submit eurBank do
    exerciseCmd iouEurBankCid Iou_Transfer with newOwner = alice



  pure ()

it works.

1 Like

Hi,

Thanks. Above solution is working fine on daml studio but not with sandbox.

daml script --dar .daml/dist/quickstart-0.0.1.dar --script-name CreateFile:trade_exercise --ledger-host localhost --ledger-port 6865

It is working but if after running this command when I run

daml script --dar .daml/dist/quickstart-0.0.1.dar --script-name ExerciseFile:trade_exercise --ledger-host localhost --ledger-port 6865

Exception in thread “main” io.grpc.StatusRuntimeException: INVALID_ARGUMENT: Invalid argument: Party already exists

In the second scenario, you don’t need to allocate the parties anymore, because you already did when running the first script. Instead, you can pass the parties as arguments to the second script and run the script with the --input-file option with a file containing the allocated parties. The process is described in more detail here: https://docs.daml.com/daml-script/index.html.

It is giving ambiguous party issue

module MoorgateCreate where

import Daml.Script
import Iou
import IouTrade()

data Parties = Parties with
  alice : Party
  bob : Party
  usBank : Party
  eurBank : Party

createDefaultIou : Parties -> Script (ContractId Iou)
createDefaultIou parties = do
submit parties.eurBank do
  createCmd Iou with
          issuer = parties.eurBank
          owner = parties.eurBank
          currency = "EUR"
          amount = 100.0
          observers = [parties.alice,parties.bob,parties.usBank]

module MoorgateExercise where

import MoorgateCreate
import Daml.Script
import Iou
import IouTrade()

data Parties = Parties with
  alice : Party
  bob : Party
  usBank : Party
  eurBank : Party

trade : MoorgateExercise.Parties -> Script ()
trade parties = do
  
iouEurBankCid <- createDefaultIou MoorgateExercise.Parties{..}
iouTransferAliceCid <- submit parties.eurBank do
        exerciseCmd iouEurBankCid Iou_Transfer with newOwner = parties.alice



pure ()

You only need to define the datatype Parties one in MoorgateCreate. Remove the second definition from MoorgateExercise.

If I remove it. It says party doesn’t have required strict fileds.

module MoorgateExercise where

import MoorgateCreate
import Daml.Script
import Iou
import IouTrade()

trade : Parties -> Script ()
trade parties = do
  
iouEurBankCid <- createDefaultIou Parties{..}
iouTransferAliceCid <- submit parties.eurBank do
        exerciseCmd iouEurBankCid Iou_Transfer with newOwner = parties.alice

pure ()

Hi @Nishant_Bansal,

I’m not entirely sure what you’re trying to do here, but I’ve taken what you have and tried to make the minimal amount of changes to get something that runs. Hopefully that helps. I have had to guess a bit around Iou and IouTrade as you hadn’t shared those; hope that’s not going to be confusing.

So here we go. First, IouTrade is not actually used so far, so I’ve just defined an empty module. Here is IouTrade.daml:

module IouTrade where

Then, Iou.daml, reconstructed from the way you use it:

module Iou where

template Iou with
    issuer: Party
    owner: Party
    currency: Text
    amount: Decimal
    observers: [Party]
  where
    signatory issuer, owner
    observer observers
    preconsuming choice Iou_Transfer: ContractId Transfer
      with newOwner: Party
      controller owner
      do
        create Transfer with currentOwner = owner, proposedOwner = newOwner

-- TODO
template Transfer with
    currentOwner: Party
    proposedOwner: Party
  where
    signatory currentOwner

Now, on to the interesting ones. Looking first at MoorgateCreate.daml, it’s essentially unchanged, except for me pushing my pet peeve on qualified imports. I do believe that in tis case they make things a bit clearer.

module MoorgateCreate where

import Daml.Script
import qualified Iou
import qualified IouTrade()

data Parties = Parties with
  alice : Party
  bob : Party
  usBank : Party
  eurBank : Party

createDefaultIou : Parties -> Script (ContractId Iou.Iou)
createDefaultIou parties = do
submit parties.eurBank do
  createCmd Iou.Iou with
          issuer = parties.eurBank
          owner = parties.eurBank
          currency = "EUR"
          amount = 100.0
          observers = [parties.alice,parties.bob,parties.usBank]

Importantly, note that createDefaultIou is in this case a function, so it does not run yet.

And, finally, the one that brings them together, MoorgateExercise.daml:

module MoorgateExercise where

import qualified MoorgateCreate
import Daml.Script
import qualified Iou
import qualified IouTrade()

p: Text -> Script Party
p t = allocatePartyWithHint t (PartyIdHint t)

trade : Script ()
trade = do
  alice <- p "alice"
  bob <- p "bob"
  eurBank <- p "eurBank"
  usBank <- p "usBank"
  let parties = MoorgateCreate.Parties { alice, bob, usBank, eurBank }
  iouEurBankCid <- MoorgateCreate.createDefaultIou parties
  iouTransferAliceCid <- submit parties.eurBank do
        exerciseCmd iouEurBankCid Iou.Iou_Transfer with newOwner = parties.alice
  return ()

Hi @Gary_Verhaegen,

I actually want to keep them separate. I have sandbox running. Now I want to

  1. Create a contract on ledger through daml script command.
    daml script --dar .daml/dist/quickstart-0.0.1.dar --script-name CreateFile:trade_exercise --ledger-host localhost --ledger-port 6865
  2. I want contract id from this script and want to erercise choice on it. through
    daml script --dar .daml/dist/quickstart-0.0.1.dar --script-name ExerciseFile:trade_exercise --ledger-host localhost --ledger-port 6865

These two are my separate script. One script is creating a contract other is exercising choice on it.
Here script1 means CreateFile.daml and Script2 means ExerciseFile.daml

So if you want to be able to run them as separate runs, you’ll still need some way of communicating between them. One mechanism we provide for that is by serializing DAML values as JSON through the --input-file and --output-file options to daml script. So in your case, you should make sure that the end result of the script in MoorgateCreate returns the ContractID of the contract it has created, save that as an --output-file, and then when you run the second script, you can use that same file as the --input-file.

You can find out how DAML-LF values translate to JSON here, though in this case you may not need to look at it at all as long as you ensure that the types align in your DAML code.

In particular, you’ll want for the second script to be a function (the first one probably shouldn’t be), and for that function to take as argument the same type as the output type of the first script.

Here’s a small, self-contained, concrete example. The Main.daml file would read:

module Main where

import Daml.Script

template Asset
  with
    p: Party
    mark: Text
  where
    signatory p
    nonconsuming choice Echo: Text
      with
        msg: Text
      controller p
      do
        return $ mark <> msg

step1: Text -> Script (ContractId Asset)
step1 mark = script do
  alice <- allocatePartyWithHint "alice" (PartyIdHint "alice")
  submit alice $ createCmd Asset with p = alice, mark

step2: ContractId Asset -> Script ()
step2 cid = script do
  -- created by step 1
  let Some alice = partyFromText "alice"
  msg <- submit alice $ exerciseCmd cid Echo with msg = "hello"
  debug msg

The important point here is that the step1 function returns a ContractId Asset, and the step2 function takes a ContractId Asset as an argument. So then you can run (assuming a project named t because that’s my default name for projects, and after having started a sandbox somewhere with daml sandbox):

daml build
daml ledger upload-dar .daml/dist/t-0.0.1.dar --host localhost --port 6865
echo '"mark1"' > input_step_1.json
daml script --dar .daml/dist/t-0.0.1.dar \
            --ledger-host localhost \
            --ledger-port 6865 \
            --script-name Main:step1 \
            --input-file input_step_1.json \
            --output-file output_step_1.json

and that should create a file output_step_1.json with a contract ID in it. That’s not a very readable format, but we can pass it back into the next script:

daml script --dar .daml/dist/t-0.0.1.dar \
            --ledger-host localhost \
            --ledger-port 6865 \
            --script-name Main:step2 \
            --input-file output_step_1.json

And you should see something like:

[DA.Internal.Prelude:540]: "mark1hello"

An important point compared to your existing code is that there is no link between step1 and step2 in DAML, except for the return/input types matching. In particular, step2 does not call step1.

Hope that helps.

Hi,

On the same grounds, I made

module TestScript where

import Daml.Script
import Iou
import IouTrade()

data Parties = Parties with
  alice : Party
  bob : Party
  usBank : Party
  eurBank : Party

trade_create : Parties ->Script (ContractId Iou)
trade_create parties = do

  submit parties.eurBank do
    createCmd Iou with
          issuer = parties.eurBank
          owner = parties.eurBank
          currency = "EUR"
          amount = 100.0
          observers = []
          
 
trade_exercise : ContractId Iou -> Script ()
trade_exercise cid_iou = do
  
let Some alice = partyFromText "Alice"
let Some bob = partyFromText "Bob"
let Some usBank = partyFromText "USD_Bank" 
let Some eurBank = partyFromText "EUR_Bank"

iouTransferAliceCid <- submit eurBank do
        exerciseCmd cid_iou Iou_Transfer with newOwner = alice

pure ()

But on running:
daml script --dar .daml/dist/quickstart-0.0.1.dar --script-name TestScript:trade_create --ledger-host localhost --ledger-port 6865 --input-file parties.json --output-file result.json

It gives:
Error: User abort: Submit failed with code 3: Command interpretation error in LF-DAMLe: Couldn't find package cad2ce82e656bcb662fe8025ac74132f5e731efc850da95691be106f4a4797c0. Details: N/A.

Thar error means that one of the templates you tried to access is not known to the ledger. You have to upload the DAR to the ledger/pass it on startup. Note that any modification of your project will result in a different package id so even if you leave your templates untouched but modify the script, you have to upload the resulting new DAR. To the ledger the templates have still changed.