diff --git a/daml/dars.lock b/daml/dars.lock index 2a2b38172a..2c19e5b633 100644 --- a/daml/dars.lock +++ b/daml/dars.lock @@ -5,7 +5,7 @@ splice-amulet 0.1.11 9824927cdb455f833867b74c01cffcd8cb8cc5edd4d2273cea1329b708e splice-amulet 0.1.12 95a88ff9ffd509e097802ecf3bbd58c83a5dff408e439cca4e2105ebd2cd0760 splice-amulet 0.1.13 6e9fc50fb94e56751b49f09ba2dc84da53a9d7cff08115ebb4f6b7a12d0c990c splice-amulet 0.1.14 3ca1343ab26b453d38c8adb70dca5f1ead8440c42b59b68f070786955cbf9ec1 -splice-amulet 0.1.15 0ee7b4ce705113f9748a3d36f199bcdf508c28985644dbe24b58cb65e85d68cd +splice-amulet 0.1.15 4e37012d8b49ede13289005828334d66dde574a7d326035f260e655791d9c600 splice-amulet 0.1.2 1446ffdf23326cef2de97923df96618eb615792bea36cf1431f03639448f1645 splice-amulet 0.1.3 0d89016d5a90eb8bced48bbac99e81c57781b3a36094b8d48b8e4389851e19af splice-amulet 0.1.4 a36ef8888fb44caae13d96341ce1fabd84fc9e2e7b209bbc3caabb48b6be1668 @@ -22,7 +22,7 @@ splice-amulet-name-service 0.1.12 557a74491324790b5cf5a379f2481ab1cd2c8b75530858 splice-amulet-name-service 0.1.13 0e8c7e1cb828336bb4d537f6c4ecd94128b94733eb2ae1f55b5962757d357b4b splice-amulet-name-service 0.1.14 6cb1318176e758c256c2e385f87b86c5060e80fb68a72e8ceb08ac5f9045fff2 splice-amulet-name-service 0.1.15 d4724b90dce9fb08badbb367962d237710b3a603e4f57806a1b0af308cc70fdb -splice-amulet-name-service 0.1.16 e65a5b3dbf85a4f519de28929196560956d7c94e3731e2218967c0aea352bbd1 +splice-amulet-name-service 0.1.16 17134850e6573b5fd617d3aec8c266f73a74177d5174bd9637ffe61524b9d078 splice-amulet-name-service 0.1.2 711a2974d65e6ebd149704da75f3f71234798687ab895b92f066c865dbdeeabb splice-amulet-name-service 0.1.3 beb4b85f3f0cf36dfb93fc917d3ac218ee5d41b6e70604720cb228d85e168ee0 splice-amulet-name-service 0.1.4 053c7f4c2a77312e7d465a4fa7dc8cb298754ad12c0c987a7c401bd724e65efc @@ -31,8 +31,8 @@ splice-amulet-name-service 0.1.6 a208aab2c4a248ab2eff352bd382f8b3bbadc92464123db splice-amulet-name-service 0.1.7 ba7806d9b2d593eac74a050161c54ae1325d170bf175cb66a9c1e5e5ffb88c3d splice-amulet-name-service 0.1.8 efeb3f9b2b92e55fac4ec2d6164f95407a01477240c7465e576df4e310f54bd3 splice-amulet-name-service 0.1.9 f1b5915ad45ded616f43f83c735b7ee158b5eb58abe758a721e50eee19b3e531 -splice-amulet-name-service-test 0.1.18 ca7f53160acd4328a6ef9b98c7601edcf9af76813af4c1cd87dc6e644f811f72 -splice-amulet-test 0.1.17 26de365e3a2c68aea02f581fe1437267bce9fc88427155ba482a823ea7d08a5c +splice-amulet-name-service-test 0.1.18 6bac19b93ce3c7762b85f0b5778a2c48cca539c5062d5e40e2e76bd420b3972c +splice-amulet-test 0.1.17 513d0f48517f68c3cba1eac3b3501915593d7a0170b2a2cfbc4d81f23f95818d splice-api-featured-app-v1 1.0.0 7804375fe5e4c6d5afe067bd314c42fe0b7d005a1300019c73154dd939da4dda splice-api-token-allocation-instruction-v1 1.0.0 275064aacfe99cea72ee0c80563936129563776f67415ef9f13e4297eecbc520 splice-api-token-allocation-request-v1 1.0.0 6fe848530b2404017c4a12874c956ad7d5c8a419ee9b040f96b5c13172d2e193 @@ -57,7 +57,7 @@ splice-dso-governance 0.1.18 136484875714fcf24c24858e1d573ac756524eeb607c26999fc splice-dso-governance 0.1.19 759d1cf002fc1225ac43a55d73f0058becce3e62cfb5485c197f2b69ed8d9d98 splice-dso-governance 0.1.2 4206e127be8b111ac84bd7f98bd9dbf03ed489f1642b46ab31a46ee6d688e7e8 splice-dso-governance 0.1.20 996a3b619d6b65ca7812881978c44c650cac119de78f5317d1f317658943001c -splice-dso-governance 0.1.21 e03bdd0e09404e3cf4b0a86967d38d24fe770fee96634a139416c937bc85fd2c +splice-dso-governance 0.1.21 282bd7f7a7cb5a0a97ac68f233ffef0a23bd872e740f35660488944830276a8b splice-dso-governance 0.1.3 b0ae3cc03e418790305a3c15f761fe495572de5827f8d322fb8b96996b783c13 splice-dso-governance 0.1.4 dc24fd18b4d151cd1e0ff6bfb7438bafb2f50fe076d0f16f50565e60b153a0be splice-dso-governance 0.1.5 9e3ca1d22ad495dfabf3d61acae3dc1a7718f527f02092280b58cf69edfdc84c @@ -65,8 +65,8 @@ splice-dso-governance 0.1.6 4e7653cfbf7ca249de4507aca9cd3b91060e5489042a522c589d splice-dso-governance 0.1.7 d406eba1132d464605f4dae3edf8cf5ecbbb34bd8edef0e047e7e526d328718c splice-dso-governance 0.1.8 1790a114f83d5f290261fae1e7e46fba75a861a3dd603c6b4ef6b67b49053948 splice-dso-governance 0.1.9 9ee83bfd872f91e659b8a8439c5b4eaf240bcf6f19698f884d7d7993ab48c401 -splice-dso-governance-test 0.1.24 9fc27d76e86db28d2495e0a2b0f82848b4548c6676ca546491522abc1c81913a -splice-token-standard-test 1.0.8 c737ca98e9415611f46d8a5f965cc2f113c487866a6aebc5fe78f4aaaa4aa1ff +splice-dso-governance-test 0.1.24 c9b542b033991b98f2094995132ca929da1b2fe67d70d3e59c529b714be66dc1 +splice-token-standard-test 1.0.8 6fccaed2cb7e867bd5b7fd56a8327f8844a2808d4d5518d8fb865edbed6a1988 splice-token-test-dummy-holding 0.0.1 1cd171c6c42ab46dc9cf12d80c6111369e00cea5cdf054924b4f26ce94b1ef5b splice-token-test-dummy-holding 0.0.2 4f40fb033ef3db89623642c1b494e846097fa32af138b3864a63aa15937a323d splice-token-test-trading-app 1.0.0 e5c9847d5a88d3b8d65436f01765fc5ba142cc58529692e2dacdd865d9939f71 @@ -78,9 +78,9 @@ splice-util 0.1.4 b7356fbb2cf8a3b22194d8c743c3c216d9c7527b257c8c38b257eb22942be3 splice-util-featured-app-proxies 1.0.0 48e0c4fe4ea05e3b740404ebe37004ddd741efbdcd665c1c3199a5d6d9d944d7 splice-util-featured-app-proxies 1.1.0 81dd5a9e5c02d0de03208522a895fb85eeb12fbea4aca7c4ad0ad106f3b0bfce splice-util-featured-app-proxies 1.2.0 653c48879064332d34af5008bdfd8e349493460e67e62b85e8e7e3392831c842 -splice-util-featured-app-proxies-test 1.0.5 b98cd185a09bb29dd5dfbfb4de00a4fba664c0fb0051c3adbcf844de885daefe +splice-util-featured-app-proxies-test 1.0.5 aaffdbd70751525f5374f2f0fff7b96cf2fe9742060dfd6e8a4eb51f88fc25cf splice-util-token-standard-wallet 1.0.0 1da198cb7968fa478cfa12aba9fdf128a63a8af6ab284ea6be238cf92a3733ac -splice-util-token-standard-wallet-test 1.0.1 6b9dfbe788401d0a3cda7a56d40d6a50a2d02c3c2ae2ddfc6ab91d2742c4f8c8 +splice-util-token-standard-wallet-test 1.0.1 381dae7d1236b62e3944c91773cd98a5d2adb437f59f0bb7a4e54c138f570e96 splice-validator-lifecycle 0.1.0 cef96fac957362f1fc097120bd13686cac7f84fbc8053afa994a1f9214d9570c splice-validator-lifecycle 0.1.1 1ddf05c96002914593c929848b786f34c753fb0be07717d1786be177a564aada splice-validator-lifecycle 0.1.2 57e2f15f9755db1f00e51c52c319294264a21ad71c6bc1e7cd70db4b164c0aaa @@ -95,7 +95,7 @@ splice-wallet 0.1.11 991842eee48ec3caa3a649e8f47e3544dd7b688ce4b363aa934a83db7da splice-wallet 0.1.12 b30bb727552cf6b624dbc9a5ff95f6c158e0a654e2e9c5c27bcfe3f5d0f9ada2 splice-wallet 0.1.13 eb6e01efacc3397e23c6be8b9be7db4bf37672211974d69e24b48980e2f98b7e splice-wallet 0.1.14 690c1d47bac06db419db344d59a7a30c53fa3f5d961943fe1782cfc6c78794d8 -splice-wallet 0.1.15 941bd097a2ff4166b3b3100c211f3c890f994d97406fd206cfab0cd7fbf7ce1f +splice-wallet 0.1.15 8519f772f8bbfe8fdc2d98f09b5d770c145a297a312cd7b3a07c9d6d7518bc31 splice-wallet 0.1.2 c162e08a4ec0428bfa870b6d9040989e575c74199c3a80558c62e03196dd5146 splice-wallet 0.1.3 2c35bb4f5084ea66db59717d21750bfd64c43147ef5fd5166615092d592a6917 splice-wallet 0.1.4 141dad2d33b6410b8e1c35a0c4f8f76cb691e4d9a4410ce89f33f373855317e1 @@ -111,7 +111,7 @@ splice-wallet-payments 0.1.11 7266d861727757f3482857a77f25f4d647d8925b469e46938a splice-wallet-payments 0.1.12 88516902a9f045d3fd3835c8f5c8c6bfe4b44d83fae11369241f1883bb5b3ab4 splice-wallet-payments 0.1.13 0b9250642d3864e6bbea553264dcac0d286104f24efad2fbaf4645520bcb4053 splice-wallet-payments 0.1.14 45b29d6e05b5352c39edde850c66b4535c682b9991b06eec312176b1a48ecab5 -splice-wallet-payments 0.1.15 6be0d759d1a665b8ae2348f22a7c7da0cd09ba7d8d5e1796eda58e8e73bf91d5 +splice-wallet-payments 0.1.15 dec92f846f8320172c1a1ba60bfe67382dc2d0e7d31ab9a982fe95add1325192 splice-wallet-payments 0.1.2 775f5eb9c0249509adda5eb3ea4ee31bb953601168c18880df6f2ff09ec4298a splice-wallet-payments 0.1.3 b953b3729c81a55e598a364be7d0c0574750df3de12a7a1b53a300f217cb5c5c splice-wallet-payments 0.1.4 12177f54873c1094ea169874ad0d7838383fd137f302d16356e93f28dfbc0fcc @@ -120,7 +120,7 @@ splice-wallet-payments 0.1.6 6124379528eeb6fa17ecdab15577c29abb33d0c0d34dc5f2680 splice-wallet-payments 0.1.7 4e3e0d9cdadf80f4bf8f3cd3660d5287c084c9a29f23c901aabce597d72fd467 splice-wallet-payments 0.1.8 e48ea337ee3335c8bb3206a2501ce947ac1a7bdb1825cee8f28bad64f5a7bc4b splice-wallet-payments 0.1.9 7f4e081ad96f2ccded0c053b0cf5ddddae1139dfc3bb89cefcf77ea70f2cecb7 -splice-wallet-test 0.1.17 7e0d78fd70aa414aa88921843da913c800a3e36a24d85c6ce7d40e51dfc91cf3 +splice-wallet-test 0.1.17 ce60f39d2796913d32826d963bea1c3213c8e74afb24122ee54e8828c5caa17e splitwell 0.1.0 075c76de553ab88383a7c69de134afa82aacfdf8ea8fcfe8852c4b199c3b2669 splitwell 0.1.1 ccb1a0215053062202052e1a052f9214da3fdae5253a6d43e2e155ff4f57fe75 splitwell 0.1.10 d42676a366f7ca7a2409974dd3054aa4d83ab29baa3b2086ad021407b0a1a295 @@ -128,7 +128,7 @@ splitwell 0.1.11 03b487fa26a8ef67df0876fb337904624c3fac27f11b7ad2d131a4eab26ee1b splitwell 0.1.12 cc047977ee8da70e858f203a14c3fd302c6aaed27be42383e61a026854d76112 splitwell 0.1.13 c2cf7b5fb3c615cdd2c8e14af42f1ca5fe4df8647cb656c7d02a72420152c3dd splitwell 0.1.14 bf2ec3fec9bcb58ed5e2ff63072a1e4994d0415ea7a0275942be282906a42021 -splitwell 0.1.15 2d1f9538d3bc77f28ea9d72c4d17cc3901a2623a236f1f41e7dd3a0d9dc94d7b +splitwell 0.1.15 9c7c62bf9246f4e9774c90f271ce7e409e6486fc6a8db10c0752a0cdb818b834 splitwell 0.1.2 778edd2c228c6b68198d4d033885b2d0dae7daaee55d7df3edd9dfdf1f10fbd0 splitwell 0.1.3 7cde068cde689584f86a2499689d5cb165264d96496721e24ac6fb909f770a58 splitwell 0.1.4 85557b86cd4f330f093915db1ea26eac5092de6b5ddae0690146f6059c89419b @@ -137,4 +137,4 @@ splitwell 0.1.6 872da0dd7986fd768930f85d6a7310a94a0ef924e7fbb7bb7a4e149f2b5feb74 splitwell 0.1.7 841d1c9c86b5c8f3a39059459ecd8febedf7703e18f117300bb0ebf4423db096 splitwell 0.1.8 63b8153a08ceb4bf40d807acc5712372c3eac548c266be4d5e92470b4f655515 splitwell 0.1.9 b6267905698d2798b9ef171e27d49fb88e052ec0ec0e0675a3a1b275c7d037d4 -splitwell-test 0.1.17 ea948d1b956a5f065af06d1534c7d5a0708be749c6d251701874cc2eaff2c326 \ No newline at end of file +splitwell-test 0.1.17 97a7f202da57f27b068014de3b00804fb3f20700e158b80877d57418297a9e32 \ No newline at end of file diff --git a/daml/dars/splice-amulet-0.1.15.dar b/daml/dars/splice-amulet-0.1.15.dar index b6679a535a..06909d995f 100644 Binary files a/daml/dars/splice-amulet-0.1.15.dar and b/daml/dars/splice-amulet-0.1.15.dar differ diff --git a/daml/dars/splice-amulet-name-service-0.1.16.dar b/daml/dars/splice-amulet-name-service-0.1.16.dar index a9d36f2138..d566caf955 100644 Binary files a/daml/dars/splice-amulet-name-service-0.1.16.dar and b/daml/dars/splice-amulet-name-service-0.1.16.dar differ diff --git a/daml/dars/splice-dso-governance-0.1.21.dar b/daml/dars/splice-dso-governance-0.1.21.dar index 4d1d62ee04..4d44810e54 100644 Binary files a/daml/dars/splice-dso-governance-0.1.21.dar and b/daml/dars/splice-dso-governance-0.1.21.dar differ diff --git a/daml/dars/splice-wallet-0.1.15.dar b/daml/dars/splice-wallet-0.1.15.dar index ac369b02b3..2830a999bf 100644 Binary files a/daml/dars/splice-wallet-0.1.15.dar and b/daml/dars/splice-wallet-0.1.15.dar differ diff --git a/daml/dars/splice-wallet-payments-0.1.15.dar b/daml/dars/splice-wallet-payments-0.1.15.dar index 3e341b3a96..caacc92e21 100644 Binary files a/daml/dars/splice-wallet-payments-0.1.15.dar and b/daml/dars/splice-wallet-payments-0.1.15.dar differ diff --git a/daml/dars/splitwell-0.1.15.dar b/daml/dars/splitwell-0.1.15.dar index df215d0951..8230990cb4 100644 Binary files a/daml/dars/splitwell-0.1.15.dar and b/daml/dars/splitwell-0.1.15.dar differ diff --git a/daml/splice-amulet-test/daml/Splice/Scripts/TestValidatorFaucet.daml b/daml/splice-amulet-test/daml/Splice/Scripts/TestValidatorFaucet.daml index 05c1bc1d6c..24da0902f8 100644 --- a/daml/splice-amulet-test/daml/Splice/Scripts/TestValidatorFaucet.daml +++ b/daml/splice-amulet-test/daml/Splice/Scripts/TestValidatorFaucet.daml @@ -3,6 +3,7 @@ module Splice.Scripts.TestValidatorFaucet where +import DA.Action (void) import DA.Assert import DA.List (head) import DA.Time @@ -126,18 +127,25 @@ test_ValidatorFaucetWithWeight3 = do bobExpectedMin = aliceExpectedMin * bobWeight bobExpectedMax = aliceExpectedMax * bobWeight --- With weight 0.0, Bob should receive no rewards -test_ValidatorFaucetWithWeight0: Script () -test_ValidatorFaucetWithWeight0 = do - let bobWeight = 0.0 - aliceExpectedMin = 2.85 - aliceExpectedMax = 3.0 - testValidatorFaucetWithWeight_Helper ValidatorWeightTestParams with - bobWeight = bobWeight - aliceExpectedMin = aliceExpectedMin - aliceExpectedMax = aliceExpectedMax - bobExpectedMin = aliceExpectedMin * bobWeight - bobExpectedMax = aliceExpectedMax * bobWeight +-- ValidatorLicense_RecordValidatorLivenessActivity should fail for weight 0 +test_RecordValidatorLivenessActivityFailsForWeight0: Script () +test_RecordValidatorLivenessActivityFailsForWeight0 = do + DefaultAppWithUsers{..} <- setupDefaultAppWithUsers + + [(bobLicenseCid, bobLicense)] <- query @ValidatorLicense bobValidator.primaryParty + + -- Update bob's license to have weight 0.0 + submit app.dso $ archiveCmd bobLicenseCid + bobLicenseCid <- submitMulti [bobValidator.primaryParty, app.dso] [] $ createCmd bobLicense with + weight = Some 0.0 + + rounds <- getOpenRoundsSorted app + let (round0Cid, _) = head rounds + + submitMultiMustFail [bobValidator.primaryParty] [app.dso] $ exerciseCmd bobLicenseCid ValidatorLicense_RecordValidatorLivenessActivity with + openRoundCid = round0Cid + + return () -- Test that runNextIssuance properly handles ValidatorLicense weights -- in the optTotalValidatorLivenessActivityRecords calculation @@ -181,7 +189,7 @@ testValidatorFaucetWithWeight_Helper params = do _ <- submitMulti [aliceValidator.primaryParty] [app.dso] $ exerciseCmd aliceLicenseCid ValidatorLicense_RecordValidatorLivenessActivity with openRoundCid = round0Cid - _ <- submitMulti [bobValidator.primaryParty] [app.dso] $ exerciseCmd bobLicenseCid ValidatorLicense_RecordValidatorLivenessActivity with + void $ submitMulti [bobValidator.primaryParty] [app.dso] $ exerciseCmd bobLicenseCid ValidatorLicense_RecordValidatorLivenessActivity with openRoundCid = round0Cid -- Verify the activity records were created with correct weights diff --git a/daml/splice-amulet/daml/Splice/ValidatorLicense.daml b/daml/splice-amulet/daml/Splice/ValidatorLicense.daml index 11efed84dd..856b9b116f 100644 --- a/daml/splice-amulet/daml/Splice/ValidatorLicense.daml +++ b/daml/splice-amulet/daml/Splice/ValidatorLicense.daml @@ -211,6 +211,8 @@ template ValidatorLivenessActivityRecord with signatory dso observer validator + ensure (optional True (0.0 <) weight) + choice ValidatorLivenessActivityRecord_DsoExpire : ValidatorLivenessActivityRecord_DsoExpireResult with closedRoundCid : ContractId ClosedMiningRound @@ -230,5 +232,11 @@ instance HasCheckedFetch ValidatorFaucetCoupon ForOwner where instance HasCheckedFetch ValidatorLivenessActivityRecord ForOwner where contractGroupId ValidatorLivenessActivityRecord{..} = ForOwner with dso; owner = validator +instance HasCheckedFetch ValidatorLivenessActivityRecord ForDso where + contractGroupId ValidatorLivenessActivityRecord{..} = ForDso with dso + instance HasCheckedFetch ValidatorLicense ForDso where contractGroupId ValidatorLicense{..} = ForDso with dso + +instance HasCheckedFetch ValidatorLicense ForOwner where + contractGroupId ValidatorLicense{..} = ForOwner with dso; owner = validator diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml index e6efe45e79..4b4cd7fcd5 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/DsoTestUtils.daml @@ -31,6 +31,7 @@ import Splice.DsoBootstrap import Splice.DSO.AmuletPrice import Splice.DSO.DecentralizedSynchronizer import Splice.DSO.SvState +import Splice.DSO.VoteExecution import Splice.Scripts.AnsRulesParameters import Splice.Util @@ -83,6 +84,7 @@ initDecentralizedSynchronizerWithAmuletPrice isDevNet initialRound amuletPrice = decentralizedSynchronizer = initialDsoDecentralizedSynchronizerConfig nextScheduledSynchronizerUpgrade = None voteCooldownTime = None -- use default value of 1 minute + voteExecutionInstructionTimeout = None -- use default value of 1 hour let amuletConfig = defaultAmuletConfig let ansRulesConfig = defaultAnsRulesConfig @@ -369,6 +371,25 @@ castVote app sv requestCid vote = do optCastAt = None return result.voteRequest +-- | Execute all pending vote execution instructions +executeAllVoteExecutionInstructions : AmuletApp -> Script () +executeAllVoteExecutionInstructions app = do + [(dsoRulesCid, dsoRules)] <- query @DsoRules app.dso + let sv = head (Map.keys dsoRules.svs) + instructions <- query @VoteExecutionInstruction app.dso + + forA_ instructions $ \(instructionCid, instruction) -> do + let validator = case instruction.instruction of + EI_ValidatorLicenseChangeWeight {validator} -> validator + EI_ValidatorLicenseWithdraw {validator} -> validator + + [(licenseCid, _)] <- query @ValidatorLicense validator + + submitMulti [sv] [app.dso] $ exerciseCmd dsoRulesCid DsoRules_Execute_VoteInstruction with + sv + inputs = EII_ValidatorLicense licenseCid + instructionCid + -- | Update all currently desired prices to the target price updateAgreedAmuletPrice : Party -> Decimal -> Script [ContractId AmuletPriceVote] updateAgreedAmuletPrice dso amuletPrice = do diff --git a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestValidatorLicense.daml b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestValidatorLicense.daml index c4117c58db..c1cdff3446 100644 --- a/daml/splice-dso-governance-test/daml/Splice/Scripts/TestValidatorLicense.daml +++ b/daml/splice-dso-governance-test/daml/Splice/Scripts/TestValidatorLicense.daml @@ -11,6 +11,7 @@ import DA.Time import Splice.ValidatorLicense import Splice.DsoRules +import Splice.DSO.VoteExecution import Splice.Scripts.DsoTestUtils import Splice.Scripts.Util import Splice.Types @@ -195,3 +196,112 @@ test_ValidatorLivenessWeightInRunNextIssuanceD = do abort ("Bob should receive ~15000 times rewards. Expected within: [" <> show bobExpectedMin <> ", " <> show bobExpectedMax <> "], Got: " <> show bobReward) return () + +-- Test multiple validator license weight changes can be done via single vote +testValidatorLicenseWeightChangeBatch : Script () +testValidatorLicenseWeightChangeBatch = do + (app, _, (sv1, sv2, sv3, sv4)) <- initDevNet + + validator1 <- setupValidator app "validator1" + validator2 <- setupValidator app "validator2" + validator3 <- setupValidator app "validator3" + + -- Verify all validators have default weight (None) + [(_, license1)] <- query @ValidatorLicense validator1 + [(_, license2)] <- query @ValidatorLicense validator2 + [(_, license3)] <- query @ValidatorLicense validator3 + license1.weight === None + license2.weight === None + license3.weight === None + + -- Change weights for all three validators in a single vote + initiateAndAcceptVote app [sv1, sv2, sv3, sv4] (ARC_DsoRules (SRARC_ValidatorLicense + (DsoRules_ValidatorLicense with + actions = [VLAK_ChangeWeight with validator = validator1; weight = 5.0 + , VLAK_ChangeWeight with validator = validator2; weight = 10.0 + , VLAK_ChangeWeight with validator = validator3; weight = 0.0]))) + + executeAllVoteExecutionInstructions app + + -- Verify all weights are updated correctly + [(_, license1)] <- query @ValidatorLicense validator1 + [(_, license2)] <- query @ValidatorLicense validator2 + [(_, license3)] <- query @ValidatorLicense validator3 + license1.weight === Some 5.0 + license2.weight === Some 10.0 + license3.weight === Some 0.0 + + pure () + +-- Test multiple validator license withdrawals can be done via single vote +testValidatorLicenseWithdrawBatch : Script () +testValidatorLicenseWithdrawBatch = do + (app, _, (sv1, sv2, sv3, sv4)) <- initDevNet + + validator1 <- setupValidator app "validator1" + validator2 <- setupValidator app "validator2" + validator3 <- setupValidator app "validator3" + + -- Verify all validator licenses exist + [_] <- query @ValidatorLicense validator1 + [_] <- query @ValidatorLicense validator2 + [_] <- query @ValidatorLicense validator3 + + -- Withdraw all three validator licenses in a single vote + initiateAndAcceptVote app [sv1, sv2, sv3, sv4] (ARC_DsoRules (SRARC_ValidatorLicense + (DsoRules_ValidatorLicense with + actions = [VLAK_Withdraw with validator = validator1 + , VLAK_Withdraw with validator = validator2 + , VLAK_Withdraw with validator = validator3]))) + + executeAllVoteExecutionInstructions app + + -- Verify all validator licenses are withdrawn + [] <- query @ValidatorLicense validator1 + [] <- query @ValidatorLicense validator2 + [] <- query @ValidatorLicense validator3 + + pure () + +-- Test that VoteExecutionInstructions can be expired only after they expire +testVoteExecutionInstructionExpire : Script () +testVoteExecutionInstructionExpire = do + (app, _, (sv1, sv2, sv3, sv4)) <- initDevNet + + validator1 <- setupValidator app "validator1" + validator2 <- setupValidator app "validator2" + + initiateAndAcceptVote app [sv1, sv2, sv3, sv4] (ARC_DsoRules (SRARC_ValidatorLicense + (DsoRules_ValidatorLicense with + actions = [VLAK_ChangeWeight with validator = validator1; weight = 5.0 + , VLAK_ChangeWeight with validator = validator2; weight = 10.0]))) + + [(dsoRulesCid, _)] <- query @DsoRules app.dso + instructions <- query @VoteExecutionInstruction app.dso + length instructions === 2 + let [(instruction1Cid, _), (instruction2Cid, _)] = instructions + + -- Cannot expire before timeout (default: 1 hour) + submitMultiMustFail [sv1] [app.dso] $ + exerciseCmd dsoRulesCid DsoRules_ExpireVoteInstruction with + instructionCid = instruction1Cid; sv = sv1 + + submitMultiMustFail [sv1] [app.dso] $ + exerciseCmd dsoRulesCid DsoRules_ExpireVoteInstruction with + instructionCid = instruction2Cid; sv = sv1 + + passTime (hours 2) + + -- Can expire after timeout + submitMulti [sv1] [app.dso] $ + exerciseCmd dsoRulesCid DsoRules_ExpireVoteInstruction with + instructionCid = instruction1Cid; sv = sv1 + + submitMulti [sv2] [app.dso] $ + exerciseCmd dsoRulesCid DsoRules_ExpireVoteInstruction with + instructionCid = instruction2Cid; sv = sv2 + + -- Verify all instructions are now expired + [] <- query @VoteExecutionInstruction app.dso + + pure () diff --git a/daml/splice-dso-governance/daml/Splice/DSO/VoteExecution.daml b/daml/splice-dso-governance/daml/Splice/DSO/VoteExecution.daml new file mode 100644 index 0000000000..ae40f71fd6 --- /dev/null +++ b/daml/splice-dso-governance/daml/Splice/DSO/VoteExecution.daml @@ -0,0 +1,82 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +-- | Support for execution of a voted action +-- The ARC vote requests execution doesn't work well for choices that need to act on +-- contracts that get cycled frequently, e.g., ValidatorLicense. +-- The contracts here act as an indirection to handle such scenarios + +module Splice.DSO.VoteExecution where + +import DA.Assert (assertDeadlineExceeded) +import Splice.Types (ForDso(..), ForOwner(..)) +import Splice.Util +import Splice.ValidatorLicense + + +-- | Instruction for executing a voted action. +data ExecutionInstruction + = EI_ValidatorLicenseChangeWeight with + validator : Party + weight : Decimal + | EI_ValidatorLicenseWithdraw with + validator : Party + deriving (Eq, Show) + + +-- | Inputs required to execute an instruction. +-- The inputs depend on the instruction type. +data ExecutionInstructionInputs + = EII_ValidatorLicense (ContractId ValidatorLicense) + | EII_Dummy -- added to ensure this is compiled as a variant + deriving (Eq, Show) + +data VoteExecutionInstruction_ExpireResult = VoteExecutionInstruction_ExpireResult with + dummyUnitField : () -- dummy field to make this compile as a record + deriving (Eq, Show) + +-- | These instruction contracts serve as an indirection mechanism that allows +-- voted actions to be executed on a batch of contracts. +template VoteExecutionInstruction + with + dso : Party + instruction : ExecutionInstruction + expiresAt : Time + where + signatory dso + ensure validExecutionInstruction instruction + + choice VoteExecutionInstruction_Expire : VoteExecutionInstruction_ExpireResult + controller dso + do assertDeadlineExceeded "expiresAt" expiresAt + return VoteExecutionInstruction_ExpireResult with dummyUnitField = () + + +-- | Validates an execution instruction. +validExecutionInstruction : ExecutionInstruction -> Bool +validExecutionInstruction instruction = case instruction of + EI_ValidatorLicenseChangeWeight {weight} -> weight >= 0.0 + EI_ValidatorLicenseWithdraw {} -> True + +-- | Executes a vote instruction with the given inputs. +executeVoteInstruction : Party -> VoteExecutionInstruction -> ExecutionInstructionInputs -> Update () +executeVoteInstruction dso voteInstruction inputs = do + case (voteInstruction.instruction, inputs) of + (EI_ValidatorLicenseChangeWeight {validator, weight}, EII_ValidatorLicense validatorLicenseCid) -> do + license <- fetchAndArchive (ForOwner with dso; owner = validator) validatorLicenseCid + _ <- create license with weight = Some weight + pure () + + (EI_ValidatorLicenseWithdraw {validator}, EII_ValidatorLicense validatorLicenseCid) -> do + _ <- fetchButArchiveLater (ForOwner with dso; owner = validator) validatorLicenseCid + exercise validatorLicenseCid ValidatorLicense_Withdraw with reason = "revoked by governance decision" + pure () + + (_, _) -> + error "Encountered unexpected ExecutionInstructionInputs" + +-- Instances +------------- + +instance HasCheckedFetch VoteExecutionInstruction ForDso where + contractGroupId VoteExecutionInstruction {..} = ForDso with dso diff --git a/daml/splice-dso-governance/daml/Splice/DsoRules.daml b/daml/splice-dso-governance/daml/Splice/DsoRules.daml index b4498a63f6..12d144561d 100644 --- a/daml/splice-dso-governance/daml/Splice/DsoRules.daml +++ b/daml/splice-dso-governance/daml/Splice/DsoRules.daml @@ -33,6 +33,7 @@ import Splice.SvOnboarding import Splice.DSO.AmuletPrice import Splice.DSO.DecentralizedSynchronizer import Splice.DSO.SvState +import Splice.DSO.VoteExecution import Splice.DsoRules.Utils import Splice.Schedule import Splice.Util @@ -115,6 +116,8 @@ data DsoRules_ActionRequiringConfirmation -- ^ Create TransferCommandCounter contract for the given sender if it does not already exist | SRARC_CreateUnallocatedUnclaimedActivityRecord DsoRules_CreateUnallocatedUnclaimedActivityRecord -- ^ Voted action to create an UnallocatedUnclaimedActivityRecord contract. + | SRARC_ValidatorLicense DsoRules_ValidatorLicense + -- ^ Voted action to change weights or withdraw validator licenses. deriving (Eq, Show) data AnsEntryContext_ActionRequiringConfirmation @@ -146,6 +149,15 @@ data DsoSummary = DsoSummary with -- ^ The number of votes required for considering a confirmation, or a request for a vote deriving (Eq, Show) +-- | Actions possible on a validator license via vote +data ValidatorLicenseActionKind + = VLAK_ChangeWeight with + validator : Party + weight : Decimal + | VLAK_Withdraw with + validator : Party + deriving (Eq, Show) + -- | Choice return types ------------------------- -- In order to support upgrades of the Daml models, all choices should return records, which can @@ -298,6 +310,18 @@ data DsoRules_ExpireUnallocatedUnclaimedActivityRecordResult = DsoRules_ExpireUn data DsoRules_ExpireUnclaimedActivityRecordResult = DsoRules_ExpireUnclaimedActivityRecordResult with unclaimedRewardCid : ContractId UnclaimedReward +data DsoRules_ValidatorLicenseResult = DsoRules_ValidatorLicenseResult with + instructionCids : [ContractId VoteExecutionInstruction] + deriving (Show, Eq) + +data DsoRules_Execute_VoteInstructionResult = DsoRules_Execute_VoteInstructionResult with + dummyUnitField : () + deriving (Eq, Show) + +data DsoRules_ExpireVoteInstructionResult = DsoRules_ExpireVoteInstructionResult with + dummyUnitField : () + deriving (Eq, Show) + -- Workflow templates --------------------- @@ -425,12 +449,17 @@ data DsoRulesConfig = DsoRulesConfig with decentralizedSynchronizer : DsoDecentralizedSynchronizerConfig nextScheduledSynchronizerUpgrade: Optional SynchronizerUpgradeSchedule voteCooldownTime : Optional RelTime -- ^ The minimum time between two votes by the same SV. + voteExecutionInstructionTimeout : Optional RelTime -- ^ The TTL for vote execution instruction contracts deriving (Eq, Show) -- | Read the `voteCooldownTime` from the `DsoRulesConfig` with a default value of 1 minute. getVoteCooldownTime : DsoRulesConfig -> RelTime getVoteCooldownTime config = fromOptional (minutes 1) config.voteCooldownTime +-- | Read the `voteExecutionInstructionTimeout` from the `DsoRulesConfig` with a default value of 1 hour. +getVoteExecutionInstructionTimeout : DsoRulesConfig -> RelTime +getVoteExecutionInstructionTimeout config = fromOptional (hours 1) config.voteExecutionInstructionTimeout + data SynchronizerUpgradeSchedule = SynchronizerUpgradeSchedule with time : Time -- ^ The time at which the migration is scheduled to start. @@ -1296,10 +1325,7 @@ template DsoRules with -- expire validator liveness activity records and compute their unclaimed rewards expiredValidatorLivenessActivityRecords <- forA (fromOptional [] optValidatorLivenessActivityRecordCids) $ \validatorLivenessActivityRecordCid -> do - -- We need to fetch the weight, and would also prefer doing 'Expire' - -- We can't do checked fetch as the owner is the validator - -- hence can't use either fetchAndArchive or fetchUncheckedAndArchive - record <- fetchUncheckedButArchiveLater validatorLivenessActivityRecordCid + record <- fetchButArchiveLater (ForDso with dso) validatorLivenessActivityRecordCid exercise validatorLivenessActivityRecordCid ValidatorLivenessActivityRecord_DsoExpire with closedRoundCid return (fromOptional defaultValidatorLicenseWeight record.weight) @@ -1532,6 +1558,45 @@ template DsoRules with exercise unclaimedActivityRecordCid UnclaimedActivityRecord_DsoExpire pure DsoRules_ExpireUnclaimedActivityRecordResult with unclaimedRewardCid + nonconsuming choice DsoRules_ValidatorLicense : DsoRules_ValidatorLicenseResult + with + actions : [ValidatorLicenseActionKind] + controller dso + do now <- getTime + let expiresAt = now `addRelTime` getVoteExecutionInstructionTimeout config + instructionCids <- forA actions $ \action -> do + case action of + VLAK_ChangeWeight {validator, weight} -> do + require ("Weight is non-negative for " <> show validator) (weight >= 0.0) + let instruction = EI_ValidatorLicenseChangeWeight with validator; weight + create VoteExecutionInstruction with dso; instruction; expiresAt + VLAK_Withdraw {validator} -> do + let instruction = EI_ValidatorLicenseWithdraw with validator + create VoteExecutionInstruction with dso; instruction; expiresAt + return DsoRules_ValidatorLicenseResult with instructionCids + + nonconsuming choice DsoRules_Execute_VoteInstruction : DsoRules_Execute_VoteInstructionResult + with + sv : Party + inputs : ExecutionInstructionInputs + instructionCid : ContractId VoteExecutionInstruction + controller sv + do _ <- getAndValidateSvParty this (Some sv) + voteInstruction <- fetchAndArchive (ForDso dso) instructionCid + executeVoteInstruction dso voteInstruction inputs + return DsoRules_Execute_VoteInstructionResult with dummyUnitField = () + + nonconsuming choice DsoRules_ExpireVoteInstruction : DsoRules_ExpireVoteInstructionResult + with + instructionCid : ContractId VoteExecutionInstruction + sv : Party + controller sv + do + _ <- getAndValidateSvParty this (Some sv) + fetchChecked (ForDso with dso) instructionCid + exercise instructionCid VoteExecutionInstruction_Expire + return DsoRules_ExpireVoteInstructionResult with dummyUnitField = () + pruneAtLeastOne : Ord t => t -> Schedule t a -> Optional (Schedule t a) pruneAtLeastOne now schedule = @@ -1591,6 +1656,7 @@ executeActionRequiringConfirmation dso dsoRulesCid amuletRulesCid act = case act SRARC_CreateExternalPartyAmuletRules choiceArg -> void $ exercise dsoRulesCid choiceArg SRARC_CreateTransferCommandCounter choiceArg -> void $ exercise dsoRulesCid choiceArg SRARC_CreateUnallocatedUnclaimedActivityRecord choiceArg -> void $ exercise dsoRulesCid choiceArg + SRARC_ValidatorLicense choiceArg -> void $ exercise dsoRulesCid choiceArg ARC_AnsEntryContext with .. -> do void $ fetchChecked (ForDso with dso) ansEntryContextCid case ansEntryContextAction of @@ -1818,6 +1884,7 @@ instance Patchable DsoRulesConfig where decentralizedSynchronizer = patch new.decentralizedSynchronizer base.decentralizedSynchronizer current.decentralizedSynchronizer nextScheduledSynchronizerUpgrade = patch new.nextScheduledSynchronizerUpgrade base.nextScheduledSynchronizerUpgrade current.nextScheduledSynchronizerUpgrade voteCooldownTime = patch new.voteCooldownTime base.voteCooldownTime current.voteCooldownTime + voteExecutionInstructionTimeout = patch new.voteExecutionInstructionTimeout base.voteExecutionInstructionTimeout current.voteExecutionInstructionTimeout instance Patchable SynchronizerUpgradeSchedule where patch new base current = SynchronizerUpgradeSchedule with