4 Transforming data using choices

In the example in Contract keys the accountant party wanted to change some data on a contract. They did so by archiving the contract and re-creating it with the updated data. That works because the accountant is the sole signatory on the Account contract defined there.

But what if the accountant wanted to allow the bank to change their own telephone number? Or what if the owner of a CashBalance should be able to transfer ownership to someone else?

In this section you will learn about how to define simple data transformations using choices and how to delegate the right to exercise these choices to other parties.

Choices as methods

If you think of templates as classes and contracts as objects, where are the methods?

Take as an example a Contact contract on which the contact owner wants to be able to change the telephone number, just like on the Account in Contract keys. Rather than requiring them to manually look up the contract, archive the old one and create a new one, you can provide them a convenience method on Contact:

template Contact
    owner : Party
    party : Party
    address : Text
    telephone : Text
    signatory owner

    controller owner can
        : ContractId Contact
          newTelephone : Text
          create this with
            telephone = newTelephone

The above defines a choice called UpdateTelephone. Choices are part of a contract template. They’re permissioned functions that result in an Update. Using choices, authority can be passed around, allowing the construction of complex transactions.

Let’s unpack the code snippet above:

  • The first line, controller owner can says that the following choices are controlled by owner, meaning owner is the only party that is allowed to exercise them. The line starts a new block in which multiple choices can be defined.

  • UpdateTelephone is the name of a choice. It starts a new block in which that choice is defined.

  • : ContractId Contact is the return type of the choice.

    This particular choice archives the current Contact, and creates a new one. What it returns is a reference to the new contract, in the form of a ContractId Contact

  • The following with block is that of a record. Just like with templates, in the background, a new record type is declared: data UpdateTelephone = UpdateTelephone with

  • The do starts a block defining the action the choice should perform when exercised. In this case a new Contact is created.

  • The new Contact is created using this with. this is a special value available within the where block of templates and takes the value of the current contract’s arguments.

There is nothing here explicitly saying that the current Contact should be archived. That’s because choices are consuming by default. That means when the above choice is exercised on a contract, that contract is archived.

If you paid a lot of attention in 3 Data types, you may have noticed that the create statement returns an Update (ContractId Contact), not a ContractId Contact. As a do block always returns the value of the last statement within it, the whole do block returns an Update, but the return type on the choice is just a ContractId Contact. This is a conveneience. Choices always return an Update so for readability it’s omitted on the type declaration of a choice.

Now to exercise the new choice in a scenario:

choice_test = scenario do
  owner <- getParty "Alice"
  party <- getParty "Bob"

  contactCid <- submit owner do
     create Contact with
      address = "1 Bobstreet"
      telephone = "012 345 6789"

  -- The bank can't change its own telephone number as the accountant controls
  -- that choice.
  submitMustFail party do
    exercise contactCid UpdateTelephone with
      newTelephone = "098 7654 321"

  new_contact_Cid <- submit owner do
    exercise contactCid UpdateTelephone with
      newTelephone = "098 7654 321"

  submit owner do
    new_contact <- fetch new_contact_Cid
    assert (new_contact.telephone == "098 7654 321")

You exercise choices using the exercise function, which takes a ContractId a, and a value of type c, where c is a choice on template a. Since c is just a record, you can also just fill in the choice parameters using the with syntax you are already familiar with.

exercise returns an Update r where r is the return type specified on the choice, allowing the new ContractId Contact to be stored in the variable new_contactCid.

Choices as delegation

Up to this point all the contracts only involved one party. party may have been stored as Party field in the above, which suggests they are actors on the ledger, but they couldn’t see the contracts, nor change them in any way. It would be reasonable for the party for which a Contact is stored to be able to update their own address and telephone number. In other words, the owner of a Contact should be able to delegate the right to perform a certain kind of data transformation to party.

The below demonstrates this using an UpdateAddress choice and corresponding extension of the scenario:

    controller party can
        : ContractId Contact
          newAddress : Text
          create this with
            address = newAddress
  new_contact_Cid <- submit party do
    exercise new_contact_Cid UpdateAddress with
      newAddress = "1-10 Bobstreet"

  submit owner do
    new_contact <- fetch new_contact_Cid
    assert (new_contact.address == "1-10 Bobstreet")

If you open the scenario view in the IDE, you will notice that Bob sees the Contact. Controllers specified via controller c can syntax become observers of the contract. More on observers later, but in short, they get to see any changes to the contract.

Choices in the Ledger Model

In 1 Basic contracts you learned about the high-level structure of a DAML ledger. With choices and the exercise function, you have the next important ingredient to understand the structure of the ledger and transactions.

A transaction is a list of actions, and there are just three kinds of action: create, exercise and fetch. All actions are performed on a contract.

  • A create action contains the contract arguments and changes the contract’s status from non-existent to active.
  • A fetch action checks the existence and activeness of a contract.
  • An exercise action contains the choice arguments and a transaction called the consequences. Exercises come in two kinds called consuming and nonconsuming. consuming is the default kind and changes the contract’s status from active to archived.

The consequences of exercise nodes turn each transaction into an ordered tree of (sub-) transactions, or, equivalently, a forest of actions. Actions are in one-to-one correspondence with proper sub-transactions. You can see the action and their consequences in the transaction view of the above scenario:

  TX #0 1970-01-01T00:00:00Z (Contact:43:17)
  │   consumed by: #2:0
  │   referenced by #2:0
  │   known to (since): 'Alice' (#0), 'Bob' (#0)
  └─> create Contact:Contact
        owner = 'Alice'; party = 'Bob'; address = "1 Bobstreet"; telephone = "012 345 6789"

  TX #1 1970-01-01T00:00:00Z
    mustFailAt 'Bob' (Contact:52:3)

  TX #2 1970-01-01T00:00:00Z (Contact:56:22)
  │   known to (since): 'Alice' (#2), 'Bob' (#2)
  └─> 'Alice' exercises UpdateTelephone on #0:0 (Contact:Contact)
                newTelephone = "098 7654 321"
      │   consumed by: #4:0
      │   referenced by #3:0, #4:0
      │   known to (since): 'Alice' (#2), 'Bob' (#2)
      └─> create Contact:Contact
            owner = 'Alice'; party = 'Bob'; address = "1 Bobstreet"; telephone = "098 7654 321"

  TX #3 1970-01-01T00:00:00Z (Contact:60:3)
  └─> fetch #2:1 (Contact:Contact)

  TX #4 1970-01-01T00:00:00Z (Contact:66:22)
  │   known to (since): 'Alice' (#4), 'Bob' (#4)
  └─> 'Bob' exercises UpdateAddress on #2:1 (Contact:Contact)
              newAddress = "1-10 Bobstreet"
      │   referenced by #5:0
      │   known to (since): 'Alice' (#4), 'Bob' (#4)
      └─> create Contact:Contact
            owner = 'Alice';
            party = 'Bob';
            address = "1-10 Bobstreet";
            telephone = "098 7654 321"

  TX #5 1970-01-01T00:00:00Z (Contact:70:3)
  └─> fetch #4:1 (Contact:Contact)

Active contracts:  #4:1

Return value: {}

There are four commits corresponding to the four submit statements in the scenario. Within each commit, we see that it’s actually actions that have IDs of the form #commit_number:action_number. Contract IDs are just the ID of their create action.

So commits #2 and #4 contain exercise actions with ids #2:0 and #4:0. The create actions of the updated, Contact contracts, #2:1 and #4:1, are indented and found below a line reading children:, making the tree structure apparent.

The Archive choice

You may have noticed that there is no archive action. That’s because archive cid is just shorthand for exercise cid Archive, where Archive is a choice implicitly added to every template, with the signatories as controllers.

A simple cash model

With the power of choices, you can build your first interesting model: issuance of cash IOUs (I owe you). The model presented here is simpler than the one in 3 Data types as it’s not concerned with the location of the physical cash, but merely with liabilities:

-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

daml 1.2
module SimpleIou where

data Cash = Cash with
  currency : Text
  amount : Decimal
    deriving (Eq, Show)

template SimpleIou
    issuer : Party
    owner : Party
    cash : Cash
    signatory issuer

    controller owner can
        : ContractId SimpleIou
          newOwner : Party
          create this with owner = newOwner

test_iou = scenario do
  alice <- getParty "Alice"
  bob <- getParty "Bob"
  charlie <- getParty "Charlie"
  dora <- getParty "Dora"

  -- The bank issues an Iou for $100 to alice
  iou <- submit dora do
    create SimpleIou with
      issuer = dora
      owner = alice
      cash = Cash with
        amount = 100.0
        currency = "USD"

  -- Alice transfers it to Bob
  iou2 <- submit alice do
    exercise iou Transfer with
      newOwner = bob

  -- Bob transfers it to Charlie
  submit bob do
    exercise iou2 Transfer with
      newOwner = charlie

The above model is fine as long as everyone trusts Dora. Dora could revoke the SimpleIou at any point by archiving it. However, the provenance of all transactions would be on the ledger so the owner could prove that Dora was dishonest and cancelled her debt.

Next up

You can now store and transform data on the ledger, even giving other parties specific write access through choices.

In 5 Adding constraints to a contract, you will learn how to restrict data and transformations further. In that context, you will also learn about time on DAML ledgers, do blocks and <- notation within those.