diff --git a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltinFun.scala b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltinFun.scala index 110cee81d901..c0acaba66432 100644 --- a/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltinFun.scala +++ b/sdk/daml-lf/interpreter/src/main/scala/com/digitalasset/daml/lf/speedy/SBuiltinFun.scala @@ -1154,25 +1154,6 @@ private[lf] object SBuiltinFun { coid: V.ContractId, interfaceId: TypeConName, )(k: SAny => Control[Question.Update]): Control[Question.Update] = { - // Continuation called by two different branches of the expression below. Factorized out to avoid duplication. - def cacheContractAndReturnAny( - machine: UpdateMachine, - coid: V.ContractId, - dstTplId: Ref.ValueRef, - dstArg: SValue, - )(k: SAny => Control[Question.Update]): Control[Question.Update] = { - // ensure the contract and its metadata are cached - getContractInfo( - machine, - coid, - dstTplId, - dstArg, - allowCatchingContractInfoErrors = false, - ) { _ => - k(SAny(Ast.TTyCon(dstTplId), dstArg)) - } - } - hardFetchTemplate(machine, coid) { (pkgName, srcTplId, srcArg) => ensureTemplateImplementsInterface(machine, interfaceId, coid, srcTplId) { viewInterface(machine, interfaceId, srcTplId, srcArg) { srcView => @@ -1189,31 +1170,41 @@ private[lf] object SBuiltinFun { case Some(dstArg) => viewInterface(machine, interfaceId, dstTplId, dstArg) { dstView => executeExpression(machine, SEPreventCatch(srcView)) { srcViewValue => - // If the destination and src templates are the same, we skip the computation - // of the destination template's view. - if (dstTplId == srcTplId) - cacheContractAndReturnAny(machine, coid, dstTplId, dstArg)(k) - else - executeExpression(machine, SEPreventCatch(dstView)) { dstViewValue => - if (srcViewValue != dstViewValue) { - Control.Error( - IE.Dev( - NameOf.qualifiedNameOfCurrentFunc, - IE.Dev.Upgrade( - IE.Dev.Upgrade.ViewMismatch( - coid, - interfaceId, - srcTplId, - dstTplId, - srcView = srcViewValue.toUnnormalizedValue, - dstView = dstViewValue.toUnnormalizedValue, + getContractInfo( + machine, + coid, + dstTplId, + dstArg, + allowCatchingContractInfoErrors = false, + ) { contract => + // If the destination and src templates are the same, we skip the computation + // of the destination template's view and the validation of the contract info. + if (dstTplId == srcTplId) + k(SAny(Ast.TTyCon(dstTplId), dstArg)) + else + validateContractInfo(machine, coid, dstTplId, contract) { () => + executeExpression(machine, SEPreventCatch(dstView)) { dstViewValue => + if (srcViewValue != dstViewValue) { + Control.Error( + IE.Dev( + NameOf.qualifiedNameOfCurrentFunc, + IE.Dev.Upgrade( + IE.Dev.Upgrade.ViewMismatch( + coid, + interfaceId, + srcTplId, + dstTplId, + srcView = srcViewValue.toUnnormalizedValue, + dstView = dstViewValue.toUnnormalizedValue, + ) + ), ) - ), - ) - ) - } else - cacheContractAndReturnAny(machine, coid, dstTplId, dstArg)(k) - } + ) + } else + k(SAny(Ast.TTyCon(dstTplId), dstArg)) + } + } + } } } } diff --git a/sdk/daml-script/test/daml/upgrades/ContractKeys.daml b/sdk/daml-script/test/daml/upgrades/ContractKeys.daml index 3fb5474e544b..642429180bdf 100644 --- a/sdk/daml-script/test/daml/upgrades/ContractKeys.daml +++ b/sdk/daml-script/test/daml/upgrades/ContractKeys.daml @@ -13,10 +13,33 @@ import qualified V1.ChangedKeyExpr as V1 import qualified V2.ChangedKeyExpr as V2 import qualified V1.UpgradedContractKeys as V1 import qualified V2.UpgradedContractKeys as V2 +import qualified V1.IfaceMod as Iface + +{- PACKAGE +name: contract-key-upgrades-iface +versions: 1 +-} + +{- MODULE +package: contract-key-upgrades-iface +contents: | + module IfaceMod where + + data MyUnit = MyUnit {} + deriving (Eq, Show) + + interface I where + viewtype MyUnit + + nonconsuming choice NoOp : () + controller signatory this + do pure () +-} {- PACKAGE name: contract-key-upgrades versions: 2 +depends: contract-key-upgrades-iface-1.0.0 -} main : TestTree @@ -33,8 +56,10 @@ main = tests [ ("queryContractId, src=v1 tgt=v2", queryKeyChangedExprSameValue) , ("queryContractKey, src=v1 tgt=v2", qckKeyChangedExprSameValue) , ("fetch, src=v1 tgt=v2", fetchKeyChangedExprSameValue) + , ("fetchByInterface, src=v1 tgt=i", fbiKeyChangedExprSameValue) , ("fetchByKey, src=v1 tgt=v2", fbkKeyChangedExprSameValue) , ("exercise, src=v1 tgt=v2", exerciseKeyChangedExprSameValue) + , ("exerciseByInterface, src=v1 tgt=i", ebiKeyChangedExprSameValue) , ("exerciseByKey, src=v1 tgt=v2", ebkKeyChangedExprSameValue) , ("exerciseCmd, src=v1 tgt=v2", exerciseCmdKeyChangedExprSameValue) , ("exerciseByKeyCmd, src=v1 tgt=v2", ebkCmdKeyChangedExprSameValue) @@ -43,8 +68,10 @@ main = tests [ broken ("queryContractId, src=v1 tgt=v2", queryKeyChangedExprChangedValue) , broken ("queryContractKey, src=v1 tgt=v2", qckKeyChangedExprChangedValue) , ("fetch, src=v1 tgt=v2", fetchKeyChangedExprChangedValue) + , ("fetchByInterface, src=v1 tgt=i", fbiKeyChangedExprChangedValue) , ("fetchByKey, src=v1 tgt=v2", fbkKeyChangedExprChangedValue) , ("exercise, src=v1 tgt=v2", exerciseKeyChangedExprChangedValue) + , ("exerciseByInterface, src=v1 tgt=i", ebiKeyChangedExprChangedValue) , ("exerciseByKey, src=v1 tgt=v2", ebkKeyChangedExprChangedValue) , ("exerciseCmd, src=v1 tgt=v2", exerciseCmdKeyChangedExprChangedValue) , ("exerciseByKeyCmd, src=v1 tgt=v2", ebkCmdKeyChangedExprChangedValue) @@ -138,6 +165,8 @@ package: contract-key-upgrades contents: | module ChangedKeyExpr where + import IfaceMod + data ChangedKeyExprKey = ChangedKeyExprKey with p : Party b : Bool @@ -153,6 +182,9 @@ contents: | key (ChangedKeyExprKey party b) : ChangedKeyExprKey -- @V 2 maintainer key.p + interface instance I for ChangedKeyExpr where + view = MyUnit {} + choice ChangedKeyExprCall : Text controller party do pure "V1" -- @V 1 @@ -169,6 +201,13 @@ contents: | controller party do fetch cid + choice ChangedKeyExprFetchByInterface : MyUnit with + cid : ContractId I + controller party + do + i <- fetch cid + pure (view i) + choice ChangedKeyExprFetchByKey : (ContractId ChangedKeyExpr, ChangedKeyExpr) with k : ChangedKeyExprKey controller party @@ -179,6 +218,11 @@ contents: | controller party do exercise @ChangedKeyExpr cid ChangedKeyExprCall + choice ChangedKeyExprExerciseByInterface : () with + cid : ContractId I + controller party + do exercise @I cid NoOp + choice ChangedKeyExprExerciseByKey : Text with k : ChangedKeyExprKey controller party @@ -233,6 +277,20 @@ fetchKeyChangedExprChangedValue = test $ do expectKeyChangedError =<< a `trySubmit` createAndExerciseCmd (V2.ChangedKeyExprHelper a) (V2.ChangedKeyExprFetch $ coerceContractId cid) +fbiKeyChangedExprSameValue : Test +fbiKeyChangedExprSameValue = test $ do + a <- allocateParty "alice" + cid <- a `submit` createExactCmd (V1.ChangedKeyExpr a False) + foundContract <- a `submit` createAndExerciseCmd (V2.ChangedKeyExprHelper a) (V2.ChangedKeyExprFetchByInterface $ coerceContractId cid) + foundContract === Iface.MyUnit {} + +fbiKeyChangedExprChangedValue : Test +fbiKeyChangedExprChangedValue = test $ do + a <- allocateParty "alice" + cid <- a `submit` createExactCmd (V1.ChangedKeyExpr a True) + expectKeyChangedError =<< + a `trySubmit` createAndExerciseCmd (V2.ChangedKeyExprHelper a) (V2.ChangedKeyExprFetchByInterface $ coerceContractId cid) + fbkKeyChangedExprSameValue : Test fbkKeyChangedExprSameValue = test $ do a <- allocateParty "alice" @@ -245,7 +303,6 @@ fbkKeyChangedExprChangedValue : Test fbkKeyChangedExprChangedValue = test $ do a <- allocateParty "alice" cid <- a `submit` createExactCmd (V1.ChangedKeyExpr a True) - -- the fetch inside the following command works, even though the key value changed! expectKeyChangedError =<< a `trySubmit` createAndExerciseCmd (V2.ChangedKeyExprHelper a) (V2.ChangedKeyExprFetchByKey $ V2.ChangedKeyExprKey a False) @@ -256,6 +313,13 @@ exerciseKeyChangedExprSameValue = test $ do res <- a `submit` createAndExerciseCmd (V2.ChangedKeyExprHelper a) (V2.ChangedKeyExprExercise $ coerceContractId cid) res === "V2" +ebiKeyChangedExprSameValue : Test +ebiKeyChangedExprSameValue = test $ do + a <- allocateParty "alice" + cid <- a `submit` createExactCmd (V1.ChangedKeyExpr a False) + res <- a `submit` createAndExerciseCmd (V2.ChangedKeyExprHelper a) (V2.ChangedKeyExprExerciseByInterface $ coerceContractId cid) + res === () + ebkKeyChangedExprSameValue : Test ebkKeyChangedExprSameValue = test $ do a <- allocateParty "alice" @@ -284,6 +348,13 @@ exerciseKeyChangedExprChangedValue = test $ do expectKeyChangedError =<< a `trySubmit` createAndExerciseCmd (V2.ChangedKeyExprHelper a) (V2.ChangedKeyExprExercise $ coerceContractId cid) +ebiKeyChangedExprChangedValue : Test +ebiKeyChangedExprChangedValue = test $ do + a <- allocateParty "alice" + cid <- a `submit` createExactCmd (V1.ChangedKeyExpr a True) + expectKeyChangedError =<< + a `trySubmit` createAndExerciseCmd (V2.ChangedKeyExprHelper a) (V2.ChangedKeyExprExerciseByInterface $ coerceContractId cid) + ebkKeyChangedExprChangedValue : Test ebkKeyChangedExprChangedValue = test $ do a <- allocateParty "alice" diff --git a/sdk/daml-script/test/daml/upgrades/Ensure.daml b/sdk/daml-script/test/daml/upgrades/Ensure.daml index 44b79b89970a..f196cd4f2f82 100644 --- a/sdk/daml-script/test/daml/upgrades/Ensure.daml +++ b/sdk/daml-script/test/daml/upgrades/Ensure.daml @@ -1,18 +1,38 @@ -- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -- SPDX-License-Identifier: Apache-2.0 -{-# LANGUAGE AllowAmbiguousTypes #-} - module Ensure (main) where import UpgradeTestLib import qualified V1.EnsureChanges as V1 import qualified V2.EnsureChanges as V2 +import qualified V1.IfaceMod as Iface import DA.Exception +{- PACKAGE +name: ensure-changes-iface +versions: 1 +-} + +{- MODULE +package: ensure-changes-iface +contents: | + module IfaceMod where + + data MyUnit = MyUnit {} + + interface I where + viewtype MyUnit + + nonconsuming choice NoOp : () + controller signatory this + do pure () +-} + {- PACKAGE name: ensure-changes versions: 2 +depends: ensure-changes-iface-1.0.0 -} {- MODULE @@ -20,6 +40,8 @@ package: ensure-changes contents: | module EnsureChanges where + import IfaceMod + template EnsureChangesTemplate with v1Valid : Bool @@ -30,6 +52,9 @@ contents: | ensure v1Valid -- @V 1 ensure v2Valid -- @V 2 + interface instance I for EnsureChangesTemplate where + view = MyUnit {} + choice EnsureChangesCall : Text controller party do pure "V1" -- @V 1 @@ -39,29 +64,34 @@ contents: | main : TestTree main = tests [ ("Fails if the ensure clause changes such that V1 is not longer valid", ensureClauseBecomesInvalid) + , ("Fails if the ensure clause changes such that V1 is not longer valid, exercise by interface", ensureClauseBecomesInvalidDynamic) , ("Succeeds when implicitly creating a V1 contract such that the ensure clause only passes in V2", onlyV2EnsureClauseRequiredForImplicitUpgrade) , ("Fails when explicitly calling a V1 choice on a V2 contract that doesn't pass the ensure clause in V1", ensureClauseDowngradeToNoLongerValid) ] testForPreconditionFailed - : forall t2 t1 c2 r - . (Template t1, HasEnsure t1, Choice t2 c2 r, Show r) + : (Template t1, HasEnsure t1, Show r) => (Party -> t1) - -> c2 - -> Bool + -> (ContractId t1 -> Commands r) -> Test -testForPreconditionFailed makeV1Contract v2Choice explicitPackageIds = test $ do +testForPreconditionFailed makeV1Contract exerciseChoice = test $ do a <- allocatePartyOn "alice" participant0 cid <- a `submit` createExactCmd (makeV1Contract a) - let cidV2 = coerceContractId @t1 @t2 cid - res <- a `trySubmit` (if explicitPackageIds then exerciseExactCmd else exerciseCmd) cidV2 v2Choice + res <- a `trySubmit` exerciseChoice cid case res of Left (UnhandledException (Some (fromAnyException -> Some (PreconditionFailed _)))) -> pure () res -> assertFail $ "Expected PreconditionFailed, got " <> show res ensureClauseBecomesInvalid : Test ensureClauseBecomesInvalid = - testForPreconditionFailed @V2.EnsureChangesTemplate (V1.EnsureChangesTemplate True False) V2.EnsureChangesCall False + testForPreconditionFailed (V1.EnsureChangesTemplate True False) $ \cidV1 -> + -- because the exercise is not exact, the contract should be upgraded to V2 before evaluating the ensure clause + exerciseCmd cidV1 V1.EnsureChangesCall + +ensureClauseBecomesInvalidDynamic : Test +ensureClauseBecomesInvalidDynamic = + testForPreconditionFailed (V1.EnsureChangesTemplate True False) $ \cidV1 -> + exerciseExactCmd (coerceContractId @V1.EnsureChangesTemplate @Iface.I cidV1) Iface.NoOp onlyV2EnsureClauseRequiredForImplicitUpgrade : Test onlyV2EnsureClauseRequiredForImplicitUpgrade = test $ do @@ -72,4 +102,5 @@ onlyV2EnsureClauseRequiredForImplicitUpgrade = test $ do ensureClauseDowngradeToNoLongerValid : Test ensureClauseDowngradeToNoLongerValid = - testForPreconditionFailed @V1.EnsureChangesTemplate (V2.EnsureChangesTemplate False True) V1.EnsureChangesCall True + testForPreconditionFailed (V2.EnsureChangesTemplate False True) $ \cidV2 -> + exerciseExactCmd (coerceContractId @V2.EnsureChangesTemplate @V1.EnsureChangesTemplate cidV2) V1.EnsureChangesCall diff --git a/sdk/daml-script/test/daml/upgrades/SignatoryObserverChanges.daml b/sdk/daml-script/test/daml/upgrades/SignatoryObserverChanges.daml index 116c66305d19..312f44d1fcb3 100644 --- a/sdk/daml-script/test/daml/upgrades/SignatoryObserverChanges.daml +++ b/sdk/daml-script/test/daml/upgrades/SignatoryObserverChanges.daml @@ -6,11 +6,33 @@ module SignatoryObserverChanges (main) where import UpgradeTestLib import qualified V1.SignatoryObserverChanges as V1 import qualified V2.SignatoryObserverChanges as V2 +import qualified V1.IfaceMod as Iface import DA.Text +{- PACKAGE +name: signatory-observer-changes-iface +versions: 1 +-} + +{- MODULE +package: signatory-observer-changes-iface +contents: | + module IfaceMod where + + data MyUnit = MyUnit {} + + interface I where + viewtype MyUnit + + nonconsuming choice NoOp : () + controller signatory this + do pure () +-} + {- PACKAGE name: signatory-observer-changes versions: 2 +depends: signatory-observer-changes-iface-1.0.0 -} {- MODULE @@ -18,6 +40,8 @@ package: signatory-observer-changes contents: | module SignatoryObserverChanges where + import IfaceMod + template SignatoryObserverChangesTemplate with signatories : [Party] @@ -30,22 +54,43 @@ contents: | observer observers -- @V 1 observer replacementObservers -- @V 2 + interface instance I for SignatoryObserverChangesTemplate where + view = MyUnit {} + choice InvalidUpgradeStakeholdersCall : () with -- @V 2 controller signatory this -- @V 2 do pure () -- @V 2 -} main : TestTree -main = tests - [ ("Succeeds if the signatories don't change", unchangedSignatoryUpgrade) - , ("Fails if the signatories set gets larger", largerSignatoryUpgrade) - , ("Fails if the signatories set gets smaller", smallerSignatoryUpgrade) - , ("Succeeds if the observers don't change", unchangeObserverUpgrade) - , ("Fails if the observers set gets larger", largerObserverUpgrade) - , ("Fails if the observers set gets smaller", smallerObserverUpgrade) - , ("Succeeds if the observer set loses parties that are already signatories", canRemoveObserversThatAreSignatories) +main = tests $ + [ subtree description + [ ("static exercise", testCase exerciseStaticChoice) + , ("dynamic exercise", testCase exerciseDynamicChoice) + ] + | (description, testCase) <- + [ ("Succeeds if the signatories don't change", unchangedSignatoryUpgrade) + , ("Fails if the signatories set gets larger", largerSignatoryUpgrade) + , ("Fails if the signatories set gets smaller", smallerSignatoryUpgrade) + , ("Succeeds if the observers don't change", unchangeObserverUpgrade) + , ("Fails if the observers set gets larger", largerObserverUpgrade) + , ("Fails if the observers set gets smaller", smallerObserverUpgrade) + , ("Succeeds if the observer set loses parties that are already signatories", canRemoveObserversThatAreSignatories) + ] ] +type ChoiceExerciser = ContractId V1.SignatoryObserverChangesTemplate -> Commands () + +exerciseStaticChoice : ChoiceExerciser +exerciseStaticChoice cidV1 = + let cidV2 = coerceContractId @V1.SignatoryObserverChangesTemplate @V2.SignatoryObserverChangesTemplate cidV1 + in exerciseCmd cidV2 V2.InvalidUpgradeStakeholdersCall + +exerciseDynamicChoice : ChoiceExerciser +exerciseDynamicChoice cidV1 = + let cidI = coerceContractId @V1.SignatoryObserverChangesTemplate @Iface.I cidV1 + in exerciseCmd cidI Iface.NoOp + -- Given a function that maps a set of 3 parties to the pre-upgrade and post-upgrade signatory set -- and the same for observers -- along side an expected result flag (success or failure), test the upgrade behaviour @@ -53,8 +98,9 @@ signatoryObserverUpgrade : Bool -> ((Party, Party, Party) -> ([Party], [Party])) -> ((Party, Party, Party) -> ([Party], [Party])) + -> ChoiceExerciser -> Test -signatoryObserverUpgrade shouldSucceed sigF obsF = test $ do +signatoryObserverUpgrade shouldSucceed sigF obsF exerciseChoice = test $ do alice <- allocatePartyOn "alice" participant0 bob <- allocatePartyOn "bob" participant0 charlie <- allocatePartyOn "charlie" participant0 @@ -67,8 +113,7 @@ signatoryObserverUpgrade shouldSucceed sigF obsF = test $ do replacementSignatories = postSignatories replacementObservers = postObservers - let cidV2 = coerceContractId @V1.SignatoryObserverChangesTemplate @V2.SignatoryObserverChangesTemplate cid - res <- trySubmitMulti [alice, bob, charlie] [] $ exerciseCmd cidV2 V2.InvalidUpgradeStakeholdersCall + res <- trySubmitMulti [alice, bob, charlie] [] $ exerciseChoice cid case (res, shouldSucceed) of (Right _, True) -> pure () (Left (DevError Upgrade msg), False) @@ -79,31 +124,31 @@ signatoryObserverUpgrade shouldSucceed sigF obsF = test $ do unchanged : (Party, Party, Party) -> ([Party], [Party]) unchanged (alice, bob, charlie) = ([alice], [alice]) -signatoryUpgrade : Bool -> ((Party, Party, Party) -> ([Party], [Party])) -> Test +signatoryUpgrade : Bool -> ((Party, Party, Party) -> ([Party], [Party])) -> ChoiceExerciser ->Test signatoryUpgrade shouldSucceed f = signatoryObserverUpgrade shouldSucceed f unchanged -observerUpgrade : Bool -> ((Party, Party, Party) -> ([Party], [Party])) -> Test +observerUpgrade : Bool -> ((Party, Party, Party) -> ([Party], [Party])) -> ChoiceExerciser -> Test observerUpgrade shouldSucceed = signatoryObserverUpgrade shouldSucceed unchanged -unchangedSignatoryUpgrade : Test +unchangedSignatoryUpgrade : ChoiceExerciser -> Test unchangedSignatoryUpgrade = signatoryUpgrade True unchanged -largerSignatoryUpgrade : Test +largerSignatoryUpgrade : ChoiceExerciser -> Test largerSignatoryUpgrade = signatoryUpgrade False $ \(alice, bob, charlie) -> ([alice, bob], [alice, bob, charlie]) -smallerSignatoryUpgrade : Test +smallerSignatoryUpgrade : ChoiceExerciser -> Test smallerSignatoryUpgrade = signatoryUpgrade False $ \(alice, bob, charlie) -> ([alice, bob, charlie], [alice, bob]) -unchangeObserverUpgrade : Test +unchangeObserverUpgrade : ChoiceExerciser -> Test unchangeObserverUpgrade = observerUpgrade True unchanged -largerObserverUpgrade : Test +largerObserverUpgrade : ChoiceExerciser -> Test largerObserverUpgrade = observerUpgrade False $ \(alice, bob, charlie) -> ([alice, bob], [alice, bob, charlie]) -smallerObserverUpgrade : Test +smallerObserverUpgrade : ChoiceExerciser -> Test smallerObserverUpgrade = observerUpgrade False $ \(alice, bob, charlie) -> ([alice, bob, charlie], [alice, bob]) -canRemoveObserversThatAreSignatories : Test +canRemoveObserversThatAreSignatories : ChoiceExerciser -> Test canRemoveObserversThatAreSignatories = signatoryObserverUpgrade True