From 46f41a886eafcfac1fe4a71524ea9de7bfe83dae Mon Sep 17 00:00:00 2001 From: Moritz Kiefer Date: Mon, 17 Aug 2020 13:40:22 +0200 Subject: [PATCH] Use TO_TEXT_CONTRACT_ID in Show instance of ContractId MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #7114 This PR changes the Show instance of ContractId and flips the switch on triggers and DAML Script to run in off-ledger mode. It also adds a test that for DAML Script we actually get back the correct contract id. There is a bit of a design decision here in how we want to print contract ids, so let me list the options I considered. $cid will stand for the actual cid and all options are wrapped in markdown inline code. 1. `"$cid"`. Indistinguishable from string. Suggests that there might be an IsString instance for ContractId. 2. `<$cid>`. Matches the dummy `` but it’s not a dummy so I don’t think matching that is benefitial. 3. `$cid`. Easy to spot (contract ids start with # and have no spaces), clearly not a string but might look slightly weird. changelog_begin changelog_end --- .../daml-lf-ast/src/DA/Daml/LF/Ast/Version.hs | 9 +++ .../src/DA/Daml/LFConversion/Primitives.hs | 6 ++ .../damlc/daml-stdlib-src/DA/Internal/LF.daml | 9 ++- .../daml/lf/engine/script/Runner.scala | 3 +- daml-script/test/BUILD.bazel | 62 +++++++++++++++++-- daml-script/test/daml/TestContractId.daml | 18 ++++++ .../script/test/SingleParticipant.scala | 40 +++++++++++- .../daml/lf/engine/trigger/Runner.scala | 3 +- 8 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 daml-script/test/daml/TestContractId.daml diff --git a/compiler/daml-lf-ast/src/DA/Daml/LF/Ast/Version.hs b/compiler/daml-lf-ast/src/DA/Daml/LF/Ast/Version.hs index bd1bc1959aa2..32b74c9867a5 100644 --- a/compiler/daml-lf-ast/src/DA/Daml/LF/Ast/Version.hs +++ b/compiler/daml-lf-ast/src/DA/Daml/LF/Ast/Version.hs @@ -119,6 +119,14 @@ featureUnstable = Feature , featureCppFlag = "DAML_UNSTABLE" } +featureToTextContractId :: Feature +featureToTextContractId = Feature + { featureName = "TO_TEXT_CONTRACT_ID primitive" + -- TODO Change as part of #7139 + , featureMinVersion = versionDev + , featureCppFlag = "DAML_TO_TEXT_CONTRACT_ID" + } + allFeatures :: [Feature] allFeatures = [ featureNumeric @@ -130,6 +138,7 @@ allFeatures = , featureGenMap , featurePackageMetadata , featureUnstable + , featureToTextContractId ] allFeaturesForVersion :: Version -> [Feature] diff --git a/compiler/damlc/daml-lf-conversion/src/DA/Daml/LFConversion/Primitives.hs b/compiler/damlc/daml-lf-conversion/src/DA/Daml/LFConversion/Primitives.hs index 69aa2dc61afa..35e47ceb5e09 100644 --- a/compiler/damlc/daml-lf-conversion/src/DA/Daml/LFConversion/Primitives.hs +++ b/compiler/damlc/daml-lf-conversion/src/DA/Daml/LFConversion/Primitives.hs @@ -269,6 +269,12 @@ convertPrim _ "BETextReplicate" (TInt64 :-> TText :-> TText) = EBuiltin BETextRe convertPrim _ "BETextSplitOn" (TText :-> TText :-> TList TText) = EBuiltin BETextSplitOn convertPrim _ "BETextIntercalate" (TText :-> TList TText :-> TText) = EBuiltin BETextIntercalate +-- Conversion from ContractId to Text + +convertPrim _ "BEToTextContractId" (TContractId t :-> TOptional TText) = + ETyApp (EBuiltin BEToTextContractId) t + + -- Template Desugaring. convertPrim _ "UCreate" (TCon template :-> TUpdate (TContractId (TCon template'))) diff --git a/compiler/damlc/daml-stdlib-src/DA/Internal/LF.daml b/compiler/damlc/daml-stdlib-src/DA/Internal/LF.daml index eaf9c0901ad3..845f16367e5e 100644 --- a/compiler/damlc/daml-stdlib-src/DA/Internal/LF.daml +++ b/compiler/damlc/daml-stdlib-src/DA/Internal/LF.daml @@ -133,7 +133,14 @@ data Map a b = data ContractId a = ContractId Opaque instance Eq (ContractId a) where (==) = primitive @"BEEqualContractId" -instance Show (ContractId a) where show _ = "" +instance Show (ContractId a) where +#ifdef DAML_TO_TEXT_CONTRACT_ID + show cid = case primitive @"BEToTextContractId" cid of + None -> "" + Some t -> t +#else + show _ = "" +#endif -- | HIDE This is currently used in the internals of DAML script and DAML triggers -- but not really something that we want to expose to users. diff --git a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Runner.scala b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Runner.scala index 96790e914c92..1da72b83195f 100644 --- a/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Runner.scala +++ b/daml-script/runner/src/main/scala/com/digitalasset/daml/lf/engine/script/Runner.scala @@ -338,7 +338,8 @@ class Runner(compiledPackages: CompiledPackages, script: Script.Action, timeMode esf: ExecutionSequencerFactory, mat: Materializer): Future[SValue] = { var clients = initialClients - val machine = Speedy.Machine.fromPureSExpr(extendedCompiledPackages, script.expr) + val machine = + Speedy.Machine.fromPureSExpr(extendedCompiledPackages, script.expr, onLedger = false) def stepToValue(): Either[RuntimeException, SValue] = machine.run() match { diff --git a/daml-script/test/BUILD.bazel b/daml-script/test/BUILD.bazel index 594eab9c4251..37d5e259bc9f 100644 --- a/daml-script/test/BUILD.bazel +++ b/daml-script/test/BUILD.bazel @@ -47,6 +47,37 @@ EOF visibility = ["//visibility:public"], ) +# Test DAR in 1.dev to test new features. +genrule( + name = "script-test-1.dev", + srcs = + glob(["**/*.daml"]) + ["//daml-script/daml:daml-script-1.dev.dar"], + outs = ["script-test-1.dev.dar"], + cmd = """ + set -eou pipefail + TMP_DIR=$$(mktemp -d) + mkdir -p $$TMP_DIR/daml + cp -L $(location :daml/TestContractId.daml) $$TMP_DIR/daml + cp -L $(location //daml-script/daml:daml-script-1.dev.dar) $$TMP_DIR/ + cat << EOF > $$TMP_DIR/daml.yaml +sdk-version: {sdk} +name: script-test-1dev +version: 0.0.1 +source: daml +build-options: + - --target=1.dev +dependencies: + - daml-stdlib + - daml-prim + - daml-script-1.dev.dar +EOF + $(location //compiler/damlc) build --project-root=$$TMP_DIR -o $$PWD/$(location script-test-1.dev.dar) + rm -rf $$TMP_DIR + """.format(sdk = sdk_version), + tools = ["//compiler/damlc"], + visibility = ["//visibility:public"], +) + # A variant of script-test that has not been uploaded to the ledger # to test missing template ids. We only care that this has a different package id. genrule( @@ -93,6 +124,7 @@ da_scala_library( "//daml-lf/data", "//daml-lf/interpreter", "//daml-lf/language", + "//daml-lf/transaction", "//daml-script/runner:script-runner-lib", "//language-support/scala/bindings", "//language-support/scala/bindings-akka", @@ -175,28 +207,46 @@ da_scala_test_suite( client_server_test( name = "test_static_time", client = ":test_client_single_participant", - client_files = ["$(rootpath :script-test.dar)"], - data = [":script-test.dar"], + client_files = [ + "$(rootpath :script-test.dar)", + "$(rootpath :script-test-1.dev.dar)", + ], + data = [ + ":script-test.dar", + ":script-test-1.dev.dar", + ], server = "//ledger/sandbox-classic:sandbox-classic-binary", server_args = [ "--static-time", "--port=0", ], - server_files = ["$(rootpath :script-test.dar)"], + server_files = [ + "$(rootpath :script-test.dar)", + "$(rootpath :script-test-1.dev.dar)", + ], ) client_server_test( name = "test_wallclock_time", client = ":test_client_single_participant", client_args = ["-w"], - client_files = ["$(rootpath :script-test.dar)"], - data = [":script-test.dar"], + client_files = [ + "$(rootpath :script-test.dar)", + "$(rootpath :script-test-1.dev.dar)", + ], + data = [ + ":script-test.dar", + ":script-test-1.dev.dar", + ], server = "//ledger/sandbox-classic:sandbox-classic-binary", server_args = [ "--wall-clock-time", "--port=0", ], - server_files = ["$(rootpath :script-test.dar)"], + server_files = [ + "$(rootpath :script-test.dar)", + "$(rootpath :script-test-1.dev.dar)", + ], ) client_server_test( diff --git a/daml-script/test/daml/TestContractId.daml b/daml-script/test/daml/TestContractId.daml new file mode 100644 index 000000000000..63435ac5bd70 --- /dev/null +++ b/daml-script/test/daml/TestContractId.daml @@ -0,0 +1,18 @@ +-- Copyright (c) 2020 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +module TestContractId where + +import Daml.Script + +template T + with + t : Party + where + signatory t + +testContractId = do + p <- allocateParty "p" + cid <- submit p (createCmd (T p)) + pure (cid, show cid) + diff --git a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/SingleParticipant.scala b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/SingleParticipant.scala index 1dd396a18689..66c0e91ef32a 100644 --- a/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/SingleParticipant.scala +++ b/daml-script/test/src/com/digitalasset/daml/lf/engine/script/test/SingleParticipant.scala @@ -29,6 +29,8 @@ import com.daml.lf.engine.script.{Party => ScriptParty, _} case class Config( ledgerPort: Int, darPath: File, + // 1.dev DAR + devDarPath: File, wallclockTime: Boolean, auth: Boolean, // We use the presence of a root CA as a proxy for whether to enable TLS or not. @@ -384,6 +386,28 @@ case class TestAuth(dar: Dar[(PackageId, Package)], runner: TestRunner) { } } +case class TestContractId(dar: Dar[(PackageId, Package)], runner: TestRunner) { + val scriptId = + Identifier(dar.main._1, QualifiedName.assertFromString("TestContractId:testContractId")) + def runTests(): Unit = { + runner.genericTest( + "testContractId", + scriptId, + None, { + case SRecord(_, _, vals) if vals.size == 2 => { + (vals.get(0), vals.get(1)) match { + case (SContractId(cid), SText(t)) => + TestRunner.assertEqual(t, cid.coid, "contract ids") + case (a, b) => + Left(s"Expected SContractId, SText but got $a, $b") + } + } + case v => Left(s"Expected Tuple2 but got $v") + } + ) + } +} + object SingleParticipant { private val configParser = new scopt.OptionParser[Config]("daml_script_test") { @@ -397,6 +421,10 @@ object SingleParticipant { .required() .action((d, c) => c.copy(darPath = d)) + arg[File]("dev-dar") + .required() + .action((d, c) => c.copy(devDarPath = d)) + opt[Unit]('w', "wall-clock-time") .action { (_, c) => c.copy(wallclockTime = true) @@ -432,7 +460,7 @@ object SingleParticipant { } def main(args: Array[String]): Unit = { - configParser.parse(args, Config(0, null, false, false, None)) match { + configParser.parse(args, Config(0, null, null, false, false, None)) match { case None => sys.exit(1) case Some(config) => @@ -442,6 +470,13 @@ object SingleParticipant { case (pkgId, pkgArchive) => Decode.readArchivePayload(pkgId, pkgArchive) } + println(config.devDarPath) + val encodedDevDar: Dar[(PackageId, DamlLf.ArchivePayload)] = + DarReader().readArchiveFromFile(config.devDarPath).get + val devDar: Dar[(PackageId, Package)] = encodedDevDar.map { + case (pkgId, pkgArchive) => Decode.readArchivePayload(pkgId, pkgArchive) + } + val participantParams = if (config.auth) { Participants( None, @@ -474,6 +509,8 @@ object SingleParticipant { val runner = new TestRunner(participantParams, dar, config.wallclockTime, config.rootCa) + val devRunner = + new TestRunner(participantParams, devDar, config.wallclockTime, config.rootCa) if (!config.auth) { TraceOrder(dar, runner).runTests() Test0(dar, runner).runTests() @@ -490,6 +527,7 @@ object SingleParticipant { TestStack(dar, runner).runTests() TestMaxInboundMessageSize(dar, runner).runTests() ScriptExample(dar, runner).runTests() + TestContractId(devDar, devRunner).runTests() // Keep this at the end since it changes the time and we cannot go backwards. if (!config.wallclockTime) { SetTime(dar, runner).runTests() diff --git a/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Runner.scala b/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Runner.scala index aff886cca3df..3e836a826431 100644 --- a/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Runner.scala +++ b/triggers/runner/src/main/scala/com/digitalasset/daml/lf/engine/trigger/Runner.scala @@ -346,7 +346,8 @@ class Runner( getInitialState, Array(SParty(Party.assertFromString(party)), STimestamp(clientTime), createdValue)) // Prepare a speedy machine for evaluting expressions. - val machine: Speedy.Machine = Speedy.Machine.fromPureSExpr(compiledPackages, initialState) + val machine: Speedy.Machine = + Speedy.Machine.fromPureSExpr(compiledPackages, initialState, onLedger = false) // Evaluate it. machine.setExpressionToEvaluate(initialState) val value = Machine.stepToValue(machine)