interfaces: Preserve/provide by_interface data for create actions. (#11639)

* interfaces: Preserve by_interface data for create.

Part of #10915

This was a lot more involved than fetch or exercise. The first issue is
that we need to preserve the interface id into speedy, so it needs a
separate primitive ("experimental" won't cut it). Second, because
speedy's create requires the template definition, and now the interface
id as well, we basically need to compile a separate version of "create"
for each interface that a template implements, hence the separate
`CreateByInterfaceDefRef(templateId, ifaceId)`.

changelog_begin
changelog_end

* scalafmt and refactoring

* fixx merge conflict

* fix silly mistakes
This commit is contained in:
Sofia Faro 2021-11-11 12:57:55 +00:00 committed by GitHub
parent a9de728575
commit 87f282c7f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 148 additions and 27 deletions

View File

@ -299,6 +299,10 @@ alphaUpdate env = \case
UCreate t2 e2 -> alphaTypeCon t1 t2
&& alphaExpr' env e1 e2
_ -> False
UCreateInterface t1 e1 -> \case
UCreateInterface t2 e2 -> alphaTypeCon t1 t2
&& alphaExpr' env e1 e2
_ -> False
UExercise t1 c1 e1a e1b -> \case
UExercise t2 c2 e2a e2b -> alphaTypeCon t1 t2
&& c1 == c2

View File

@ -654,6 +654,14 @@ data Update
, creArg :: !Expr
-- ^ Argument for the contract template.
}
-- | Create contract instance based on interface payload.
| UCreateInterface
{ creInterface :: !(Qualified TypeConName)
-- ^ Interface type.
, creArg :: !Expr
-- ^ Payload expression.
}
-- | Exercise choice on a contract given a contract ID.
| UExercise
{ exeTemplate :: !(Qualified TypeConName)

View File

@ -135,6 +135,7 @@ freeVarsStep = \case
UPureF t e -> freeVarsInType t <> e
UBindF b e -> goBinding b e
UCreateF _ e -> e
UCreateInterfaceF _ e -> e
UExerciseF _ _ e1 e2 -> e1 <> e2
UExerciseInterfaceF _ _ e1 e2 -> e1 <> e2
UExerciseByKeyF _ _ e1 e2 -> e1 <> e2

View File

@ -412,6 +412,8 @@ instance Pretty Update where
$$ keyword_ "in" <-> pPrintPrec lvl precELam body
UCreate tpl arg ->
pPrintAppKeyword lvl prec "create" [tplArg tpl, TmArg arg]
UCreateInterface interface arg ->
pPrintAppKeyword lvl prec "create_interface" [interfaceArg interface, TmArg arg]
UExercise tpl choice cid arg ->
-- NOTE(MH): Converting the choice name into a variable is a bit of a hack.
pPrintAppKeyword lvl prec "exercise"

View File

@ -63,6 +63,7 @@ data UpdateF expr
= UPureF !Type !expr
| UBindF !(BindingF expr) !expr
| UCreateF !(Qualified TypeConName) !expr
| UCreateInterfaceF !(Qualified TypeConName) !expr
| UExerciseF !(Qualified TypeConName) !ChoiceName !expr !expr
| UExerciseInterfaceF !(Qualified TypeConName) !ChoiceName !expr !expr
| UExerciseByKeyF !(Qualified TypeConName) !ChoiceName !expr !expr
@ -111,6 +112,7 @@ projectUpdate = \case
UPure a b -> UPureF a b
UBind a b -> UBindF (projectBinding a) b
UCreate a b -> UCreateF a b
UCreateInterface a b -> UCreateInterfaceF a b
UExercise a b c d -> UExerciseF a b c d
UExerciseInterface a b c d -> UExerciseInterfaceF a b c d
UExerciseByKey a b c d -> UExerciseByKeyF a b c d
@ -130,6 +132,7 @@ embedUpdate = \case
UPureF a b -> UPure a b
UBindF a b -> UBind (embedBinding a) b
UCreateF a b -> UCreate a b
UCreateInterfaceF a b -> UCreateInterface a b
UExerciseF a b c d -> UExercise a b c d
UExerciseInterfaceF a b c d -> UExerciseInterface a b c d
UExerciseByKeyF a b c d -> UExerciseByKey a b c d

View File

@ -244,6 +244,9 @@ applySubstInUpdate subst = \case
UCreate templateName e -> UCreate
templateName
(applySubstInExpr subst e)
UCreateInterface interface e -> UCreateInterface
interface
(applySubstInExpr subst e)
UExercise templateName choiceName e1 e2 -> UExercise
templateName
choiceName

View File

@ -681,6 +681,10 @@ decodeUpdate LF1.Update{..} = mayDecode "updateSum" updateSum $ \case
fmap EUpdate $ UCreate
<$> mayDecode "update_CreateTemplate" mbTycon decodeTypeConName
<*> mayDecode "update_CreateExpr" mbExpr decodeExpr
LF1.UpdateSumCreateInterface (LF1.Update_CreateInterface mbTycon mbExpr) ->
fmap EUpdate $ UCreateInterface
<$> mayDecode "update_CreateInterfaceInterface" mbTycon decodeTypeConName
<*> mayDecode "update_CreateInterfaceExpr" mbExpr decodeExpr
LF1.UpdateSumExercise LF1.Update_Exercise{..} ->
fmap EUpdate $ UExercise
<$> mayDecode "update_ExerciseTemplate" update_ExerciseTemplate decodeTypeConName

View File

@ -746,6 +746,10 @@ encodeUpdate = fmap (P.Update . Just) . \case
update_CreateTemplate <- encodeQualTypeConName creTemplate
update_CreateExpr <- encodeExpr creArg
pure $ P.UpdateSumCreate P.Update_Create{..}
UCreateInterface{..} -> do
update_CreateInterfaceInterface <- encodeQualTypeConName creInterface
update_CreateInterfaceExpr <- encodeExpr creArg
pure $ P.UpdateSumCreateInterface P.Update_CreateInterface{..}
UExercise{..} -> do
update_ExerciseTemplate <- encodeQualTypeConName exeTemplate
update_ExerciseChoice <- encodeName unChoiceName exeChoice

View File

@ -584,6 +584,11 @@ checkCreate tpl arg = do
_ :: Template <- inWorld (lookupTemplate tpl)
checkExpr arg (TCon tpl)
checkCreateInterface :: MonadGamma m => Qualified TypeConName -> Expr -> m ()
checkCreateInterface iface arg = do
_ :: DefInterface <- inWorld (lookupInterface iface)
checkExpr arg (TCon iface)
typeOfExercise :: MonadGamma m =>
Qualified TypeConName -> ChoiceName -> Expr -> Expr -> m Type
typeOfExercise tpl chName cid arg = do
@ -645,6 +650,7 @@ typeOfUpdate = \case
UPure typ expr -> checkPure typ expr $> TUpdate typ
UBind binding body -> typeOfBind binding body
UCreate tpl arg -> checkCreate tpl arg $> TUpdate (TContractId (TCon tpl))
UCreateInterface iface arg -> checkCreateInterface iface arg $> TUpdate (TContractId (TCon iface))
UExercise tpl choice cid arg -> typeOfExercise tpl choice cid arg
UExerciseInterface tpl choice cid arg -> typeOfExerciseInterface tpl choice cid arg
UExerciseByKey tpl choice key arg -> typeOfExerciseByKey tpl choice key arg

View File

@ -311,6 +311,11 @@ convertPrim _ "UCreate" (TCon template :-> TUpdate (TContractId (TCon template')
ETmLam (mkVar "this", TCon template) $
EUpdate $ UCreate template (EVar (mkVar "this"))
convertPrim _ "UCreateInterface" (TCon interface :-> TUpdate (TContractId (TCon interface')))
| interface == interface' =
ETmLam (mkVar "this", TCon interface) $
EUpdate $ UCreateInterface interface (EVar (mkVar "this"))
convertPrim _ "UFetch" (TContractId (TCon template) :-> TUpdate (TCon template'))
| template == template' =
ETmLam (mkVar "this", TContractId (TCon template)) $
@ -415,13 +420,6 @@ convertPrim version "EToAnyContractKey"
ETmLam (mkVar "key", key) $
EToAny key (EVar $ mkVar "key")
convertPrim _ "UCreateInterface" (TCon interface :-> TUpdate (TContractId (TCon interface')))
| interface == interface' =
ETmLam (mkVar "this", TCon interface) $
EExperimental "RESOLVE_VIRTUAL_CREATE"
(TCon interface :-> TCon interface :-> TUpdate (TContractId (TCon interface)))
`ETmApp` EVar (mkVar "this") `ETmApp` EVar (mkVar "this")
convertPrim _ "ESignatoryInterface" (TCon interface :-> TList TParty) =
ETmLam (mkVar "this", TCon interface) $
EExperimental "RESOLVE_VIRTUAL_SIGNATORIES"

View File

@ -136,6 +136,8 @@ startFromUpdate seen world update = case update of
LF.UGetTime -> Set.empty
LF.UEmbedExpr _ upEx -> startFromExpr seen world upEx
LF.UCreate tpl _ -> Set.singleton (ACreate tpl)
LF.UCreateInterface{} ->
error "Interfaces are not supported"
LF.UExercise tpl choice _ _ -> Set.singleton (AExercise tpl choice)
LF.UExerciseInterface{} ->
-- TODO https://github.com/digital-asset/daml/issues/10810

View File

@ -1187,6 +1187,14 @@ message Update {
Expr expr = 2;
}
// Interface Create Update
message CreateInterface {
// Interface type
TypeConName interface = 1;
// Interface argument
Expr expr = 2;
}
// Exercise Update
message Exercise {
// Template type
@ -1293,6 +1301,7 @@ message Update {
TryCatch try_catch = 11; // *Available in versions >= 1.14*
ExerciseInterface exercise_interface = 12; // *Available in versions >= 1.dev*
FetchInterface fetch_interface = 13; // *Available in versions >= 1.dev*
CreateInterface create_interface = 14; // *Available in versions >= 1.dev*
}
}

View File

@ -1281,6 +1281,13 @@ private[archive] class DecodeV1(minor: LV.Minor) {
arg = decodeExpr(create.getExpr, definition),
)
case PLF.Update.SumCase.CREATE_INTERFACE =>
val create = lfUpdate.getCreateInterface
UpdateCreateInterface(
interface = decodeTypeConName(create.getInterface),
arg = decodeExpr(create.getExpr, definition),
)
case PLF.Update.SumCase.EXERCISE =>
val exercise = lfUpdate.getExercise
UpdateExercise(

View File

@ -363,6 +363,10 @@ private[daml] class EncodeV1(minor: LV.Minor) {
)
case UpdateCreate(templateId, arg) =>
builder.setCreate(PLF.Update.Create.newBuilder().setTemplate(templateId).setExpr(arg))
case UpdateCreateInterface(interface, arg) =>
builder.setCreateInterface(
PLF.Update.CreateInterface.newBuilder().setInterface(interface).setExpr(arg)
)
case UpdateFetch(templateId, contractId) =>
builder.setFetch(PLF.Update.Fetch.newBuilder().setTemplate(templateId).setCid(contractId))
case UpdateFetchInterface(interface, contractId) =>

View File

@ -375,6 +375,7 @@ private[lf] final class Compiler(
addDef(compileSignatories(identifier, tmpl))
addDef(compileObservers(identifier, tmpl))
tmpl.implements.values.foreach { impl =>
addDef(compileCreateByInterface(identifier, tmpl, impl.interface))
addDef(compileImplements(identifier, impl.interface))
impl.methods.values.foreach(method =>
addDef(compileImplementsMethod(identifier, impl.interface, method))
@ -392,6 +393,7 @@ private[lf] final class Compiler(
module.interfaces.foreach { case (ifaceName, iface) =>
val identifier = Identifier(pkgId, QualifiedName(module.name, ifaceName))
addDef(compileCreateInterface(identifier))
addDef(compileFetchInterface(identifier))
iface.fixedChoices.values.foreach(
builder += compileFixedChoice(identifier, iface.param, _)
@ -850,6 +852,8 @@ private[lf] final class Compiler(
compileEmbedExpr(env, e)
case UpdateCreate(tmplId, arg) =>
t.CreateDefRef(tmplId)(compile(env, arg))
case UpdateCreateInterface(iface, arg) =>
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) =>
@ -1549,26 +1553,24 @@ private[lf] final class Compiler(
ref -> SDefinition(withLabelT(ref, unsafeCompile(method.value)))
}
private[this] def compileCreate(
private[this] def compileCreateBody(
tmplId: Identifier,
tmpl: Template,
): (t.SDefinitionRef, SDefinition) = {
byInterface: Option[Identifier],
tmplArgPos: Position,
env: Env,
) = {
val precondsArray =
(Iterator(tmpl.precond) ++ (tmpl.implements.iterator.map(impl => impl._2.precond)))
.to(ImmArray)
val preconds = ECons(TBuiltin(BTBool), precondsArray, ENil(TBuiltin(BTBool)))
// Translates 'create Foo with <params>' into:
// CreateDefRef(tmplId) = \ <tmplArg> <token> ->
// let _ = $checkPrecond(tmplId)(<tmplArg> [tmpl.precond ++ [precond | precond <- tmpl.implements]]
// in $create <tmplArg> [tmpl.agreementText] [tmpl.signatories] [tmpl.observers] [tmpl.key]
topLevelFunction2(t.CreateDefRef(tmplId)) { (tmplArgPos, _, _env) =>
val env = _env.bindExprVar(tmpl.param, tmplArgPos)
val env2 = env.bindExprVar(tmpl.param, tmplArgPos)
// We check precondition in a separated builtin to prevent
// further evaluation of agreement, signatories, observers and key
// in case of failed precondition.
let(env, SBCheckPrecond(tmplId)(env.toSEVar(tmplArgPos), compile(env, preconds))) {
let(env2, SBCheckPrecond(tmplId)(env2.toSEVar(tmplArgPos), compile(env2, preconds))) {
(_, env) =>
SBUCreate(tmplId)(
SBUCreate(tmplId, byInterface)(
env.toSEVar(tmplArgPos),
compile(env, tmpl.agreementText),
compile(env, tmpl.signatories),
@ -1577,6 +1579,41 @@ private[lf] final class Compiler(
)
}
}
private[this] def compileCreate(
tmplId: Identifier,
tmpl: Template,
): (t.SDefinitionRef, SDefinition) = {
// Translates 'create Foo with <params>' into:
// CreateDefRef(tmplId) = \ <tmplArg> <token> ->
// let _ = $checkPrecond(tmplId)(<tmplArg> [tmpl.precond ++ [precond | precond <- tmpl.implements]]
// in $create <tmplArg> [tmpl.agreementText] [tmpl.signatories] [tmpl.observers] [tmpl.key]
topLevelFunction2(t.CreateDefRef(tmplId))((tmplArgPos, _, env) =>
compileCreateBody(tmplId, tmpl, None, tmplArgPos, env)
)
}
private[this] def compileCreateByInterface(
tmplId: Identifier,
tmpl: Template,
ifaceId: Identifier,
): (t.SDefinitionRef, SDefinition) = {
// Similar to compileCreate, but sets the 'byInterface' field in the transaction.
topLevelFunction2(t.CreateByInterfaceDefRef(tmplId, ifaceId))((tmplArgPos, _, env) =>
compileCreateBody(tmplId, tmpl, Some(ifaceId), tmplArgPos, env)
)
}
private[this] def compileCreateInterface(
ifaceId: Identifier
): (t.SDefinitionRef, SDefinition) = {
topLevelFunction2(t.CreateDefRef(ifaceId)) { (tmplArgPos, tokenPos, env) =>
SBResolveCreateByInterface(ifaceId)(
env.toSEVar(tmplArgPos),
env.toSEVar(tmplArgPos),
env.toSEVar(tokenPos),
)
}
}
private[this] def compileCreateAndExercise(

View File

@ -452,8 +452,12 @@ private[lf] object Pretty {
) + char(
']'
)
case SBUCreate(ref) =>
case SBUCreate(ref, None) =>
text("$create") + char('[') + text(ref.qualifiedName.toString) + char(']')
case SBUCreate(ref, Some(iface)) =>
text("$createByInterface") + char('[') + text(ref.qualifiedName.toString) + char(
','
) + text(iface.qualifiedName.toString) + char(']')
case SBUFetch(ref) =>
text("$fetch") + char('[') + text(ref.qualifiedName.toString) + char(']')
case SBGetTime => text("$getTime")

View File

@ -238,6 +238,7 @@ object Profile {
implicit val anonClosure: Allowed[AnonymousClosure.type] = allowAll
implicit val lfDefRef: Allowed[LfDefRef] = allowAll
implicit val createDefRef: Allowed[CreateDefRef] = allowAll
implicit val createByInterfaceDefRef: Allowed[CreateByInterfaceDefRef] = allowAll
implicit val keyDefRef: Allowed[KeyDefRef] = allowAll
implicit val signatoriesDefRef: Allowed[SignatoriesDefRef] = allowAll
implicit val observersDefRef: Allowed[ObserversDefRef] = allowAll
@ -262,6 +263,8 @@ object Profile {
case AnonymousClosure => "<lambda>"
case LfDefRef(ref) => ref.qualifiedName.toString()
case CreateDefRef(tmplRef) => s"create @${tmplRef.qualifiedName}"
case CreateByInterfaceDefRef(tmplRef, iface) =>
s"creatByInterface @${tmplRef.qualifiedName} @${iface.qualifiedName}"
case KeyDefRef(tmplRef) => s"keyAndMaintainers @${tmplRef.qualifiedName}"
case SignatoriesDefRef(tmplRef) => s"signatories @${tmplRef.qualifiedName}"
case ObserversDefRef(tmplRef) => s"observers @${tmplRef.qualifiedName}"

View File

@ -923,7 +923,8 @@ private[lf] object SBuiltin {
* -> Optional {key: key, maintainers: List Party} (template key, if present)
* -> ContractId arg
*/
final case class SBUCreate(templateId: TypeConName) extends OnLedgerBuiltin(5) {
final case class SBUCreate(templateId: TypeConName, byInterface: Option[TypeConName])
extends OnLedgerBuiltin(5) {
override protected def execute(
args: util.ArrayList[SValue],
machine: Machine,
@ -957,7 +958,7 @@ private[lf] object SBuiltin {
signatories = sigs,
stakeholders = sigs union obs,
key = mbKey,
byInterface = None, // TODO https://github.com/digital-asset/daml/issues/10915
byInterface = byInterface,
)
machine.addLocalContract(coid, templateId, createArg, sigs, obs, mbKey)
@ -1184,6 +1185,9 @@ private[lf] object SBuiltin {
machine.ctrl = SEVal(toDef(getSRecord(args, 0).id))
}
final case class SBResolveCreateByInterface(ifaceId: TypeConName)
extends SBResolveVirtual(ref => CreateByInterfaceDefRef(ref, ifaceId))
// Convert an interface to a given template type if possible. Since interfaces have the
// same representation as the underlying template, we only need to perform a check
// that the record type matches the template type.

View File

@ -405,6 +405,8 @@ object SExpr {
final case class ChoiceByKeyDefRef(ref: DefinitionRef, choiceName: ChoiceName)
extends SDefinitionRef
final case class CreateDefRef(ref: DefinitionRef) extends SDefinitionRef
final case class CreateByInterfaceDefRef(ref: DefinitionRef, iface: TypeConName)
extends SDefinitionRef
final case class FetchDefRef(ref: DefinitionRef) extends SDefinitionRef
final case class FetchByKeyDefRef(ref: DefinitionRef) extends SDefinitionRef
final case class LookupByKeyDefRef(ref: DefinitionRef) extends SDefinitionRef

View File

@ -487,6 +487,7 @@ object Ast {
final case class UpdatePure(t: Type, expr: Expr) extends Update
final case class UpdateBlock(bindings: ImmArray[Binding], body: Expr) extends Update
final case class UpdateCreate(templateId: TypeConName, arg: Expr) extends Update
final case class UpdateCreateInterface(interface: TypeConName, arg: Expr) extends Update
final case class UpdateFetch(templateId: TypeConName, contractId: Expr) extends Update
final case class UpdateFetchInterface(interface: TypeConName, contractId: Expr) extends Update
final case class UpdateExercise(

View File

@ -158,6 +158,8 @@ private[daml] class AstRewriter(
UpdateBlock(bindings.map(apply), apply(body))
case UpdateCreate(templateId, arg) =>
UpdateCreate(apply(templateId), apply(arg))
case UpdateCreateInterface(interface, arg) =>
UpdateCreateInterface(apply(interface), apply(arg))
case UpdateFetch(templateId, contractId) =>
UpdateFetch(apply(templateId), apply(contractId))
case UpdateFetchInterface(interface, contractId) =>

View File

@ -884,6 +884,12 @@ private[validation] object Typing {
TUpdate(TContractId(TTyCon(tpl)))
}
private def typeOfCreateInterface(iface: TypeConName, arg: Expr): Type = {
discard(handleLookup(ctx, interface.lookupInterface(iface)))
checkExpr(arg, TTyCon(iface))
TUpdate(TContractId(TTyCon(iface)))
}
private def typeOfExercise(
tpl: TypeConName,
chName: ChoiceName,
@ -954,6 +960,8 @@ private[validation] object Typing {
typeOfUpdateBlock(bindings, body)
case UpdateCreate(tpl, arg) =>
typeOfCreate(tpl, arg)
case UpdateCreateInterface(iface, arg) =>
typeOfCreateInterface(iface, arg)
case UpdateExercise(tpl, choice, cid, arg) =>
typeOfExercise(tpl, choice, cid, arg)
case UpdateExerciseInterface(tpl, choice, cid, arg) =>

View File

@ -81,6 +81,8 @@ private[validation] object ExprIterable {
bindings.iterator.map(_.bound) ++ Iterator(body)
case UpdateCreate(templateId @ _, arg) =>
Iterator(arg)
case UpdateCreateInterface(interface @ _, arg) =>
Iterator(arg)
case UpdateFetch(templateId @ _, contractId) =>
Iterator(contractId)
case UpdateFetchInterface(interface @ _, contractId) =>

View File

@ -101,6 +101,9 @@ private[validation] object TypeIterable {
case UpdateCreate(templateId, arg) =>
Iterator(TTyCon(templateId)) ++
iterator(arg)
case UpdateCreateInterface(interface, arg) =>
Iterator(TTyCon(interface)) ++
iterator(arg)
case UpdateFetch(templateId, contractId) =>
Iterator(TTyCon(templateId)) ++
iterator(contractId)