@@ -26,9 +26,11 @@ import Cardano.Slotting.EpochInfo qualified as Slotting
2626import Cardano.Slotting.Slot qualified as Slotting
2727import Cardano.Slotting.Time qualified as Slotting
2828
29+ import Data.Default (def )
2930import Data.Foldable (toList )
3031import Data.Map.Strict qualified as Map
3132import Data.Sequence.Strict qualified as Seq
33+ import Data.Set qualified as Set
3234import Data.Time.Clock.POSIX qualified as Time
3335import Lens.Micro
3436
@@ -43,7 +45,7 @@ import Test.Gen.Cardano.Api.Typed
4345
4446import Test.Cardano.Api.Experimental (exampleProtocolParams , exampleProtocolParamsEra )
4547
46- import Hedgehog (Gen , Property )
48+ import Hedgehog (Gen , Property , (===) )
4749import Hedgehog qualified as H
4850import Hedgehog.Extras qualified as H
4951import Hedgehog.Gen qualified as Gen
@@ -79,6 +81,9 @@ tests =
7981 , testProperty
8082 " cross-role key witness dedupe"
8183 prop_estimateTransactionKeyWitnessCount_dedupes_across_roles
84+ , testProperty
85+ " pool registration counts operator and every owner"
86+ prop_estimateTransactionKeyWitnessCount_counts_pool_owners
8287 ]
8388 , testGroup
8489 " createCompatibleTx"
@@ -583,7 +588,7 @@ prop_calcMinFeeRecursive_well_funded_succeeds = H.property $ do
583588 (const False )
584589 utxo
585590 (resultLedgerTx ^. L. bodyTxL)
586- balance H. === mempty
591+ balance === mempty
587592
588593-- | Like 'prop_calcMinFeeRecursive_well_funded_succeeds' but the UTxO and
589594-- output carry native tokens. Verifies that surplus tokens are correctly
@@ -603,7 +608,7 @@ prop_calcMinFeeRecursive_well_funded_multi_asset = H.property $ do
603608 (const False )
604609 utxo
605610 (resultLedgerTx ^. L. bodyTxL)
606- balance H. === mempty
611+ balance === mempty
607612
608613-- | 'calcMinFeeRecursive' is idempotent: applying it to its own result
609614-- yields the same 'UnsignedTx'. This confirms the fee has reached a
@@ -617,7 +622,7 @@ prop_calcMinFeeRecursive_fee_fixpoint = H.property $ do
617622 secondResult <-
618623 H. leftFail $
619624 Exp. calcMinFeeRecursive changeAddr resultTx utxo exampleProtocolParams mempty mempty 0
620- resultTx H. === secondResult
625+ resultTx === secondResult
621626
622627-- | When the outputs exceed the UTxO value the function returns
623628-- 'Left (NotEnoughAdaForNewOutput _)' with a negative deficit coin.
@@ -670,7 +675,7 @@ prop_calcMinFeeRecursive_no_tx_outs = H.property $ do
670675 Exp. calcMinFeeRecursive changeAddr unsignedTx utxo exampleProtocolParams mempty mempty 0
671676 let outs = toList $ resultLedgerTx ^. L. bodyTxL . L. outputsTxBodyL
672677 -- The result should have exactly one output (the change output)
673- length outs H. === 1
678+ length outs === 1
674679
675680-- | When the surplus is just barely enough to cover the initial fee but not
676681-- the higher fee after adding a change output, the change output balance
@@ -709,7 +714,7 @@ prop_calcMinFeeRecursive_withdrawal_funded_succeeds = H.property $ do
709714 (const False )
710715 utxo
711716 (resultLedgerTx ^. L. bodyTxL)
712- balance H. === mempty
717+ balance === mempty
713718
714719-- | When the input is tiny (below the minimum fee) and the withdrawal only
715720-- covers @output - input@ exactly, 'calcMinFeeRecursive' must fail
@@ -798,7 +803,7 @@ prop_evaluateSignedTx_balanced_mempty = H.property $ do
798803 mempty
799804 utxo
800805 signedTx
801- Exp. txEvalBalance result H. === mempty
806+ Exp. txEvalBalance result === mempty
802807
803808-- | Evaluate a simple signed transaction, returning the result and UTxO.
804809evalSimpleTx
@@ -841,7 +846,7 @@ prop_substituteExecutionUnits_preserves_certs = H.property $ do
841846 Exp. defaultTxBodyContent
842847 & Exp. setTxCertificates inputCerts
843848 result <- H. evalEither $ Exp. substituteExecutionUnits Map. empty txBodyContent
844- Exp. txCertificates result H. === inputCerts
849+ Exp. txCertificates result === inputCerts
845850
846851-- | 'collectTxBodyScriptWitnesses' must return exactly the script-witnessed
847852-- certs (1 simple script witness in the generator) and must not include
@@ -854,7 +859,7 @@ prop_collectTxBodyScriptWitnesses_ignores_unwitnessed_certs = H.property $ do
854859 Exp. defaultTxBodyContent
855860 & Exp. setTxCertificates inputCerts
856861 scriptWitnesses = Exp. collectTxBodyScriptWitnesses txBodyContent
857- length scriptWitnesses H. === 1
862+ length scriptWitnesses === 1
858863
859864-- | 'createCompatibleTx' must include every certificate (both witnessed and
860865-- unwitnessed) in the resulting ledger transaction body. This ensures that
@@ -867,7 +872,7 @@ prop_createCompatibleTx_preserves_all_certs = H.property $ do
867872 Api. ShelleyTx _ ledgerTx <-
868873 H. evalEither $ createCompatibleTx sbe [] [] mempty 0 (NoPParamsUpdate sbe) NoVotes inputCerts
869874 let bodyCerts = ledgerTx ^. L. bodyTxL . L. certsTxBodyL
870- Seq. length bodyCerts H. === expectedCount
875+ Seq. length bodyCerts === expectedCount
871876
872877-- | Regression test for: a key-credentialed voter (e.g. a key-hash DRep)
873878-- requires a VKey witness to satisfy the ledger, but
@@ -889,7 +894,7 @@ prop_estimateTransactionKeyWitnessCount_counts_vote_key_witnesses = H.property $
889894 Exp. defaultTxBodyContent
890895 & Exp. setTxVotingProcedures txVotingProcedures
891896 keyWitnessCount = Exp. estimateTransactionKeyWitnessCount @ Exp. ConwayEra txBodyContent
892- keyWitnessCount H. === fromIntegral expectedKeyWitnessCount
897+ keyWitnessCount === fromIntegral expectedKeyWitnessCount
893898
894899-- | Cross-role dedupe: a stake key that both withdraws rewards and is
895900-- deregistered by a certificate in the same transaction needs one key
@@ -919,11 +924,60 @@ prop_estimateTransactionKeyWitnessCount_dedupes_across_roles = H.property $ do
919924 & Exp. setTxExtraKeyWits (Exp. TxExtraKeyWitnesses [extraPaymentKeyHash])
920925 -- the deregistered key is the withdrawing key: counted once, plus the extra key witness
921926 Exp. estimateTransactionKeyWitnessCount @ Exp. ConwayEra (contentWithCertFor stakeCredential)
922- H. === 2
927+ === 2
923928 -- an unrelated deregistered key: nothing collides
924929 Exp. estimateTransactionKeyWitnessCount @ Exp. ConwayEra
925930 (contentWithCertFor (Api. StakeCredentialByKey unrelatedStakeKeyHash))
926- H. === 3
931+ === 3
932+
933+ -- | A pool registration certificate requires a signature from the operator
934+ -- and from every owner, mirroring ledger's 'getShelleyWitsVKeyNeeded'. An
935+ -- owner who also withdraws rewards in the same transaction still counts once.
936+ prop_estimateTransactionKeyWitnessCount_counts_pool_owners :: Property
937+ prop_estimateTransactionKeyWitnessCount_counts_pool_owners = H. property $ do
938+ operatorKeyHash <- H. forAll $ genVerificationKeyHash Api. AsStakePoolKey
939+ withdrawingStakeKeyHash <- H. forAll $ genVerificationKeyHash Api. AsStakeKey
940+ otherOwnerStakeKeyHash <- H. forAll $ genVerificationKeyHash Api. AsStakeKey
941+ unrelatedOwnerStakeKeyHash <- H. forAll $ genVerificationKeyHash Api. AsStakeKey
942+ Api. VrfKeyHash vrfKeyHash <- H. forAll $ genVerificationKeyHash Api. AsVrfKey
943+ let stakeAddress = Api. makeStakeAddress Api. Mainnet (Api. StakeCredentialByKey withdrawingStakeKeyHash)
944+ poolParamsWithOwners owners =
945+ L. StakePoolParams
946+ { L. sppId = Api. unStakePoolKeyHash operatorKeyHash
947+ , L. sppVrf = L. toVRFVerKeyHash vrfKeyHash
948+ , L. sppPledge = L. Coin 0
949+ , L. sppCost = L. Coin 0
950+ , L. sppMargin = minBound
951+ , L. sppAccountAddress = def
952+ , L. sppOwners = owners
953+ , L. sppRelays = mempty
954+ , L. sppMetadata = L. SNothing
955+ }
956+ registrationCertFor owners =
957+ Exp. Certificate $ L. RegPoolTxCert (poolParamsWithOwners owners)
958+ contentWithOwners owners =
959+ Exp. defaultTxBodyContent
960+ & Exp. setTxWithdrawals
961+ (Exp. TxWithdrawals [(stakeAddress, L. Coin 0 , Exp. AnyKeyWitnessPlaceholder )])
962+ & Exp. setTxCertificates
963+ ( Exp. mkTxCertificates
964+ Exp. ConwayEra
965+ [(registrationCertFor owners, Exp. AnyKeyWitnessPlaceholder )]
966+ )
967+ -- the withdrawing key is also a pool owner: counted once, plus the operator and the other owner
968+ Exp. estimateTransactionKeyWitnessCount @ Exp. ConwayEra
969+ ( contentWithOwners
970+ (Set. fromList [Api. unStakeKeyHash withdrawingStakeKeyHash, Api. unStakeKeyHash otherOwnerStakeKeyHash])
971+ )
972+ === 3
973+ -- owners disjoint from the withdrawing key: nothing collides, so all four keys count
974+ Exp. estimateTransactionKeyWitnessCount @ Exp. ConwayEra
975+ ( contentWithOwners
976+ ( Set. fromList
977+ [Api. unStakeKeyHash otherOwnerStakeKeyHash, Api. unStakeKeyHash unrelatedOwnerStakeKeyHash]
978+ )
979+ )
980+ === 4
927981
928982-- ---------------------------------------------------------------------------
929983-- Shared cert generators
@@ -1042,7 +1096,7 @@ genVotingProceduresWithKeyWitnessCount = do
10421096 where
10431097 votingProcedure = L. VotingProcedure {L. vProcVote = L. VoteYes , L. vProcAnchor = L. SNothing }
10441098
1045- -- \| One entry per action id the voter votes on, witnessed identically each
1099+ -- One entry per action id the voter votes on, witnessed identically each
10461100 -- time - 'Exp.mkTxVotingProcedures' merges same-voter entries with
10471101 -- disjoint action ids and 'Map.union's their (identical) witnesses, so the
10481102 -- resulting witness map still has exactly one entry per voter.
@@ -1064,7 +1118,7 @@ genVotingProceduresWithKeyWitnessCount = do
10641118 (L. GovActionId . Api. toShelleyTxId <$> genTxId)
10651119 <*> (L. GovActionIx <$> Gen. word16 (Range. linear 0 5 ))
10661120
1067- -- \| A random non-empty subset of the pool - the source of the same voter
1121+ -- A random non-empty subset of the pool - the source of the same voter
10681122 -- voting on several action ids.
10691123 genVotedActionIds :: [L. GovActionId ] -> Gen [L. GovActionId ]
10701124 genVotedActionIds actionIdPool = do
0 commit comments