mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-19 16:57:40 +03:00
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:
parent
aa3e5a7dbe
commit
cf89f6a74d
@ -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 ()
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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..
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user