Multiple, individually unique keys on a contract

Hello,

We have a need for a template that satisfies the following constraints:

  • There should be at most one Child contract per parent contract
  • Which Child contract has multiples values that should be unique across all Child contracts

We’re currently working with something like the following

template Child
  with
    uniqueValueA:   Text
    uniqueValueB:   Text
    parentKey:      Text
    agent :         Party
  where
    signatory agent
    key (agent, parentKey) : (Party, Text)
    maintainer key._1

As you can see, the first constraint is satified by key but we’re stumped on how to implement the second.

Does anyone have any guidance on how we can implement a solution for this?

I see a couple of possible approaches.

  1. Delegate the uniqueness check to the client application.
    In your client application before submitting a command to create a Child contract you could query the ledger for all Child contracts visible to the agent party and check the values of uniqueValueA and uniqueValueB fields in the new contract being created against the values of the same fields in the contracts already on the ledger. This does not make it impossible to create Child contracts on the ledger with non-unique values of uniqueValueA and uniqueValueB fields. E.g. if you have two instances of the client application using credentials that allow them to act as the agent party, those instances could simultaneously submit the command to create a Child contract with the same value for uniqueValueA field. Both of these commands would succeed. You could possibly address this scenario by implementing ODS and checking the uniqueness of the fields values against it rather than against the ledger. But in general this approach is only viable if you can ensure that no client app that can act as the agent party is creating contracts with non unique values.
  2. If you’re looking for the ledger to guarantee the uniqueness of the values in these fields in the Child contracts, I don’t think you can do this with a single signatory contract. You would need another template to store the list of values of uniqueValueA and uniqueValueB fields in Child contracts on the ledger, with a choice to create a new Child contract, where you can implement the uniqueness check for the fields values. And you would need another party as a signatory on the Child template to ensure that this contract can only be created by exercising a choice on the other template. Here’s a quick example. Since the Child template has two signatories, the authority of both agent and ombudsmen parties is required to create or archive the Child contract. Therefore the agent party can only create the Child contract by exercising the Add_Child choice on the Kindergarten contract, and in this choice we can perform the uniqueness check for uniqueValueA and uniqueValueB fields.
template Child
  with
    uniqueValueA:   Text
    uniqueValueB:   Text
    parentKey:      Text
    agent :         Party
    ombudsman :     Party
  where
    signatory agent, ombudsman
    key (agent, parentKey) : (Party, Text)
    maintainer key._1

    ensure agent /= ombudsman

template Kindergarten
  with
    principal : Party
    agent : Party
    uniqueValASet : Set Text
    uniqueValBSet : Set Text
  where
    signatory principal
    observer agent
    key (principal, agent) : (Party, Party)
    maintainer key._1

    choice Add_Child : ContractId Child
      with
        uniqueValueA : Text
        uniqueValueB : Text
        parentKey : Text
      controller agent
      do
        assertMsg "The value of uniqueValueA is not unique" $
          Set.notMember uniqueValueA uniqueValASet
        assertMsg "The value of uniqueValueB is not unique" $
          Set.notMember uniqueValueB uniqueValBSet
        create this with
          uniqueValASet = Set.insert uniqueValueA uniqueValASet
          uniqueValBSet = Set.insert uniqueValueB uniqueValBSet        
        create Child with
          ombudsman = principal
          ..

    choice Remove_Child : ()
      with
        childCid : ContractId Child
      controller agent
      do
        child <- fetch childCid
        create this with
          uniqueValASet = Set.delete child.uniqueValueA uniqueValASet
          uniqueValBSet = Set.delete child.uniqueValueB uniqueValBSet
        archive childCid
1 Like

That’s very helpful! Thank you very much.