LF: distinguish template Id from interface Id in exercise command (#14755)

preliminary work for #14747

CHANGELOG_BEGIN
CHANGELOG_END
This commit is contained in:
Remy 2022-08-19 14:00:40 +02:00 committed by GitHub
parent e3c2662ed9
commit 48abfeb9dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 151 additions and 72 deletions

View File

@ -5,9 +5,17 @@ package com.daml.lf
package data package data
// glorified Either to handle template or interface cases // glorified Either to handle template or interface cases
sealed abstract class TemplateOrInterface[+T, +I] sealed abstract class TemplateOrInterface[+T, +I] extends Product with Serializable
object TemplateOrInterface { object TemplateOrInterface {
final case class Template[+T](value: T) extends TemplateOrInterface[T, Nothing] final case class Template[+T](value: T) extends TemplateOrInterface[T, Nothing]
final case class Interface[+I](value: I) extends TemplateOrInterface[Nothing, I] final case class Interface[+I](value: I) extends TemplateOrInterface[Nothing, I]
implicit class MergeableTemplateOrInterface[A](private val x: TemplateOrInterface[A, A])
extends AnyVal {
def merge: A = x match {
case Template(a) => a
case Interface(a) => a
}
}
} }

View File

@ -5,6 +5,7 @@ package com.daml.lf
package engine package engine
package preprocessing package preprocessing
import com.daml.lf.data
import com.daml.lf.data._ import com.daml.lf.data._
import com.daml.lf.language.{Ast, PackageInterface} import com.daml.lf.language.{Ast, PackageInterface}
import com.daml.lf.transaction.TransactionVersion import com.daml.lf.transaction.TransactionVersion
@ -79,17 +80,24 @@ private[lf] final class CommandPreprocessor(
} }
def unsafePreprocessExercise( def unsafePreprocessExercise(
typeId: Ref.Identifier, typeId: data.TemplateOrInterface[Ref.Identifier, Ref.Identifier],
contractId: Value.ContractId, contractId: Value.ContractId,
choiceId: Ref.ChoiceName, choiceId: Ref.ChoiceName,
argument: Value, argument: Value,
): speedy.Command = ): speedy.Command = typeId match {
handleLookup(pkgInterface.lookupTemplateOrInterface(typeId)) match { // TODO: https://github.com/digital-asset/daml/issues/14747
case TemplateOrInterface.Template(_) => // In order to split the issue in several PRs, we allow abusing the templateId case as an interface.
unsafePreprocessExerciseTemplate(typeId, contractId, choiceId, argument) // We will change once we have added the interface_id field to the legder API Exercise command
case TemplateOrInterface.Interface(_) => case TemplateOrInterface.Template(templateId) =>
unsafePreprocessExerciseInterface(typeId, contractId, choiceId, argument) handleLookup(pkgInterface.lookupTemplateOrInterface(templateId)) match {
} case TemplateOrInterface.Template(_) =>
unsafePreprocessExerciseTemplate(templateId, contractId, choiceId, argument)
case TemplateOrInterface.Interface(_) =>
unsafePreprocessExerciseInterface(templateId, contractId, choiceId, argument)
}
case TemplateOrInterface.Interface(ifaceId) =>
unsafePreprocessExerciseInterface(ifaceId, contractId, choiceId, argument)
}
def unsafePreprocessExerciseTemplate( def unsafePreprocessExerciseTemplate(
templateId: Ref.Identifier, templateId: Ref.Identifier,
@ -178,8 +186,8 @@ private[lf] final class CommandPreprocessor(
cmd match { cmd match {
case command.ApiCommand.Create(templateId, argument) => case command.ApiCommand.Create(templateId, argument) =>
unsafePreprocessCreate(templateId, argument) unsafePreprocessCreate(templateId, argument)
case command.ApiCommand.Exercise(templateId, contractId, choiceId, argument) => case command.ApiCommand.Exercise(typeId, contractId, choiceId, argument) =>
unsafePreprocessExercise(templateId, contractId, choiceId, argument) unsafePreprocessExercise(typeId, contractId, choiceId, argument)
case command.ApiCommand.ExerciseByKey(templateId, contractKey, choiceId, argument) => case command.ApiCommand.ExerciseByKey(templateId, contractKey, choiceId, argument) =>
unsafePreprocessExerciseByKey(templateId, contractKey, choiceId, argument) unsafePreprocessExerciseByKey(templateId, contractKey, choiceId, argument)
case command.ApiCommand.CreateAndExercise( case command.ApiCommand.CreateAndExercise(

View File

@ -137,7 +137,7 @@ private[engine] final class Preprocessor(
private[engine] def preprocessApiCommand( private[engine] def preprocessApiCommand(
cmd: command.ApiCommand cmd: command.ApiCommand
): Result[speedy.Command] = ): Result[speedy.Command] =
safelyRun(pullTemplatePackage(List(cmd.typeId))) { safelyRun(pullTemplatePackage(List(cmd.typeId.merge))) {
commandPreprocessor.unsafePreprocessApiCommand(cmd) commandPreprocessor.unsafePreprocessApiCommand(cmd)
} }
@ -146,7 +146,7 @@ private[engine] final class Preprocessor(
def preprocessApiCommands( def preprocessApiCommands(
cmds: data.ImmArray[command.ApiCommand] cmds: data.ImmArray[command.ApiCommand]
): Result[ImmArray[speedy.Command]] = ): Result[ImmArray[speedy.Command]] =
safelyRun(pullTemplatePackage(cmds.toSeq.view.map(_.typeId))) { safelyRun(pullTemplatePackage(cmds.toSeq.view.map(_.typeId.merge))) {
commandPreprocessor.unsafePreprocessApiCommands(cmds) commandPreprocessor.unsafePreprocessApiCommands(cmds)
} }

View File

@ -119,7 +119,7 @@ class ApiCommandPreprocessorSpec
) )
// TEST_EVIDENCE: Input Validation: well formed exercise API command is accepted // TEST_EVIDENCE: Input Validation: well formed exercise API command is accepted
val validExeTemplate = ApiCommand.Exercise( val validExeTemplate = ApiCommand.Exercise(
"Mod:Record", TemplateOrInterface.Template("Mod:Record"),
newCid, newCid,
"Transfer", "Transfer",
ValueRecord("", ImmArray("content" -> ValueList(FrontStack(ValueParty("Clara"))))), ValueRecord("", ImmArray("content" -> ValueList(FrontStack(ValueParty("Clara"))))),
@ -133,7 +133,7 @@ class ApiCommandPreprocessorSpec
) )
// TEST_EVIDENCE: Input Validation: well formed exercise-by-interface command is accepted // TEST_EVIDENCE: Input Validation: well formed exercise-by-interface command is accepted
val validExeInterface = ApiCommand.Exercise( val validExeInterface = ApiCommand.Exercise(
"Mod:Iface", TemplateOrInterface.Interface("Mod:Iface"),
newCid, newCid,
"IfaceChoice", "IfaceChoice",
ValueUnit, ValueUnit,
@ -162,7 +162,9 @@ class ApiCommandPreprocessorSpec
validCreate.copy(argument = ValueRecord("", ImmArray("content" -> ValueInt64(42)))) -> validCreate.copy(argument = ValueRecord("", ImmArray("content" -> ValueInt64(42)))) ->
a[Error.Preprocessing.TypeMismatch], a[Error.Preprocessing.TypeMismatch],
// TEST_EVIDENCE: Input Validation: ill-formed exercise API command is rejected // TEST_EVIDENCE: Input Validation: ill-formed exercise API command is rejected
validExeTemplate.copy(typeId = "Mod:Undefined") -> validExeTemplate.copy(typeId = TemplateOrInterface.Template("Mod:Undefined")) ->
a[Error.Preprocessing.Lookup],
validExeTemplate.copy(typeId = TemplateOrInterface.Interface("Mod:Undefined")) ->
a[Error.Preprocessing.Lookup], a[Error.Preprocessing.Lookup],
validExeTemplate.copy(choiceId = "Undefined") -> validExeTemplate.copy(choiceId = "Undefined") ->
a[Error.Preprocessing.Lookup], a[Error.Preprocessing.Lookup],
@ -215,13 +217,13 @@ class ApiCommandPreprocessorSpec
ValueRecord("", ImmArray("" -> valueParties, "" -> ValueContractId(culpritCid))), ValueRecord("", ImmArray("" -> valueParties, "" -> ValueContractId(culpritCid))),
), ),
ApiCommand.Exercise( ApiCommand.Exercise(
"Mod:RecordRef", TemplateOrInterface.Template("Mod:RecordRef"),
innocentCid, innocentCid,
"Change", "Change",
ValueContractId(culpritCid), ValueContractId(culpritCid),
), ),
ApiCommand.Exercise( ApiCommand.Exercise(
"Mod:RecordRef", TemplateOrInterface.Template("Mod:RecordRef"),
culpritCid, culpritCid,
"Change", "Change",
ValueContractId(innocentCid), ValueContractId(innocentCid),

View File

@ -7,10 +7,8 @@ package engine
import com.daml.bazeltools.BazelRunfiles import com.daml.bazeltools.BazelRunfiles
import com.daml.lf.archive.UniversalArchiveDecoder import com.daml.lf.archive.UniversalArchiveDecoder
import com.daml.lf.command.{ApiCommand, ApiCommands} import com.daml.lf.command.{ApiCommand, ApiCommands}
import com.daml.lf.data.FrontStack import com.daml.lf.data.{Bytes, FrontStack, ImmArray, TemplateOrInterface, Time}
import com.daml.lf.data.Ref.{Identifier, Name, PackageId, ParticipantId, Party, QualifiedName} import com.daml.lf.data.Ref.{Identifier, Name, PackageId, ParticipantId, Party, QualifiedName}
import com.daml.lf.data.Time
import com.daml.lf.data.{Bytes, ImmArray}
import com.daml.lf.language.Ast.Package import com.daml.lf.language.Ast.Package
import com.daml.lf.ledger.FailedAuthorization.{ import com.daml.lf.ledger.FailedAuthorization.{
CreateMissingAuthorization, CreateMissingAuthorization,
@ -222,7 +220,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
"ok (Alice signed contract; Bob exercised Choice)" in { "ok (Alice signed contract; Bob exercised Choice)" in {
val command: ApiCommand = val command: ApiCommand =
ApiCommand.Exercise( ApiCommand.Exercise(
"T1", TemplateOrInterface.Template("T1"),
toContractId("t1a"), toContractId("t1a"),
"Choice1", "Choice1",
ValueRecord( ValueRecord(
@ -244,7 +242,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
"fail: ExerciseMissingAuthorization" in { "fail: ExerciseMissingAuthorization" in {
val command: ApiCommand = val command: ApiCommand =
ApiCommand.Exercise( ApiCommand.Exercise(
"T1", TemplateOrInterface.Template("T1"),
toContractId("t1a"), toContractId("t1a"),
"Choice1", "Choice1",
ValueRecord( ValueRecord(
@ -276,7 +274,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
"fail: CreateMissingAuthorization" in { "fail: CreateMissingAuthorization" in {
val command: ApiCommand = val command: ApiCommand =
ApiCommand.Exercise( ApiCommand.Exercise(
"T1", TemplateOrInterface.Template("T1"),
toContractId("t1a"), toContractId("t1a"),
"Choice1", "Choice1",
ValueRecord( ValueRecord(
@ -307,7 +305,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
"ok (Bob signed contract; Alice exercised Choice)" in { "ok (Bob signed contract; Alice exercised Choice)" in {
val command: ApiCommand = val command: ApiCommand =
ApiCommand.Exercise( ApiCommand.Exercise(
"T1", TemplateOrInterface.Template("T1"),
toContractId("t1b"), toContractId("t1b"),
"Choice1", "Choice1",
ValueRecord( ValueRecord(
@ -335,7 +333,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
"fail (no implicit authority from outer exercise's contract's signatories)" in { "fail (no implicit authority from outer exercise's contract's signatories)" in {
val command: ApiCommand = val command: ApiCommand =
ApiCommand.Exercise( ApiCommand.Exercise(
"X1", TemplateOrInterface.Template("X1"),
toContractId("x1b"), toContractId("x1b"),
"ChoiceA", "ChoiceA",
ValueRecord( ValueRecord(
@ -378,7 +376,7 @@ class AuthPropagationSpec extends AnyFreeSpec with Matchers with Inside with Baz
"ok" in { "ok" in {
val command: ApiCommand = val command: ApiCommand =
ApiCommand.Exercise( ApiCommand.Exercise(
"X1", TemplateOrInterface.Template("X1"),
toContractId("x1b"), toContractId("x1b"),
"ChoiceA", "ChoiceA",
ValueRecord( ValueRecord(

View File

@ -266,7 +266,12 @@ class EngineTest
val seeding = Engine.initialSeeding(submissionSeed, participant, let) val seeding = Engine.initialSeeding(submissionSeed, participant, let)
val cid = toContractId("BasicTests:Simple:1") val cid = toContractId("BasicTests:Simple:1")
val command = val command =
ApiCommand.Exercise(templateId, cid, "Hello", ValueRecord(Some(hello), ImmArray.Empty)) ApiCommand.Exercise(
TemplateOrInterface.Template(templateId),
cid,
"Hello",
ValueRecord(Some(hello), ImmArray.Empty),
)
val submitters = Set(party) val submitters = Set(party)
val readAs = (Set.empty: Set[Party]) val readAs = (Set.empty: Set[Party])
@ -1043,7 +1048,7 @@ class EngineTest
// we need to fix time as cid are depending on it // we need to fix time as cid are depending on it
val let = Time.Timestamp.assertFromString("1969-07-20T20:17:00Z") val let = Time.Timestamp.assertFromString("1969-07-20T20:17:00Z")
val command = ApiCommand.Exercise( val command = ApiCommand.Exercise(
templateId, TemplateOrInterface.Template(templateId),
originalCoid, originalCoid,
"Transfer", "Transfer",
ValueRecord(None, ImmArray((Some[Name]("newReceiver"), ValueParty(clara)))), ValueRecord(None, ImmArray((Some[Name]("newReceiver"), ValueParty(clara)))),
@ -1226,7 +1231,7 @@ class EngineTest
def runExample(cid: ContractId, exerciseActor: Party) = { def runExample(cid: ContractId, exerciseActor: Party) = {
val command = ApiCommand.Exercise( val command = ApiCommand.Exercise(
fetcherTid, TemplateOrInterface.Template(fetcherTid),
cid, cid,
"DoFetch", "DoFetch",
ValueRecord(None, ImmArray((Some[Name]("cid"), ValueContractId(fetchedCid)))), ValueRecord(None, ImmArray((Some[Name]("cid"), ValueContractId(fetchedCid)))),
@ -1390,7 +1395,7 @@ class EngineTest
"mark all lookupByKey nodes as byKey" in { "mark all lookupByKey nodes as byKey" in {
val exerciseCmd = ApiCommand.Exercise( val exerciseCmd = ApiCommand.Exercise(
lookerUpTemplateId, TemplateOrInterface.Template(lookerUpTemplateId),
lookerUpCid, lookerUpCid,
"Lookup", "Lookup",
ValueRecord(None, ImmArray((Some[Name]("n"), ValueInt64(42)))), ValueRecord(None, ImmArray((Some[Name]("n"), ValueInt64(42)))),
@ -1422,7 +1427,7 @@ class EngineTest
"be reinterpreted to the same node when lookup finds a contract" in { "be reinterpreted to the same node when lookup finds a contract" in {
val exerciseCmd = ApiCommand.Exercise( val exerciseCmd = ApiCommand.Exercise(
lookerUpTemplateId, TemplateOrInterface.Template(lookerUpTemplateId),
lookerUpCid, lookerUpCid,
"Lookup", "Lookup",
ValueRecord(None, ImmArray((Some[Name]("n"), ValueInt64(42)))), ValueRecord(None, ImmArray((Some[Name]("n"), ValueInt64(42)))),
@ -1465,7 +1470,7 @@ class EngineTest
"be reinterpreted to the same node when lookup doesn't find a contract" in { "be reinterpreted to the same node when lookup doesn't find a contract" in {
val exerciseCmd = ApiCommand.Exercise( val exerciseCmd = ApiCommand.Exercise(
lookerUpTemplateId, TemplateOrInterface.Template(lookerUpTemplateId),
lookerUpCid, lookerUpCid,
"Lookup", "Lookup",
ValueRecord(None, ImmArray((Some[Name]("n"), ValueInt64(57)))), ValueRecord(None, ImmArray((Some[Name]("n"), ValueInt64(57)))),
@ -1742,7 +1747,7 @@ class EngineTest
.preprocessApiCommands( .preprocessApiCommands(
ImmArray( ImmArray(
ApiCommand.Exercise( ApiCommand.Exercise(
fetcherTemplateId, TemplateOrInterface.Template(fetcherTemplateId),
fetcherCid, fetcherCid,
"Fetch", "Fetch",
ValueRecord(None, ImmArray((Some[Name]("n"), ValueInt64(42)))), ValueRecord(None, ImmArray((Some[Name]("n"), ValueInt64(42)))),
@ -1809,16 +1814,21 @@ class EngineTest
val lookupContract = contracts.get _ val lookupContract = contracts.get _
val correctCommand = val correctCommand =
ApiCommand.Exercise( ApiCommand.Exercise(
withKeyId, TemplateOrInterface.Template(withKeyId),
cid, cid,
"SumToK", "SumToK",
ValueRecord(None, ImmArray((None, ValueInt64(42)))), ValueRecord(None, ImmArray((None, ValueInt64(42)))),
) )
val incorrectCommand = val incorrectCommand =
ApiCommand.Exercise(simpleId, cid, "Hello", ValueRecord(None, ImmArray.Empty)) ApiCommand.Exercise(
TemplateOrInterface.Template(simpleId),
cid,
"Hello",
ValueRecord(None, ImmArray.Empty),
)
val incorrectFetch = val incorrectFetch =
ApiCommand.Exercise( ApiCommand.Exercise(
fetcherId, TemplateOrInterface.Template(fetcherId),
fetcherCid, fetcherCid,
"DoFetch", "DoFetch",
ValueRecord(None, ImmArray((None, ValueContractId(cid)))), ValueRecord(None, ImmArray((None, ValueContractId(cid)))),

View File

@ -110,19 +110,39 @@ class InterfacesTest
/* generic exercise tests */ /* generic exercise tests */
"be able to exercise interface I1 on a T1 contract" in { "be able to exercise interface I1 on a T1 contract" in {
val command = ApiCommand.Exercise(idI1, cid1, "C1", ValueRecord(None, ImmArray.empty)) val command = ApiCommand.Exercise(
TemplateOrInterface.Interface(idI1),
cid1,
"C1",
ValueRecord(None, ImmArray.empty),
)
runApi(command) shouldBe a[Right[_, _]] runApi(command) shouldBe a[Right[_, _]]
} }
"be able to exercise interface I1 on a T2 contract" in { "be able to exercise interface I1 on a T2 contract" in {
val command = ApiCommand.Exercise(idI1, cid2, "C1", ValueRecord(None, ImmArray.empty)) val command = ApiCommand.Exercise(
TemplateOrInterface.Interface(idI1),
cid2,
"C1",
ValueRecord(None, ImmArray.empty),
)
runApi(command) shouldBe a[Right[_, _]] runApi(command) shouldBe a[Right[_, _]]
} }
"be able to exercise interface I2 on a T2 contract" in { "be able to exercise interface I2 on a T2 contract" in {
val command = ApiCommand.Exercise(idI2, cid2, "C2", ValueRecord(None, ImmArray.empty)) val command = ApiCommand.Exercise(
TemplateOrInterface.Interface(idI2),
cid2,
"C2",
ValueRecord(None, ImmArray.empty),
)
runApi(command) shouldBe a[Right[_, _]] runApi(command) shouldBe a[Right[_, _]]
} }
"be unable to exercise interface I2 on a T1 contract" in { "be unable to exercise interface I2 on a T1 contract" in {
val command = ApiCommand.Exercise(idI2, cid1, "C2", ValueRecord(None, ImmArray.empty)) val command = ApiCommand.Exercise(
TemplateOrInterface.Interface(idI2),
cid1,
"C2",
ValueRecord(None, ImmArray.empty),
)
inside(runApi(command)) { case Left(Error.Interpretation(err, _)) => inside(runApi(command)) { case Left(Error.Interpretation(err, _)) =>
err shouldBe Error.Interpretation.DamlException( err shouldBe Error.Interpretation.DamlException(
IE.ContractDoesNotImplementInterface(idI2, cid1, idT1) IE.ContractDoesNotImplementInterface(idI2, cid1, idT1)
@ -130,19 +150,39 @@ class InterfacesTest
} }
} }
"be able to exercise T1 by interface I1" in { "be able to exercise T1 by interface I1" in {
val command = ApiCommand.Exercise(idI1, cid1, "C1", ValueRecord(None, ImmArray.empty)) val command = ApiCommand.Exercise(
TemplateOrInterface.Interface(idI1),
cid1,
"C1",
ValueRecord(None, ImmArray.empty),
)
runApi(command) shouldBe a[Right[_, _]] runApi(command) shouldBe a[Right[_, _]]
} }
"be able to exercise T2 by interface I1" in { "be able to exercise T2 by interface I1" in {
val command = ApiCommand.Exercise(idI1, cid2, "C1", ValueRecord(None, ImmArray.empty)) val command = ApiCommand.Exercise(
TemplateOrInterface.Interface(idI1),
cid2,
"C1",
ValueRecord(None, ImmArray.empty),
)
runApi(command) shouldBe a[Right[_, _]] runApi(command) shouldBe a[Right[_, _]]
} }
"be able to exercise T2 by interface I2" in { "be able to exercise T2 by interface I2" in {
val command = ApiCommand.Exercise(idI2, cid2, "C2", ValueRecord(None, ImmArray.empty)) val command = ApiCommand.Exercise(
TemplateOrInterface.Interface(idI2),
cid2,
"C2",
ValueRecord(None, ImmArray.empty),
)
runApi(command) shouldBe a[Right[_, _]] runApi(command) shouldBe a[Right[_, _]]
} }
"be unable to exercise T1 by interface I2 (stopped in preprocessor)" in { "be unable to exercise T1 by interface I2 (stopped in preprocessor)" in {
val command = ApiCommand.Exercise(idT1, cid1, "C2", ValueRecord(None, ImmArray.empty)) val command = ApiCommand.Exercise(
TemplateOrInterface.Interface(idT1),
cid1,
"C2",
ValueRecord(None, ImmArray.empty),
)
preprocessApi(command) shouldBe a[Left[_, _]] preprocessApi(command) shouldBe a[Left[_, _]]
} }
} }

View File

@ -5,11 +5,10 @@ package com.daml.lf
package engine package engine
import java.io.File import java.io.File
import com.daml.bazeltools.BazelRunfiles import com.daml.bazeltools.BazelRunfiles
import com.daml.lf.archive.UniversalArchiveDecoder import com.daml.lf.archive.UniversalArchiveDecoder
import com.daml.lf.data.Ref._ import com.daml.lf.data.Ref._
import com.daml.lf.data.{FrontStack, ImmArray, Ref, Time} import com.daml.lf.data.{FrontStack, ImmArray, Ref, TemplateOrInterface, Time}
import com.daml.lf.language.Ast import com.daml.lf.language.Ast
import com.daml.lf.scenario.ScenarioLedger import com.daml.lf.scenario.ScenarioLedger
import com.daml.lf.transaction.{Node, SubmittedTransaction, VersionedTransaction} import com.daml.lf.transaction.{Node, SubmittedTransaction, VersionedTransaction}
@ -322,7 +321,7 @@ class LargeTransactionTest extends AnyWordSpec with Matchers with BazelRunfiles
): ApiCommand.Exercise = { ): ApiCommand.Exercise = {
val choice = "ToListContainer" val choice = "ToListContainer"
val emptyArgs = ValueRecord(None, ImmArray.Empty) val emptyArgs = ValueRecord(None, ImmArray.Empty)
ApiCommand.Exercise(templateId, contractId, choice, (emptyArgs)) ApiCommand.Exercise(TemplateOrInterface.Template(templateId), contractId, choice, (emptyArgs))
} }
private def toListOfIntContainers( private def toListOfIntContainers(
@ -331,7 +330,7 @@ class LargeTransactionTest extends AnyWordSpec with Matchers with BazelRunfiles
): ApiCommand.Exercise = { ): ApiCommand.Exercise = {
val choice = "ToListOfIntContainers" val choice = "ToListOfIntContainers"
val emptyArgs = ValueRecord(None, ImmArray.Empty) val emptyArgs = ValueRecord(None, ImmArray.Empty)
ApiCommand.Exercise(templateId, contractId, choice, (emptyArgs)) ApiCommand.Exercise(TemplateOrInterface.Template(templateId), contractId, choice, (emptyArgs))
} }
private def listUtilCreateCmd(templateId: Identifier): ApiCommand.Create = { private def listUtilCreateCmd(templateId: Identifier): ApiCommand.Create = {
@ -346,7 +345,7 @@ class LargeTransactionTest extends AnyWordSpec with Matchers with BazelRunfiles
val choiceDefRef = Identifier(templateId.packageId, qn(s"LargeTransaction:$choice")) val choiceDefRef = Identifier(templateId.packageId, qn(s"LargeTransaction:$choice"))
val damlList = ValueList(List.range(0L, size.toLong).map(ValueInt64).to(FrontStack)) val damlList = ValueList(List.range(0L, size.toLong).map(ValueInt64).to(FrontStack))
val choiceArgs = ValueRecord(Some(choiceDefRef), ImmArray((None, damlList))) val choiceArgs = ValueRecord(Some(choiceDefRef), ImmArray((None, damlList)))
ApiCommand.Exercise(templateId, contractId, choice, choiceArgs) ApiCommand.Exercise(TemplateOrInterface.Template(templateId), contractId, choice, choiceArgs)
} }
private def assertSizeExerciseTransaction( private def assertSizeExerciseTransaction(

View File

@ -380,6 +380,14 @@ object TransactionBuilder {
implicit def toField(t: (String, Value)): (Option[Ref.Name], Value) = toTuple(t) implicit def toField(t: (String, Value)): (Option[Ref.Name], Value) = toTuple(t)
implicit def toTypeId(
x: TemplateOrInterface[String, String]
): TemplateOrInterface[Ref.TypeConName, Ref.TypeConName] =
x match {
case TemplateOrInterface.Template(value) => TemplateOrInterface.Template(value)
case TemplateOrInterface.Interface(value) => TemplateOrInterface.Interface(value)
}
} }
def valueRecord(id: Option[Ref.Identifier], fields: (Option[Ref.Name], Value)*) = def valueRecord(id: Option[Ref.Identifier], fields: (Option[Ref.Name], Value)*) =

View File

@ -6,11 +6,11 @@ package command
import com.daml.lf.data.Ref._ import com.daml.lf.data.Ref._
import com.daml.lf.value.Value import com.daml.lf.value.Value
import com.daml.lf.data.{ImmArray, Time} import com.daml.lf.data.{ImmArray, TemplateOrInterface, Time}
/** Accepted commands coming from API */ /** Accepted commands coming from API */
sealed abstract class ApiCommand extends Product with Serializable { sealed abstract class ApiCommand extends Product with Serializable {
def typeId: TypeConName def typeId: TemplateOrInterface[TypeConName, TypeConName]
} }
object ApiCommand { object ApiCommand {
@ -21,7 +21,7 @@ object ApiCommand {
* @param argument value passed to the template * @param argument value passed to the template
*/ */
final case class Create(templateId: TypeConName, argument: Value) extends ApiCommand { final case class Create(templateId: TypeConName, argument: Value) extends ApiCommand {
def typeId: TypeConName = templateId def typeId: TemplateOrInterface.Template[TypeConName] = TemplateOrInterface.Template(templateId)
} }
/** Command for exercising a choice on an existing contract /** Command for exercising a choice on an existing contract
@ -32,7 +32,7 @@ object ApiCommand {
* @param argument value passed for the choice * @param argument value passed for the choice
*/ */
final case class Exercise( final case class Exercise(
typeId: TypeConName, typeId: TemplateOrInterface[TypeConName, TypeConName],
contractId: Value.ContractId, contractId: Value.ContractId,
choiceId: ChoiceName, choiceId: ChoiceName,
argument: Value, argument: Value,
@ -51,7 +51,7 @@ object ApiCommand {
choiceId: ChoiceName, choiceId: ChoiceName,
argument: Value, argument: Value,
) extends ApiCommand { ) extends ApiCommand {
def typeId: TypeConName = templateId def typeId: TemplateOrInterface.Template[TypeConName] = TemplateOrInterface.Template(templateId)
} }
/** Command for creating a contract and exercising a choice /** Command for creating a contract and exercising a choice
@ -68,7 +68,7 @@ object ApiCommand {
choiceId: ChoiceName, choiceId: ChoiceName,
choiceArgument: Value, choiceArgument: Value,
) extends ApiCommand { ) extends ApiCommand {
def typeId: TypeConName = templateId def typeId: TemplateOrInterface.Template[TypeConName] = TemplateOrInterface.Template(templateId)
} }
} }

View File

@ -268,14 +268,14 @@ object Converter {
} yield anyChoice match { } yield anyChoice match {
case AnyChoice.Template(name, arg) => case AnyChoice.Template(name, arg) =>
command.ApiCommand.Exercise( command.ApiCommand.Exercise(
typeId = tplId, typeId = TemplateOrInterface.Template(tplId),
contractId = cid, contractId = cid,
choiceId = name, choiceId = name,
argument = arg.toUnnormalizedValue, argument = arg.toUnnormalizedValue,
) )
case AnyChoice.Interface(ifaceId, name, arg) => case AnyChoice.Interface(ifaceId, name, arg) =>
command.ApiCommand.Exercise( command.ApiCommand.Exercise(
typeId = ifaceId, typeId = TemplateOrInterface.Interface(ifaceId),
contractId = cid, contractId = cid,
choiceId = name, choiceId = name,
argument = arg.toUnnormalizedValue, argument = arg.toUnnormalizedValue,

View File

@ -296,11 +296,13 @@ class GrpcLedgerClient(val grpcClient: LedgerClient, val applicationId: Applicat
for { for {
arg <- lfValueToApiRecord(true, argument) arg <- lfValueToApiRecord(true, argument)
} yield Command().withCreate(CreateCommand(Some(toApiIdentifier(templateId)), Some(arg))) } yield Command().withCreate(CreateCommand(Some(toApiIdentifier(templateId)), Some(arg)))
case command.ExerciseCommand(templateId, contractId, choice, argument) => case command.ExerciseCommand(typeId, contractId, choice, argument) =>
for { for {
arg <- lfValueToApiValue(true, argument) arg <- lfValueToApiValue(true, argument)
} yield Command().withExercise( } yield Command().withExercise(
ExerciseCommand(Some(toApiIdentifier(templateId)), contractId.coid, choice, Some(arg)) // TODO: https://github.com/digital-asset/daml/issues/14747
// Fix once the new field interface_id have been added to the API Exercise Command
ExerciseCommand(Some(toApiIdentifier(typeId.merge)), contractId.coid, choice, Some(arg))
) )
case command.ExerciseByKeyCommand(templateId, key, choice, argument) => case command.ExerciseByKeyCommand(templateId, key, choice, argument) =>
for { for {

View File

@ -251,8 +251,10 @@ class JsonLedgerClient(
cmd match { cmd match {
case command.CreateCommand(tplId, argument) => case command.CreateCommand(tplId, argument) =>
create(tplId, argument, partySets) create(tplId, argument, partySets)
case command.ExerciseCommand(tplId, cid, choice, argument) => case command.ExerciseCommand(typeId, cid, choice, argument) =>
exercise(tplId, cid, choice, argument, partySets) // TODO: https://github.com/digital-asset/daml/issues/14747
// Fix once Json API distinguish between template and interfaceId within Exercise Command
exercise(typeId.merge, cid, choice, argument, partySets)
case command.ExerciseByKeyCommand(tplId, key, choice, argument) => case command.ExerciseByKeyCommand(tplId, key, choice, argument) =>
exerciseByKey(tplId, key, choice, argument, partySets) exerciseByKey(tplId, key, choice, argument, partySets)
case command.CreateAndExerciseCommand(tplId, template, choice, argument) => case command.CreateAndExerciseCommand(tplId, template, choice, argument) =>

View File

@ -154,7 +154,9 @@ final class CommandsValidator(ledgerId: LedgerId) {
value <- requirePresence(e.value.choiceArgument, "value") value <- requirePresence(e.value.choiceArgument, "value")
validatedValue <- validateValue(value) validatedValue <- validateValue(value)
} yield ApiCommand.Exercise( } yield ApiCommand.Exercise(
typeId = validatedTemplateId, // TODO: https://github.com/digital-asset/daml/issues/14747
// Fix once the new field interface_id have been added to the API Exercise Command
typeId = TemplateOrInterface.Template(validatedTemplateId),
contractId = contractId, contractId = contractId,
choiceId = choice, choiceId = choice,
argument = validatedValue, argument = validatedValue,

View File

@ -7,9 +7,9 @@
- badly-authorized create is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L60) - badly-authorized create is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L60)
- badly-authorized create is rejected: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L1174) - badly-authorized create is rejected: [AbstractHttpServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractHttpServiceIntegrationTest.scala#L1174)
- badly-authorized exercise is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L158) - badly-authorized exercise is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L158)
- badly-authorized exercise/create (create is unauthorized) is rejected: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L275) - badly-authorized exercise/create (create is unauthorized) is rejected: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L273)
- badly-authorized exercise/create (exercise is unauthorized) is rejected: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L243) - badly-authorized exercise/create (exercise is unauthorized) is rejected: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L241)
- badly-authorized exercise/exercise (no implicit authority from outer exercise) is rejected: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L334) - badly-authorized exercise/exercise (no implicit authority from outer exercise) is rejected: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L332)
- badly-authorized fetch is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L95) - badly-authorized fetch is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L95)
- badly-authorized lookup is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L117) - badly-authorized lookup is rejected: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L117)
- create IOU should fail if overwritten actAs & readAs result in missing permission even if the user would have the rights: [HttpServiceIntegrationTestUserManagement.scala](ledger-service/http-json/src/it/scala/http/HttpServiceIntegrationTestUserManagement.scala#L135) - create IOU should fail if overwritten actAs & readAs result in missing permission even if the user would have the rights: [HttpServiceIntegrationTestUserManagement.scala](ledger-service/http-json/src/it/scala/http/HttpServiceIntegrationTestUserManagement.scala#L135)
@ -40,8 +40,8 @@
- websocket request without protocol token should be denied: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L105) - websocket request without protocol token should be denied: [AbstractWebsocketServiceIntegrationTest.scala](ledger-service/http-json/src/itlib/scala/http/AbstractWebsocketServiceIntegrationTest.scala#L105)
- well-authorized create is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L43) - well-authorized create is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L43)
- well-authorized exercise is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L141) - well-authorized exercise is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L141)
- well-authorized exercise/create is accepted: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L221) - well-authorized exercise/create is accepted: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L219)
- well-authorized exercise/exercise is accepted: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L377) - well-authorized exercise/exercise is accepted: [AuthPropagationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthPropagationSpec.scala#L375)
- well-authorized fetch is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L89) - well-authorized fetch is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L89)
- well-authorized lookup is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L111) - well-authorized lookup is accepted: [AuthorizationSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/AuthorizationSpec.scala#L111)
@ -175,7 +175,7 @@
- Evaluation order of successful lookup_by_key of a local contract: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2925) - Evaluation order of successful lookup_by_key of a local contract: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2925)
- Evaluation order of successful lookup_by_key of a non-cached global contract: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2773) - Evaluation order of successful lookup_by_key of a non-cached global contract: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2773)
- Exceptions, throw/catch.: [ExceptionTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/ExceptionTest.scala#L25) - Exceptions, throw/catch.: [ExceptionTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/ExceptionTest.scala#L25)
- Rollback creates cannot be exercise: [EngineTest.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/EngineTest.scala#L2064) - Rollback creates cannot be exercise: [EngineTest.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/EngineTest.scala#L2074)
- This checks that type checking in exercise_interface is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L1860) - This checks that type checking in exercise_interface is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L1860)
- This checks that type checking is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L1750) - This checks that type checking is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L1750)
- This checks that type checking is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2717) - This checks that type checking is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2717)
@ -218,16 +218,16 @@
- ensure expression forms have the correct type: [TypingSpec.scala](daml-lf/validation/src/test/scala/com/digitalasset/daml/lf/validation/TypingSpec.scala#L108) - ensure expression forms have the correct type: [TypingSpec.scala](daml-lf/validation/src/test/scala/com/digitalasset/daml/lf/validation/TypingSpec.scala#L108)
- error on specifying both authCommonUri and authInternalUri/authExternalUri for the trigger service: [AuthorizationConfigTest.scala](triggers/service/src/test-suite/scala/com/daml/lf/engine/trigger/AuthorizationConfigTest.scala#L24) - error on specifying both authCommonUri and authInternalUri/authExternalUri for the trigger service: [AuthorizationConfigTest.scala](triggers/service/src/test-suite/scala/com/daml/lf/engine/trigger/AuthorizationConfigTest.scala#L24)
- error on specifying only authInternalUri and no authExternalUri for the trigger service: [AuthorizationConfigTest.scala](triggers/service/src/test-suite/scala/com/daml/lf/engine/trigger/AuthorizationConfigTest.scala#L52) - error on specifying only authInternalUri and no authExternalUri for the trigger service: [AuthorizationConfigTest.scala](triggers/service/src/test-suite/scala/com/daml/lf/engine/trigger/AuthorizationConfigTest.scala#L52)
- exercise-by-interface command is rejected for a: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L171) - exercise-by-interface command is rejected for a: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L173)
- give a 'not found' response for a stop request on an unknown UUID in the trigger service: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L552) - give a 'not found' response for a stop request on an unknown UUID in the trigger service: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L552)
- give a 'not found' response for a stop request with an unparseable UUID in the trigger service: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L537) - give a 'not found' response for a stop request with an unparseable UUID in the trigger service: [TriggerServiceTest.scala](triggers/service/src/test/scala/com/digitalasset/daml/lf/engine/trigger/TriggerServiceTest.scala#L537)
- ill-formed create API command is rejected: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L159) - ill-formed create API command is rejected: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L159)
- ill-formed create replay command is rejected: [ReplayCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ReplayCommandPreprocessorSpec.scala#L109) - ill-formed create replay command is rejected: [ReplayCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ReplayCommandPreprocessorSpec.scala#L109)
- ill-formed create-and-exercise API command is rejected: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L184) - ill-formed create-and-exercise API command is rejected: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L186)
- ill-formed exception definitions are rejected: [TypingSpec.scala](daml-lf/validation/src/test/scala/com/digitalasset/daml/lf/validation/TypingSpec.scala#L1764) - ill-formed exception definitions are rejected: [TypingSpec.scala](daml-lf/validation/src/test/scala/com/digitalasset/daml/lf/validation/TypingSpec.scala#L1764)
- ill-formed exercise API command is rejected: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L164) - ill-formed exercise API command is rejected: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L164)
- ill-formed exercise replay command is rejected: [ReplayCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ReplayCommandPreprocessorSpec.scala#L114) - ill-formed exercise replay command is rejected: [ReplayCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ReplayCommandPreprocessorSpec.scala#L114)
- ill-formed exercise-by-key API command is rejected: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L175) - ill-formed exercise-by-key API command is rejected: [ApiCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ApiCommandPreprocessorSpec.scala#L177)
- ill-formed exercise-by-key replay command is rejected: [ReplayCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ReplayCommandPreprocessorSpec.scala#L121) - ill-formed exercise-by-key replay command is rejected: [ReplayCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ReplayCommandPreprocessorSpec.scala#L121)
- ill-formed expressions are rejected: [TypingSpec.scala](daml-lf/validation/src/test/scala/com/digitalasset/daml/lf/validation/TypingSpec.scala#L450) - ill-formed expressions are rejected: [TypingSpec.scala](daml-lf/validation/src/test/scala/com/digitalasset/daml/lf/validation/TypingSpec.scala#L450)
- ill-formed fetch command is rejected: [ReplayCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ReplayCommandPreprocessorSpec.scala#L168) - ill-formed fetch command is rejected: [ReplayCommandPreprocessorSpec.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/ReplayCommandPreprocessorSpec.scala#L168)