Should runtime/API respect namespace scopes?

Create a new skeleton project

daml new foo

And then change the module definition in Main.daml to only explicitly export the following:

module Main (Asset, AssetId) where

So far, so good. You should be able to do daml start or daml test and everything should be peachy.

Note that by the above change, we’re no longer exporting Asset(..) constructor, or Give choice.

Now, if we move the included test script from Main.daml, into say a new module Test.daml, you’ll find that the code no longer compiles:

Test.daml|13 col 15| typecheck:Error:/tmp/foo/daml/Test.daml:13:15: error: Not in scope: data constructor ‘Asset’
Test.daml|19 col 25| typecheck:Error:/tmp/foo/daml/Test.daml:19:25: error: Not in scope: data constructor ‘Give’
Test.daml|22 col 23| typecheck:Error:/tmp/foo/daml/Test.daml:22:23: error: Not in scope: data constructor ‘Give’

I would say this is expected.

However, if I run daml start, I can open the navigator, and I can still create Assets and Give them!

Isn’t this a bug? I would further argue that, if the API/runtime respected the module scoping, it would give us a very useful feature: the ability to restrict the creation of assets/tokens in this example; you could still provide an initial mint via a script within the module, but it would prevent e.g. Bob from minting his own tokens.

The Daml language seems to respect the scoping, so why doesn’t the API?

2 Likes

Daml-LF does not have a concept of module imports and exports at the moment so given that the API is based on that, it inherits that limitation.

I do agree, that this is a shortcoming but it’s not quite as simple as adding exports to Daml-LF:

Consider your example where the constructor Asset is not exported. Now I define a template

template AssetWrapper
    asset : Asset
  where
    signatory (signatory asset)

You haven’t exposed the constructor so I can’t create this in Daml but what happens if I create it over the Ledger API?

I think a proper solution here needs to be less focused on copying module exports directly like they work in Haskell and more on providing a solution for supporting data abstraction in Daml:

  1. You need to be able to define an abstract type.
  2. You need to be able to define the ledger API representation on that type.
  3. You need to be able to define validation for translating the ledger API representation to the abstract type or fail if it’s not valid.
3 Likes

I don’t quite follow. If “the constructor Asset is not exported”, then your example code would either
a) Not compile, if it was in a different module from Asset, or
b) Compile, and and allow creation inside Daml and also through the API (assuming AssetWrapper is exported)

What am I missing? :confused:

1 Like

My example code only references the type not the value-level constructor. That’s how you usually do data abstraction in Haskell. E.g., Data.Map exports the Map type but not the underlying data constructors.

1 Like

Ah I think I get it now … so you wouldn’t be able to instantiate AssetWrapper.asset needed to create a AssetWrapper?

1 Like

Exactly. You can try it out in a Daml Script if you want. You can’t call createCmd (AssetWrapper …) but you can still create it if you call the JSON or gRPC API directly (e.g. via navigator).

1 Like

The problem with providing such abstract data types at the ledger level is that, given DAML’s privacy guarantees, you can’t enforce them while retaining compositionality.

For example, consider your Asset template, and take the bread-and-butter DAML example of an asset swap. Assume you write another DvD template in the same module that allows you to trade two assets, and Alice and Bob want to use this to trade. So far so good. Now due to sub-transaction privacy, DAML allows you to do the swap such that the issuer of Alice’s asset doesn’t see who the issuer of Bob’s asset is, or what the conditions of the DvD are. Which means that the issuers only get to see the execution of the Give choice on the Asset, but not the swap on the DvD. Which means that the issuer - in a distributed ledger - has no way to ensure that the Alice and Bob actually traded the Asset through a choice on a DvD contract. So an abstract data type would give you a false sense of security.

1 Like

@oggy I’m not entirely sure I agree with you here. Privacy forces us to expose the values of that type but I don’t see how it stops us from enforcing invariants on a type. So I agree that export lists don’t solve the problem but I think data abstraction should still be feasible in some form.

Obviously you can already enforce invariants in DAML with an ensure clause. but you have to pay the price of a runtime check. With ADTs you would normally eschew paying this price, but I don’t see how you can do so in DAML in a distributed setting. More generally, ADTs normally give you history guarantees, in the sense of “how did this value come about”, which is I think the direction Luciano was going with the question. But since in, e.g., a Canton-based ledger, it may be that no-one actually sees the entire history of a value, it’s not possible to ensure the guarantees in general. Sure we can think about restricted settings, like a module where the visibility of all templates is the same, but that doesn’t seem very useful for an asset for example.

I also ran into this same problem. I was thinking you could make a change such that any private data constructors would still be visible to all users of a package. However, when commands are received by the ledger API, a check would be done to validate that none of the data received makes use of private constructors. E.g. I try to issue a create command for Asset in @Luciano’s example.

2 Likes