Skip to content

Commit

Permalink
Add documentation for DAML script and bundle it in the SDK (digital-a…
Browse files Browse the repository at this point in the history
  • Loading branch information
cocreature authored and mergify[bot] committed Nov 19, 2019
1 parent ff03ed5 commit 7deca90
Show file tree
Hide file tree
Showing 14 changed files with 344 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ packagingTests = testGroup "packaging"
withCurrentDirectory projDir $ callCommandQuiet "daml build"
let dar = projDir </> ".daml" </> "dist" </> "copy-trigger-0.0.1.dar"
assertBool "copy-trigger-0.1.0.dar was not created." =<< doesFileExist dar
, testCase "Build DAML script example" $ withTempDir $ \tmpDir -> do
let projDir = tmpDir </> "script-example"
callCommandQuiet $ unwords ["daml", "new", projDir, "script-example"]
withCurrentDirectory projDir $ callCommandQuiet "daml build"
let dar = projDir </> ".daml/dist/script-example-0.0.1.dar"
assertBool "script-example-0.0.1.dar was not created." =<< doesFileExist dar
]
<> [ testCaseSteps "Build migration package" $ \step -> withTempDir $ \tmpDir -> do
-- it's important that we have fresh empty directories here!
Expand Down
4 changes: 4 additions & 0 deletions daml-script/daml/Daml/Script.daml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ data QueryACS a = QueryACS
, continue : [AnyTemplate] -> a
} deriving Functor

-- TODO This should also return contract ids.
query : forall t. Template t => Party -> Script [t]
query p = Script $ Free $ Query (QueryACS p (templateTypeRep @t) (pure . map (fromSome . fromAnyTemplate)))

Expand Down Expand Up @@ -90,6 +91,9 @@ submitMustFail p cmds = Script $ Free (fmap pure $ Submit $ SubmitCmd p (fail <$
newtype Script a = Script (Free ScriptF a)
deriving (Functor, Applicative, Action)

instance CanAbort Script where
abort = error

data LedgerValue = LedgerValue {}

fromLedgerValue : LedgerValue -> a
Expand Down
2 changes: 2 additions & 0 deletions daml-script/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ genrule(
srcs =
glob(["**/*.daml"]) + [
"//daml-script/daml:daml-script.dar",
"//docs:source/daml-script/template-root/src/ScriptExample.daml",
],
outs = ["script-test.dar"],
cmd = """
set -eou pipefail
TMP_DIR=$$(mktemp -d)
mkdir -p $$TMP_DIR/daml
cp -L $(location :daml/ScriptTest.daml) $$TMP_DIR/daml
cp -L $(location //docs:source/daml-script/template-root/src/ScriptExample.daml) $$TMP_DIR/daml
cp -L $(location //daml-script/daml:daml-script.dar) $$TMP_DIR/
cat << EOF > $$TMP_DIR/daml.yaml
sdk-version: 0.0.0
Expand Down
15 changes: 14 additions & 1 deletion docs/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,17 @@ pkg_tar(
visibility = ["//visibility:public"],
)

exports_files(["source/triggers/template-root/src/CopyTrigger.daml"])
pkg_tar(
name = "script-example-template",
srcs = glob(
["source/daml-script/template-root/**"],
exclude = ["**/*~"],
),
strip_prefix = "source/daml-script/template-root",
visibility = ["//visibility:public"],
)

exports_files([
"source/triggers/template-root/src/CopyTrigger.daml",
"source/daml-script/template-root/src/ScriptExample.daml",
])
1 change: 1 addition & 0 deletions docs/configs/pdf/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ Experimental features
daml-integration-kit/index
json-api/index
triggers/index
daml-script/index
tools/visual

Support and updates
Expand Down
191 changes: 191 additions & 0 deletions docs/source/daml-script/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
.. Copyright (c) 2019 The DAML Authors. All rights reserved.
.. SPDX-License-Identifier: Apache-2.0
DAML Script
###########

**WARNING:** DAML Script is an experimental feature that is actively
being designed and is *subject to breaking changes*.
We welcome feedback about DAML script on
`our issue tracker <https://github.com/digital-asset/daml/issues/new?milestone=DAML+Script>`_
or `on Slack <https://hub.daml.com/slack/>`_.

DAML scenarios provide a simple API for experimenting with DAML models
and getting quick feedback in DAML studio. However, scenarios are run
in a special process and do not interact with an actual ledger. This
means that you cannot use scenarios to test other ledger clients,
e.g., your UI or :doc:`DAML triggers </triggers/index>`.

DAML script addresses this problem by providing you with an API with
the simplicity of DAML scenarios and all the benefits such as being
able to reuse your DAML types and logic while running against an
actual ledger. This means that you can use it to test automation
logic, your UI but also for ledger initialization where scenarios
cannot be used (with the exception of :doc:`/tools/sandbox`).

Usage
=====

Our example for this tutorial consists of 2 templates.

First, we have a template called ``Coin``:

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- COIN_TEMPLATE_BEGIN
:end-before: -- COIN_TEMPLATE_END

This template represents a coin issued to ``owner`` by ``issuer``.
``Coin`` has both the ``owner`` and the ``issuer`` as signatories.

Second, we have a template called ``CoinProposal``:

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- COIN_PROPOSAL_TEMPLATE_BEGIN
:end-before: -- COIN_PROPOSAL_TEMPLATE_END

``CoinProposal`` is only signed by the ``issuer`` and it provides a
single ``Accept`` choice which, when exercised by the controller will
create the corresponding ``Coin``.

Having defined the templates, we can now move on to write DAML scripts
that operate on these templates. To get accees to the API used to implement DAML scripts, you need to add the ``daml-script``
library to the ``dependencies`` field in ``daml.yaml``.

.. literalinclude:: ./template-root/daml.yaml.template
:start-after: # script-dependencies-begin
:end-before: # script-dependencies-end

In addition to that you also need to import the ``Daml.Script`` module
and since DAML script provides ``submit`` and ``submitMustFail``
functions that collide with the ones used in scenarios, we need to
hide those. We also enable the ``ApplicativeDo`` extension. We will
see below why this is useful.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- DAML_SCRIPT_HEADER_BEGIN
:end-before: -- DAML_SCRIPT_HEADER_END

Since on an actual ledger parties cannot be arbitrary strings, we
define a record containing all the parties that we will use in our
script so that we can easily swap them out.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- LEDGER_PARTIES_BEGIN
:end-before: -- LEDGER_PARTIES_END

Let us now write a function to initialize the ledger with 3
``CoinProposal``s and accept 2 of them. This function takes the
``LedgerParties`` as an argument and return something of type ``Script
()`` which is DAML script’s equivalent of ``Scenario ()``.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_SIGNATURE_BEGIN
:end-before: -- INITIALIZE_SIGNATURE_END

First we create the proposals. To do so, we use the ``submit``
function to submit a transaction. The first argument is the party
submitting the transaction. In our case, we want all proposals to be
created by the bank so we use ``parties.bank``. The second argument
must be of type ``Commands a`` so in our case ``Commands (ContractId
CoinProposal, ContractId CoinProposal, ContractId CoinProposal)``
corresponding to the 3 proposals that we create. ``Commands`` is
similar to ``Update`` which is used in the ``submit`` function in
scenarios. However, ``Commands`` requires that the individual commands
do not depend on each other. This matches the restriction on the
Ledger API where a transaction consists of a list of commands. Using
``ApplicativeDo`` we can still use ``do``-notation as long as we
respect this. In ``Commands`` we use ``createCmd`` instead of
``create`` and ``exerciseCmd`` instead of ``exercise``.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_PROPOSAL_BEGIN
:end-before: -- INITIALIZE_PROPOSAL_END

Now that we have created the ``CoinProposal``s, we want ``Alice`` and
``Bob`` to accept the proposal while the ``Bank`` will ignore the
proposal that it has created for itself. To do so we use separate
``submit`` statements for ``Alice`` and ``Bob`` and call
``exerciseCmd``.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_ACCEPT_BEGIN
:end-before: -- INITIALIZE_ACCEPT_END

Finally, we call ``pure ()`` on the last line of our script to match
the type ``Script ()``.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- INITIALIZE_PURE_BEGIN
:end-before: -- INITIALIZE_PURE_END

We have now defined a way to initialize the ledger so we can write a
test that checks that the contracts that we expect exist afterwards.

First, we define the signature of our test. We will create the parties
used here in the test, so it does not take any arguments.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- TEST_SIGNATURE_BEGIN
:end-before: -- TEST_SIGNATURE_END

Now, we create the parties using the ``allocateParty`` function. This
uses the party management service to create new parties with the given
display name. Note that the display name does not identify a party
uniquely. If you call ``allocateParty`` twice with the same display
name, it will create 2 different parties. This is very convenient for
testing since a new party cannot see any old contracts on the ledger
so using new parties for each test removes the need to reset the
ledger.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- TEST_ALLOCATE_BEGIN
:end-before: -- TEST_ALLOCATE_END

We now call the ``initialize`` function that we defined before on the
parties that we have just allocated.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- TEST_INITIALIZE_BEGIN
:end-before: -- TEST_INITIALIZE_END

To verify the contracts on the ledger, we use the ``query``
function. We pass it the type of the template and a party. It will
then give us all active contracts of the given type visible to the
party. In our example, we expect to see one active ``CoinProposal``
for ``bank`` and one ``Coin`` contract for each of ``Alice`` and
``Bob``.

.. literalinclude:: ./template-root/src/ScriptExample.daml
:language: daml
:start-after: -- TEST_QUERIES_BEGIN
:end-before: -- TEST_QUERIES_END

To run our script, we first build it with ``daml build`` and then run
it by pointing to the DAR, the name of our script and the host and
port our ledger is running on.

``daml script --dar .daml/dist/script-example-0.0.1.dar --script-name ScriptExample:test --ledger-host localhost --ledger-port 6865``

Up to now, we have worked with parties that we have allocated in the
test. We can also pass in the path to a file containing
the input in the :doc:`/json-api/lf-value-specification`.

.. literalinclude:: ./template-root/ledger-parties.json
:language: daml

We can then initialize our ledger passing in the json file via ``--input-file``.

``daml script daml script --dar .daml/dist/script-example-0.0.1.dar --script-name ScriptExample:initialize --ledger-host localhost --ledger-port 6865 --input-file ledger-parties.json``

If you open Navigator, you can now see the contracts that have been created.
16 changes: 16 additions & 0 deletions docs/source/daml-script/template-root/daml.yaml.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
sdk-version: __VERSION__
name: script-example
source: src
parties:
- Alice
- Bob
- Bank
version: 0.0.1
# script-dependencies-begin
dependencies:
- daml-prim
- daml-stdlib
- daml-script
# script-dependencies-end
build-options:
- --target=1.7
5 changes: 5 additions & 0 deletions docs/source/daml-script/template-root/ledger-parties.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"alice": "Alice",
"bob": "Bob",
"bank": "Bank"
}
88 changes: 88 additions & 0 deletions docs/source/daml-script/template-root/src/ScriptExample.daml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

-- DAML_SCRIPT_HEADER_BEGIN
{-# LANGUAGE ApplicativeDo #-}
daml 1.2
module ScriptExample where
import Prelude hiding (submit, submitMustFail)
import Daml.Script
-- DAML_SCRIPT_HEADER_END

import DA.Assert

-- COIN_TEMPLATE_BEGIN
template Coin
with
issuer : Party
owner : Party
where
signatory issuer, owner
-- COIN_TEMPLATE_END

-- COIN_PROPOSAL_TEMPLATE_BEGIN
template CoinProposal
with
coin : Coin
where
signatory coin.issuer
observer coin.owner

choice Accept : ContractId Coin
controller coin.owner
do create coin
-- COIN_PROPOSAL_TEMPLATE_END

-- LEDGER_PARTIES_BEGIN
data LedgerParties = LedgerParties with
bank : Party
alice : Party
bob : Party
-- LEDGER_PARTIES_END

-- INITIALIZE_SIGNATURE_BEGIN
initialize : LedgerParties -> Script ()
initialize parties = do
-- INITIALIZE_SIGNATURE_END
-- INITIALIZE_PROPOSAL_BEGIN
(coinProposalAlice, coinProposalBob, coinProposalBank) <- submit parties.bank $ do
coinProposalAlice <- createCmd (CoinProposal (Coin parties.bank parties.alice))
coinProposalBob <- createCmd (CoinProposal (Coin parties.bank parties.bob))
coinProposalBank <- createCmd (CoinProposal (Coin parties.bank parties.bank))
pure (coinProposalAlice, coinProposalBob, coinProposalBank)
-- INITIALIZE_PROPOSAL_END

-- INITIALIZE_ACCEPT_BEGIN
coinAlice <- submit parties.alice $ exerciseCmd coinProposalAlice Accept
coinBob <- submit parties.bob $ exerciseCmd coinProposalBob Accept
-- INITIALIZE_ACCEPT_END
-- INITIALIZE_PURE_BEGIN
pure ()
-- INITIALIZE_PURE_END

-- TEST_SIGNATURE_BEGIN
test : Script ()
test = do
-- TEST_SIGNATURE_END
-- TEST_ALLOCATE_BEGIN
alice <- allocateParty "Alice"
bob <- allocateParty "Bob"
bank <- allocateParty "Bank"
let parties = LedgerParties bank alice bob
-- TEST_ALLOCATE_END

-- TEST_INITIALIZE_BEGIN
initialize parties
-- TEST_INITIALIZE_END

-- TEST_QUERIES_BEGIN
proposals <- query @CoinProposal bank
assertEq [CoinProposal (Coin bank bank)] proposals

aliceCoins <- query @Coin alice
assertEq [Coin bank alice] aliceCoins

bobCoins <- query @Coin bob
assertEq [Coin bank bob] bobCoins
-- TEST_QUERIES_END

1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ DAML SDK documentation
migrate/index
json-api/index
DAML Triggers <triggers/index>
DAML Script <daml-script/index>
tools/visual

.. toctree::
Expand Down
Loading

0 comments on commit 7deca90

Please sign in to comment.