From a9b5fcd33bb1c5bd4e89291e6fe768f855b2c437 Mon Sep 17 00:00:00 2001 From: Dzmitry Shuysky Date: Thu, 25 Jul 2024 14:34:44 +0200 Subject: [PATCH] Add functions for retrieving own (un)registered pub stake keys Add DelegateVoteAbstain example --- examples/ByUrl.purs | 2 - examples/Gov/DelegateVoteAbstain.purs | 58 +++++++++++++++++++++++++++ examples/Gov/RegisterDrep.purs | 2 +- packages.dhall | 2 +- spago-packages.nix | 6 +-- src/Contract/Wallet.purs | 2 + src/Internal/BalanceTx/BalanceTx.purs | 8 +++- src/Internal/Contract/Wallet.purs | 57 ++++++++++++++++++++++++-- src/Internal/Types/TxConstraints.purs | 6 ++- src/Internal/Wallet/Cip30.purs | 40 ++++++++++++++---- 10 files changed, 164 insertions(+), 19 deletions(-) create mode 100644 examples/Gov/DelegateVoteAbstain.purs diff --git a/examples/ByUrl.purs b/examples/ByUrl.purs index 85b779b54..25c7645cd 100644 --- a/examples/ByUrl.purs +++ b/examples/ByUrl.purs @@ -22,7 +22,6 @@ import Ctl.Examples.Cip30 as Cip30 import Ctl.Examples.Datums as Datums import Ctl.Examples.DropTokens as DropTokens import Ctl.Examples.ECDSA as ECDSA -import Ctl.Examples.Gov.RegisterDrep (contract) as RegisterDrep import Ctl.Examples.IncludeDatum (contract) as IncludeDatum import Ctl.Examples.MintsMultipleTokens as MintsMultipleTokens import Ctl.Examples.NativeScriptMints as NativeScriptMints @@ -241,7 +240,6 @@ examples = addSuccessLog <$> Map.fromFoldable , "ChangeGeneration1-3" /\ ChangeGeneration.checkChangeOutputsDistribution 1 3 7 , "IncludeDatum" /\ IncludeDatum.contract - , "Gov.RegisterDrep" /\ RegisterDrep.contract ] addSuccessLog :: Contract Unit -> Contract Unit diff --git a/examples/Gov/DelegateVoteAbstain.purs b/examples/Gov/DelegateVoteAbstain.purs new file mode 100644 index 000000000..7387e82fb --- /dev/null +++ b/examples/Gov/DelegateVoteAbstain.purs @@ -0,0 +1,58 @@ +module Ctl.Examples.Gov.DelegateVoteAbstain + ( contract + , example + , main + ) where + +import Contract.Prelude + +import Cardano.Transaction.Builder (TransactionBuilderStep(IssueCertificate)) +import Cardano.Types.Certificate (Certificate(VoteRegDelegCert)) +import Cardano.Types.Credential (Credential(PubKeyHashCredential)) +import Cardano.Types.DRep (DRep(AlwaysAbstain)) +import Cardano.Types.PublicKey (hash) as PublicKey +import Cardano.Types.Transaction (hash) as Transaction +import Contract.Config + ( ContractParams + , WalletSpec(ConnectToGenericCip30) + , testnetConfig + ) +import Contract.Log (logDebug', logInfo') +import Contract.Monad (Contract, launchAff_, runContract) +import Contract.ProtocolParameters (getProtocolParameters) +import Contract.Transaction (awaitTxConfirmed, submitTxFromBuildPlan) +import Contract.Wallet (ownUnregisteredPubStakeKeys) +import Data.Array (head) as Array +import Data.Map (empty) as Map +import Effect.Exception (error) + +main :: Effect Unit +main = example $ testnetConfig + { walletSpec = Just $ ConnectToGenericCip30 "eternl" { cip95: true } + } + +example :: ContractParams -> Effect Unit +example = launchAff_ <<< flip runContract contract + +contract :: Contract Unit +contract = do + logInfo' "Running Examples.Gov.DelegateVoteAbstain" + + unregPubStakeKeys <- ownUnregisteredPubStakeKeys + logDebug' $ "Unregistered public stake keys: " <> show unregPubStakeKeys + + pubStakeKey <- liftM (error "Failed to get unregistered pub stake key") $ + Array.head unregPubStakeKeys + let stakeCred = wrap $ PubKeyHashCredential $ PublicKey.hash pubStakeKey + + stakeCredDeposit <- _.stakeAddressDeposit <<< unwrap <$> + getProtocolParameters + + tx <- submitTxFromBuildPlan Map.empty mempty + [ IssueCertificate + (VoteRegDelegCert stakeCred AlwaysAbstain stakeCredDeposit) + Nothing + ] + + awaitTxConfirmed $ Transaction.hash tx + logInfo' "Tx submitted successfully!" diff --git a/examples/Gov/RegisterDrep.purs b/examples/Gov/RegisterDrep.purs index 79f50d63b..4f2d5a9fe 100644 --- a/examples/Gov/RegisterDrep.purs +++ b/examples/Gov/RegisterDrep.purs @@ -21,7 +21,7 @@ import Contract.Wallet (ownDrepPubKeyHash) main :: Effect Unit main = example $ testnetConfig - { walletSpec = Just $ ConnectToGenericCip30 "nami" { cip95: true } + { walletSpec = Just $ ConnectToGenericCip30 "eternl" { cip95: true } } example :: ContractParams -> Effect Unit diff --git a/packages.dhall b/packages.dhall index 77ceae2e6..8f6e993a8 100644 --- a/packages.dhall +++ b/packages.dhall @@ -148,7 +148,7 @@ let additions = , "prelude" ] , repo = "https://github.com/mlabs-haskell/purescript-cip95" - , version = "9d92a38cddd318245010286ae3966cd515d6952f" + , version = "ddcabbcf96ec6e292ca821c86eada1f828da0daf" } , cip95-typesafe = { dependencies = diff --git a/spago-packages.nix b/spago-packages.nix index 7dd94899d..883af265a 100644 --- a/spago-packages.nix +++ b/spago-packages.nix @@ -367,11 +367,11 @@ let "cip95" = pkgs.stdenv.mkDerivation { name = "cip95"; - version = "9d92a38cddd318245010286ae3966cd515d6952f"; + version = "ddcabbcf96ec6e292ca821c86eada1f828da0daf"; src = pkgs.fetchgit { url = "https://github.com/mlabs-haskell/purescript-cip95"; - rev = "9d92a38cddd318245010286ae3966cd515d6952f"; - sha256 = "0wzyq1yni2nj46k97faffhjl7afvxlvjgmfbs5k4ga9a1vbd2ijm"; + rev = "ddcabbcf96ec6e292ca821c86eada1f828da0daf"; + sha256 = "1vhan4fx4yq2h2p4ilvid97qnfs6wx5nj6rjyvyj30s1wi1cb1rz"; }; phases = "installPhase"; installPhase = "ln -s $src $out"; diff --git a/src/Contract/Wallet.purs b/src/Contract/Wallet.purs index 49f920b0f..fdca5d065 100644 --- a/src/Contract/Wallet.purs +++ b/src/Contract/Wallet.purs @@ -41,7 +41,9 @@ import Ctl.Internal.Contract.Wallet , ownDrepPubKey , ownDrepPubKeyHash , ownPaymentPubKeyHashes + , ownRegisteredPubStakeKeys , ownStakePubKeyHashes + , ownUnregisteredPubStakeKeys , signData ) as X import Ctl.Internal.Contract.Wallet diff --git a/src/Internal/BalanceTx/BalanceTx.purs b/src/Internal/BalanceTx/BalanceTx.purs index 8c9c9fede..d9abe77d4 100644 --- a/src/Internal/BalanceTx/BalanceTx.purs +++ b/src/Internal/BalanceTx/BalanceTx.purs @@ -7,7 +7,12 @@ import Prelude import Cardano.Transaction.Edit (editTransaction) import Cardano.Types ( AssetClass(AssetClass) - , Certificate(StakeDeregistration, StakeRegistration, RegDrepCert) + , Certificate + ( StakeDeregistration + , StakeRegistration + , VoteRegDelegCert + , RegDrepCert + ) , Coin(Coin) , Language(PlutusV1) , PlutusScript(PlutusScript) @@ -834,6 +839,7 @@ getCertsBalance tx (ProtocolParameters pparams) = ( case _ of StakeRegistration _ -> stakeAddressDeposit StakeDeregistration _ -> negate $ stakeAddressDeposit + VoteRegDelegCert _ _ deposit -> BigNum.toBigInt $ unwrap deposit RegDrepCert _ deposit _ -> BigNum.toBigInt $ unwrap deposit _ -> zero ) diff --git a/src/Internal/Contract/Wallet.purs b/src/Internal/Contract/Wallet.purs index 9e3d10eef..5989ff01e 100644 --- a/src/Internal/Contract/Wallet.purs +++ b/src/Internal/Contract/Wallet.purs @@ -9,7 +9,9 @@ module Ctl.Internal.Contract.Wallet , ownDrepPubKeyHash , ownPubKeyHashes , ownPaymentPubKeyHashes + , ownRegisteredPubStakeKeys , ownStakePubKeyHashes + , ownUnregisteredPubStakeKeys , withWallet , getWalletCollateral , getWalletBalance @@ -31,7 +33,12 @@ import Cardano.Types.TransactionUnspentOutput (TransactionUnspentOutput) import Cardano.Types.UtxoMap (UtxoMap) import Cardano.Types.Value (Value, valueToCoin) import Cardano.Types.Value (geq, lovelaceValueOf, sum) as Value -import Cardano.Wallet.Key (getPrivateDrepKey) +import Cardano.Wallet.Key + ( KeyWallet + , PrivateStakeKey(PrivateStakeKey) + , getPrivateDrepKey + , getPrivateStakeKey + ) import Control.Monad.Reader.Trans (asks) import Control.Parallel (parTraverse) import Ctl.Internal.BalanceTx.Collateral.Select (minRequiredCollateral) @@ -259,7 +266,7 @@ getWalletUtxos = do (unwrap >>> \({ input, output }) -> input /\ output) ownDrepPubKey :: Contract PublicKey -ownDrepPubKey = do +ownDrepPubKey = withWallet do actionBasedOnWallet _.getPubDrepKey ( \kw -> do @@ -270,7 +277,7 @@ ownDrepPubKey = do ) ownDrepPubKeyHash :: Contract Ed25519KeyHash -ownDrepPubKeyHash = do +ownDrepPubKeyHash = withWallet do actionBasedOnWallet (map PublicKey.hash <<< _.getPubDrepKey) ( \kw -> do @@ -280,3 +287,47 @@ ownDrepPubKeyHash = do pure $ PublicKey.hash $ PrivateKey.toPublicKey $ unwrap drepKey ) + +ownRegisteredPubStakeKeys :: Contract (Array PublicKey) +ownRegisteredPubStakeKeys = + withWallet do + actionBasedOnWallet _.getRegisteredPubStakeKeys + (map _.reg <<< kwPubStakeKeys) + +ownUnregisteredPubStakeKeys :: Contract (Array PublicKey) +ownUnregisteredPubStakeKeys = + withWallet do + actionBasedOnWallet _.getUnregisteredPubStakeKeys + (map _.unreg <<< kwPubStakeKeys) + +kwPubStakeKeys + :: KeyWallet + -> Contract { reg :: Array PublicKey, unreg :: Array PublicKey } +kwPubStakeKeys kw = + liftAff (getPrivateStakeKey kw) >>= case _ of + Nothing -> + pure mempty + Just (PrivateStakeKey stakeKey) -> do + queryHandle <- getQueryHandle + network <- asks _.networkId + let + pubStakeKey = PrivateKey.toPublicKey stakeKey + stakePkh = wrap $ PublicKey.hash pubStakeKey + resp <- liftAff $ queryHandle.getPubKeyHashDelegationsAndRewards + network + stakePkh + case resp of + Left err -> + liftEffect $ throw $ + "kwPubStakeKeys: getPubKeyHashDelegationsAndRewards call error: " + <> pprintClientError err + Right mStakeAccount -> + pure case mStakeAccount of + Nothing -> + { reg: mempty + , unreg: Array.singleton pubStakeKey + } + Just _ -> + { reg: Array.singleton pubStakeKey + , unreg: mempty + } diff --git a/src/Internal/Types/TxConstraints.purs b/src/Internal/Types/TxConstraints.purs index 0329a0f64..467d463f5 100644 --- a/src/Internal/Types/TxConstraints.purs +++ b/src/Internal/Types/TxConstraints.purs @@ -670,5 +670,9 @@ mustSatisfyAnyOf = mustNotBeValid :: Warn TxConstraintsDeprecated => TxConstraints mustNotBeValid = singleton $ MustNotBeValid -mustRegisterDrep :: Credential -> Maybe Anchor -> TxConstraints +mustRegisterDrep + :: Warn TxConstraintsDeprecated + => Credential + -> Maybe Anchor + -> TxConstraints mustRegisterDrep drepCred = singleton <<< MustRegisterDrep drepCred diff --git a/src/Internal/Wallet/Cip30.purs b/src/Internal/Wallet/Cip30.purs index a332353c4..296723b25 100644 --- a/src/Internal/Wallet/Cip30.purs +++ b/src/Internal/Wallet/Cip30.purs @@ -26,7 +26,11 @@ import Cardano.Wallet.Cip30 (Api) import Cardano.Wallet.Cip30.TypeSafe (APIError) import Cardano.Wallet.Cip30.TypeSafe as Cip30 import Cardano.Wallet.Cip95 (Api) as Cip95 -import Cardano.Wallet.Cip95.TypeSafe (getPubDrepKey) as Cip95 +import Cardano.Wallet.Cip95.TypeSafe + ( getPubDrepKey + , getRegisteredPubStakeKeys + , getUnregisteredPubStakeKeys + ) as Cip95 import Control.Monad.Error.Class (catchError, liftMaybe, throwError) import Ctl.Internal.Helpers (liftM) import Data.ByteArray (byteArrayToHex, hexToByteArray) @@ -85,6 +89,8 @@ type Cip30Wallet = , signTx :: Transaction -> Aff Transaction , signData :: Address -> RawBytes -> Aff DataSignature , getPubDrepKey :: Aff PublicKey + , getRegisteredPubStakeKeys :: Aff (Array PublicKey) + , getUnregisteredPubStakeKeys :: Aff (Array PublicKey) } mkCip30WalletAff @@ -106,6 +112,8 @@ mkCip30WalletAff conn95 = do , signTx: signTx connection , signData: signData connection , getPubDrepKey: getPubDrepKey conn95 + , getRegisteredPubStakeKeys: getRegisteredPubStakeKeys conn95 + , getUnregisteredPubStakeKeys: getUnregisteredPubStakeKeys conn95 } ------------------------------------------------------------------------------- @@ -247,9 +255,27 @@ getCip30Collateral conn (Coin requiredValue) = do getPubDrepKey :: Cip95.Api -> Aff PublicKey getPubDrepKey conn = do drepKeyHex <- handleApiError =<< Cip95.getPubDrepKey conn - let drepKey = PublicKey.fromRawBytes <<< wrap =<< hexToByteArray drepKeyHex - liftM - ( error $ "CIP-95 getPubDRepKey returned invalid DRep key: " - <> drepKeyHex - ) - drepKey + pubKeyFromHex drepKeyHex $ + "CIP-95 getPubDRepKey returned invalid DRep key: " + <> drepKeyHex + +getRegisteredPubStakeKeys :: Cip95.Api -> Aff (Array PublicKey) +getRegisteredPubStakeKeys conn = do + keys <- handleApiError =<< Cip95.getRegisteredPubStakeKeys conn + for keys \pubStakeKeyHex -> + pubKeyFromHex pubStakeKeyHex $ + "CIP-95 getRegisteredPubStakeKeys returned invalid key: " + <> pubStakeKeyHex + +getUnregisteredPubStakeKeys :: Cip95.Api -> Aff (Array PublicKey) +getUnregisteredPubStakeKeys conn = do + keys <- handleApiError =<< Cip95.getUnregisteredPubStakeKeys conn + for keys \pubStakeKeyHex -> + pubKeyFromHex pubStakeKeyHex $ + "CIP-95 getUnregisteredPubStakeKeys returned invalid key: " + <> pubStakeKeyHex + +pubKeyFromHex :: String -> String -> Aff PublicKey +pubKeyFromHex keyHex err = + liftM (error err) + (PublicKey.fromRawBytes <<< wrap =<< hexToByteArray keyHex)