LF: enforce non-empty maintainer in contracts key. (#7597)

This fixes a bug in the Speedy interpreter.  With this change, the
interpreter crashes if it attempts to create a contract containing a 
contract key that has an empty set of maintainers.

This is a breaking change approved by Bernhard Elsner. 

CHANGELOG_BEGIN
- [LF] (Bug fix) enforce non-empty maintainers in contract key during
  contract creation.
CHANGELOG_END
This commit is contained in:
Remy 2020-10-08 14:59:41 +02:00 committed by GitHub
parent aa3e5a7dbe
commit cf89f6a74d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 108 additions and 10 deletions

View File

@ -0,0 +1,16 @@
-- @ERROR Attempt to create a contract key with an empty set of maintainers
module EmptyContractKeyMaintainers where
template NoMaintainer
with
sig: Party
where
signatory sig
key sig : Party
maintainer [] @Party
noMaintainer = scenario do
alice <- getParty "Alice"
submit alice $ create NoMaintainer with sig = alice
pure ()

View File

@ -229,6 +229,16 @@ prettyScenarioErrorError (Just err) = do
(prettyContractRef world)
scenarioError_ContractNotActiveContractRef
]
ScenarioErrorErrorUpdateEmptyContractKeyMaintainers ScenarioError_EmptyContractKeyMaintainers{..} ->
pure $
"Attempt to create a contract key with an empty set of maintainers in:"
$$ nest 2
( "create"
<-> prettyMay "<missing template id>" (prettyDefName world) scenarioError_EmptyContractKeyMaintainersTemplateId
$$ ( keyword_ "with"
$$ nest 2 (prettyMay "<missing argument>" (prettyValue' False 0 world) scenarioError_EmptyContractKeyMaintainersArg)
)
)
ScenarioErrorErrorScenarioContractNotActive ScenarioError_ContractNotActive{..} -> do
pure $ vcat
[ "Attempt to exercise a consumed contract"

View File

@ -165,6 +165,12 @@ message ScenarioError {
Location location = 3;
}
message EmptyContractKeyMaintainers {
Identifier template_id = 1;
Value arg = 2;
Value key = 3;
}
message ContractNotActive {
ContractRef contract_ref = 1;
NodeId consumed_by = 2;
@ -230,16 +236,17 @@ message ScenarioError {
// Errors related to update interpretation.
TemplatePreconditionViolated template_precond_violated = 14;
ContractNotActive update_local_contract_not_active = 15;
EmptyContractKeyMaintainers update_empty_contract_key_maintainers = 16;
// Errors related to scenario interpretation
ContractNotEffective scenario_contract_not_effective = 16;
ContractNotActive scenario_contract_not_active = 17;
ContractNotVisible scenario_contract_not_visible = 18;
CommitError scenario_commit_error = 19;
Empty scenario_mustfail_succeeded = 20;
string scenario_invalid_party_name = 21;
ContractKeyNotVisible scenario_contract_key_not_visible = 22;
string scenario_party_already_exists = 23;
ContractNotEffective scenario_contract_not_effective = 17;
ContractNotActive scenario_contract_not_active = 18;
ContractNotVisible scenario_contract_not_visible = 19;
CommitError scenario_commit_error = 20;
Empty scenario_mustfail_succeeded = 21;
string scenario_invalid_party_name = 22;
ContractKeyNotVisible scenario_contract_key_not_visible = 23;
string scenario_party_already_exists = 24;
}
}

View File

@ -117,6 +117,14 @@ final class Conversions(
.build
)
case SError.DamlEEmptyContractKeyMaintainers(tid, arg, key) =>
builder.setUpdateEmptyContractKeyMaintainers(
proto.ScenarioError.EmptyContractKeyMaintainers.newBuilder
.setTemplateId(convertIdentifier(tid))
.setArg(convertValue(arg))
.setKey(convertValue(key))
)
case SError.ScenarioErrorContractNotEffective(coid, tid, effectiveAt) =>
builder.setScenarioContractNotEffective(
proto.ScenarioError.ContractNotEffective.newBuilder

View File

@ -1633,6 +1633,36 @@ class EngineTest
err.msg should not include ("Boom")
err.msg should include("precondition violation")
}
"not be create if has an empty set of maintainer" in {
val templateId =
Identifier(basicTestsPkgId, "BasicTests:NoMaintainer")
val createArg =
ValueRecord(
Some(templateId),
ImmArray((Some[Name]("sig"), ValueParty(alice)))
)
val Right((cmds, globalCids)) = preprocessor
.preprocessCommands(ImmArray(CreateCommand(templateId, createArg)))
.consume(_ => None, lookupPackage, lookupKey)
val result = engine
.interpretCommands(
validating = false,
submitters = Set(alice),
commands = cmds,
ledgerTime = now,
submissionTime = now,
seeding = InitialSeeding.TransactionSeed(txSeed),
globalCids = globalCids,
)
.consume(_ => None, lookupPackage, lookupKey)
result shouldBe 'left
val Left(err) = result
err.msg should include("Update failed due to a contract key with an empty sey of maintainers")
}
}
"Engine#submit" should {

View File

@ -87,11 +87,17 @@ private[lf] object Pretty {
actual,
)
case DamlEEmptyContractKeyMaintainers(tid, arg, _) =>
text("Update failed due to a contract key with an empty sey of maintainers when creating") &
prettyTypeConName(tid) &
text("with") & prettyValue(true)(arg)
case DamlEDisallowedInputValueVersion(VersionRange(expectedMin, expectedMax), actual) =>
text("Update failed due to disallowed value version") /
text("Expected value version between") & text(expectedMin.protoValue) &
text("and") & text(expectedMax.protoValue) & text("but got") &
text(actual.protoValue)
}
// A minimal pretty-print of an update transaction node, without recursing into child nodes..

View File

@ -864,7 +864,12 @@ private[lf] object SBuiltin {
}
val sigs = extractParties(args.get(2))
val obs = extractParties(args.get(3))
val key = extractOptionalKeyWithMaintainers(args.get(4))
val mbKey = extractOptionalKeyWithMaintainers(args.get(4))
mbKey.foreach {
case Node.KeyWithMaintainers(key, maintainers) =>
if (maintainers.isEmpty)
throw DamlEEmptyContractKeyMaintainers(templateId, createArg.toValue, key)
}
val auth = machine.auth
val (coid, newPtx) = onLedger.ptx
.insertCreate(
@ -874,7 +879,7 @@ private[lf] object SBuiltin {
optLocation = machine.lastLocation,
signatories = sigs,
stakeholders = sigs union obs,
key = key,
key = mbKey,
)
.fold(err => throw DamlETransactionError(err), identity)

View File

@ -71,6 +71,13 @@ object SError {
reason: String,
) extends SErrorDamlException
/** A create with a contract key without maintainers */
final case class DamlEEmptyContractKeyMaintainers(
templateId: TypeConName,
arg: Value[ContractId],
key: Value[Nothing],
) extends SErrorDamlException
/** Errors from scenario interpretation. */
sealed trait SErrorScenario extends SError

View File

@ -4,6 +4,7 @@
module BasicTests where
import DA.Time
import DA.Date
@ -589,3 +590,11 @@ template ComputeContractKeyWhenExecutingCreate with
do
let _ignore = create ComputeContractKeyAfterEnsureClause with owner
pure ()
template NoMaintainer
with
sig: Party
where
signatory sig
key sig : Party
maintainer [] @Party