mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
LF: Contract ID suffix check in Preprocessor (#10642)
This PR makes possible to check for contract IDs suffix during preprocessing. This is the first part of the task 3 described in #10504. CHANGELOG_BEGIN CHANGELOG_END
This commit is contained in:
parent
7b94b0674e
commit
b22de6893b
@ -29,6 +29,7 @@ da_scala_library(
|
||||
"//daml-lf/transaction",
|
||||
"//daml-lf/validation",
|
||||
"//libs-scala/nameof",
|
||||
"@maven//:com_google_protobuf_protobuf_java",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -13,12 +13,10 @@ import com.daml.lf.speedy.Speedy.Machine
|
||||
import com.daml.lf.speedy.SResult._
|
||||
import com.daml.lf.transaction.{SubmittedTransaction, Transaction => Tx}
|
||||
import com.daml.lf.transaction.Node._
|
||||
import com.daml.lf.value.Value
|
||||
import java.nio.file.Files
|
||||
|
||||
import com.daml.lf.language.{Interface, LanguageVersion, LookupError, StablePackages}
|
||||
import com.daml.lf.validation.Validation
|
||||
import com.daml.lf.value.Value.ContractId
|
||||
import com.daml.nameof.NameOf
|
||||
|
||||
/** Allows for evaluating [[Commands]] and validating [[Transaction]]s.
|
||||
@ -50,13 +48,17 @@ import com.daml.nameof.NameOf
|
||||
*
|
||||
* This class is thread safe as long `nextRandomInt` is.
|
||||
*/
|
||||
class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableVersions)) {
|
||||
class Engine(val config: EngineConfig = Engine.StableConfig) {
|
||||
|
||||
config.profileDir.foreach(Files.createDirectories(_))
|
||||
|
||||
private[this] val compiledPackages = ConcurrentCompiledPackages(config.getCompilerConfig)
|
||||
|
||||
private[this] val preprocessor = new preprocessing.Preprocessor(compiledPackages)
|
||||
private[engine] val preprocessor =
|
||||
new preprocessing.Preprocessor(
|
||||
compiledPackages = compiledPackages,
|
||||
requiredCidSuffix = config.requireSuffixedGlobalCids,
|
||||
)
|
||||
|
||||
def info = new EngineInfo(config)
|
||||
|
||||
@ -169,7 +171,7 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
|
||||
submissionSeed: crypto.Hash,
|
||||
): Result[(SubmittedTransaction, Tx.Metadata)] =
|
||||
for {
|
||||
commands <- preprocessor.translateTransactionRoots(tx.transaction)
|
||||
commands <- preprocessor.translateTransactionRoots(tx)
|
||||
result <- interpretCommands(
|
||||
validating = true,
|
||||
submitters = submitters,
|
||||
@ -466,9 +468,6 @@ class Engine(val config: EngineConfig = new EngineConfig(LanguageVersion.StableV
|
||||
} yield ()
|
||||
}
|
||||
|
||||
private[engine] def enrich(typ: Type, value: Value[ContractId]): Result[Value[ContractId]] =
|
||||
preprocessor.translateValue(typ, value).map(_.toValue)
|
||||
|
||||
}
|
||||
|
||||
object Engine {
|
||||
@ -501,8 +500,13 @@ object Engine {
|
||||
}
|
||||
}
|
||||
|
||||
def DevEngine(): Engine = new Engine(new EngineConfig(LanguageVersion.DevVersions))
|
||||
private def StableConfig =
|
||||
EngineConfig(allowedLanguageVersions = LanguageVersion.StableVersions)
|
||||
|
||||
def StableEngine(): Engine = new Engine()
|
||||
def StableEngine(): Engine = new Engine(StableConfig)
|
||||
|
||||
def DevEngine(): Engine = new Engine(
|
||||
StableConfig.copy(allowedLanguageVersions = LanguageVersion.DevVersions)
|
||||
)
|
||||
|
||||
}
|
||||
|
@ -22,6 +22,10 @@ import com.daml.lf.transaction.ContractKeyUniquenessMode
|
||||
* @param profileDir The optional specifies the directory where to
|
||||
* save the output of the Daml scenario profiler. The profiler is
|
||||
* disabled if the option is empty.
|
||||
* @param requireSuffixedGlobalCids Since August 2018 we expect new
|
||||
* ledgers to suffix CIDs before committing a transaction.
|
||||
* This option should be disable for backward compatibility in ledger
|
||||
* that do not (i.e. Sandboxes, KV, Corda).
|
||||
*/
|
||||
final case class EngineConfig(
|
||||
allowedLanguageVersions: VersionRange[language.LanguageVersion],
|
||||
@ -29,6 +33,9 @@ final case class EngineConfig(
|
||||
stackTraceMode: Boolean = false,
|
||||
profileDir: Option[Path] = None,
|
||||
contractKeyUniqueness: ContractKeyUniquenessMode = ContractKeyUniquenessMode.On,
|
||||
// TODO: https://github.com/digital-asset/daml/issues/10504
|
||||
// switch the default to true
|
||||
requireSuffixedGlobalCids: Boolean = false,
|
||||
) {
|
||||
|
||||
private[lf] def getCompilerConfig: speedy.Compiler.Config =
|
||||
|
@ -105,6 +105,11 @@ object Error {
|
||||
s"Provided value exceeds maximum nesting level of ${Value.MAXIMUM_NESTING}"
|
||||
}
|
||||
|
||||
final case class NonSuffixedCid(cid: Value.ContractId.V1) extends Error {
|
||||
override def message: String =
|
||||
s"Provided Contract ID $cid is not suffixed"
|
||||
}
|
||||
|
||||
final case class RootNode(nodeId: NodeId, override val message: String) extends Error
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import com.daml.lf.value.Value.ContractId
|
||||
final class ValueEnricher(engine: Engine) {
|
||||
|
||||
def enrichValue(typ: Ast.Type, value: Value[ContractId]): Result[Value[ContractId]] =
|
||||
engine.enrich(typ, value)
|
||||
engine.preprocessor.translateValue(typ, value).map(_.toValue)
|
||||
|
||||
def enrichContract(
|
||||
contract: Value.ContractInst[Value[ContractId]]
|
||||
|
@ -7,17 +7,18 @@ package preprocessing
|
||||
|
||||
import com.daml.lf.data._
|
||||
import com.daml.lf.language.Ast
|
||||
import com.daml.lf.speedy.SValue
|
||||
import com.daml.lf.value.Value
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages) {
|
||||
private[lf] final class CommandPreprocessor(
|
||||
interface: language.Interface,
|
||||
requiredCidSuffix: Boolean,
|
||||
) {
|
||||
|
||||
val valueTranslator = new ValueTranslator(interface, requiredCidSuffix)
|
||||
|
||||
import Preprocessor._
|
||||
import compiledPackages.interface
|
||||
|
||||
val valueTranslator = new ValueTranslator(interface)
|
||||
|
||||
@throws[Error.Preprocessing.Error]
|
||||
def unsafePreprocessCreate(
|
||||
@ -35,9 +36,10 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
|
||||
choiceId: Ref.ChoiceName,
|
||||
argument: Value[Value.ContractId],
|
||||
): speedy.Command.Exercise = {
|
||||
val cid = valueTranslator.unsafeTranslateCid(contractId)
|
||||
val choice = handleLookup(interface.lookupChoice(templateId, choiceId)).argBinder._2
|
||||
val arg = valueTranslator.unsafeTranslateValue(choice, argument)
|
||||
speedy.Command.Exercise(templateId, SValue.SContractId(contractId), choiceId, arg)
|
||||
speedy.Command.Exercise(templateId, cid, choiceId, arg)
|
||||
}
|
||||
|
||||
@throws[Error.Preprocessing.Error]
|
||||
@ -109,7 +111,8 @@ private[lf] final class CommandPreprocessor(compiledPackages: CompiledPackages)
|
||||
choiceArgument,
|
||||
)
|
||||
case command.FetchCommand(templateId, coid) =>
|
||||
speedy.Command.Fetch(templateId, SValue.SContractId(coid))
|
||||
val cid = valueTranslator.unsafeTranslateCid(coid)
|
||||
speedy.Command.Fetch(templateId, cid)
|
||||
case command.FetchByKeyCommand(templateId, key) =>
|
||||
val ckTtype = handleLookup(interface.lookupTemplateKey(templateId)).typ
|
||||
val sKey = valueTranslator.unsafeTranslateValue(ckTtype, key)
|
||||
|
@ -6,25 +6,47 @@ package engine
|
||||
package preprocessing
|
||||
|
||||
import java.util
|
||||
|
||||
import com.daml.lf.data.{ImmArray, Ref}
|
||||
import com.daml.lf.language.{Ast, LookupError}
|
||||
import com.daml.lf.speedy.SValue
|
||||
import com.daml.lf.transaction.{GenTransaction, NodeId}
|
||||
import com.daml.lf.transaction.SubmittedTransaction
|
||||
import com.daml.lf.value.Value
|
||||
import com.daml.nameof.NameOf
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackages) {
|
||||
/** The Command Preprocessor is responsible of the following tasks:
|
||||
* - normalizes value representation (e.g. resolves missing type
|
||||
* reference in record/variant/enumeration, infers missing labeled
|
||||
* record fields, orders labeled record fields, ...);
|
||||
* - checks value nesting does not overpass 100;
|
||||
* - checks a LF command/value is properly typed according the
|
||||
* Daml-LF package definitions;
|
||||
* - checks for Contract ID suffix (see [[requiredCidSuffix]]);
|
||||
* - translates a LF command/value into speedy command/value; and
|
||||
* - translates a complete transaction into a list of speedy
|
||||
* commands.
|
||||
*
|
||||
* @param compiledPackages a [[MutableCompiledPackages]] contains the
|
||||
* Daml-LF package definitions against the command should
|
||||
* resolved/typechecked. It is updated dynamically each time the
|
||||
* [[ResultNeedPackage]] continuation is called.
|
||||
* @param requiredCidSuffix when `true` the preprocessor will reject
|
||||
* any value/command/transaction that contains V1 Contract IDs
|
||||
* without suffixed.
|
||||
*/
|
||||
private[engine] final class Preprocessor(
|
||||
compiledPackages: MutableCompiledPackages,
|
||||
requiredCidSuffix: Boolean = true,
|
||||
) {
|
||||
|
||||
import Preprocessor._
|
||||
val transactionPreprocessor = new TransactionPreprocessor(compiledPackages)
|
||||
import transactionPreprocessor._
|
||||
import commandPreprocessor._
|
||||
import valueTranslator.unsafeTranslateValue
|
||||
|
||||
import compiledPackages.interface
|
||||
|
||||
val commandPreprocessor = new CommandPreprocessor(interface, requiredCidSuffix)
|
||||
val transactionPreprocessor = new TransactionPreprocessor(commandPreprocessor)
|
||||
|
||||
// This pulls all the dependencies of in `typesToProcess0` and `tyConAlreadySeen0`
|
||||
private def getDependencies(
|
||||
typesToProcess0: List[Ast.Type],
|
||||
@ -127,14 +149,14 @@ private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackag
|
||||
*/
|
||||
def translateValue(ty0: Ast.Type, v0: Value[Value.ContractId]): Result[SValue] =
|
||||
safelyRun(getDependencies(List(ty0), List.empty)) {
|
||||
unsafeTranslateValue(ty0, v0)
|
||||
commandPreprocessor.valueTranslator.unsafeTranslateValue(ty0, v0)
|
||||
}
|
||||
|
||||
private[engine] def preprocessCommand(
|
||||
cmd: command.Command
|
||||
): Result[speedy.Command] =
|
||||
safelyRun(getDependencies(List.empty, List(cmd.templateId))) {
|
||||
unsafePreprocessCommand(cmd)
|
||||
commandPreprocessor.unsafePreprocessCommand(cmd)
|
||||
}
|
||||
|
||||
/** Translates LF commands to a speedy commands.
|
||||
@ -143,16 +165,17 @@ private[engine] final class Preprocessor(compiledPackages: MutableCompiledPackag
|
||||
cmds: data.ImmArray[command.ApiCommand]
|
||||
): Result[ImmArray[speedy.Command]] =
|
||||
safelyRun(getDependencies(List.empty, cmds.map(_.templateId).toList)) {
|
||||
unsafePreprocessCommands(cmds)
|
||||
commandPreprocessor.unsafePreprocessCommands(cmds)
|
||||
}
|
||||
|
||||
def translateTransactionRoots[Cid <: Value.ContractId](
|
||||
tx: GenTransaction[NodeId, Cid]
|
||||
/** Translates a complete transaction. Assumes no contract ID suffixes are used */
|
||||
def translateTransactionRoots(
|
||||
tx: SubmittedTransaction
|
||||
): Result[ImmArray[speedy.Command]] =
|
||||
safelyRun(
|
||||
getDependencies(List.empty, tx.rootNodes.toList.map(_.templateId))
|
||||
) {
|
||||
unsafeTranslateTransactionRoots(tx)
|
||||
transactionPreprocessor.unsafeTranslateTransactionRoots(tx)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,15 +6,13 @@ package engine
|
||||
package preprocessing
|
||||
|
||||
import com.daml.lf.data.{BackStack, ImmArray}
|
||||
import com.daml.lf.transaction.{GenTransaction, Node, NodeId}
|
||||
import com.daml.lf.value.Value
|
||||
import com.daml.lf.transaction.{Node, NodeId, SubmittedTransaction}
|
||||
import com.daml.lf.value.Value.ContractId
|
||||
|
||||
private[preprocessing] final class TransactionPreprocessor(
|
||||
compiledPackages: MutableCompiledPackages
|
||||
commandPreprocessor: CommandPreprocessor
|
||||
) {
|
||||
|
||||
val commandPreprocessor = new CommandPreprocessor(compiledPackages)
|
||||
|
||||
private[this] def invalidRootNode(nodeId: NodeId, message: String) =
|
||||
throw Error.Preprocessing.RootNode(nodeId, message)
|
||||
|
||||
@ -65,18 +63,18 @@ private[preprocessing] final class TransactionPreprocessor(
|
||||
* for more details.
|
||||
*/
|
||||
@throws[Error.Preprocessing.Error]
|
||||
def unsafeTranslateTransactionRoots[Cid <: Value.ContractId](
|
||||
tx: GenTransaction[NodeId, Cid]
|
||||
def unsafeTranslateTransactionRoots(
|
||||
tx: SubmittedTransaction
|
||||
): ImmArray[speedy.Command] = {
|
||||
|
||||
val result = tx.roots.foldLeft(BackStack.empty[speedy.Command]) { (acc, id) =>
|
||||
tx.nodes.get(id) match {
|
||||
case Some(node: Node.GenActionNode[_, Cid]) =>
|
||||
case Some(node: Node.GenActionNode[_, ContractId]) =>
|
||||
node match {
|
||||
case create: Node.NodeCreate[Cid] =>
|
||||
case create: Node.NodeCreate[ContractId] =>
|
||||
val cmd = commandPreprocessor.unsafePreprocessCreate(create.templateId, create.arg)
|
||||
acc :+ cmd
|
||||
case exe: Node.NodeExercises[_, Cid] =>
|
||||
case exe: Node.NodeExercises[_, ContractId] =>
|
||||
val cmd = exe.key match {
|
||||
case Some(key) if exe.byKey =>
|
||||
commandPreprocessor.unsafePreprocessExerciseByKey(
|
||||
|
@ -14,7 +14,11 @@ import com.daml.lf.value.Value._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
|
||||
private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
private[engine] final class ValueTranslator(
|
||||
interface: language.Interface,
|
||||
// See Preprocessor.requiredCidSuffix for more details about the following flag.
|
||||
requiredCidSuffix: Boolean,
|
||||
) {
|
||||
|
||||
import Preprocessor._
|
||||
|
||||
@ -38,6 +42,19 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
go(fields, Map.empty)
|
||||
}
|
||||
|
||||
private[preprocessing] val unsafeTranslateCid: ContractId => SValue.SContractId =
|
||||
if (requiredCidSuffix) {
|
||||
case cid1: ContractId.V1 =>
|
||||
if (cid1.suffix.isEmpty)
|
||||
throw Error.Preprocessing.NonSuffixedCid(cid1)
|
||||
else
|
||||
SValue.SContractId(cid1)
|
||||
case cid0: ContractId.V0 =>
|
||||
SValue.SContractId(cid0)
|
||||
}
|
||||
else
|
||||
SValue.SContractId
|
||||
|
||||
// For efficient reason we do not produce here the monad Result[SValue] but rather throw
|
||||
// exception in case of error or package missing.
|
||||
@throws[Error.Preprocessing.Error]
|
||||
@ -89,7 +106,7 @@ private[engine] final class ValueTranslator(interface: language.Interface) {
|
||||
typeError()
|
||||
}
|
||||
case (BTContractId, ValueContractId(c)) =>
|
||||
SValue.SContractId(c)
|
||||
unsafeTranslateCid(c)
|
||||
case (BTOptional, ValueOptional(mbValue)) =>
|
||||
mbValue match {
|
||||
case Some(v) =>
|
||||
|
@ -39,8 +39,6 @@ import org.scalatest.EitherValues
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
import org.scalatest.matchers.should.Matchers
|
||||
import org.scalatest.Inside._
|
||||
import scalaz.std.either._
|
||||
import scalaz.syntax.apply._
|
||||
|
||||
import scala.collection.immutable.HashMap
|
||||
import scala.language.implicitConversions
|
||||
@ -158,10 +156,12 @@ class EngineTest
|
||||
None
|
||||
}
|
||||
|
||||
// TODO make these two per-test, so that we make sure not to pollute the package cache and other possibly mutable stuff
|
||||
val engine = Engine.DevEngine()
|
||||
val suffixLenientEngine = newEngine()
|
||||
val suffixStrictEngine = newEngine(requireCidSuffixes = true)
|
||||
val preprocessor =
|
||||
new preprocessing.Preprocessor(ConcurrentCompiledPackages(engine.config.getCompilerConfig))
|
||||
new preprocessing.Preprocessor(
|
||||
ConcurrentCompiledPackages(suffixLenientEngine.config.getCompilerConfig)
|
||||
)
|
||||
|
||||
"valid data variant identifier" should {
|
||||
"found and return the argument types" in {
|
||||
@ -437,7 +437,9 @@ class EngineTest
|
||||
loadPackage("daml-lf/tests/Optional.dar")
|
||||
|
||||
val translator =
|
||||
new preprocessing.Preprocessor(ConcurrentCompiledPackages(engine.config.getCompilerConfig))
|
||||
new preprocessing.Preprocessor(
|
||||
ConcurrentCompiledPackages(suffixLenientEngine.config.getCompilerConfig)
|
||||
)
|
||||
|
||||
val id = Identifier(optionalPkgId, "Optional:Rec")
|
||||
val someValue =
|
||||
@ -463,7 +465,9 @@ class EngineTest
|
||||
|
||||
"returns correct error when resuming" in {
|
||||
val translator =
|
||||
new preprocessing.Preprocessor(ConcurrentCompiledPackages(engine.config.getCompilerConfig))
|
||||
new preprocessing.Preprocessor(
|
||||
ConcurrentCompiledPackages(suffixLenientEngine.config.getCompilerConfig)
|
||||
)
|
||||
val id = Identifier(basicTestsPkgId, "BasicTests:MyRec")
|
||||
val wrongRecord =
|
||||
ValueRecord(Some(id), ImmArray(Some[Name]("wrongLbl") -> ValueText("foo")))
|
||||
@ -491,7 +495,7 @@ class EngineTest
|
||||
.preprocessCommands(ImmArray(command))
|
||||
.consume(lookupContract, lookupPackage, lookupKey)
|
||||
res shouldBe a[Right[_, _]]
|
||||
val interpretResult = engine
|
||||
val interpretResult = suffixLenientEngine
|
||||
.submit(
|
||||
submitters,
|
||||
readAs,
|
||||
@ -507,18 +511,19 @@ class EngineTest
|
||||
|
||||
"reinterpret to the same result" in {
|
||||
val Right((tx, txMeta)) = interpretResult
|
||||
val stx = suffix(tx)
|
||||
|
||||
val Right((rtx, newMeta)) =
|
||||
reinterpret(
|
||||
engine,
|
||||
suffixStrictEngine,
|
||||
Set(party),
|
||||
tx.roots,
|
||||
tx,
|
||||
stx.roots,
|
||||
stx,
|
||||
txMeta,
|
||||
let,
|
||||
lookupPackage,
|
||||
)
|
||||
isReplayedBy(tx, rtx) shouldBe Right(())
|
||||
isReplayedBy(stx, rtx) shouldBe Right(())
|
||||
txMeta.nodeSeeds shouldBe newMeta.nodeSeeds
|
||||
}
|
||||
|
||||
@ -527,7 +532,7 @@ class EngineTest
|
||||
val Right(submitter) = tx.guessSubmitter
|
||||
val submitters = Set(submitter)
|
||||
val ntx = SubmittedTransaction(Normalization.normalizeTx(tx))
|
||||
val validated = engine
|
||||
val validated = suffixLenientEngine
|
||||
.validate(submitters, ntx, let, participant, meta.submissionTime, submissionSeed)
|
||||
.consume(lookupContract, lookupPackage, lookupKey)
|
||||
validated match {
|
||||
@ -581,7 +586,7 @@ class EngineTest
|
||||
.consume(lookupContract, lookupPackage, lookupKey)
|
||||
withClue("Preprocessing result: ")(res shouldBe a[Right[_, _]])
|
||||
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.submit(actAs, readAs, Commands(ImmArray(cmd), let, "test"), participant, submissionSeed)
|
||||
.consume(lookupContract, lookupPackage, lookupKey)
|
||||
}
|
||||
@ -595,18 +600,19 @@ class EngineTest
|
||||
"reinterpret to the same result" in {
|
||||
forAll(cases) { case (templateId, signatories, submitters) =>
|
||||
val Right((tx, txMeta)) = interpretResult(templateId, signatories, submitters)
|
||||
val stx = suffix(tx)
|
||||
|
||||
val Right((rtx, _)) =
|
||||
reinterpret(
|
||||
engine,
|
||||
suffixStrictEngine,
|
||||
signatories.map(_._2),
|
||||
tx.roots,
|
||||
tx,
|
||||
stx.roots,
|
||||
stx,
|
||||
txMeta,
|
||||
let,
|
||||
lookupPackage,
|
||||
)
|
||||
isReplayedBy(tx, rtx) shouldBe Right(())
|
||||
isReplayedBy(stx, rtx) shouldBe Right(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,7 +620,7 @@ class EngineTest
|
||||
forAll(cases) { case (templateId, signatories, submitters) =>
|
||||
val Right((tx, meta)) = interpretResult(templateId, signatories, submitters)
|
||||
val ntx = SubmittedTransaction(Normalization.normalizeTx(tx))
|
||||
val validated = engine
|
||||
val validated = suffixLenientEngine
|
||||
.validate(submitters, ntx, let, participant, meta.submissionTime, submissionSeed)
|
||||
.consume(
|
||||
lookupContract,
|
||||
@ -634,7 +640,7 @@ class EngineTest
|
||||
val Right((tx, _)) = interpretResult(templateId, signatories, submitters)
|
||||
|
||||
val replaySubmitters = submitters + party
|
||||
val replayResult = engine.replay(
|
||||
val replayResult = suffixLenientEngine.replay(
|
||||
submitters = replaySubmitters,
|
||||
tx = tx,
|
||||
ledgerEffectiveTime = let,
|
||||
@ -652,7 +658,7 @@ class EngineTest
|
||||
val Right((tx, _)) = interpretResult(templateId, signatories, submitters)
|
||||
|
||||
val replaySubmitters = submitters.drop(1)
|
||||
val replayResult = engine.replay(
|
||||
val replayResult = suffixLenientEngine.replay(
|
||||
submitters = replaySubmitters,
|
||||
tx = tx,
|
||||
ledgerEffectiveTime = let,
|
||||
@ -685,7 +691,7 @@ class EngineTest
|
||||
val interpretResult =
|
||||
res
|
||||
.flatMap { cmds =>
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -705,7 +711,7 @@ class EngineTest
|
||||
val Right(submitter) = tx.guessSubmitter
|
||||
|
||||
"be translated" in {
|
||||
val Right((rtx, _)) = engine
|
||||
val Right((rtx, _)) = suffixLenientEngine
|
||||
.submit(
|
||||
Set(party),
|
||||
readAs,
|
||||
@ -722,23 +728,25 @@ class EngineTest
|
||||
}
|
||||
|
||||
"reinterpret to the same result" in {
|
||||
val stx = suffix(tx)
|
||||
|
||||
val Right((rtx, _)) =
|
||||
reinterpret(
|
||||
engine,
|
||||
suffixStrictEngine,
|
||||
Set(party),
|
||||
tx.roots,
|
||||
tx,
|
||||
stx.roots,
|
||||
stx,
|
||||
txMeta,
|
||||
let,
|
||||
lookupPackage,
|
||||
defaultContracts,
|
||||
)
|
||||
isReplayedBy(tx, rtx) shouldBe Right(())
|
||||
isReplayedBy(stx, rtx) shouldBe Right(())
|
||||
}
|
||||
|
||||
"be validated" in {
|
||||
val ntx = SubmittedTransaction(Normalization.normalizeTx(tx))
|
||||
val validated = engine
|
||||
val validated = suffixLenientEngine
|
||||
.validate(Set(submitter), ntx, let, participant, let, submissionSeed)
|
||||
.consume(
|
||||
lookupContract,
|
||||
@ -772,7 +780,7 @@ class EngineTest
|
||||
res shouldBe a[Right[_, _]]
|
||||
|
||||
"fail at submission" in {
|
||||
val submitResult = engine
|
||||
val submitResult = suffixLenientEngine
|
||||
.submit(
|
||||
submitters,
|
||||
readAs,
|
||||
@ -821,7 +829,7 @@ class EngineTest
|
||||
val result =
|
||||
res
|
||||
.flatMap { cmds =>
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -840,7 +848,7 @@ class EngineTest
|
||||
val Right((tx, txMeta)) = result
|
||||
|
||||
"be translated" in {
|
||||
val submitResult = engine
|
||||
val Right((rtx, _)) = suffixLenientEngine
|
||||
.submit(
|
||||
submitters,
|
||||
readAs,
|
||||
@ -848,40 +856,33 @@ class EngineTest
|
||||
participant,
|
||||
submissionSeed,
|
||||
)
|
||||
.consume(
|
||||
lookupContract,
|
||||
lookupPackage,
|
||||
lookupKey,
|
||||
)
|
||||
.map(_._1)
|
||||
(result.map(_._1) |@| submitResult)((tx, rtx) => isReplayedBy(tx, rtx)) shouldBe Right(
|
||||
Right(())
|
||||
)
|
||||
.consume(lookupContract, lookupPackage, lookupKey)
|
||||
|
||||
isReplayedBy(tx, rtx) shouldBe Right(())
|
||||
}
|
||||
|
||||
"reinterpret to the same result" in {
|
||||
val stx = suffix(tx)
|
||||
|
||||
val reinterpretResult =
|
||||
val Right((rtx, _)) =
|
||||
reinterpret(
|
||||
engine,
|
||||
suffixStrictEngine,
|
||||
Set(alice),
|
||||
tx.roots,
|
||||
tx,
|
||||
stx.roots,
|
||||
stx,
|
||||
txMeta,
|
||||
let,
|
||||
lookupPackage,
|
||||
defaultContracts,
|
||||
defaultKey,
|
||||
)
|
||||
.map(_._1)
|
||||
(result.map(_._1) |@| reinterpretResult)((tx, rtx) => isReplayedBy(tx, rtx)) shouldBe Right(
|
||||
Right(())
|
||||
)
|
||||
|
||||
isReplayedBy(stx, rtx) shouldBe Right(())
|
||||
}
|
||||
|
||||
"be validated" in {
|
||||
val ntx = SubmittedTransaction(Normalization.normalizeTx(tx))
|
||||
val validated = engine
|
||||
val validated = suffixLenientEngine
|
||||
.validate(submitters, ntx, let, participant, let, submissionSeed)
|
||||
.consume(
|
||||
lookupContract,
|
||||
@ -924,7 +925,7 @@ class EngineTest
|
||||
)
|
||||
val submitters = Set(alice)
|
||||
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -962,7 +963,7 @@ class EngineTest
|
||||
|
||||
val submitters = Set(alice)
|
||||
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -997,7 +998,7 @@ class EngineTest
|
||||
|
||||
val submitters = Set(alice)
|
||||
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -1043,7 +1044,7 @@ class EngineTest
|
||||
|
||||
val submitters = Set(alice)
|
||||
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -1098,7 +1099,7 @@ class EngineTest
|
||||
val interpretResult =
|
||||
res
|
||||
.flatMap { cmds =>
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -1127,18 +1128,17 @@ class EngineTest
|
||||
}
|
||||
|
||||
"reinterpret to the same result" in {
|
||||
val stx = suffix(tx)
|
||||
|
||||
val reinterpretResult =
|
||||
reinterpret(engine, Set(party), tx.roots, tx, txMeta, let, lookupPackage)
|
||||
.map(_._1)
|
||||
(interpretResult.map(_._1) |@| reinterpretResult)((tx, rtx) =>
|
||||
isReplayedBy(tx, rtx)
|
||||
) shouldBe Right(Right(()))
|
||||
val Right((rtx, _)) =
|
||||
reinterpret(suffixStrictEngine, Set(party), stx.roots, stx, txMeta, let, lookupPackage)
|
||||
|
||||
isReplayedBy(stx, rtx) shouldBe Right(())
|
||||
}
|
||||
|
||||
"be validated" in {
|
||||
val ntx = SubmittedTransaction(Normalization.normalizeTx(tx))
|
||||
val validated = engine
|
||||
val validated = suffixLenientEngine
|
||||
.validate(Set(submitter), ntx, let, participant, let, submissionSeed)
|
||||
.consume(
|
||||
lookupContract,
|
||||
@ -1363,7 +1363,7 @@ class EngineTest
|
||||
val submitters = Set(bob)
|
||||
val readAs = (Set.empty: Set[Party])
|
||||
|
||||
val Right((tx, txMeta)) = engine
|
||||
val Right((tx, txMeta)) = suffixLenientEngine
|
||||
.submit(
|
||||
submitters,
|
||||
readAs,
|
||||
@ -1380,7 +1380,7 @@ class EngineTest
|
||||
val Right(cmds) = preprocessor
|
||||
.preprocessCommands(ImmArray(command))
|
||||
.consume(lookupContract, lookupPackage, lookupKey)
|
||||
val Right((rtx, _)) = engine
|
||||
val Right((rtx, _)) = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -1399,19 +1399,21 @@ class EngineTest
|
||||
val blindingInfo = Blinding.blind(tx)
|
||||
|
||||
"reinterpret to the same result" in {
|
||||
val stx = suffix(tx)
|
||||
|
||||
val Right((rtx, _)) =
|
||||
reinterpret(
|
||||
engine,
|
||||
suffixStrictEngine,
|
||||
Set(bob),
|
||||
tx.transaction.roots,
|
||||
tx,
|
||||
stx.transaction.roots,
|
||||
stx,
|
||||
txMeta,
|
||||
let,
|
||||
lookupPackage,
|
||||
defaultContracts,
|
||||
)
|
||||
isReplayedBy(rtx, tx) shouldBe Right(())
|
||||
|
||||
isReplayedBy(rtx, stx) shouldBe Right(())
|
||||
}
|
||||
|
||||
"blinded correctly" in {
|
||||
@ -1545,7 +1547,7 @@ class EngineTest
|
||||
|
||||
res
|
||||
.flatMap { cmds =>
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -1586,7 +1588,7 @@ class EngineTest
|
||||
val nid = NodeId(0) //we must use node-0 so the constructed tx is normalized
|
||||
val fetchTx = VersionedTransaction(n.version, Map(nid -> n), ImmArray(nid))
|
||||
val Right((reinterpreted, _)) =
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.reinterpret(
|
||||
n.requiredAuthorizers,
|
||||
FetchCommand(n.templateId, n.coid),
|
||||
@ -1637,7 +1639,7 @@ class EngineTest
|
||||
}
|
||||
|
||||
"succeed with a fresh engine, correctly compiling packages" in {
|
||||
val engine = Engine.DevEngine()
|
||||
val engine = newEngine()
|
||||
|
||||
val fetchNode = FetchCommand(
|
||||
templateId = fetchedTid,
|
||||
@ -1713,7 +1715,7 @@ class EngineTest
|
||||
)
|
||||
val submitters = Set(alice)
|
||||
val readAs = (Set.empty: Set[Party])
|
||||
val Right((tx, _)) = engine
|
||||
val Right((tx, _)) = newEngine()
|
||||
.submit(submitters, readAs, Commands(ImmArray(exerciseCmd), now, "test"), participant, seed)
|
||||
.consume(
|
||||
lookupContractMap.get,
|
||||
@ -1739,7 +1741,7 @@ class EngineTest
|
||||
val submitters = Set(alice)
|
||||
val readAs = (Set.empty: Set[Party])
|
||||
|
||||
val Right((tx, txMeta)) = engine
|
||||
val Right((tx, txMeta)) = suffixLenientEngine
|
||||
.submit(submitters, readAs, Commands(ImmArray(exerciseCmd), now, "test"), participant, seed)
|
||||
.consume(
|
||||
lookupContractMap.get,
|
||||
@ -1752,8 +1754,7 @@ class EngineTest
|
||||
lookupNode.result shouldBe Some(lookedUpCid)
|
||||
|
||||
val Right((reinterpreted, _)) =
|
||||
Engine
|
||||
.DevEngine()
|
||||
newEngine()
|
||||
.reinterpret(
|
||||
submitters,
|
||||
LookupByKeyCommand(lookupNode.templateId, lookupNode.key.key),
|
||||
@ -1780,7 +1781,7 @@ class EngineTest
|
||||
val submitters = Set(alice)
|
||||
val readAs = (Set.empty: Set[Party])
|
||||
|
||||
val Right((tx, txMeta)) = engine
|
||||
val Right((tx, txMeta)) = suffixLenientEngine
|
||||
.submit(submitters, readAs, Commands(ImmArray(exerciseCmd), now, "test"), participant, seed)
|
||||
.consume(
|
||||
lookupContractMap.get,
|
||||
@ -1794,8 +1795,7 @@ class EngineTest
|
||||
lookupNode.result shouldBe None
|
||||
|
||||
val Right((reinterpreted, _)) =
|
||||
Engine
|
||||
.DevEngine()
|
||||
newEngine()
|
||||
.reinterpret(
|
||||
submitters,
|
||||
LookupByKeyCommand(lookupNode.templateId, lookupNode.key.key),
|
||||
@ -1822,7 +1822,7 @@ class EngineTest
|
||||
|
||||
val submitters = Set(alice)
|
||||
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -1855,7 +1855,7 @@ class EngineTest
|
||||
)
|
||||
val submitters = Set(party)
|
||||
val readAs = (Set.empty: Set[Party])
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.submit(
|
||||
submitters,
|
||||
readAs,
|
||||
@ -1888,7 +1888,7 @@ class EngineTest
|
||||
|
||||
val submitters = Set(alice)
|
||||
|
||||
val Right((tx, _)) = engine
|
||||
val Right((tx, _)) = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -1961,7 +1961,7 @@ class EngineTest
|
||||
lookupKey,
|
||||
)
|
||||
|
||||
val Right((tx, _)) = engine
|
||||
val Right((tx, _)) = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -2026,7 +2026,7 @@ class EngineTest
|
||||
val submitters = Set(alice)
|
||||
val readAs = (Set.empty: Set[Party])
|
||||
def run(cmds: ImmArray[ApiCommand]) =
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.submit(submitters, readAs, Commands(cmds, now, ""), participant, submissionSeed)
|
||||
.consume(lookupContract, lookupPackage, lookupKey)
|
||||
|
||||
@ -2075,7 +2075,7 @@ class EngineTest
|
||||
)
|
||||
val submitters = Set(party)
|
||||
val readAs = (Set.empty: Set[Party])
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.submit(
|
||||
submitters,
|
||||
readAs,
|
||||
@ -2098,7 +2098,7 @@ class EngineTest
|
||||
for {
|
||||
submitter <- tx.guessSubmitter
|
||||
ntx = SubmittedTransaction(Normalization.normalizeTx(tx))
|
||||
res <- engine
|
||||
res <- suffixLenientEngine
|
||||
.validate(
|
||||
Set(submitter),
|
||||
ntx,
|
||||
@ -2122,16 +2122,18 @@ class EngineTest
|
||||
|
||||
"be partially reinterpretable" in {
|
||||
val Right((tx, txMeta)) = run(3)
|
||||
val stx = suffix(tx)
|
||||
|
||||
val ImmArray(_, exeNode1) = tx.transaction.roots
|
||||
val Node.NodeExercises(_, _, _, _, _, _, _, _, _, children, _, _, _, _) =
|
||||
tx.transaction.nodes(exeNode1)
|
||||
val nids = children.toSeq.take(2).toImmArray
|
||||
|
||||
reinterpret(
|
||||
engine,
|
||||
suffixStrictEngine,
|
||||
Set(party),
|
||||
nids,
|
||||
tx,
|
||||
stx,
|
||||
txMeta,
|
||||
let,
|
||||
lookupPackage,
|
||||
@ -2169,7 +2171,7 @@ class EngineTest
|
||||
)
|
||||
.consume(_ => None, lookupPackage, lookupKey)
|
||||
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -2198,7 +2200,7 @@ class EngineTest
|
||||
.preprocessCommands(ImmArray(CreateCommand(templateId, createArg)))
|
||||
.consume(_ => None, lookupPackage, lookupKey)
|
||||
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -2229,7 +2231,7 @@ class EngineTest
|
||||
val Right(cmds) = preprocessor
|
||||
.preprocessCommands(ImmArray(CreateCommand(templateId, createArg)))
|
||||
.consume(_ => None, lookupPackage, lookupKey)
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -2257,12 +2259,14 @@ class EngineTest
|
||||
EngineConfig(
|
||||
allowedLanguageVersions = LV.DevVersions,
|
||||
contractKeyUniqueness = ContractKeyUniquenessMode.Off,
|
||||
requireSuffixedGlobalCids = true,
|
||||
)
|
||||
)
|
||||
val uckEngine = new Engine(
|
||||
EngineConfig(
|
||||
allowedLanguageVersions = LV.DevVersions,
|
||||
contractKeyUniqueness = ContractKeyUniquenessMode.On,
|
||||
requireSuffixedGlobalCids = true,
|
||||
)
|
||||
)
|
||||
val (multiKeysPkgId, _, allMultiKeysPkgs) = loadPackage("daml-lf/tests/MultiKeys.dar")
|
||||
@ -2427,7 +2431,7 @@ class EngineTest
|
||||
lookupPackage,
|
||||
lookupKey,
|
||||
)
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -2537,7 +2541,7 @@ class EngineTest
|
||||
lookupPackage,
|
||||
lookupKey,
|
||||
)
|
||||
engine
|
||||
suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -2619,7 +2623,7 @@ class EngineTest
|
||||
lookupPackage,
|
||||
mockedKeyLookup,
|
||||
)
|
||||
val result = engine
|
||||
val result = suffixLenientEngine
|
||||
.interpretCommands(
|
||||
validating = false,
|
||||
submitters = submitters,
|
||||
@ -2672,7 +2676,8 @@ class EngineTest
|
||||
def engine(min: LV, max: LV) =
|
||||
new Engine(
|
||||
EngineConfig(
|
||||
allowedLanguageVersions = VersionRange(min, max)
|
||||
allowedLanguageVersions = VersionRange(min, max),
|
||||
requireSuffixedGlobalCids = true,
|
||||
)
|
||||
)
|
||||
|
||||
@ -2718,6 +2723,14 @@ class EngineTest
|
||||
|
||||
object EngineTest {
|
||||
|
||||
private def engineConfig(requireCidSuffixes: Boolean) = EngineConfig(
|
||||
allowedLanguageVersions = language.LanguageVersion.DevVersions,
|
||||
requireSuffixedGlobalCids = requireCidSuffixes,
|
||||
)
|
||||
|
||||
private def newEngine(requireCidSuffixes: Boolean = false) =
|
||||
new Engine(engineConfig(requireCidSuffixes))
|
||||
|
||||
private implicit def qualifiedNameStr(s: String): QualifiedName =
|
||||
QualifiedName.assertFromString(s)
|
||||
|
||||
@ -2751,6 +2764,13 @@ object EngineTest {
|
||||
Validation.isReplayedBy(Normalization.normalizeTx(recorded), replayed)
|
||||
}
|
||||
|
||||
private val dummySuffix = Bytes.assertFromString("00")
|
||||
|
||||
private def suffix(tx: Tx.Transaction) =
|
||||
data.assertRight(tx.suffixCid(_ => dummySuffix))
|
||||
|
||||
// Mimics Canton reinterpreation
|
||||
// requires a suffixed transaction.
|
||||
private def reinterpret(
|
||||
engine: Engine,
|
||||
submitters: Set[Party],
|
||||
@ -2811,7 +2831,8 @@ object EngineTest {
|
||||
lookupPackages,
|
||||
k => keys0.get(k.globalKey),
|
||||
)
|
||||
(tr1, meta1) = currentStep
|
||||
(tr0, meta1) = currentStep
|
||||
tr1 = suffix(tr0)
|
||||
(contracts1, keys1) = tr1.transaction.fold((contracts0, keys0)) {
|
||||
case (
|
||||
(contracts, keys),
|
||||
|
@ -32,35 +32,71 @@ class PreprocessorSpec
|
||||
private implicit def toName(s: String): Ref.Name = Ref.Name.assertFromString(s)
|
||||
|
||||
private[this] val recordCon =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Record"))
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Mod:Record"))
|
||||
private[this] val recordRefCon =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Mod:RecordRef"))
|
||||
private[this] val variantCon =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Variant"))
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Mod:Either"))
|
||||
private[this] val enumCon =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Enum"))
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Mod:Enum"))
|
||||
private[this] val tricky =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:Tricky"))
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Mod:Tricky"))
|
||||
private[this] val myListTyCons =
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Module:MyList"))
|
||||
Ref.Identifier(pkgId, Ref.QualifiedName.assertFromString("Mod:MyList"))
|
||||
private[this] val myNilCons = Ref.Name.assertFromString("MyNil")
|
||||
private[this] val myConsCons = Ref.Name.assertFromString("MyCons")
|
||||
private[this] val alice = Ref.Party.assertFromString("Alice")
|
||||
private[this] val dummySuffix = Bytes.assertFromString("00")
|
||||
private[this] val cidWithSuffix =
|
||||
ContractId.V1(crypto.Hash.hashPrivateKey("cidWithSuffix"), dummySuffix)
|
||||
private[this] val cidWithoutSuffix =
|
||||
ContractId.V1(crypto.Hash.hashPrivateKey("cidWithoutSuffix"), Bytes.Empty)
|
||||
private[this] val typ = t"ContractId Mod:Record"
|
||||
|
||||
val pkg =
|
||||
private[this] def suffixValue(v: Value[ContractId]) =
|
||||
data.assertRight(v.suffixCid(_ => dummySuffix))
|
||||
|
||||
lazy val pkg =
|
||||
p"""
|
||||
module Module {
|
||||
module Mod {
|
||||
|
||||
record Record = { field : Int64 };
|
||||
variant Variant = variant1 : Text | variant2 : Int64;
|
||||
record @serializable Record = { field : Int64 };
|
||||
variant @serializable Either (a: *) (b: *) = Left : a | Right : b;
|
||||
enum Enum = value1 | value2;
|
||||
|
||||
record Tricky (b: * -> *) = { x : b Unit };
|
||||
|
||||
record MyCons = { head : Int64, tail: Module:MyList };
|
||||
variant MyList = MyNil : Unit | MyCons: Module:MyCons ;
|
||||
record MyCons = { head : Int64, tail: Mod:MyList };
|
||||
variant MyList = MyNil : Unit | MyCons: Mod:MyCons ;
|
||||
|
||||
record @serializable RecordRef = { owner: Party, cid: (ContractId Mod:Record) };
|
||||
|
||||
val @noPartyLiterals toParties: Mod:RecordRef -> List Party =
|
||||
\ (ref: Mod:RecordRef) -> Cons @Party [Mod:RecordRef {owner} ref] (Nil @Party);
|
||||
|
||||
template (this : RecordRef) = {
|
||||
precondition True,
|
||||
signatories Mod:toParties this,
|
||||
observers Mod:toParties this,
|
||||
agreement "Agreement",
|
||||
choices {
|
||||
choice Change (self) (newCid: ContractId Mod:Record) : ContractId Mod:RecordRef,
|
||||
controllers Mod:toParties this,
|
||||
observers Nil @Party
|
||||
to create @Mod:RecordRef Mod:RecordRef { owner = Mod:RecordRef {owner} this, cid = newCid }
|
||||
},
|
||||
key @Party (Mod:RecordRef {owner} this) (\ (p: Party) -> Cons @Party [p] (Nil @Party))
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
"""
|
||||
|
||||
private[this] val compiledPackage = ConcurrentCompiledPackages()
|
||||
assert(compiledPackage.addPackage(pkgId, pkg) == ResultDone.Unit)
|
||||
private[this] val preprocessor = new Preprocessor(compiledPackage, requiredCidSuffix = true)
|
||||
import preprocessor.{translateValue, preprocessCommand}
|
||||
|
||||
"translateValue" should {
|
||||
|
||||
val testCases = Table[Ast.Type, Value[ContractId], speedy.SValue](
|
||||
@ -86,11 +122,7 @@ class PreprocessorSpec
|
||||
),
|
||||
// TNumeric(TNat(9)) ,
|
||||
// ValueNumeric(Numeric.assertFromString("9.000000000")),
|
||||
(
|
||||
TParty,
|
||||
ValueParty(Ref.Party.assertFromString("Alice")),
|
||||
SParty(Ref.Party.assertFromString("Alice")),
|
||||
),
|
||||
(TParty, ValueParty(alice), SParty(alice)),
|
||||
(
|
||||
TContractId(Ast.TTyCon(recordCon)),
|
||||
ValueContractId(ContractId.assertFromString("#contractId")),
|
||||
@ -118,9 +150,9 @@ class PreprocessorSpec
|
||||
SRecord(recordCon, ImmArray("field"), ArrayList(SInt64(33))),
|
||||
),
|
||||
(
|
||||
Ast.TTyCon(variantCon),
|
||||
ValueVariant(None, "variant1", ValueText("some test")),
|
||||
SVariant(variantCon, "variant1", 0, SText("some test")),
|
||||
TTyConApp(variantCon, ImmArray(TText, TInt64)),
|
||||
ValueVariant(None, "Left", ValueText("some test")),
|
||||
SVariant(variantCon, "Left", 0, SText("some test")),
|
||||
),
|
||||
(Ast.TTyCon(enumCon), ValueEnum(None, "value1"), SEnum(enumCon, "value1", 0)),
|
||||
(
|
||||
@ -130,11 +162,6 @@ class PreprocessorSpec
|
||||
),
|
||||
)
|
||||
|
||||
val compiledPackage = ConcurrentCompiledPackages()
|
||||
assert(compiledPackage.addPackage(pkgId, pkg) == ResultDone.Unit)
|
||||
val preprocessor = new Preprocessor(compiledPackage)
|
||||
import preprocessor.translateValue
|
||||
|
||||
"succeeds on well type values" in {
|
||||
forAll(testCases) { (typ, value, svalue) =>
|
||||
translateValue(typ, value) shouldBe ResultDone(svalue)
|
||||
@ -171,6 +198,123 @@ class PreprocessorSpec
|
||||
err shouldBe Error.Preprocessing.ValueNesting(tooBig)
|
||||
}
|
||||
}
|
||||
|
||||
"reject non suffix Contract IDs" in {
|
||||
|
||||
val cidValueWithoutSuffix = ValueContractId(cidWithoutSuffix)
|
||||
|
||||
val testCases = Table[Ast.Type, Value[ContractId]](
|
||||
("type" -> "value"),
|
||||
t"ContractId Mod:Record" -> cidValueWithoutSuffix,
|
||||
TList(typ) -> ValueList(FrontStack(cidValueWithoutSuffix)),
|
||||
TTextMap(typ) -> ValueTextMap(SortedLookupList(Map("0" -> cidValueWithoutSuffix))),
|
||||
TGenMap(TInt64, typ) -> ValueGenMap(ImmArray(ValueInt64(1) -> cidValueWithoutSuffix)),
|
||||
TGenMap(typ, TInt64) -> ValueGenMap(ImmArray(cidValueWithoutSuffix -> ValueInt64(0))),
|
||||
TOptional(typ) -> ValueOptional(Some(cidValueWithoutSuffix)),
|
||||
Ast.TTyCon(recordRefCon) -> ValueRecord(
|
||||
None,
|
||||
ImmArray(None -> ValueParty(alice), None -> cidValueWithoutSuffix),
|
||||
),
|
||||
TTyConApp(variantCon, ImmArray(typ, TInt64)) -> ValueVariant(
|
||||
None,
|
||||
"Left",
|
||||
cidValueWithoutSuffix,
|
||||
),
|
||||
)
|
||||
|
||||
forAll(testCases) { (typ, value) =>
|
||||
translateValue(typ, suffixValue(value)) shouldBe a[ResultDone[_]]
|
||||
translateValue(typ, value) shouldBe a[ResultError]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"preprocessCommand" should {
|
||||
|
||||
import command._
|
||||
|
||||
def suffixCid(cid: ContractId) =
|
||||
cid match {
|
||||
case ContractId.V1(discriminator, suffix) if suffix.isEmpty =>
|
||||
ContractId.V1(discriminator, dummySuffix)
|
||||
case otherwise =>
|
||||
otherwise
|
||||
}
|
||||
|
||||
def suffixCmd(cmd: ApiCommand) =
|
||||
cmd match {
|
||||
case CreateCommand(templateId, argument) =>
|
||||
CreateCommand(templateId, suffixValue(argument))
|
||||
case ExerciseCommand(templateId, contractId, choiceId, argument) =>
|
||||
ExerciseCommand(templateId, suffixCid(contractId), choiceId, suffixValue(argument))
|
||||
case ExerciseByKeyCommand(templateId, contractKey, choiceId, argument) =>
|
||||
ExerciseByKeyCommand(templateId, contractKey, choiceId, suffixValue(argument))
|
||||
case CreateAndExerciseCommand(templateId, createArgument, choiceId, choiceArgument) =>
|
||||
CreateAndExerciseCommand(
|
||||
templateId,
|
||||
suffixValue(createArgument),
|
||||
choiceId,
|
||||
suffixValue(choiceArgument),
|
||||
)
|
||||
}
|
||||
|
||||
"reject non suffix Contract IDs" in {
|
||||
|
||||
val key = ValueParty(alice)
|
||||
val payloadWithoutSuffix = ValueRecord(
|
||||
None,
|
||||
ImmArray(None -> ValueParty(alice), None -> ValueContractId(cidWithoutSuffix)),
|
||||
)
|
||||
val payloadWithSuffix = ValueRecord(
|
||||
None,
|
||||
ImmArray(None -> ValueParty(alice), None -> ValueContractId(cidWithSuffix)),
|
||||
)
|
||||
|
||||
val testCases = Table[ApiCommand](
|
||||
"command",
|
||||
CreateCommand(
|
||||
recordRefCon,
|
||||
payloadWithoutSuffix,
|
||||
),
|
||||
ExerciseCommand(
|
||||
recordRefCon,
|
||||
cidWithSuffix,
|
||||
"Change",
|
||||
ValueContractId(cidWithoutSuffix),
|
||||
),
|
||||
ExerciseCommand(
|
||||
recordRefCon,
|
||||
cidWithoutSuffix,
|
||||
"Change",
|
||||
ValueContractId(cidWithSuffix),
|
||||
),
|
||||
CreateAndExerciseCommand(
|
||||
recordRefCon,
|
||||
payloadWithoutSuffix,
|
||||
"Change",
|
||||
ValueContractId(cidWithSuffix),
|
||||
),
|
||||
CreateAndExerciseCommand(
|
||||
recordRefCon,
|
||||
payloadWithSuffix,
|
||||
"Change",
|
||||
ValueContractId(cidWithoutSuffix),
|
||||
),
|
||||
ExerciseByKeyCommand(
|
||||
recordRefCon,
|
||||
key,
|
||||
"Change",
|
||||
ValueContractId(cidWithoutSuffix),
|
||||
),
|
||||
)
|
||||
|
||||
forAll(testCases) { cmd =>
|
||||
preprocessCommand(suffixCmd(cmd)) shouldBe a[ResultDone[_]]
|
||||
preprocessCommand(cmd) shouldBe a[ResultError]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -69,7 +69,12 @@ class ReinterpretTest
|
||||
val lookupPackage = allPackages.get(_)
|
||||
val lookupKey = { _: GlobalKeyWithMaintainers => None }
|
||||
|
||||
private val engine = Engine.DevEngine()
|
||||
private val engine = new Engine(
|
||||
EngineConfig(
|
||||
allowedLanguageVersions = language.LanguageVersion.DevVersions,
|
||||
requireSuffixedGlobalCids = true,
|
||||
)
|
||||
)
|
||||
|
||||
def Top(xs: Shape*) = Shape.Top(xs.toList)
|
||||
def Exercise(xs: Shape*) = Shape.Exercise(xs.toList)
|
||||
|
@ -675,7 +675,8 @@ object Converter {
|
||||
} catch {
|
||||
case e: Exception => Left(s"LF conversion failed: ${e.toString}")
|
||||
}
|
||||
valueTranslator = new preprocessing.ValueTranslator(compiledPackages.interface)
|
||||
valueTranslator =
|
||||
new preprocessing.ValueTranslator(compiledPackages.interface, requiredCidSuffix = false)
|
||||
sValue <- valueTranslator
|
||||
.translateValue(ty, lfValue)
|
||||
.left
|
||||
|
@ -64,7 +64,8 @@ object ScriptF {
|
||||
) {
|
||||
def clients = _clients
|
||||
def compiledPackages = machine.compiledPackages
|
||||
val valueTranslator = new ValueTranslator(compiledPackages.interface)
|
||||
val valueTranslator =
|
||||
new ValueTranslator(interface = compiledPackages.interface, requiredCidSuffix = false)
|
||||
val utcClock = Clock.systemUTC()
|
||||
def addPartyParticipantMapping(party: Party, participant: Participant) = {
|
||||
_clients =
|
||||
|
@ -46,7 +46,8 @@ class IdeLedgerClient(
|
||||
|
||||
def currentSubmission: Option[ScenarioRunner.CurrentSubmission] = _currentSubmission
|
||||
|
||||
private[this] val preprocessor = new engine.preprocessing.CommandPreprocessor(compiledPackages)
|
||||
private[this] val preprocessor =
|
||||
new preprocessing.CommandPreprocessor(compiledPackages.interface, requiredCidSuffix = false)
|
||||
|
||||
private var _ledger: ScenarioLedger = ScenarioLedger.initialLedger(Time.Timestamp.Epoch)
|
||||
def ledger: ScenarioLedger = _ledger
|
||||
|
@ -18,7 +18,11 @@ class SpeedyToValueBenchmark extends BenchmarkWithLedgerExport {
|
||||
override def setup(): Unit = {
|
||||
super.setup()
|
||||
val decodedValues = submissions.values.map(_.mapValue(assertDecode)).toVector
|
||||
val translator = new ValueTranslator(submissions.compiledPackages.interface)
|
||||
val translator =
|
||||
new ValueTranslator(
|
||||
interface = submissions.compiledPackages.interface,
|
||||
requiredCidSuffix = false,
|
||||
)
|
||||
speedyValues = decodedValues.map(x => assertTranslate(translator)(x.mapValue(_.value)))
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,8 @@ class ValueTranslatorBenchmark extends BenchmarkWithLedgerExport {
|
||||
override def setup(): Unit = {
|
||||
super.setup()
|
||||
decodedValues = submissions.values.map(_.mapValue(assertDecode).mapValue(_.value)).toVector
|
||||
translator = new ValueTranslator(submissions.compiledPackages.interface)
|
||||
translator =
|
||||
new ValueTranslator(submissions.compiledPackages.interface, requiredCidSuffix = false)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
|
@ -498,7 +498,8 @@ object Converter {
|
||||
}
|
||||
|
||||
def apply(compiledPackages: CompiledPackages, triggerIds: TriggerIds): Converter = {
|
||||
val valueTranslator = new preprocessing.ValueTranslator(compiledPackages.interface)
|
||||
val valueTranslator =
|
||||
new preprocessing.ValueTranslator(compiledPackages.interface, requiredCidSuffix = false)
|
||||
Converter(
|
||||
fromTransaction(valueTranslator, triggerIds, _),
|
||||
fromCompletion(triggerIds, _),
|
||||
|
Loading…
Reference in New Issue
Block a user