diff --git a/compiler/scenario-service/server/src/main/scala/com/digitalasset/daml/lf/scenario/Conversions.scala b/compiler/scenario-service/server/src/main/scala/com/digitalasset/daml/lf/scenario/Conversions.scala index e6123cfca71a..52742ca31fb5 100644 --- a/compiler/scenario-service/server/src/main/scala/com/digitalasset/daml/lf/scenario/Conversions.scala +++ b/compiler/scenario-service/server/src/main/scala/com/digitalasset/daml/lf/scenario/Conversions.scala @@ -182,6 +182,10 @@ final class Conversions( builder.setComparableValueError(proto.Empty.newBuilder) case ValueExceedsMaxNesting => builder.setValueExceedsMaxNesting(proto.Empty.newBuilder) + case _: ChoiceGuardFailed => + // TODO https://github.com/digital-asset/daml/issues/11703 + // Implement this. + builder.setCrash(s"ChoiceGuardFailed unhandled in scenario service") } } case Error.ContractNotEffective(coid, tid, effectiveAt) => diff --git a/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/DecodeV1.scala b/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/DecodeV1.scala index 23f7d4764c36..61c9e4cea8d0 100644 --- a/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/DecodeV1.scala +++ b/daml-lf/archive/src/main/scala/com/digitalasset/daml/lf/archive/DecodeV1.scala @@ -1304,6 +1304,7 @@ private[archive] class DecodeV1(minor: LV.Minor) { choice = handleInternedName(exercise.getChoiceInternedStr), cidE = decodeExpr(exercise.getCid, definition), argE = decodeExpr(exercise.getArg, definition), + guardE = None, // TODO https://github.com/digital-asset/daml/issues/11703 ) case PLF.Update.SumCase.EXERCISE_BY_KEY => diff --git a/daml-lf/encoder/src/main/scala/com/digitalasset/daml/lf/archive/testing/EncodeV1.scala b/daml-lf/encoder/src/main/scala/com/digitalasset/daml/lf/archive/testing/EncodeV1.scala index a85f06244cd4..9f8703169a23 100644 --- a/daml-lf/encoder/src/main/scala/com/digitalasset/daml/lf/archive/testing/EncodeV1.scala +++ b/daml-lf/encoder/src/main/scala/com/digitalasset/daml/lf/archive/testing/EncodeV1.scala @@ -380,7 +380,9 @@ private[daml] class EncodeV1(minor: LV.Minor) { b.setCid(cid) b.setArg(arg) builder.setExercise(b) - case UpdateExerciseInterface(interface, choice, cid, arg) => + case UpdateExerciseInterface(interface, choice, cid, arg, guard @ _) => + // TODO https://github.com/digital-asset/daml/issues/11703 + // Encode guard. val b = PLF.Update.ExerciseInterface.newBuilder() b.setInterface(interface) setInternedString(choice, b.setChoiceInternedStr) 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 7ca1c8e73d94..c772cd3f08f1 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 @@ -166,18 +166,14 @@ class InterfacesTest preprocess(command) shouldBe a[Left[_, _]] } - // TODO https://github.com/digital-asset/daml/issues/11703 - // Enable these tests. - /* - "be unable to exercise T1 (disguised as T2) by interface I1" in { - val command = ExerciseCommand(idT2, cid1, "C1", ValueRecord(None, ImmArray.empty)) - run(command) shouldBe a[Left[_, _]] - } - "be unable to exercise T2 (disguised as T1) by interface I1" in { - val command = ExerciseCommand(idT1, cid2, "C1", ValueRecord(None, ImmArray.empty)) - run(command) shouldBe a[Left[_, _]] - } - */ + "be unable to exercise T1 (disguised as T2) by interface I1" in { + val command = ExerciseCommand(idT2, cid1, "C1", ValueRecord(None, ImmArray.empty)) + run(command) shouldBe a[Left[_, _]] + } + "be unable to exercise T2 (disguised as T1) by interface I1" in { + val command = ExerciseCommand(idT1, cid2, "C1", ValueRecord(None, ImmArray.empty)) + run(command) shouldBe a[Left[_, _]] + } "be unable to exercise T2 (disguised as T1) by interface I2 (stopped in preprocessor)" in { val command = ExerciseCommand(idT1, cid2, "C2", ValueRecord(None, ImmArray.empty)) preprocess(command) shouldBe a[Left[_, _]] @@ -230,18 +226,16 @@ class InterfacesTest preprocess(command) shouldBe a[Left[_, _]] } - // TODO https://github.com/digital-asset/daml/issues/11703 - // Enable these tests. - /* - "be unable to exercise T1 (disguised as T2) by interface I1 via 'exercise by interface'" in { - val command = ExerciseByInterfaceCommand(idI2, idT2, cid1, "C1", ValueRecord(None, ImmArray.empty)) - run(command) shouldBe a[Left[_, _]] - } - "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[_, _]] - } - */ + "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[_, _]] + } + "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[_, _]] + } "be unable to exercise T2 (disguised as T1) by interface I2 via 'exercise by interface' (stopped in preprocessor)" in { val command = ExerciseByInterfaceCommand(idI2, idT1, cid2, "C2", ValueRecord(None, ImmArray.empty)) 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 5a4e5b8a658f..a815167c3129 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 @@ -300,6 +300,8 @@ private[lf] final class Compiler( private val Env2 = Env1.pushVar private val Position3 = Env2.nextPosition private val Env3 = Env2.pushVar + private val Position4 = Env3.nextPosition + private val Env4 = Env3.pushVar private[this] def topLevelFunction1[SDefRef <: t.SDefinitionRef: LabelModule.Allowed]( ref: SDefRef @@ -322,6 +324,13 @@ private[lf] final class Compiler( ): (SDefRef, SDefinition) = topLevelFunction(ref)(s.SEAbs(3, body(Position1, Position2, Position3, Env3))) + private[this] def topLevelFunction4[SDefRef <: t.SDefinitionRef: LabelModule.Allowed]( + ref: SDefRef + )( + body: (Position, Position, Position, Position, Env) => s.SExpr + ): (SDefRef, SDefinition) = + topLevelFunction(ref)(s.SEAbs(4, body(Position1, Position2, Position3, Position4, Env4))) + @throws[PackageNotFound] @throws[CompilationError] def unsafeCompile(cmds: ImmArray[Command]): t.SExpr = @@ -398,9 +407,10 @@ private[lf] final class Compiler( addDef(compileCreateInterface(identifier)) addDef(compileFetchInterface(identifier)) addDef(compileInterfacePrecond(identifier, iface.param, iface.precond)) - iface.fixedChoices.values.foreach( - builder += compileFixedChoice(identifier, iface.param, _) - ) + iface.fixedChoices.values.foreach { choice => + addDef(compileInterfaceChoice(identifier, iface.param, choice)) + addDef(compileInterfaceGuardedChoice(identifier, iface.param, choice)) + } } builder.result() @@ -848,8 +858,14 @@ private[lf] final class Compiler( t.CreateDefRef(iface)(compile(env, arg)) case UpdateExercise(tmplId, chId, cidE, argE) => t.ChoiceDefRef(tmplId, chId)(compile(env, cidE), compile(env, argE)) - case UpdateExerciseInterface(ifaceId, chId, cidE, argE) => + case UpdateExerciseInterface(ifaceId, chId, cidE, argE, None) => t.ChoiceDefRef(ifaceId, chId)(compile(env, cidE), compile(env, argE)) + case UpdateExerciseInterface(ifaceId, chId, cidE, argE, Some(guardE)) => + t.GuardedChoiceDefRef(ifaceId, chId)( + compile(env, cidE), + compile(env, argE), + compile(env, guardE), + ) case UpdateExerciseByKey(tmplId, chId, keyE, argE) => t.ChoiceByKeyDefRef(tmplId, chId)(compile(env, keyE), compile(env, argE)) case UpdateGetTime => @@ -1078,9 +1094,33 @@ private[lf] final class Compiler( } } + // Apply choice guard (if given) and abort transaction if false. + // Otherwise continue with exercise. + private[this] def withChoiceGuard( + env: Env, + guardPos: Option[Position], + payloadPos: Position, + cidPos: Position, + choiceName: ChoiceName, + byInterface: Option[TypeConName], + )(body: Env => s.SExpr): s.SExpr = { + guardPos match { + case None => body(env) + case Some(guardPos) => + let( + env, + SBApplyChoiceGuard(choiceName, byInterface)( + env.toSEVar(guardPos), + env.toSEVar(payloadPos), + env.toSEVar(cidPos), + ), + ) { (_, _env) => body(_env) } + } + } + // TODO https://github.com/digital-asset/daml/issues/10810: // Try to factorise this with compileChoiceBody above. - private[this] def compileFixedChoiceBody( + private[this] def compileInterfaceChoiceBody( env: Env, ifaceId: TypeConName, param: ExprVarName, @@ -1089,38 +1129,69 @@ private[lf] final class Compiler( choiceArgPos: Position, cidPos: Position, tokenPos: Position, + guardPos: Option[Position], ) = let(env, SBUFetchInterface(ifaceId)(env.toSEVar(cidPos))) { (payloadPos, _env) => val env = _env.bindExprVar(param, payloadPos).bindExprVar(choice.argBinder._1, choiceArgPos) - let( - env, - SBResolveSBUBeginExercise(choice.name, choice.consuming, byKey = false, ifaceId = ifaceId)( - env.toSEVar(payloadPos), - env.toSEVar(choiceArgPos), - env.toSEVar(cidPos), - compile(env, choice.controllers), - choice.choiceObservers match { - case Some(observers) => compile(env, observers) - case None => s.SEValue.EmptyList - }, - ), - ) { (_, _env) => - val env = _env.bindExprVar(choice.selfBinder, cidPos) - s.SEScopeExercise(app(compile(env, choice.update), env.toSEVar(tokenPos))) + withChoiceGuard( + env = env, + guardPos = guardPos, + payloadPos = payloadPos, + cidPos = cidPos, + choiceName = choice.name, + byInterface = Some(ifaceId), + ) { env => + let( + env, + SBResolveSBUBeginExercise( + choice.name, + choice.consuming, + byKey = false, + ifaceId = ifaceId, + )( + env.toSEVar(payloadPos), + env.toSEVar(choiceArgPos), + env.toSEVar(cidPos), + compile(env, choice.controllers), + choice.choiceObservers match { + case Some(observers) => compile(env, observers) + case None => s.SEValue.EmptyList + }, + ), + ) { (_, _env) => + val env = _env.bindExprVar(choice.selfBinder, cidPos) + s.SEScopeExercise(app(compile(env, choice.update), env.toSEVar(tokenPos))) + } } } - private[this] def compileFixedChoice( + private[this] def compileInterfaceChoice( ifaceId: TypeConName, param: ExprVarName, choice: TemplateChoice, ): (t.SDefinitionRef, SDefinition) = topLevelFunction3(t.ChoiceDefRef(ifaceId, choice.name)) { (cidPos, choiceArgPos, tokenPos, env) => - compileFixedChoiceBody(env, ifaceId, param, choice)( + compileInterfaceChoiceBody(env, ifaceId, param, choice)( choiceArgPos, cidPos, tokenPos, + None, + ) + } + + private[this] def compileInterfaceGuardedChoice( + ifaceId: TypeConName, + param: ExprVarName, + choice: TemplateChoice, + ): (t.SDefinitionRef, SDefinition) = + topLevelFunction4(t.GuardedChoiceDefRef(ifaceId, choice.name)) { + (cidPos, choiceArgPos, guardPos, tokenPos, env) => + compileInterfaceChoiceBody(env, ifaceId, param, choice)( + choiceArgPos, + cidPos, + tokenPos, + Some(guardPos), ) } @@ -1424,6 +1495,24 @@ private[lf] final class Compiler( } } + private[this] def compileExerciseByInterface( + interfaceId: TypeConName, + templateId: TypeConName, + contractId: SValue, + choiceId: ChoiceName, + argument: SValue, + ): s.SExpr = + unaryFunction(Env.Empty) { (tokenPos, env) => + let(env, SBGuardTemplateId(templateId)(s.SEValue(contractId))) { (guardPos, env) => + t.GuardedChoiceDefRef(interfaceId, choiceId)( + s.SEValue(contractId), + s.SEValue(argument), + env.toSEVar(guardPos), + env.toSEVar(tokenPos), + ) + } + } + private[this] def compileCommand(cmd: Command): s.SExpr = cmd match { case Command.Create(templateId, argument) => t.CreateDefRef(templateId)(s.SEValue(argument)) @@ -1431,10 +1520,8 @@ private[lf] final class Compiler( t.CreateByInterfaceDefRef(templateId, interfaceId)(s.SEValue(argument)) case Command.Exercise(templateId, contractId, choiceId, argument) => t.ChoiceDefRef(templateId, choiceId)(s.SEValue(contractId), s.SEValue(argument)) - case Command.ExerciseByInterface(interfaceId, templateId @ _, contractId, choiceId, argument) => - // TODO https://github.com/digital-asset/daml/issues/11703 - // Ensure that fetched template has expected templateId. - t.ChoiceDefRef(interfaceId, choiceId)(s.SEValue(contractId), s.SEValue(argument)) + case Command.ExerciseByInterface(interfaceId, templateId, contractId, choiceId, argument) => + compileExerciseByInterface(interfaceId, templateId, contractId, choiceId, argument) case Command.ExerciseInterface(interfaceId, contractId, choiceId, argument) => t.ChoiceDefRef(interfaceId, choiceId)(s.SEValue(contractId), s.SEValue(argument)) case Command.ExerciseByKey(templateId, contractKey, choiceId, argument) => diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala index 079cb80de752..d734015b1f61 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Pretty.scala @@ -103,6 +103,15 @@ private[lf] object Pretty { prettyContractId(key.cids.head) case ValueExceedsMaxNesting => text(s"Value exceeds maximum nesting value of 100") + case ChoiceGuardFailed(cid, templateId, choiceName, byInterface) => ( + text(s"Choice guard failed for") & prettyTypeConName(templateId) & + text(s"contract") & prettyContractId(cid) & + text(s"when exercising choice $choiceName") & + (byInterface match { + case None => text("by template") + case Some(interfaceId) => text("by interface") & prettyTypeConName(interfaceId) + }) + ) } } diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Profile.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Profile.scala index 118fcf0241d7..7f7dc860a5b9 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Profile.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Profile.scala @@ -245,6 +245,7 @@ object Profile { implicit val implementsDefRef: Allowed[ImplementsDefRef] = allowAll implicit val implementsMethodDefRef: Allowed[ImplementsMethodDefRef] = allowAll implicit val choiceDefRef: Allowed[ChoiceDefRef] = allowAll + implicit val guardedChoiceDefRef: Allowed[GuardedChoiceDefRef] = allowAll implicit val fetchDefRef: Allowed[FetchDefRef] = allowAll implicit val choiceByKeyDefRef: Allowed[ChoiceByKeyDefRef] = allowAll implicit val fetchByKeyDefRef: Allowed[FetchByKeyDefRef] = allowAll @@ -273,6 +274,8 @@ object Profile { case ImplementsMethodDefRef(tmplRef, ifaceId, methodName) => s"implementsMethod @${tmplRef.qualifiedName} @${ifaceId.qualifiedName} ${methodName}" case ChoiceDefRef(tmplRef, name) => s"exercise @${tmplRef.qualifiedName} ${name}" + case GuardedChoiceDefRef(tmplRef, name) => + s"guarded exercise @${tmplRef.qualifiedName} ${name}" case FetchDefRef(tmplRef) => s"fetch @${tmplRef.qualifiedName}" case ChoiceByKeyDefRef(tmplRef, name) => s"exerciseByKey @${tmplRef.qualifiedName} ${name}" diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala index 41ff5e8f0596..f89aaf924411 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltin.scala @@ -1152,6 +1152,40 @@ private[lf] object SBuiltin { } } + final case class SBApplyChoiceGuard( + choiceName: ChoiceName, + byInterface: Option[TypeConName], + ) extends SBuiltin(3) { + override private[speedy] def execute( + args: util.ArrayList[SValue], + machine: Machine, + ): Unit = { + val guard = args.get(0) + val payload = getSRecord(args, 1) + val coid = getSContractId(args, 2) + val templateId = payload.id + + machine.ctrl = SEApp(SEValue(guard), Array(SEValue(payload))) + machine.pushKont(KCheckChoiceGuard(machine, coid, templateId, choiceName, byInterface)) + } + } + + final case class SBGuardTemplateId( + templateId: TypeConName + ) extends SBuiltin(2) { + override private[speedy] def execute( + args: util.ArrayList[SValue], + machine: Machine, + ): Unit = { + val coid = getSContractId(args, 0) + val record = getSRecord(args, 1) + if (record.id != templateId) + machine.ctrl = SEDamlException(IE.WronglyTypedContract(coid, templateId, record.id)) + else + machine.returnValue = SBool(true) + } + } + final case class SBResolveSBUBeginExercise( choiceName: ChoiceName, consuming: Boolean, diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala index fdcf266d3ed9..b7a6608e12ea 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SExpr.scala @@ -415,6 +415,8 @@ object SExpr { final case class LfDefRef(ref: DefinitionRef) extends SDefinitionRef // references to definitions generated by the Speedy compiler final case class ChoiceDefRef(ref: DefinitionRef, choiceName: ChoiceName) extends SDefinitionRef + final case class GuardedChoiceDefRef(ref: DefinitionRef, choiceName: ChoiceName) + extends SDefinitionRef final case class ChoiceByKeyDefRef(ref: DefinitionRef, choiceName: ChoiceName) extends SDefinitionRef final case class CreateDefRef(ref: DefinitionRef) extends SDefinitionRef diff --git a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala index 9b9e047828b4..82e6efbbdbef 100644 --- a/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala +++ b/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/Speedy.scala @@ -1332,6 +1332,36 @@ private[lf] object Speedy { } } + private[speedy] final case class KCheckChoiceGuard( + machine: Machine, + coid: V.ContractId, + templateId: TypeConName, + choiceName: ChoiceName, + byInterface: Option[TypeConName], + ) extends Kont { + def abort[E](): E = + throw SErrorDamlException( + interpretation.Error.ChoiceGuardFailed( + coid, + templateId, + choiceName, + byInterface, + ) + ) + + def execute(v: SValue) = { + v match { + case SValue.SBool(b) => + if (b) + machine.returnValue = SValue.SUnit + else + abort() + case _ => + throw SErrorCrash("KCheckChoiceGuard", "Expected SBool value.") + } + } + } + /** unwindToHandler is called when an exception is thrown by the builtin SBThrow or * re-thrown by the builtin SBTryHandler. If a catch-handler is found, we initiate * execution of the handler code (which might decide to re-throw). Otherwise we call @@ -1351,6 +1381,15 @@ private[lf] object Speedy { onLedger.ptx = onLedger.ptx.abortExercises } unwind() + case k: KCheckChoiceGuard => { + // We must abort, because the transaction has failed in a way that is + // unrecoverable (it depends on the state of an input contract that + // we may not have the authority to fetch). + machine.kontStack.clear() + machine.env.clear() + machine.envBase = 0 + k.abort() + } case _ => unwind() } diff --git a/daml-lf/language/src/main/scala/com/digitalasset/daml/lf/language/Ast.scala b/daml-lf/language/src/main/scala/com/digitalasset/daml/lf/language/Ast.scala index d5403cdaf75c..790dbeff702d 100644 --- a/daml-lf/language/src/main/scala/com/digitalasset/daml/lf/language/Ast.scala +++ b/daml-lf/language/src/main/scala/com/digitalasset/daml/lf/language/Ast.scala @@ -504,6 +504,11 @@ object Ast { choice: ChoiceName, cidE: Expr, argE: Expr, + guardE: Option[Expr], + // `guardE` is an optional expression of type Interface -> Bool which is evaluated + // after fetching the contract but before running the exercise body. If the guard + // returns false, or an exception is raised during evaluation, the transaction is + // aborted. ) extends Update final case class UpdateExerciseByKey( templateId: TypeConName, diff --git a/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/AstRewriter.scala b/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/AstRewriter.scala index 5b0cc007d155..4b379b0b010c 100644 --- a/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/AstRewriter.scala +++ b/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/AstRewriter.scala @@ -166,8 +166,8 @@ private[daml] class AstRewriter( UpdateFetchInterface(apply(interface), apply(contractId)) case UpdateExercise(templateId, choice, cid, arg) => UpdateExercise(apply(templateId), choice, cid, apply(arg)) - case UpdateExerciseInterface(interface, choice, cid, arg) => - UpdateExerciseInterface(apply(interface), choice, cid, apply(arg)) + case UpdateExerciseInterface(interface, choice, cid, arg, guard) => + UpdateExerciseInterface(apply(interface), choice, cid, apply(arg), guard.map(apply)) case UpdateExerciseByKey(templateId, choice, key, arg) => UpdateExerciseByKey(apply(templateId), choice, apply(key), apply(arg)) case UpdateGetTime => x diff --git a/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/ExprParser.scala b/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/ExprParser.scala index f4abdd51700c..65f0399df259 100644 --- a/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/ExprParser.scala +++ b/daml-lf/parser/src/main/scala/com/digitalasset/daml/lf/testing/parser/ExprParser.scala @@ -433,7 +433,9 @@ private[parser] class ExprParser[P](parserParameters: ParserParameters[P]) { private lazy val updateExerciseInterface = Id("exercise_by_interface") ~! `@` ~> fullIdentifier ~ id ~ expr0 ~ expr0 ^^ { case iface ~ choice ~ cid ~ arg => - UpdateExerciseInterface(iface, choice, cid, arg) + UpdateExerciseInterface(iface, choice, cid, arg, None) + // TODO https://github.com/digital-asset/daml/issues/11703 + // Implement choice guard argument. } private lazy val updateExerciseByKey = diff --git a/daml-lf/parser/src/test/scala/com/digitalasset/daml/lf/testing/parser/ParsersSpec.scala b/daml-lf/parser/src/test/scala/com/digitalasset/daml/lf/testing/parser/ParsersSpec.scala index a3df0c619d61..ee4377d92609 100644 --- a/daml-lf/parser/src/test/scala/com/digitalasset/daml/lf/testing/parser/ParsersSpec.scala +++ b/daml-lf/parser/src/test/scala/com/digitalasset/daml/lf/testing/parser/ParsersSpec.scala @@ -433,7 +433,7 @@ class ParsersSpec extends AnyWordSpec with ScalaCheckPropertyChecks with Matcher "exercise @Mod:T Choice cid arg" -> UpdateExercise(T.tycon, n"Choice", e"cid", e"arg"), "exercise_by_interface @Mod:I Choice cid arg" -> - UpdateExerciseInterface(I.tycon, n"Choice", e"cid", e"arg"), + UpdateExerciseInterface(I.tycon, n"Choice", e"cid", e"arg", None), "exercise_by_key @Mod:T Choice key arg" -> UpdateExerciseByKey(T.tycon, n"Choice", e"key", e"arg"), "fetch_by_key @Mod:T e" -> @@ -569,7 +569,7 @@ class ParsersSpec extends AnyWordSpec with ScalaCheckPropertyChecks with Matcher choice Feed; choice Rest; }; - implements '-pkgId-':Mod2:Referenceable { + implements '-pkgId-':Mod2:Referenceable { method uuid = "123e4567-e89b-12d3-a456-426614174000"; }; key @Party (Mod:Person {name} this) (\ (p: Party) -> p); @@ -756,7 +756,7 @@ class ParsersSpec extends AnyWordSpec with ScalaCheckPropertyChecks with Matcher val p = """ module Mod { - + interface (this: Person) = { precondition False; method asParty: Party; @@ -770,7 +770,7 @@ class ParsersSpec extends AnyWordSpec with ScalaCheckPropertyChecks with Matcher to upure @Int64 i; } ; } - + """ val interface = diff --git a/daml-lf/transaction/src/main/scala/com/daml/lf/Error.scala b/daml-lf/transaction/src/main/scala/com/daml/lf/Error.scala index 6c0c1e190b36..44c530dffefb 100644 --- a/daml-lf/transaction/src/main/scala/com/daml/lf/Error.scala +++ b/daml-lf/transaction/src/main/scala/com/daml/lf/Error.scala @@ -4,7 +4,7 @@ package com.daml.lf package interpretation -import com.daml.lf.data.Ref.{Location, Party, TypeConName} +import com.daml.lf.data.Ref.{Location, Party, TypeConName, ChoiceName} import com.daml.lf.transaction.{GlobalKey, NodeId} import com.daml.lf.language.Ast import com.daml.lf.value.Value @@ -109,4 +109,12 @@ object Error { final case object ValueExceedsMaxNesting extends Error + /** A choice guard returned false, invalidating some expectation. */ + final case class ChoiceGuardFailed( + coid: ContractId, + templateId: TypeConName, + choiceName: ChoiceName, + byInterface: Option[TypeConName], + ) extends Error + } diff --git a/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala b/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala index 805cbc0d9f87..6dca19070d47 100644 --- a/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala +++ b/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/Typing.scala @@ -906,10 +906,14 @@ private[validation] object Typing { chName: ChoiceName, cid: Expr, arg: Expr, + guard: Option[Expr], ): Type = { checkExpr(cid, TContractId(TTyCon(tpl))) val choice = handleLookup(ctx, interface.lookupInterfaceChoice(tpl, chName)) checkExpr(arg, choice.argBinder._2) + guard.foreach(guardExpr => checkExpr(guardExpr, TFun(TTyCon(tpl), TBool))) + // TODO https://github.com/digital-asset/daml/issues/11703 + // Verify that guard typechecks correctly in typechecker tests. TUpdate(choice.returnType) } @@ -963,8 +967,8 @@ private[validation] object Typing { typeOfCreateInterface(iface, arg) case UpdateExercise(tpl, choice, cid, arg) => typeOfExercise(tpl, choice, cid, arg) - case UpdateExerciseInterface(tpl, choice, cid, arg) => - typeOfExerciseInterface(tpl, choice, cid, arg) + case UpdateExerciseInterface(tpl, choice, cid, arg, guard) => + typeOfExerciseInterface(tpl, choice, cid, arg, guard) case UpdateExerciseByKey(tpl, choice, key, arg) => typeOfExerciseByKey(tpl, choice, key, arg) case UpdateFetch(tpl, cid) => diff --git a/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/iterable/ExprIterable.scala b/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/iterable/ExprIterable.scala index 6a528f4d5ee0..2c483a3838e7 100644 --- a/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/iterable/ExprIterable.scala +++ b/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/iterable/ExprIterable.scala @@ -89,8 +89,8 @@ private[validation] object ExprIterable { Iterator(contractId) case UpdateExercise(templateId @ _, choice @ _, cid, arg) => Iterator(cid, arg) - case UpdateExerciseInterface(interface @ _, choice @ _, cid, arg) => - Iterator(cid, arg) + case UpdateExerciseInterface(interface @ _, choice @ _, cid, arg, guard) => + Iterator(cid, arg) ++ guard.iterator case UpdateExerciseByKey(templateId @ _, choice @ _, key, arg) => Iterator(key, arg) case UpdateGetTime => Iterator.empty diff --git a/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/iterable/TypeIterable.scala b/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/iterable/TypeIterable.scala index 5aceae097de4..279df4cd4417 100644 --- a/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/iterable/TypeIterable.scala +++ b/daml-lf/validation/src/main/scala/com/digitalasset/daml/lf/validation/iterable/TypeIterable.scala @@ -114,10 +114,11 @@ private[validation] object TypeIterable { Iterator(TTyCon(templateId)) ++ iterator(cid) ++ iterator(arg) - case UpdateExerciseInterface(interface, choice @ _, cid, arg) => + case UpdateExerciseInterface(interface, choice @ _, cid, arg, guard) => Iterator(TTyCon(interface)) ++ iterator(cid) ++ - iterator(arg) + iterator(arg) ++ + guard.iterator.flatMap(iterator) case UpdateExerciseByKey(templateId, choice @ _, key, arg) => Iterator(TTyCon(templateId)) ++ iterator(key) ++ diff --git a/ledger/error/src/main/scala/com/daml/error/definitions/RejectionGenerators.scala b/ledger/error/src/main/scala/com/daml/error/definitions/RejectionGenerators.scala index 09376087d036..4ad730c05b3d 100644 --- a/ledger/error/src/main/scala/com/daml/error/definitions/RejectionGenerators.scala +++ b/ledger/error/src/main/scala/com/daml/error/definitions/RejectionGenerators.scala @@ -150,6 +150,11 @@ class RejectionGenerators(conformanceMode: Boolean) { .Error( renderedMessage ) + case _: LfInterpretationError.ChoiceGuardFailed => + LedgerApiErrors.CommandExecution.Interpreter.InvalidArgumentInterpretationError + .Error( + renderedMessage + ) } }