mirror of
https://github.com/digital-asset/daml.git
synced 2024-11-10 10:46:11 +03:00
Test expected behaviour when superfluous (i.e. unused) contracts are disclosed in commands (#14617)
* Resolves #14350 CHANGELOG_BEGIN * Engine/speedy-level tests for explicit disclosure (#14227): Test expected behaviour when superfluous (i.e. unused) contracts are disclosed in commands. CHANGELOG_END
This commit is contained in:
parent
3ed252a9c1
commit
26b48133b4
@ -111,7 +111,11 @@ class Engine(val config: EngineConfig = Engine.StableConfig) {
|
||||
|
||||
// TODO (drsk) remove this assertion once disclosed contracts feature becomes stable.
|
||||
// https://github.com/digital-asset/daml/issues/13952.
|
||||
assert(disclosures.isEmpty || config.allowedLanguageVersions.contains(LanguageVersion.v1_dev))
|
||||
assert(
|
||||
disclosures.isEmpty || config.allowedLanguageVersions.contains(
|
||||
LanguageVersion.Features.explicitDisclosure
|
||||
)
|
||||
)
|
||||
val submissionTime = cmds.ledgerEffectiveTime
|
||||
|
||||
for {
|
||||
@ -462,6 +466,7 @@ class Engine(val config: EngineConfig = Engine.StableConfig) {
|
||||
}
|
||||
ResultDone((tx, meta))
|
||||
}
|
||||
|
||||
case SResultFinal(_, None) =>
|
||||
ResultError(
|
||||
Error.Interpretation.Internal(
|
||||
|
@ -6,9 +6,8 @@ package engine
|
||||
|
||||
import java.io.File
|
||||
import com.daml.lf.archive.UniversalArchiveDecoder
|
||||
import com.daml.bazeltools.BazelRunfiles
|
||||
import com.daml.lf.data.Ref._
|
||||
import com.daml.lf.data._
|
||||
import com.daml.lf.data.{Ref, _}
|
||||
import com.daml.lf.language.Ast._
|
||||
import com.daml.lf.language.Util._
|
||||
import com.daml.lf.transaction.{
|
||||
@ -16,27 +15,34 @@ import com.daml.lf.transaction.{
|
||||
GlobalKeyWithMaintainers,
|
||||
Node,
|
||||
NodeId,
|
||||
Normalization,
|
||||
ReplayMismatch,
|
||||
SubmittedTransaction,
|
||||
Validation,
|
||||
VersionedTransaction,
|
||||
Transaction => Tx,
|
||||
TransactionVersion => TxVersions,
|
||||
}
|
||||
import com.daml.lf.transaction.{Normalization, Validation, ReplayMismatch}
|
||||
import com.daml.lf.value.Value
|
||||
import Value._
|
||||
import com.daml.lf.speedy.{ArrayList, InitialSeeding, SValue, svalue}
|
||||
import com.daml.bazeltools.BazelRunfiles.rlocation
|
||||
import com.daml.lf
|
||||
import com.daml.lf.speedy.{ArrayList, DisclosedContract, InitialSeeding, SValue, svalue}
|
||||
import com.daml.lf.speedy.SValue._
|
||||
import com.daml.lf.command._
|
||||
import com.daml.lf.crypto.Hash
|
||||
import com.daml.lf.engine.Error.Interpretation
|
||||
import com.daml.lf.engine.Error.Interpretation.DamlException
|
||||
import com.daml.lf.language.PackageInterface
|
||||
import com.daml.lf.transaction.test.TransactionBuilder.assertAsVersionedContract
|
||||
import com.daml.logging.LoggingContext
|
||||
import org.scalactic.Equality
|
||||
import org.scalatest.prop.TableDrivenPropertyChecks
|
||||
import org.scalatest.EitherValues
|
||||
import org.scalatest.{Assertion, EitherValues}
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.Inside._
|
||||
import org.scalatest.matchers.{MatchResult, Matcher}
|
||||
|
||||
import scala.collection.immutable.HashMap
|
||||
import scala.language.implicitConversions
|
||||
@ -52,104 +58,10 @@ class EngineTest
|
||||
extends AnyWordSpec
|
||||
with Matchers
|
||||
with TableDrivenPropertyChecks
|
||||
with EitherValues
|
||||
with BazelRunfiles {
|
||||
with EitherValues {
|
||||
|
||||
import EngineTest._
|
||||
|
||||
private def loadPackage(resource: String): (PackageId, Package, Map[PackageId, Package]) = {
|
||||
val packages = UniversalArchiveDecoder.assertReadFile(new File(rlocation(resource)))
|
||||
val (mainPkgId, mainPkg) = packages.main
|
||||
(mainPkgId, mainPkg, packages.all.toMap)
|
||||
}
|
||||
|
||||
private val (basicTestsPkgId, basicTestsPkg, allPackages) = loadPackage(
|
||||
"daml-lf/tests/BasicTests.dar"
|
||||
)
|
||||
|
||||
val basicTestsSignatures = language.PackageInterface(Map(basicTestsPkgId -> basicTestsPkg))
|
||||
|
||||
val withKeyTemplate = "BasicTests:WithKey"
|
||||
val BasicTests_WithKey = Identifier(basicTestsPkgId, withKeyTemplate)
|
||||
val withKeyContractInst: VersionedContractInstance =
|
||||
assertAsVersionedContract(
|
||||
ContractInstance(
|
||||
TypeConName(basicTestsPkgId, withKeyTemplate),
|
||||
ValueRecord(
|
||||
Some(BasicTests_WithKey),
|
||||
ImmArray(
|
||||
(Some[Ref.Name]("p"), ValueParty(alice)),
|
||||
(Some[Ref.Name]("k"), ValueInt64(42)),
|
||||
),
|
||||
),
|
||||
"",
|
||||
)
|
||||
)
|
||||
|
||||
val defaultContracts: Map[ContractId, VersionedContractInstance] =
|
||||
Map(
|
||||
toContractId("BasicTests:Simple:1") ->
|
||||
assertAsVersionedContract(
|
||||
ContractInstance(
|
||||
TypeConName(basicTestsPkgId, "BasicTests:Simple"),
|
||||
ValueRecord(
|
||||
Some(Identifier(basicTestsPkgId, "BasicTests:Simple")),
|
||||
ImmArray((Some[Name]("p"), ValueParty(party))),
|
||||
),
|
||||
"",
|
||||
)
|
||||
),
|
||||
toContractId("BasicTests:CallablePayout:1") ->
|
||||
assertAsVersionedContract(
|
||||
ContractInstance(
|
||||
TypeConName(basicTestsPkgId, "BasicTests:CallablePayout"),
|
||||
ValueRecord(
|
||||
Some(Identifier(basicTestsPkgId, "BasicTests:CallablePayout")),
|
||||
ImmArray(
|
||||
(Some[Ref.Name]("giver"), ValueParty(alice)),
|
||||
(Some[Ref.Name]("receiver"), ValueParty(bob)),
|
||||
),
|
||||
),
|
||||
"",
|
||||
)
|
||||
),
|
||||
toContractId("BasicTests:WithKey:1") ->
|
||||
withKeyContractInst,
|
||||
)
|
||||
|
||||
val defaultKey = Map(
|
||||
GlobalKey.assertBuild(
|
||||
TypeConName(basicTestsPkgId, withKeyTemplate),
|
||||
ValueRecord(None, ImmArray((None, ValueParty(alice)), (None, ValueInt64(42)))),
|
||||
)
|
||||
->
|
||||
toContractId("BasicTests:WithKey:1")
|
||||
)
|
||||
|
||||
private[this] val lookupContract = defaultContracts.get(_)
|
||||
|
||||
private[this] def lookupPackage(pkgId: PackageId): Option[Package] = {
|
||||
allPackages.get(pkgId)
|
||||
}
|
||||
|
||||
private[this] def lookupKey(key: GlobalKeyWithMaintainers): Option[ContractId] =
|
||||
(key.globalKey.templateId, key.globalKey.key) match {
|
||||
case (
|
||||
BasicTests_WithKey,
|
||||
ValueRecord(_, ImmArray((_, ValueParty(`alice`)), (_, ValueInt64(42)))),
|
||||
) =>
|
||||
Some(toContractId("BasicTests:WithKey:1"))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
|
||||
private[this] val suffixLenientEngine = newEngine()
|
||||
private[this] val suffixStrictEngine = newEngine(requireCidSuffixes = true)
|
||||
private[this] val preprocessor =
|
||||
new preprocessing.Preprocessor(
|
||||
ConcurrentCompiledPackages(suffixLenientEngine.config.getCompilerConfig)
|
||||
)
|
||||
|
||||
"minimal create command" should {
|
||||
val id = Identifier(basicTestsPkgId, "BasicTests:Simple")
|
||||
val let = Time.Timestamp.now()
|
||||
@ -666,7 +578,7 @@ class EngineTest
|
||||
}
|
||||
}
|
||||
|
||||
"fecth-by-key" should {
|
||||
"fetch-by-key" should {
|
||||
val seed = hash("fetch-by-key")
|
||||
|
||||
val now = Time.Timestamp.now()
|
||||
@ -749,6 +661,7 @@ class EngineTest
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"error if Speedy fails to find the key" in {
|
||||
val templateId = Identifier(basicTestsPkgId, "BasicTests:FailedFetchByKey")
|
||||
|
||||
@ -795,6 +708,61 @@ class EngineTest
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"unused disclosed contracts not saved to ledger" in {
|
||||
val templateId = Identifier(basicTestsPkgId, "BasicTests:WithKey")
|
||||
val usedContractSKey = SValue.SRecord(
|
||||
templateId,
|
||||
ImmArray("_1", "_2").map(Ref.Name.assertFromString),
|
||||
values = ArrayList(SValue.SParty(alice), SValue.SInt64(42)),
|
||||
)
|
||||
val usedContractKey = usedContractSKey.toNormalizedValue(TxVersions.minExplicitDisclosure)
|
||||
val unusedContractKey = Value.ValueRecord(
|
||||
None,
|
||||
ImmArray(
|
||||
None -> Value.ValueParty(alice),
|
||||
None -> Value.ValueInt64(69),
|
||||
),
|
||||
)
|
||||
val usedDisclosedContract = DisclosedContract(
|
||||
templateId,
|
||||
SValue.SContractId(toContractId("BasicTests:WithKey:1")),
|
||||
SValue.SRecord(
|
||||
templateId,
|
||||
ImmArray(Ref.Name.assertFromString("p"), Ref.Name.assertFromString("k")),
|
||||
ArrayList(SValue.SParty(alice), SValue.SInt64(42)),
|
||||
),
|
||||
ContractMetadata(
|
||||
now,
|
||||
Some(crypto.Hash.assertHashContractKey(templateId, usedContractKey)),
|
||||
ImmArray.empty,
|
||||
),
|
||||
)
|
||||
val unusedDisclosedContract = DisclosedContract(
|
||||
templateId,
|
||||
SValue.SContractId(toContractId("BasicTests:WithKey:2")),
|
||||
SValue.SRecord(
|
||||
templateId,
|
||||
ImmArray(Ref.Name.assertFromString("p"), Ref.Name.assertFromString("k")),
|
||||
ArrayList(SValue.SParty(alice), SValue.SInt64(42)),
|
||||
),
|
||||
ContractMetadata(
|
||||
now,
|
||||
Some(crypto.Hash.assertHashContractKey(templateId, unusedContractKey)),
|
||||
ImmArray.empty,
|
||||
),
|
||||
)
|
||||
val fetchByKeyCommand = speedy.Command.FetchByKey(
|
||||
templateId = templateId,
|
||||
key = usedContractSKey,
|
||||
)
|
||||
|
||||
ExplicitDisclosureTesting.unusedDisclosedContractsNotSavedToLedger(
|
||||
fetchByKeyCommand,
|
||||
unusedDisclosedContract,
|
||||
usedDisclosedContract,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"create-and-exercise command" should {
|
||||
@ -1379,13 +1347,10 @@ class EngineTest
|
||||
|
||||
reinterpreted shouldBe a[Right[_, _]]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"lookup by key" should {
|
||||
|
||||
val seed = hash("interpreting lookup by key nodes")
|
||||
|
||||
val lookedUpCid = toContractId("1")
|
||||
val lookerUpTemplate = "BasicTests:LookerUpByKey"
|
||||
val lookerUpTemplateId = Identifier(basicTestsPkgId, lookerUpTemplate)
|
||||
@ -1570,6 +1535,104 @@ class EngineTest
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"unused disclosed contracts not saved to ledger" in {
|
||||
val templateId = Identifier(basicTestsPkgId, "BasicTests:WithKey")
|
||||
val usedContractSKey = SValue.SRecord(
|
||||
templateId,
|
||||
ImmArray("_1", "_2").map(Ref.Name.assertFromString),
|
||||
values = ArrayList(SValue.SParty(alice), SValue.SInt64(42)),
|
||||
)
|
||||
val usedContractKey = Value.ValueRecord(
|
||||
None,
|
||||
ImmArray(
|
||||
None -> Value.ValueParty(alice),
|
||||
None -> Value.ValueInt64(42),
|
||||
),
|
||||
)
|
||||
val unusedContractKey = Value.ValueRecord(
|
||||
None,
|
||||
ImmArray(
|
||||
None -> Value.ValueParty(alice),
|
||||
None -> Value.ValueInt64(69),
|
||||
),
|
||||
)
|
||||
val usedDisclosedContract = DisclosedContract(
|
||||
templateId,
|
||||
SValue.SContractId(toContractId("BasicTests:WithKey:1")),
|
||||
SValue.SRecord(
|
||||
templateId,
|
||||
ImmArray(Ref.Name.assertFromString("p"), Ref.Name.assertFromString("k")),
|
||||
ArrayList(SValue.SParty(alice), SValue.SInt64(42)),
|
||||
),
|
||||
ContractMetadata(
|
||||
now,
|
||||
Some(crypto.Hash.assertHashContractKey(templateId, usedContractKey)),
|
||||
ImmArray.empty,
|
||||
),
|
||||
)
|
||||
val unusedDisclosedContract = DisclosedContract(
|
||||
templateId,
|
||||
SValue.SContractId(toContractId("BasicTests:WithKey:2")),
|
||||
SValue.SRecord(
|
||||
templateId,
|
||||
ImmArray(Ref.Name.assertFromString("p"), Ref.Name.assertFromString("k")),
|
||||
ArrayList(SValue.SParty(alice), SValue.SInt64(42)),
|
||||
),
|
||||
ContractMetadata(
|
||||
now,
|
||||
Some(crypto.Hash.assertHashContractKey(templateId, unusedContractKey)),
|
||||
ImmArray.empty,
|
||||
),
|
||||
)
|
||||
val lookupByKeyCommand = speedy.Command.LookupByKey(
|
||||
templateId = templateId,
|
||||
contractKey = usedContractSKey,
|
||||
)
|
||||
|
||||
ExplicitDisclosureTesting.unusedDisclosedContractsNotSavedToLedger(
|
||||
lookupByKeyCommand,
|
||||
unusedDisclosedContract,
|
||||
usedDisclosedContract,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"fetch template" should {
|
||||
val templateId = Identifier(basicTestsPkgId, "BasicTests:Simple")
|
||||
val usedDisclosedContract = DisclosedContract(
|
||||
templateId,
|
||||
SValue.SContractId(toContractId("BasicTests:Simple:1")),
|
||||
SValue.SRecord(
|
||||
templateId,
|
||||
ImmArray(Ref.Name.assertFromString("p")),
|
||||
ArrayList(SValue.SParty(alice)),
|
||||
),
|
||||
ContractMetadata(Time.Timestamp.now(), None, ImmArray.empty),
|
||||
)
|
||||
val unusedDisclosedContract = DisclosedContract(
|
||||
templateId,
|
||||
SValue.SContractId(toContractId("BasicTests:Simple:2")),
|
||||
SValue.SRecord(
|
||||
templateId,
|
||||
ImmArray(Ref.Name.assertFromString("p")),
|
||||
ArrayList(SValue.SParty(alice)),
|
||||
),
|
||||
ContractMetadata(Time.Timestamp.now(), None, ImmArray.empty),
|
||||
)
|
||||
|
||||
"unused disclosed contracts not saved to ledger" in {
|
||||
val fetchTemplateCommand = speedy.Command.FetchTemplate(
|
||||
templateId = templateId,
|
||||
coid = usedDisclosedContract.contractId,
|
||||
)
|
||||
|
||||
ExplicitDisclosureTesting.unusedDisclosedContractsNotSavedToLedger(
|
||||
fetchTemplateCommand,
|
||||
unusedDisclosedContract,
|
||||
usedDisclosedContract,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"getTime set dependsOnTime flag" in {
|
||||
@ -1743,7 +1806,7 @@ class EngineTest
|
||||
)
|
||||
)
|
||||
val contracts = defaultContracts + (fetcherCid -> fetcherInst)
|
||||
val lookupContract = contracts.get(_)
|
||||
val lookupContract = contracts.get _
|
||||
val correctCommand =
|
||||
ApiCommand.Exercise(
|
||||
withKeyId,
|
||||
@ -1892,7 +1955,7 @@ class EngineTest
|
||||
|
||||
"exceptions" should {
|
||||
val (exceptionsPkgId, _, allExceptionsPkgs) = loadPackage("daml-lf/tests/Exceptions.dar")
|
||||
val lookupPackage = allExceptionsPkgs.get(_)
|
||||
val lookupPackage = allExceptionsPkgs.get _
|
||||
val kId = Identifier(exceptionsPkgId, "Exceptions:K")
|
||||
val tId = Identifier(exceptionsPkgId, "Exceptions:T")
|
||||
val let = Time.Timestamp.now()
|
||||
@ -1908,7 +1971,7 @@ class EngineTest
|
||||
)
|
||||
)
|
||||
)
|
||||
val lookupContract = contracts.get(_)
|
||||
val lookupContract = contracts.get _
|
||||
def lookupKey(key: GlobalKeyWithMaintainers): Option[ContractId] =
|
||||
(key.globalKey.templateId, key.globalKey.key) match {
|
||||
case (
|
||||
@ -2041,7 +2104,7 @@ class EngineTest
|
||||
|
||||
"action node seeds" should {
|
||||
val (exceptionsPkgId, _, allExceptionsPkgs) = loadPackage("daml-lf/tests/Exceptions.dar")
|
||||
val lookupPackage = allExceptionsPkgs.get(_)
|
||||
val lookupPackage = allExceptionsPkgs.get _
|
||||
val kId = Identifier(exceptionsPkgId, "Exceptions:K")
|
||||
val seedId = Identifier(exceptionsPkgId, "Exceptions:NodeSeeds")
|
||||
val let = Time.Timestamp.now()
|
||||
@ -2057,7 +2120,7 @@ class EngineTest
|
||||
)
|
||||
)
|
||||
)
|
||||
val lookupContract = contracts.get(_)
|
||||
val lookupContract = contracts.get _
|
||||
def lookupKey(key: GlobalKeyWithMaintainers): Option[ContractId] =
|
||||
(key.globalKey.templateId, key.globalKey.key) match {
|
||||
case (
|
||||
@ -2093,6 +2156,7 @@ class EngineTest
|
||||
lookupKey,
|
||||
)
|
||||
}
|
||||
|
||||
"Only create and exercise nodes end up in actionNodeSeeds" in {
|
||||
val command = ApiCommand.CreateAndExercise(
|
||||
seedId,
|
||||
@ -2118,7 +2182,7 @@ class EngineTest
|
||||
|
||||
"global key lookups" should {
|
||||
val (exceptionsPkgId, _, allExceptionsPkgs) = loadPackage("daml-lf/tests/Exceptions.dar")
|
||||
val lookupPackage = allExceptionsPkgs.get(_)
|
||||
val lookupPackage = allExceptionsPkgs.get _
|
||||
val kId = Identifier(exceptionsPkgId, "Exceptions:K")
|
||||
val tId = Identifier(exceptionsPkgId, "Exceptions:GlobalLookups")
|
||||
val let = Time.Timestamp.now()
|
||||
@ -2134,7 +2198,7 @@ class EngineTest
|
||||
)
|
||||
)
|
||||
)
|
||||
val lookupContract = contracts.get(_)
|
||||
val lookupContract = contracts.get _
|
||||
def lookupKey(key: GlobalKeyWithMaintainers): Option[ContractId] =
|
||||
(key.globalKey.templateId, key.globalKey.key) match {
|
||||
case (
|
||||
@ -2249,24 +2313,134 @@ class EngineTest
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object EngineTest {
|
||||
|
||||
private implicit def logContext: LoggingContext = LoggingContext.ForTesting
|
||||
import Matchers._
|
||||
|
||||
private def hash(s: String) = crypto.Hash.hashPrivateKey(s)
|
||||
private def participant = Ref.ParticipantId.assertFromString("participant")
|
||||
private def byKeyNodes(tx: VersionedTransaction) =
|
||||
implicit val logContext: LoggingContext = LoggingContext.ForTesting
|
||||
|
||||
@SuppressWarnings(Array("org.wartremover.warts.Any"))
|
||||
implicit val resultEq: Equality[Either[Error, SValue]] = {
|
||||
case (Right(v1: SValue), Right(v2: SValue)) => svalue.Equality.areEqual(v1, v2)
|
||||
case (Left(e1), Left(e2)) => e1 == e2
|
||||
case _ => false
|
||||
}
|
||||
|
||||
implicit def qualifiedNameStr(s: String): QualifiedName =
|
||||
QualifiedName.assertFromString(s)
|
||||
|
||||
implicit def toName(s: String): Name =
|
||||
Name.assertFromString(s)
|
||||
|
||||
val (basicTestsPkgId, basicTestsPkg, allPackages) = loadPackage(
|
||||
"daml-lf/tests/BasicTests.dar"
|
||||
)
|
||||
|
||||
val basicTestsSignatures: PackageInterface =
|
||||
language.PackageInterface(Map(basicTestsPkgId -> basicTestsPkg))
|
||||
|
||||
val party: Ref.IdString.Party = Party.assertFromString("Party")
|
||||
val alice: Ref.IdString.Party = Party.assertFromString("Alice")
|
||||
val bob: Ref.IdString.Party = Party.assertFromString("Bob")
|
||||
val clara: Ref.IdString.Party = Party.assertFromString("Clara")
|
||||
|
||||
val dummySuffix: Bytes = Bytes.assertFromString("00")
|
||||
|
||||
val withKeyTemplate = "BasicTests:WithKey"
|
||||
val BasicTests_WithKey: lf.data.Ref.ValueRef = Identifier(basicTestsPkgId, withKeyTemplate)
|
||||
val withKeyContractInst: VersionedContractInstance =
|
||||
assertAsVersionedContract(
|
||||
ContractInstance(
|
||||
TypeConName(basicTestsPkgId, withKeyTemplate),
|
||||
ValueRecord(
|
||||
Some(BasicTests_WithKey),
|
||||
ImmArray(
|
||||
(Some[Ref.Name]("p"), ValueParty(alice)),
|
||||
(Some[Ref.Name]("k"), ValueInt64(42)),
|
||||
),
|
||||
),
|
||||
"",
|
||||
)
|
||||
)
|
||||
|
||||
val defaultContracts: Map[ContractId, VersionedContractInstance] =
|
||||
Map(
|
||||
toContractId("BasicTests:Simple:1") ->
|
||||
assertAsVersionedContract(
|
||||
ContractInstance(
|
||||
TypeConName(basicTestsPkgId, "BasicTests:Simple"),
|
||||
ValueRecord(
|
||||
Some(Identifier(basicTestsPkgId, "BasicTests:Simple")),
|
||||
ImmArray((Some[Name]("p"), ValueParty(party))),
|
||||
),
|
||||
"",
|
||||
)
|
||||
),
|
||||
toContractId("BasicTests:CallablePayout:1") ->
|
||||
assertAsVersionedContract(
|
||||
ContractInstance(
|
||||
TypeConName(basicTestsPkgId, "BasicTests:CallablePayout"),
|
||||
ValueRecord(
|
||||
Some(Identifier(basicTestsPkgId, "BasicTests:CallablePayout")),
|
||||
ImmArray(
|
||||
(Some[Ref.Name]("giver"), ValueParty(alice)),
|
||||
(Some[Ref.Name]("receiver"), ValueParty(bob)),
|
||||
),
|
||||
),
|
||||
"",
|
||||
)
|
||||
),
|
||||
toContractId("BasicTests:WithKey:1") ->
|
||||
withKeyContractInst,
|
||||
)
|
||||
|
||||
val defaultKey = Map(
|
||||
GlobalKey.assertBuild(
|
||||
TypeConName(basicTestsPkgId, withKeyTemplate),
|
||||
ValueRecord(None, ImmArray((None, ValueParty(alice)), (None, ValueInt64(42)))),
|
||||
)
|
||||
->
|
||||
toContractId("BasicTests:WithKey:1")
|
||||
)
|
||||
|
||||
val lookupContract: ContractId => Option[VersionedContractInstance] = defaultContracts.get
|
||||
|
||||
val suffixLenientEngine: Engine = newEngine()
|
||||
val suffixStrictEngine: Engine = newEngine(requireCidSuffixes = true)
|
||||
val preprocessor =
|
||||
new preprocessing.Preprocessor(
|
||||
ConcurrentCompiledPackages(suffixLenientEngine.config.getCompilerConfig)
|
||||
)
|
||||
|
||||
def loadPackage(resource: String): (PackageId, Package, Map[PackageId, Package]) = {
|
||||
val packages = UniversalArchiveDecoder.assertReadFile(new File(rlocation(resource)))
|
||||
val (mainPkgId, mainPkg) = packages.main
|
||||
(mainPkgId, mainPkg, packages.all.toMap)
|
||||
}
|
||||
|
||||
def lookupPackage(pkgId: PackageId): Option[Package] = {
|
||||
allPackages.get(pkgId)
|
||||
}
|
||||
|
||||
def lookupKey(key: GlobalKeyWithMaintainers): Option[ContractId] =
|
||||
(key.globalKey.templateId, key.globalKey.key) match {
|
||||
case (
|
||||
BasicTests_WithKey,
|
||||
ValueRecord(_, ImmArray((_, ValueParty(`alice`)), (_, ValueInt64(42)))),
|
||||
) =>
|
||||
Some(toContractId("BasicTests:WithKey:1"))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
|
||||
def hash(s: String): Hash = crypto.Hash.hashPrivateKey(s)
|
||||
def participant: Ref.IdString.ParticipantId = Ref.ParticipantId.assertFromString("participant")
|
||||
def byKeyNodes(tx: VersionedTransaction): Set[NodeId] =
|
||||
tx.nodes.collect { case (nodeId, node: Node.Action) if node.byKey => nodeId }.toSet
|
||||
|
||||
private val party = Party.assertFromString("Party")
|
||||
private val alice = Party.assertFromString("Alice")
|
||||
private val bob = Party.assertFromString("Bob")
|
||||
private val clara = Party.assertFromString("Clara")
|
||||
|
||||
private def newEngine(requireCidSuffixes: Boolean = false) =
|
||||
def newEngine(requireCidSuffixes: Boolean = false) =
|
||||
new Engine(
|
||||
EngineConfig(
|
||||
allowedLanguageVersions = language.LanguageVersion.DevVersions,
|
||||
@ -2275,28 +2449,13 @@ object EngineTest {
|
||||
)
|
||||
)
|
||||
|
||||
private implicit def qualifiedNameStr(s: String): QualifiedName =
|
||||
QualifiedName.assertFromString(s)
|
||||
|
||||
private implicit def toName(s: String): Name =
|
||||
Name.assertFromString(s)
|
||||
|
||||
private val dummySuffix = Bytes.assertFromString("00")
|
||||
|
||||
private def toContractId(s: String): ContractId =
|
||||
def toContractId(s: String): ContractId =
|
||||
ContractId.V1.assertBuild(crypto.Hash.hashPrivateKey(s), dummySuffix)
|
||||
|
||||
private def findNodeByIdx[Cid](nodes: Map[NodeId, Node], idx: Int) =
|
||||
def findNodeByIdx[Cid](nodes: Map[NodeId, Node], idx: Int): Option[Node] =
|
||||
nodes.collectFirst { case (nodeId, node) if nodeId.index == idx => node }
|
||||
|
||||
@SuppressWarnings(Array("org.wartremover.warts.Any"))
|
||||
private implicit def resultEq: Equality[Either[Error, SValue]] = {
|
||||
case (Right(v1: SValue), Right(v2: SValue)) => svalue.Equality.areEqual(v1, v2)
|
||||
case (Left(e1), Left(e2)) => e1 == e2
|
||||
case _ => false
|
||||
}
|
||||
|
||||
private def isReplayedBy(
|
||||
def isReplayedBy(
|
||||
recorded: VersionedTransaction,
|
||||
replayed: VersionedTransaction,
|
||||
): Either[ReplayMismatch, Unit] = {
|
||||
@ -2304,47 +2463,12 @@ object EngineTest {
|
||||
Validation.isReplayedBy(Normalization.normalizeTx(recorded), replayed)
|
||||
}
|
||||
|
||||
private def suffix(tx: VersionedTransaction) =
|
||||
def suffix(tx: VersionedTransaction): VersionedTransaction =
|
||||
data.assertRight(tx.suffixCid(_ => dummySuffix))
|
||||
|
||||
private[this] case class ReinterpretState(
|
||||
contracts: Map[ContractId, VersionedContractInstance],
|
||||
keys: Map[GlobalKey, ContractId],
|
||||
nodes: HashMap[NodeId, Node] = HashMap.empty,
|
||||
roots: BackStack[NodeId] = BackStack.empty,
|
||||
dependsOnTime: Boolean = false,
|
||||
nodeSeeds: BackStack[(NodeId, crypto.Hash)] = BackStack.empty,
|
||||
) {
|
||||
def commit(tr: Tx, meta: Tx.Metadata) = {
|
||||
val (newContracts, newKeys) = tr.fold((contracts, keys)) {
|
||||
case ((contracts, keys), (_, exe: Node.Exercise)) =>
|
||||
(contracts - exe.targetCoid, keys)
|
||||
case ((contracts, keys), (_, create: Node.Create)) =>
|
||||
(
|
||||
contracts.updated(
|
||||
create.coid,
|
||||
create.versionedCoinst,
|
||||
),
|
||||
create.key.fold(keys)(k =>
|
||||
keys.updated(GlobalKey.assertBuild(create.templateId, k.key), create.coid)
|
||||
),
|
||||
)
|
||||
case (acc, _) => acc
|
||||
}
|
||||
ReinterpretState(
|
||||
newContracts,
|
||||
newKeys,
|
||||
nodes ++ tr.nodes,
|
||||
roots :++ tr.roots,
|
||||
dependsOnTime || meta.dependsOnTime,
|
||||
nodeSeeds :++ meta.nodeSeeds,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Mimics Canton reinterpreation
|
||||
// Mimics Canton reinterpretation
|
||||
// requires a suffixed transaction.
|
||||
private def reinterpret(
|
||||
def reinterpret(
|
||||
engine: Engine,
|
||||
submitters: Set[Party],
|
||||
nodes: ImmArray[NodeId],
|
||||
@ -2434,4 +2558,109 @@ object EngineTest {
|
||||
)
|
||||
}
|
||||
|
||||
object ExplicitDisclosureTesting {
|
||||
def unusedDisclosedContractsNotSavedToLedger(
|
||||
cmd: speedy.Command,
|
||||
unusedDisclosedContract: DisclosedContract,
|
||||
usedDisclosedContract: DisclosedContract,
|
||||
): Assertion = {
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = Set(alice),
|
||||
readAs = Set.empty,
|
||||
commands = ImmArray(cmd),
|
||||
ledgerTime = Time.Timestamp.now(),
|
||||
submissionTime = Time.Timestamp.now(),
|
||||
seeding = InitialSeeding.TransactionSeed(hash(s"$cmd")),
|
||||
disclosures = ImmArray(unusedDisclosedContract, usedDisclosedContract),
|
||||
)
|
||||
.consume(_ => None, lookupPackage, lookupKey)
|
||||
|
||||
inside(result) { case Right((transaction, metadata)) =>
|
||||
transaction should haveDisclosedInputContracts(usedDisclosedContract)
|
||||
metadata should haveDisclosedContracts(usedDisclosedContract)(preprocessor)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings(
|
||||
Array(
|
||||
"org.wartremover.warts.JavaSerializable",
|
||||
"org.wartremover.warts.Product",
|
||||
"org.wartremover.warts.Serializable",
|
||||
)
|
||||
)
|
||||
def haveDisclosedContracts(
|
||||
disclosedContracts: DisclosedContract*
|
||||
)(preprocessor: preprocessing.Preprocessor): Matcher[Tx.Metadata] =
|
||||
Matcher { metadata =>
|
||||
val expectedResult = ImmArray(disclosedContracts: _*)
|
||||
val actualResult = metadata.disclosures
|
||||
.map(_.unversioned)
|
||||
.map(preprocessor.commandPreprocessor.unsafePreprocessDisclosedContract)
|
||||
val debugMessage = Seq(
|
||||
s"expected but missing contract IDs: ${expectedResult.filter(!actualResult.toSeq.contains(_)).map(_.contractId.value)}",
|
||||
s"unexpected but found contract IDs: ${actualResult.filter(!expectedResult.toSeq.contains(_)).map(_.contractId)}",
|
||||
).mkString("\n ", "\n ", "")
|
||||
|
||||
MatchResult(
|
||||
expectedResult == actualResult,
|
||||
s"Failed with unexpected disclosed contracts: $expectedResult != $actualResult $debugMessage",
|
||||
s"Failed with unexpected disclosed contracts: $expectedResult == $actualResult",
|
||||
)
|
||||
}
|
||||
|
||||
def haveDisclosedInputContracts(
|
||||
disclosedContracts: DisclosedContract*
|
||||
): Matcher[VersionedTransaction] =
|
||||
Matcher { transaction =>
|
||||
val expectedResult = Set(disclosedContracts: _*).map(_.contractId.value)
|
||||
val actualResult = transaction.inputContracts
|
||||
val debugMessage = Seq(
|
||||
s"expected but missing contract IDs: ${expectedResult.filter(!actualResult.toSeq.contains(_))}",
|
||||
s"unexpected but found contract IDs: ${actualResult.filter(!expectedResult.toSeq.contains(_))}",
|
||||
).mkString("\n ", "\n ", "")
|
||||
|
||||
MatchResult(
|
||||
expectedResult == actualResult,
|
||||
s"Failed with unexpected disclosed contracts: $expectedResult != $actualResult $debugMessage",
|
||||
s"Failed with unexpected disclosed contracts: $expectedResult == $actualResult",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
case class ReinterpretState(
|
||||
contracts: Map[ContractId, VersionedContractInstance],
|
||||
keys: Map[GlobalKey, ContractId],
|
||||
nodes: HashMap[NodeId, Node] = HashMap.empty,
|
||||
roots: BackStack[NodeId] = BackStack.empty,
|
||||
dependsOnTime: Boolean = false,
|
||||
nodeSeeds: BackStack[(NodeId, crypto.Hash)] = BackStack.empty,
|
||||
) {
|
||||
def commit(tr: Tx, meta: Tx.Metadata): ReinterpretState = {
|
||||
val (newContracts, newKeys) = tr.fold((contracts, keys)) {
|
||||
case ((contracts, keys), (_, exe: Node.Exercise)) =>
|
||||
(contracts - exe.targetCoid, keys)
|
||||
case ((contracts, keys), (_, create: Node.Create)) =>
|
||||
(
|
||||
contracts.updated(
|
||||
create.coid,
|
||||
create.versionedCoinst,
|
||||
),
|
||||
create.key.fold(keys)(k =>
|
||||
keys.updated(GlobalKey.assertBuild(create.templateId, k.key), create.coid)
|
||||
),
|
||||
)
|
||||
case (acc, _) => acc
|
||||
}
|
||||
ReinterpretState(
|
||||
newContracts,
|
||||
newKeys,
|
||||
nodes ++ tr.nodes,
|
||||
roots :++ tr.roots,
|
||||
dependsOnTime || meta.dependsOnTime,
|
||||
nodeSeeds :++ meta.nodeSeeds,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1570,12 +1570,12 @@ private[lf] object SBuiltin {
|
||||
NameOf.qualifiedNameOfCurrentFunc,
|
||||
skey,
|
||||
)
|
||||
|
||||
if (keyWithMaintainers.maintainers.isEmpty) {
|
||||
Control.Error(
|
||||
IE.FetchEmptyContractKeyMaintainers(operation.templateId, keyWithMaintainers.key)
|
||||
)
|
||||
} else {
|
||||
|
||||
val gkey = GlobalKey(operation.templateId, keyWithMaintainers.key)
|
||||
|
||||
onLedger.ptx.contractState.resolveKey(gkey) match {
|
||||
|
@ -241,7 +241,7 @@ private[lf] object Speedy {
|
||||
)
|
||||
|
||||
case None =>
|
||||
val m1_prime = table.contractById + (coid -> (d.templateId, arg))
|
||||
val contractByIdUpdates = table.contractById + (coid -> (d.templateId, arg))
|
||||
d.metadata.keyHash match {
|
||||
case Some(hash) =>
|
||||
// check for duplicate contract key hashes
|
||||
@ -256,14 +256,18 @@ private[lf] object Speedy {
|
||||
)
|
||||
)
|
||||
|
||||
case None => DisclosureTable(table.contractIdByKey + (hash -> coid), m1_prime)
|
||||
case None =>
|
||||
DisclosureTable(
|
||||
table.contractIdByKey + (hash -> coid),
|
||||
contractByIdUpdates,
|
||||
)
|
||||
}
|
||||
|
||||
case None =>
|
||||
packageInterface.lookupTemplate(d.templateId) match {
|
||||
case Right(template) if template.key.isEmpty =>
|
||||
// Success - template exists, but has no key defined
|
||||
table.copy(contractById = m1_prime)
|
||||
table.copy(contractById = contractByIdUpdates)
|
||||
|
||||
case Right(_) =>
|
||||
// Error - disclosed contract lacks a key hash, but the template requires a key
|
||||
|
@ -216,7 +216,7 @@ private[lf] object PartialTransaction {
|
||||
* @param contractState summarizes the changes to the contract states caused by nodes up to now
|
||||
* @param actionNodeLocations The optional locations of create/exercise/fetch/lookup nodes in pre-order.
|
||||
* Used by 'locationInfo()', called by 'finish()' and 'finishIncomplete()'
|
||||
* @param disclosedContracts contracts that have been explicitly disclosed
|
||||
* @param disclosedContracts contracts that have been explicitly disclosed to Speedy (usage will be determined by 'finish()')
|
||||
*/
|
||||
private[speedy] case class PartialTransaction(
|
||||
nextNodeIdx: Int,
|
||||
@ -308,13 +308,17 @@ private[speedy] case class PartialTransaction(
|
||||
val roots = context.children.toImmArray
|
||||
val tx0 = Tx(nodes, roots)
|
||||
val (tx, seeds) = NormalizeRollbacks.normalizeTx(tx0)
|
||||
val txResult = SubmittedTransaction(TxVersion.asVersionedTransaction(tx))
|
||||
Result(
|
||||
SubmittedTransaction(TxVersion.asVersionedTransaction(tx)),
|
||||
txResult,
|
||||
locationInfo(),
|
||||
seeds.zip(actionNodeSeeds.toImmArray),
|
||||
contractState.globalKeyInputs.transform((_, v) => v.toKeyMapping),
|
||||
disclosedContracts,
|
||||
disclosedContracts.filter(disclosedContract =>
|
||||
txResult.inputContracts.contains(disclosedContract.contractId.value)
|
||||
),
|
||||
)
|
||||
|
||||
case _ =>
|
||||
InternalError.runtimeException(
|
||||
NameOf.qualifiedNameOfCurrentFunc,
|
||||
|
@ -75,7 +75,6 @@ class BuildDisclosureTableTest extends AnyFreeSpec with Inside with Matchers {
|
||||
DisclosurePreprocessing.DuplicateContractKeys(houseTemplateId, collidingKeyHash)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -298,28 +298,50 @@ object ExplicitDisclosureLib {
|
||||
|
||||
def haveInactiveContractIds(contractIds: ContractId*): Matcher[Speedy.OnLedger] = Matcher {
|
||||
ledger =>
|
||||
val expectedResult = contractIds.toSet
|
||||
val actualResult = ledger.ptx.contractState.activeState.consumedBy.keySet
|
||||
val debugMessage = Seq(
|
||||
s"expected but missing contract IDs: ${expectedResult.filter(!actualResult.toSeq.contains(_))}",
|
||||
s"unexpected but found contract IDs: ${actualResult.filter(!expectedResult.toSeq.contains(_))}",
|
||||
).mkString("\n ", "\n ", "")
|
||||
|
||||
MatchResult(
|
||||
ledger.ptx.contractState.activeState.consumedBy.keySet == contractIds.toSet,
|
||||
s"Failed with unexpected inactive contracts: ${ledger.ptx.contractState.activeState.consumedBy.keySet} != $contractIds",
|
||||
s"Failed with unexpected inactive contracts: ${ledger.ptx.contractState.activeState.consumedBy.keySet} == $contractIds",
|
||||
expectedResult == actualResult,
|
||||
s"Failed with unexpected inactive contracts: $expectedResult != $actualResult $debugMessage",
|
||||
s"Failed with unexpected inactive contracts: $expectedResult == $actualResult",
|
||||
)
|
||||
}
|
||||
|
||||
def haveCachedContractIds(contractIds: ContractId*): Matcher[Speedy.OnLedger] = Matcher {
|
||||
ledger =>
|
||||
val expectedResult = contractIds.toSet
|
||||
val actualResult = ledger.cachedContracts.keySet
|
||||
val debugMessage = Seq(
|
||||
s"expected but missing contract IDs: ${expectedResult.filter(!actualResult.toSeq.contains(_))}",
|
||||
s"unexpected but found contract IDs: ${actualResult.filter(!expectedResult.toSeq.contains(_))}",
|
||||
).mkString("\n ", "\n ", "")
|
||||
|
||||
MatchResult(
|
||||
ledger.cachedContracts.keySet == contractIds.toSet,
|
||||
s"Failed with unexpected cached contracts: ${ledger.cachedContracts.keySet} != $contractIds",
|
||||
s"Failed with unexpected cached contracts: ${ledger.cachedContracts.keySet} == $contractIds",
|
||||
expectedResult == actualResult,
|
||||
s"Failed with unexpected cached contracts: $expectedResult != $actualResult $debugMessage",
|
||||
s"Failed with unexpected cached contracts: $expectedResult == $actualResult",
|
||||
)
|
||||
}
|
||||
|
||||
def haveDisclosedContracts(contractIds: DisclosedContract*): Matcher[Speedy.OnLedger] = Matcher {
|
||||
ledger =>
|
||||
def haveDisclosedContracts(disclosedContracts: DisclosedContract*): Matcher[Speedy.OnLedger] =
|
||||
Matcher { ledger =>
|
||||
val expectedResult = ImmArray(disclosedContracts: _*)
|
||||
val actualResult = ledger.ptx.disclosedContracts
|
||||
val debugMessage = Seq(
|
||||
s"expected but missing contract IDs: ${expectedResult.filter(!actualResult.toSeq.contains(_)).map(_.contractId)}",
|
||||
s"unexpected but found contract IDs: ${actualResult.filter(!expectedResult.toSeq.contains(_)).map(_.contractId)}",
|
||||
).mkString("\n ", "\n ", "")
|
||||
|
||||
MatchResult(
|
||||
ledger.ptx.disclosedContracts == ImmArray(contractIds: _*),
|
||||
s"Failed with unexpected disclosed contracts: ${ledger.ptx.disclosedContracts} != $contractIds",
|
||||
s"Failed with unexpected disclosed contracts: ${ledger.ptx.disclosedContracts} == $contractIds",
|
||||
expectedResult == actualResult,
|
||||
s"Failed with unexpected disclosed contracts: $expectedResult != $actualResult $debugMessage",
|
||||
s"Failed with unexpected disclosed contracts: $expectedResult == $actualResult",
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -701,7 +701,7 @@ object Transaction {
|
||||
* @param nodeSeeds : An association list that maps to each ID of create and exercise
|
||||
* nodes its seeds.
|
||||
* @param globalKeyMapping : input key mapping inferred by interpretation
|
||||
* @param disclosures : contracts explicitly disclosed to this transaction
|
||||
* @param disclosures : contracts passed via explicit disclosure that have been used in this transaction
|
||||
*/
|
||||
final case class Metadata(
|
||||
submissionSeed: Option[crypto.Hash],
|
||||
|
@ -175,7 +175,7 @@
|
||||
- Evaluation order of successful lookup_by_key of a local contract: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2913)
|
||||
- Evaluation order of successful lookup_by_key of a non-cached global contract: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2761)
|
||||
- Exceptions, throw/catch.: [ExceptionTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/ExceptionTest.scala#L25)
|
||||
- Rollback creates cannot be exercise: [EngineTest.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/EngineTest.scala#L2001)
|
||||
- Rollback creates cannot be exercise: [EngineTest.scala](daml-lf/engine/src/test/scala/com/digitalasset/daml/lf/engine/EngineTest.scala#L2064)
|
||||
- This checks that type checking in exercise_interface is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L1851)
|
||||
- This checks that type checking is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L1743)
|
||||
- This checks that type checking is done after checking activeness.: [EvaluationOrderTest.scala](daml-lf/interpreter/src/test/scala/com/digitalasset/daml/lf/speedy/EvaluationOrderTest.scala#L2705)
|
||||
|
Loading…
Reference in New Issue
Block a user