How to write daml smart contract like go chaincode

Hi Team,

Like in fabric go chaincode, we have primary key and composite key concept. Like that how can we write daml smart contract? Can anyone provide some example? For example: A structure contains id, name, address, organization, emailid. I want to query using id as primary key and organization as composite key. How to implement this in daml?

Daml doesn’t provide capability to query the ledger. In Daml you can fetch a contract using either its ContractId or using the contract key. The latter is somewhat similar to the Fabric’s composite key in the sense that the key can be composed of multiple values, and it allows fetching the contract with the given value of the key. But there’s nothing in Daml that allows querying the ledger using a range of values like the primary key does in Fabric.
Querying a Canton ledger is only possible from the ledger client side. A Canton Participant Node includes a component named Participant Query Store (PQS), which serves as operational datastore (ODS) and allows querying the ledger using SQL over a JDBC connection. To be able to query the Canton ledger you don’t need to do anything special at the smart contract (Daml template) level. No matter how you design your Daml templates, you can always query the ledger for say all active contracts produced from this set of templates with these values in these fields, and a lot more.
For details see the PQS documentation.
If you’d like a quick look at what some sample queries look like click here.

1 Like

Hi Team,

Thanks for the clarification. I have gone through many examples. From all the examples, what I have understood was, using choice updating the existing record. From where the initial data are saved? How to call the function? For example: there is a contract which contains user details like userid, name, address, organization, country. I want to store many users’ data and I want to fetch particular user data. How can I do this in daml? I tried to write the contract. But in the fetch it is throwing an error. Can you please help me to fix this. If I deploy this in the canton network, using the canton console, how to call this SaveUser and GetUserByUserID function?

module UserRegistry where
import Daml.Script
template UserRegistry
with
userID : Party
userName : Text
password : Text
orgName : Text
emailID : Text
country : Text
where
signatory userID
key userID : Party
maintainer userID

choice SaveUser : ContractId UserRegistry
controller userID
do
create this with userID; userName; password; orgName; emailID; country

choice GetUsersByUserID : ContractId UserRegistry
with requestedUserID: Party
controller userID
do
fetch (userID, requestedUserID)

–usertest: Script()
usertest = script do
userID ← allocateParty “user1234”

submit userID do
createCmd $ UserRegistry with
userID
userName = “sammy”
password = “hfjadhfka”
orgName = “intain”
emailID = “sammy@gmail.com”
country = “canada”

If what you’re looking to do is to allow parties to store some data about themselves on the ledger, you don’t need any choices. I’m including a simple example below. The test script demonstrates how a party can create a contract containing the data, how it can update the data within this contract and how it can retrieve the contract from the ledger using contract key.
If your workflow is more sophisticated than the above, e.g. if you’re looking to create some registry that contains info about some items, then instead of taking database centric approach of thinking about what data you need to store and the relationship between data pieces, in Daml you need to consider who the actors are in your application, what they need to be able to do, what data they need to access to be able to do what they need to do. I strongly suggest you take the courses from the Daml Philosophy training before designing any applications in Daml. The Daml Workflows course in this training teaches best practices for designing workflows for Daml applications. Daml applications have quite specific design considerations, and failing to account for them upfront creates a very high risk of ending up with the design that’s not fit for purpose. For instance in the example below I cannot think of any good reason why this data should be on the ledger in the first place. Each party has full sovereignty over its PartyInfo data (by way of being a single signatory on the contract), there’s no shared state that needs to be agreed between multiple parties. In this case you’re much better off storing this data off the ledger - in a regular database for example.

module UserRegistry where
import DA.Optional
import Daml.Script

template PartyInfo
    with
        owner : Party
        name : Text
        password : Text
        orgName : Text
        email : Text
        country : Text
    where
        signatory owner
        key owner : Party
        maintainer key

usertest : Script ()
usertest = script do
    userParty <- allocateParty "user1234"
    --party creates PartyInfo contract
    submit userParty do 
        createCmd PartyInfo with
            owner = userParty
            name = "sammy"
            password = "hfjadhfka"
            orgName = "intain"
            email = "sammy@gmail.com"
            country = "canada"

    --party updates country field in PartyInfo contract by archiving
    --the existing contract and creating a new one in its stead
    submit userParty do
        exerciseByKeyCmd @PartyInfo userParty Archive
    submit userParty do 
        createCmd PartyInfo with
            owner = userParty
            name = "sammy"
            password = "hfjadhfka"
            orgName = "intain"
            email = "sammy@gmail.com"
            country = "mexico"

    --query the ledger for PartyInfo contract using its contract key
    --and print the value of the orgName field from the contract payload
    (piCid, pi) <- fromSome <$> queryContractKey @PartyInfo userParty userParty
    debug pi.orgName

Hi, Thanks for reply. Now I am clear. I will tell you the scenario,

  1. can you please suggest how to write daml smart contract. Consider 2 organization A and B. A and B has n no of users. Org A creating a report every month. Some of org B users has the permission to view that report data. I am going to write a daml smart contract Report.daml which contains reportid, month, year, entiredata, orgBusers. Every month orgA user creates this report. So i want savereport function. Some of orgB users wants to view that report. During save report, I am going to pass which orgB user has rights to view. I want to query a report data from the ledger for that particular orgB user.

  2. In the previous conversation - userregistry code, the create and query functionality are under script. Is this script used for testing right ( off ledger) ? If I am wrong please correct.

  3. I am going to build this userregistry and deploy the dar in the canton participant node. Then how can i perform create user and query that user detail?

This workflow doesn’t bode well for on-ledger implementation. Just like in the example I provided earlier, OrgA has sole sovereignty of all the data and there’s no shared state that needs to be agreed between multiple parties. You’d be much better off implementing this workflow off-ledger.
But if you’d still like to see an implementation in Daml, I include an example below. For this workflow you’d need to model each user from orgB as a separate party, which is an antipattern in Daml. In Daml applications the list of parties should be fairly stable. Parties normally represent role or organizational entities. This is all very well explained in the Daml Philosophy training, which I highly recommend.
The below example contains only one Daml template and no choices. It includes the test scripts containing the setup script that allocates the parties and creates some Report contracts as well as 4 unit tests for various aspects of querying the reports on the ledger.

You’re correct in thinking that Daml Script emulates ledger client and that it is primarily used for testing.
Before you build the solution you’d separate Daml templates from the test scripts and compile them into two or more separate dar files. In the example below I demonstrate this (to a degree) by separating Daml templates and test scripts into two different modules. The dar files with the templates are deployed to the Canton Participant Nodes. The dar files with the test scripts are usually not. Test scripts can be run against simulated ledger, against ledger running on Daml Sandbox or against deployed ledger. See Daml Script documentation and help for daml script command for details.

module ReportRegistry where

data ReportKey = ReportKey with
    owner : Party
    reportID : Text
        deriving (Eq, Show)

template Report
    with
        reportKey : ReportKey
        reportData : Text
        visibleTo : [Party]
    where
        signatory reportKey.owner
        observer visibleTo
        key reportKey : ReportKey
        maintainer key.owner
module ReportRegistryTest where
import ReportRegistry
import DA.Optional
import Daml.Script

data TestSetup = TestSetup with
    rk1 : ReportKey
    rk2 : ReportKey
    user1 : Party
    user2 : Party

setup = script do
    orgA <- allocateParty "orgA"
    user1 <- allocateParty "orgBUser1"
    user2 <- allocateParty "orgBUser2"

    let rk1 = ReportKey with
                owner = orgA
                reportID = "0001"
        rk2 = ReportKey with
                owner = orgA
                reportID = "0002"
        rk3 = ReportKey with
                owner = orgA
                reportID = "0003"

    --orgA party creates three Report contracts
    submit orgA do 
        createCmd Report with
            reportKey = rk1
            reportData = "This is a report for User1"
            visibleTo = [user1]

    submit orgA do 
        createCmd Report with
            reportKey = rk2
            reportData = "This is a report for User2"
            visibleTo = [user2]

    submit orgA do 
        createCmd Report with
            reportKey = rk3
            reportData = "This is another report for User2"
            visibleTo = [user2]

    pure TestSetup with rk1, rk2, user1, user2

testReportRetrievalByKeyForUser1 = script do
    TestSetup{..} <- setup
    --user1 retrieves Report contract using its contract key
    (_, rep1) <- fromSome <$> queryContractKey @Report user1 rk1
    assert $ rep1.reportData == "This is a report for User1"

testReportRetrievalByKeyForUser2 = script do
    TestSetup{..} <- setup
    --user1 retrieves Report contract using its contract key
    (_, rep2) <- fromSome <$> queryContractKey @Report user2 rk2
    assert $ rep2.reportData == "This is a report for User2"

testLedgerQueryForReports = script do
    TestSetup{..} <- setup
    --user2 queries the ledger for reports visible to user2
    --that have reportID falling in the range
    reps <- queryFilter @Report user2 
        (\x -> x.reportKey.reportID `elem` ["0002","0003"])
    assert $ length reps == 2

testReportPrivacy = script do
    TestSetup{..} <- setup
    --user2 cannot retrieve Report contract only visible to user1
    noRep <- queryContractKey @Report user2 rk1
    assert $ isNone noRep