LF: enforce non-empty maintainers in contract key in fetchByKey (#7649)

This fixes a bug in the Speedy interpreter. With this change, the
interpreter crashes if it attempts to fetch using a contract key that
has an empty set of maintainers. This propagates to exerciseByKey as
this command is compiled using fetchByKey speedy built-in.

This is a breaking change approved by @bame-da .

CHANGELOG_BEGIN
    [LF] (Bug fix) enforce non-empty maintainers in contract key during fetchByKey and exericiseByKey.
CHANGELOG_END
This commit is contained in:
Remy 2020-10-15 12:22:56 +02:00 committed by GitHub
parent ad79acdb65
commit 9b2a7d59e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 100 additions and 6 deletions

View File

@ -1,5 +1,6 @@
-- @ERROR Attempt to create a contract key with an empty set of maintainers
-- @ERROR Attempt to fetch or lookup a contract key with an empty set of maintainers
-- @ERROR range=14:0-14:18; Attempt to create a contract key with an empty set of maintainers
-- @ERROR range=20:0-20:18; Attempt to fetch, lookup or exercise a contract key with an empty set of maintainers
-- @ERROR range=26:0-26:17; Attempt to fetch, lookup or exercise a contract key with an empty set of maintainers
module EmptyContractKeyMaintainers where
template NoMaintainer
@ -17,8 +18,13 @@ createNoMaintainer = scenario do
pure ()
lookupNoMaintainer = scenario do
alice <- getParty "Alice"
alice <- getParty "Alice"
submit alice $ lookupByKey @NoMaintainer alice
pure ()
submit alice $ lookupByKey @NoMaintainer alice
pure ()
fetchNoMaintainer = scenario do
alice <- getParty "Alice"
submit alice $ fetchByKey @NoMaintainer alice
pure ()

View File

@ -246,7 +246,7 @@ prettyScenarioErrorError (Just err) = do
]
ScenarioErrorErrorFetchEmptyContractKeyMaintainers ScenarioError_FetchEmptyContractKeyMaintainers{..} ->
pure $ vcat
[ "Attempt to fetch or lookup a contract key with an empty set of maintainers"
[ "Attempt to fetch, lookup or exercise a contract key with an empty set of maintainers"
, label_ "Template:"
$ prettyMay "<missing template id>"
(prettyDefName world)

View File

@ -683,6 +683,80 @@ class EngineTest
}
}
"exercise-by-key" should {
val seed = hash("exercise-by-key")
val now = Time.Timestamp.now
"crash if use a contract key with an empty set of maintainers" in {
val templateId =
Identifier(basicTestsPkgId, "BasicTests:NoMaintainer")
val cmds = ImmArray(
speedy.Command.ExerciseByKey(
templateId = templateId,
contractKey = SParty(alice),
choiceId = ChoiceName.assertFromString("Noop"),
argument = SValue.SUnit,
)
)
val result = engine
.interpretCommands(
validating = false,
submitters = Set(alice),
commands = cmds,
ledgerTime = now,
submissionTime = now,
seeding = InitialSeeding.TransactionSeed(seed),
globalCids = Set.empty,
)
.consume(_ => None, lookupPackage, lookupKey)
inside(result) {
case Left(err) =>
err.msg should include(
"Update failed due to a contract key with an empty sey of maintainers")
}
}
}
"fecth-by-key" should {
val seed = hash("fetch-by-key")
val now = Time.Timestamp.now
"crash if use a contract key with an empty set of maintainers" in {
val templateId =
Identifier(basicTestsPkgId, "BasicTests:NoMaintainer")
val cmds = ImmArray(
speedy.Command.FetchByKey(
templateId = templateId,
key = SParty(alice),
)
)
val result = engine
.interpretCommands(
validating = false,
submitters = Set(alice),
commands = cmds,
ledgerTime = now,
submissionTime = now,
seeding = InitialSeeding.TransactionSeed(seed),
globalCids = Set.empty,
)
.consume(_ => None, lookupPackage, lookupKey)
inside(result) {
case Left(err) =>
err.msg should include(
"Update failed due to a contract key with an empty sey of maintainers")
}
}
}
"create-and-exercise command" should {
val submissionSeed = hash("create-and-exercise command")
val templateId = Identifier(basicTestsPkgId, "BasicTests:Simple")

View File

@ -39,6 +39,11 @@ object Command {
coid: SContractId,
) extends Command
final case class FetchByKey(
templateId: Identifier,
key: SValue,
) extends Command
final case class CreateAndExercise(
templateId: Identifier,
createArgument: SValue,

View File

@ -1491,6 +1491,8 @@ private[lf] final class Compiler(
compileExerciseByKey(templateId, SEValue(contractKey), choiceId, SEValue(argument))
case Command.Fetch(templateId, coid) =>
FetchDefRef(templateId)(SEValue(coid))
case Command.FetchByKey(templateId, key) =>
FetchByKeyDefRef(templateId)(SEValue(key))
case Command.CreateAndExercise(templateId, createArg, choice, choiceArg) =>
compileCreateAndExercise(
templateId,

View File

@ -1148,6 +1148,8 @@ private[lf] object SBuiltin {
onLedger: OnLedger
): Unit = {
val keyWithMaintainers = extractKeyWithMaintainers(args.get(0))
if (keyWithMaintainers.maintainers.isEmpty)
throw DamlEFetchEmptyContractKeyMaintainers(templateId, keyWithMaintainers.key)
val gkey = GlobalKey(templateId, keyWithMaintainers.key)
// check if we find it locally
onLedger.ptx.keys.get(gkey) match {

View File

@ -598,3 +598,8 @@ template NoMaintainer
signatory sig
key sig : Party
maintainer [] @Party
nonconsuming choice Noop: ()
controller sig
do
pure ()