From 024400b9e4384cc1bb72e3290fb797eca163ab62 Mon Sep 17 00:00:00 2001 From: Sofia Faro Date: Thu, 25 Nov 2021 16:19:36 +0000 Subject: [PATCH] Error when fetching the wrong template id (via fetch by interface). (#11862) * Prevent wrongly typed fetch by interface. When doing a "fetch by interface" command with a known template id, error out with a WronglyTypedContract if the fetched contract has a different template id. This doesn't affect daml, only affects replays, so it's rather minor. I also enabled the engine test that caught this. Part of #11703, follow up to #11836. changelog_begin changelog_end * strengthen test output checks --- .../daml/lf/engine/InterfacesTest.scala | 43 +++++++++++++------ .../daml/lf/speedy/Compiler.scala | 21 +++++++-- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/InterfacesTest.scala b/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/InterfacesTest.scala index c772cd3f08f1..893f03ed35e6 100644 --- a/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/InterfacesTest.scala +++ b/daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/InterfacesTest.scala @@ -19,6 +19,7 @@ import org.scalatest.prop.TableDrivenPropertyChecks import org.scalatest.EitherValues import org.scalatest.wordspec.AnyWordSpec import org.scalatest.matchers.should.Matchers +import interpretation.{Error => IE} import scala.language.implicitConversions @@ -220,7 +221,7 @@ class InterfacesTest ExerciseByInterfaceCommand(idI2, idT2, cid2, "C2", ValueRecord(None, ImmArray.empty)) run(command) shouldBe a[Right[_, _]] } - "be unable to exercise T1 by interface I2 (stopped in preprocessor)" in { + "be unable to exercise T1 by interface I2 via 'exercise by interface' (stopped in preprocessor)" in { val command = ExerciseByInterfaceCommand(idI2, idT1, cid1, "C2", ValueRecord(None, ImmArray.empty)) preprocess(command) shouldBe a[Left[_, _]] @@ -229,12 +230,22 @@ class InterfacesTest "be unable to exercise T1 (disguised as T2) by interface I1 via 'exercise by interface'" in { val command = ExerciseByInterfaceCommand(idI1, idT2, cid1, "C1", ValueRecord(None, ImmArray.empty)) - run(command) shouldBe a[Left[_, _]] + run(command) match { + case Left(Error.Interpretation(err, _)) => + err shouldBe Error.Interpretation.DamlException(IE.WronglyTypedContract(cid1, idT2, idT1)) + case result => + fail(s"Expected Left(Error.Interpretation(err, _)), not $result") + } } "be unable to exercise T2 (disguised as T1) by interface I1 via 'exercise by interface'" in { val command = ExerciseByInterfaceCommand(idI1, idT1, cid2, "C1", ValueRecord(None, ImmArray.empty)) - run(command) shouldBe a[Left[_, _]] + run(command) match { + case Left(Error.Interpretation(err, _)) => + err shouldBe Error.Interpretation.DamlException(IE.WronglyTypedContract(cid2, idT1, idT2)) + case result => + fail(s"Expected Left(Error.Interpretation(err, _)), not $result") + } } "be unable to exercise T2 (disguised as T1) by interface I2 via 'exercise by interface' (stopped in preprocessor)" in { val command = @@ -274,18 +285,24 @@ class InterfacesTest val command = FetchByInterfaceCommand(idI2, idT2, cid1) run(command) shouldBe a[Left[_, _]] } - // TODO https://github.com/digital-asset/daml/issues/11703 - // Enable these tests. - /* - "be unable to fetch T1 (disguised as T2) via interface I1" in { - val command = FetchByInterfaceCommand(idI1, idT2, cid1) - run(command) shouldBe a[Left[_, _]] + "be unable to fetch T1 (disguised as T2) via interface I1" in { + val command = FetchByInterfaceCommand(idI1, idT2, cid1) + run(command) match { + case Left(Error.Interpretation(err, _)) => + err shouldBe Error.Interpretation.DamlException(IE.WronglyTypedContract(cid1, idT2, idT1)) + case result => + fail(s"Expected Left(Error.Interpretation(err, _)), not $result") } - "be unable to fetch T2 (disguised as T1) via interface I1" in { - val command = FetchByInterfaceCommand(idI1, idT1, cid2) - run(command) shouldBe a[Left[_, _]] + } + "be unable to fetch T2 (disguised as T1) via interface I1" in { + val command = FetchByInterfaceCommand(idI1, idT1, cid2) + run(command) match { + case Left(Error.Interpretation(err, _)) => + err shouldBe Error.Interpretation.DamlException(IE.WronglyTypedContract(cid2, idT1, idT2)) + case result => + fail(s"Expected Left(Error.Interpretation(err, _)), not $result") } - */ + } "be unable to fetch T2 (disguised as T1) by interface I2 (stopped in preprocessor)" in { val command = FetchByInterfaceCommand(idI2, idT1, cid2) preprocess(command) shouldBe a[Left[_, _]] diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Compiler.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Compiler.scala index a815167c3129..a8c1ce54899d 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Compiler.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Compiler.scala @@ -1513,6 +1513,21 @@ private[lf] final class Compiler( } } + private[this] def compileFetchByInterface( + interfaceId: TypeConName, + templateId: TypeConName, + contractId: SValue, + ): s.SExpr = + unaryFunction(Env.Empty) { (tokenPos, env) => + let(env, t.FetchDefRef(interfaceId)(s.SEValue(contractId), env.toSEVar(tokenPos))) { + (payloadPos, env) => + let(env, SBGuardTemplateId(templateId)(s.SEValue(contractId), env.toSEVar(payloadPos))) { + (_, env) => + env.toSEVar(payloadPos) + } + } + } + private[this] def compileCommand(cmd: Command): s.SExpr = cmd match { case Command.Create(templateId, argument) => t.CreateDefRef(templateId)(s.SEValue(argument)) @@ -1528,10 +1543,8 @@ private[lf] final class Compiler( t.ChoiceByKeyDefRef(templateId, choiceId)(s.SEValue(contractKey), s.SEValue(argument)) case Command.Fetch(templateId, coid) => t.FetchDefRef(templateId)(s.SEValue(coid)) - case Command.FetchByInterface(interfaceId, templateId @ _, coid) => - // TODO https://github.com/digital-asset/daml/issues/11703 - // Ensure that fetched template has expected templateId. - t.FetchDefRef(interfaceId)(s.SEValue(coid)) + case Command.FetchByInterface(interfaceId, templateId, coid) => + compileFetchByInterface(interfaceId, templateId, coid) case Command.FetchByKey(templateId, key) => t.FetchByKeyDefRef(templateId)(s.SEValue(key)) case Command.CreateAndExercise(templateId, createArg, choice, choiceArg) =>