Concat/collab dataset from multiple contracts and create another contract

Hi Community,

I am stuck badly :frowning:
I am trying to pull dataset from (assume) multiple contract ContractAs which has key (party, text) and club into one dataset and then pass it to ContractB.

I have just depicted below what i am seeking for

...
controller CollabResult : ContractId MasterData
  with 
      parties : [Party]
  do 
      -- I NEED TO CREATE AN OBJECT OUTSIDE THE forA SO THAT I CAN KEEP ALL THE DATA OBTAINED FROM EACH LOOP AND THEN FINALLY SUBMIT AT ONCE - SOMETHING LIKE THIS
      let data1 = "", data2 = ""
          forA parties (\party -> 
              do
                  (CId, someData) <- lookupByKey @ContractAs (party, keyData)
                      if someData.party == party
                          then do
                              -- 1ST LOOP I'LL ASSIGN LOCAL VALUE data1
                              data1 = someData.data1
                           else do data2 = someData.data2
                ) -- END OF FOR LOOP
                create ContractB with data1, data2
...

This gives some indentation issue, but I doubt that the error is due to problem in syntax.

If possible can someone provide a working code to achieve this? or point out to a sample where the same is solved?

Every sample i see has these

  1. create a contract in each loop of forA
  2. use “do” and not just assign data inside condition statement.

Hi @Hem-M,

regarding usage of bindings I’ll refer to this answers of the question here & here on the forum, it explains well, what is different to them compared to variables you have in dynamic typed languages like Javascript. Besides, the question referred is very similar to yours.

However I’ll give here a try to explain a possible solution too.

So most important, DAML is based on Haskell. A mostly pure functional programming language. This means in practice that there don’t exist variables. There are only bindings. They are not mutable, otherwise they would have been called variables (you see the slight difference in naming here? It’s important to distinguish). However you can re-define a binding like this:

let foo = "hamspam"
[...]
let foo = "spam"

This may look like mutating foo but it’s really is just defining foo again with a different bound value.
So if you do something like this here:

let c = 2
let add a b = do
      let c = a
      b + c
add 2 3

the binding c which has been defined before the function add won’t magically be bound to a new value everytime you call add.

Now back to your code.

So I get the idea that you try to conditionally want to bind a value to data1 & data2 depending on what is in the parties list. However your current code (assuming one could mutate) has some flaws. Your call in the end with create ContractB with data1, data2 could result in usage of variables which have an empty string as a value, simply because it’s not guranteed that the value’s you’re effectively matching for aren’t in the list. However if this is your intention and assuming that you want that the last matching element in the list is used for data1 & the last non matching element is used for data2 then the adapted code probably looks like this:

data Either a b = Left a | Right b

controller CollabResult : ContractId MasterData
  with 
      parties : [Party]
  do 
    results <- forA parties \party -> do
                  (_, someData) <- lookupByKey @ContractAs (party, keyData)
                  if someData.party == party then Left someData.data1
                  else Right someData.data2
    let reversedResults = reverse results
    let optData1 = findOptional (\it ->
                      case it of
                        Left data1 -> Some data1
                        Right _ -> None) reversedResults
    let optData2 = findOptional (\it ->
                      case it of
                        Left _-> None
                        Right data2 -> Some data2) reversedResults
    let data1 = fromOptional "" optData1
    let data2 = fromOptional "" optData2
    create ContractB with data1, data2

Important to note here is that in comparison to your code, I respected the properties of how forA actually works.
forA is a function which returns not void (in daml called unit) but something of Update [A], which means a list of A's wrapped in an Update. To actually be able to do anything with this return type, you need to be within a contract declaration (which we currently are) and use YOURFANCYNAME <- such that the operation bind is executed on this value with it’s result being bound to YOURFANCYNAME.
The type of YOURFANCYNAME will then be of [A], so the unwrapped list which you now can operate on.

Besides, the code itself now maps your list of parties to a list of Either’s.
Now we want from that result the last left and last right as these are equivalent to the values which would have been assigned to data1 & data2 in your pseudo code. For searching these in the list we use findOptional, which returns the first matching entry going from the beginning to the end. Now, because we want to find the first matching entry from the end to the beginning, we need to reverse the list first, then we can do the search.

However, because it’s possible that the list never contains either an left xor right, the result of findOptional will always be something of Optional A (which means, there might be a value of A or not). To not fail here and continue, we can make use of the fromOptional function which takes first a default value and then the optional which should be checked for it’s value. If it does contain a value, this value is returned, if not your provided default value (in this case an empty string) is returned.

Now you’ve extracted data1 & data2 to use it for your call to create (which btw also returns something of Update A, however because it is the last statement in the do block it is fine to return as do blocks in contract definitions expect something of type Update A to be returned).

Finally, this logic seems to be a bit akward to me. I don’t recommend this code without knowing what exactly your intentions are here. I’m pretty sure that there is a different way to solve your idea :slight_smile:

3 Likes

Could you describe in pseudo-code what you are trying to do? Or how you’d write it in another language of your choice?

1 Like

In a functonal language, the usual solution to “I need to iterate over a list and modify some state at each step” is to use a fold. This is very well explained in the daml intro.

The way you use foldl or foldlA (the latter being a version of foldl that allows for effects like fetchByKey) is by specifying the initial state (a pair of empty strings ("", "")), a function that given the previous state and an element will produce the new state and the list of elements you want to iterate over (here your list of parties).

Putting things together, you can implement your choice as follows:

    choice CollabResult : ContractId ContractB
      with
        parties : [Party]
      controller p
      do let process (data1, data2) party = do
               (cid, someData) <- fetchByKey @ContractA (p, keyData)
               pure if someData.p == party
                 then (someData.data1, data2)
                 else (data1, someData.data2)
         (data1, data2) <- foldlA process ("", "") parties
         create ContractB with data1, data2, p

There are a few points worth pointing out compared to your original example:

  1. We use fetchByKey instead of lookupByKey which will fail if the key does not exist. You can use lookupByKey as well in combination with fetch but your original example expects something of the type that fetchByKey provides.
  2. The return type of your choice is ContractId MasterData but you return ContractId B.
  3. You don’t pass any parties to ContractB. You haven’t shown us the definition but you will have a hard time defining the required signatory of this template that way.

You can find a full standalone example that compiles at FoldOverParties.daml · GitHub.

3 Likes

Well you got me here, that implementation is much better than mine :smiley: Completely forgot about using fold here due to the effect which is executed (and now noticed there is foldA).

1 Like

Thanks a lot for asking this question
Meanwhile I’m trying out other solution posted as the solution ,I am looking for code that goes like this in JavaScript

let contractAResultingData = [{ party:"party1",a1_data1 : "someData1"},{ party:"party2",a2_data1 : "someData1"}]  -- CONTRACT CREATED BY "party1" and "party2"
let contractA1Data;
let contractA2Data; 
let masterData; // NEW CONTRACT TO BE CREATED FROM CURATED DATASET
let parties=["party1", "party2"]  -- parties : [Party]

console.log("Master Record before exercise : "+JSON.stringify(masterData));
getDataBasedOnParty = (_parties) =>{
  let tempObj = {data1: "",data2: "" };
  parties.map( party => {  -- forA
    if(party == "party1"){
      --fetch object related to party1 from contractAResultingData : lookupByKey @ContractAs (party, keyData)
      contractAResultingData.filter(contractA1Data => {
        if(contractA1Data.party === party){
           -- data1 = someData.data1
           tempObj.data1 = contractA1Data["a1_data1"]
        console.log("Master Record after adding party1 dataset : "+JSON.stringify(masterData));
        }else { 
          //do something else
        }
      })
      
    }else{
      contractAResultingData.filter(contractA2Data => {
        if(contractA2Data.party === party){
           -- data2 = someData.data2
           tempObj.data2 = contractA2Data["a2_data1"]
            console.log("Master Record after adding party2 dataset : "+JSON.stringify(masterData));
        }else { 
          //do something else
        }
      })
    }
  })
  return tempObj;
}

var _tempObj = getDataBasedOnParty(parties);
if(_tempObj != {}){
  masterData = _tempObj
  console.log("Master Record - FINAL: "+JSON.stringify(masterData));
}

STRINGIFIED Output:
Master Record before exercise : {“data1”:"",“data2”:""}
Master Record after adding party1 dataset : {“data1”:“someData1”,“data2”:""}
Master Record after adding party2 dataset : {“data1”:“someData1”,“data2”:“someData1”}
Master Record - FINAL: {“data1”:“someData1”,“data2”:“someData1”}

This could have coded in a very few lines. I have written it elaborately to meet the way I am thinking my DAML logic must look like based on contracts creation perspective.

1 Like

It’s still not 100% clear what you are trying to do as your code seems to hard-code a binary choice between “party1” and other parties, but you operate on lists.

I suspect @cocreature 's answer is the way, but with a Map as your result type.

1 Like

Let me explain other way or two

Party1

creates ContractA (signatory party1, observer mainParty) with key (party1,keyData)

Party2

creates ContractA (signatory party2, observer mainParty) with key (party2,keyData)

CollabResult – MainParty

1)Iterates over other parties available, in this case party1 and party2.
2) fetch the contracts (ContractA) created by party using key like ( fetchByKey @ContractA (party1, keyData) and same for party2). Instead of seperately fetching ContractA for each party I want to use the above 1) Iterate.
2)Once I get the respective contracts I will retrieve subset of data from fetched ContractA
3)Then store it in variable ( @victor.pr.mueller in practice that there don’t exist variables . There are only bindings)( within CollabResult )
4) Once all( in this case it’s just 2 ) the subset is stored in local variable call next contract (ContractB) with these dataset

OR

more simply put
Action1:

I create 2 individual ContractA, one with party1 as signatory and another with party2 as signator and both instances of ContractA are shared with mainParty

Action2:

Acting as mainParty I iterate through all provided/available parties (party1 & party2) , fetch the ContractA created by respective parties based on key (party1/ party2, keyData), retrieve subset of data from each ContractA and then pass this subset data to ContractB(signatory mainParty) to create a new contract with these retrieved subsets.

1 Like

Ok, I can work with that. Here is some code that I believe does what you’re asking for here:

module Main where

import Daml.Script

template ContractA
  with
    sig: Party
    id: Text
    argument: Text
    obs: Party
  where
    signatory sig
    observer obs
    key (sig, id) : (Party, Text)
    maintainer key._1

template ContractB
  with
    sig: Party
    collabData: [(Party, Text)]
  where
    signatory sig


setup : Script ()
setup = script do
  party1 <- allocatePartyWithHint "party1" (PartyIdHint "party2")
  party2 <- allocatePartyWithHint "party2" (PartyIdHint "party1")
  mainParty <- allocatePartyWithHint "mainParty" (PartyIdHint "mainParty")

  -- party1 creates ContractA (signatory party1, observer mainParty) with key (party1,keyData)
  submit party1 do
    createCmd ContractA with sig = party1, obs = mainParty, id = "keyData", argument = "some data"

  -- party2 creates ContractA (signatory party2, observer mainParty) with key (party2,keyData)
  submit party2 do
    createCmd ContractA with sig = party2, obs = mainParty, id = "keyData", argument = "some other data"

  -- mainParty
  submit mainParty do
    createAndExerciseCmd (DoCollection with sig = mainParty) CreateContractB with parties = [party1, party2], id = "keyData"
  return ()

template DoCollection
  with
    sig: Party
  where
    signatory sig
    preconsuming choice CreateContractB : ContractId ContractB
      with parties: [Party]
           id: Text
      controller sig
      do
        -- 1) iterate over given parties (here [party1,party2]), and
        -- 3)Then store it in variable results
        results <- forA parties (\party -> do
          -- 2) fetch the contract; note: there can only be (at most) one because
          --    keys enforce uniqueness
          (_, payload) <- fetchByKey @ContractA (party, id)
          -- If there is no contract with this key, or mainParty is not an observer,
          -- we crash here.

          -- 2) Once I get the respective contracts I will retrieve subset of data
          --    from fetched ContractA
          return (party, payload.argument))
        -- 4) Once all( in this case it’s just 2 ) the subset is stored in local
        --    variable call next contract (ContractB) with these dataset
        create ContractB with collabData = results, ..
2 Likes

Thanks a ton for the effort and time
This solution is very close, but i’m looking for something like below

module FoldOverParties where

import DA.Action

-- created by **parties** "p"
template ContractA
  with
    p : Party
    keyData : Text
    data1 : Text
    data2 : Text
  where
    signatory p
    key (p, keyData) : (Party, Text)
    maintainer key._1


-- created and exercised by **q**
template T
  with
    q : Party
    keyData : Text
  where
    signatory q
    choice CollabResult : ContractId ContractB
      with
        parties : [Party]   -- I'll have to fetch "p" from this array
      controller q
      do let data1 = ""; data2 = ""
         let process (data1, data2) party = do
                -- and pass it in below fetchByKey 
                -- Issue comes at below line as it handles only one party wherein I have an array of parties - "p"s
               (cid, someData) <- fetchByKey @ContractA (p, keyData) -- "p" is in an array
               pure if someData.p == party
                 then (someData.data1, data2)
                 else (data1, someData.data2)
         (data1, data2) <- foldlA process ("", "") parties
         create ContractB with data1, data2, q

-- creator is q
template ContractB
  with
    q : Party
    data1 : Text
    data2 : Text
  where
    signatory q
1 Like

Ah sorry that was a typo in my example, it should be

(cid, someData) <- fetchByKey @ContractA (party, keyData)

so party instead of p.

1 Like

Yaay…! :fireworks: that works, with some modifications according to my use case.
Thanks \m/
Modification:

do
     results <- forA parties (\party -> do
          (_, payload) <- fetchByKey @DVSecurityRecord (party, id)
          -- ToDo:
          -- If there is no contract with this key, or mainParty is not an observer, handle it here
          if payload.party == party
            then return (party, payload.argument1)
            else return (party, payload.argument2)
            )

now the result i get is [party1data, party2data]
Just one last follow up question

collabData: [(Party, Text)]

How to fetch data from this?
I need to create a key in

template ContractB
  with
    sig: Party
    collabData: [(Party, Text)]
  where
    signatory sig
    key(sig, ???) : (Party, Text)
3 Likes

First off, a note of warning:

-- If there is no contract with this key, or mainParty is not an observer, handle it here

You cannot handle it here, because the fetchByKey call will abort your entire transaction before you get to that line. If you want to be able to handle key lookup errors, you need to use lookupByKey (or visibleByKey).

That really depends on what you want to do. You have a list, in this case something that looks like (schematically; Party is a more complex type):

[(Party "party1", "some data"), (Party "party2", "some other data")]

How you turn that into a single Text is really up to you. One option could be to just change your key to include a list:

template ContractB
  with
    sig: Party
    collabData: [(Party, Text)]
  where
    signatory sig
    key (sig, map snd collabData) : (Party, [Text])
    maintainer key._1

The result of this would be a key that looks like:

(Party "mainParty", ["some data", "some other data"])

because map f ls, where f is a function and ls is a list, returns a new list by applying f to each element individually, and snd is a function that takes a tuple and returns its second element.

If you really need it to be a single Text field, you could concatenate the texts, with something like:

template ContractB
  with
    sig: Party
    collabData: [(Party, Text)]
  where
    signatory sig
    key (sig, foldl (<>) "" (map snd collabData)) : (Party, Text)
    maintainer key._1

which would result in a key that looks like:

(Party "mainParty", "some datasome other data")

You could do more complex transformations, but I think at that point you’re better off extracting the transformation to a separate function.

3 Likes

Much appreciated. Thanks for these gems

4 Likes