diff --git a/flake.lock b/flake.lock index 4cac80f01..d6f263050 100644 --- a/flake.lock +++ b/flake.lock @@ -2099,17 +2099,17 @@ "utils": "utils_7" }, "locked": { - "lastModified": 1721843629, - "narHash": "sha256-F5wgRA820x16f+8c/LlEEBG0rMJIA1XWw6X0ZwX5UWs=", + "lastModified": 1722955151, + "narHash": "sha256-pZUg2PbhK35QdMcEP0or6IyKXBr544KyebQ+xiNc6PE=", "owner": "input-output-hk", "repo": "cardano-node", - "rev": "176f99e51155cb3eaa0711db1c3c969d67438958", + "rev": "4f4e372a1641ac68cd09fb0339e6f55bef1ab85d", "type": "github" }, "original": { "owner": "input-output-hk", - "ref": "9.1.0", "repo": "cardano-node", + "rev": "4f4e372a1641ac68cd09fb0339e6f55bef1ab85d", "type": "github" } }, diff --git a/flake.nix b/flake.nix index bed57d933..047c6de0b 100644 --- a/flake.nix +++ b/flake.nix @@ -33,7 +33,7 @@ flake = false; }; - cardano-node.url = "github:input-output-hk/cardano-node/9.1.0"; + cardano-node.url = "github:input-output-hk/cardano-node/4f4e372a1641ac68cd09fb0339e6f55bef1ab85d"; # Repository with network parameters # NOTE(bladyjoker): Cardano configurations (yaml/json) often change format and break, that's why we pin to a specific known version. diff --git a/packages.dhall b/packages.dhall index 6ad74d4c2..0ad5031af 100644 --- a/packages.dhall +++ b/packages.dhall @@ -142,13 +142,14 @@ let additions = { dependencies = [ "aff" , "aff-promise" + , "cip30" , "console" , "effect" , "newtype" , "prelude" ] , repo = "https://github.com/mlabs-haskell/purescript-cip95" - , version = "ddcabbcf96ec6e292ca821c86eada1f828da0daf" + , version = "3b2761237d54e85fc313f5a9439444ebf1b827a8" } , cip95-typesafe = { dependencies = diff --git a/spago-packages.nix b/spago-packages.nix index 6ae6b9673..5ef48eb74 100644 --- a/spago-packages.nix +++ b/spago-packages.nix @@ -367,11 +367,11 @@ let "cip95" = pkgs.stdenv.mkDerivation { name = "cip95"; - version = "ddcabbcf96ec6e292ca821c86eada1f828da0daf"; + version = "3b2761237d54e85fc313f5a9439444ebf1b827a8"; src = pkgs.fetchgit { url = "https://github.com/mlabs-haskell/purescript-cip95"; - rev = "ddcabbcf96ec6e292ca821c86eada1f828da0daf"; - sha256 = "1vhan4fx4yq2h2p4ilvid97qnfs6wx5nj6rjyvyj30s1wi1cb1rz"; + rev = "3b2761237d54e85fc313f5a9439444ebf1b827a8"; + sha256 = "1javlxga7mv1z0inwpwlgh0w4b4dh0bikv5pypyvh1mk3s2605hp"; }; phases = "installPhase"; installPhase = "ln -s $src $out"; diff --git a/src/Contract/Test/Testnet.purs b/src/Contract/Test/Testnet.purs index 8b4c3222d..d3e4d2348 100644 --- a/src/Contract/Test/Testnet.purs +++ b/src/Contract/Test/Testnet.purs @@ -26,7 +26,7 @@ import Ctl.Internal.Testnet.Contract , runTestnetTestPlan , testTestnetContracts ) as X -import Ctl.Internal.Testnet.Types (Era(Babbage), TestnetConfig) +import Ctl.Internal.Testnet.Types (Era(Conway), TestnetConfig) import Data.Log.Level (LogLevel(Trace)) import Data.Maybe (Maybe(Nothing)) import Data.Time.Duration (Seconds(Seconds)) @@ -52,7 +52,7 @@ defaultTestnetConfig = , hooks: emptyHooks , clusterConfig: { testnetMagic: 2 - , era: Babbage + , era: Conway , slotLength: Seconds 0.1 , epochSize: Nothing } diff --git a/src/Internal/Contract/MinFee.purs b/src/Internal/Contract/MinFee.purs index 354321682..677abf3f2 100644 --- a/src/Internal/Contract/MinFee.purs +++ b/src/Internal/Contract/MinFee.purs @@ -3,16 +3,40 @@ module Ctl.Internal.Contract.MinFee (calculateMinFee) where import Prelude import Cardano.Types - ( Coin + ( Certificate + ( StakeRegistration + , StakeDeregistration + , StakeDelegation + , PoolRegistration + , PoolRetirement + , VoteDelegCert + , StakeVoteDelegCert + , StakeRegDelegCert + , VoteRegDelegCert + , StakeVoteRegDelegCert + , AuthCommitteeHotCert + , ResignCommitteeColdCert + , RegDrepCert + , UnregDrepCert + , UpdateDrepCert + ) + , Coin + , Credential , Ed25519KeyHash + , RewardAddress , Transaction , UtxoMap + , Voter(Cc, Drep, Spo) , _body + , _certs , _collateral , _inputs + , _withdrawals ) import Cardano.Types.Address (Address, getPaymentCredential, getStakeCredential) import Cardano.Types.Credential (asPubKeyHash) +import Cardano.Types.Credential (asPubKeyHash) as Credential +import Cardano.Types.TransactionBody (_votingProcedures) import Cardano.Types.TransactionInput (TransactionInput) import Ctl.Internal.Contract (getProtocolParameters) import Ctl.Internal.Contract.Monad (Contract, getQueryHandle) @@ -22,12 +46,22 @@ import Ctl.Internal.Serialization.MinFee (calculateMinFeeCsl) import Data.Array (fromFoldable, mapMaybe) import Data.Array as Array import Data.Either (hush) +import Data.Foldable (foldl) +import Data.Lens (view) import Data.Lens.Getter ((^.)) import Data.Map (keys, lookup, values) as Map -import Data.Maybe (Maybe(Just, Nothing)) +import Data.Maybe (Maybe(Just, Nothing), maybe) import Data.Newtype (unwrap) import Data.Set (Set) -import Data.Set (difference, fromFoldable, intersection, mapMaybe, union) as Set +import Data.Set + ( difference + , empty + , fromFoldable + , insert + , intersection + , mapMaybe + , union + ) as Set import Data.Traversable (for) import Effect.Aff (error) import Effect.Aff.Class (liftAff) @@ -101,7 +135,14 @@ getSelfSigners tx additionalUtxos = do (asPubKeyHash <<< unwrap <=< getStakeCredential) `mapMaybe` Array.fromFoldable txOwnAddrs - pure $ paymentPkhs <> stakePkhs + -- Extract signers for certificates, withdrawals, and voting procedures + let + certsPkhs = getSignersForCerts tx + withdrawalsPkhs = getSignersForWithdrawals tx + votingProceduresPkhs = getSignersForVotingProcedures tx + + pure $ paymentPkhs <> stakePkhs <> certsPkhs <> withdrawalsPkhs + <> votingProceduresPkhs where setFor :: forall (a :: Type) (b :: Type) (m :: Type -> Type) @@ -112,3 +153,54 @@ getSelfSigners tx additionalUtxos = do -> (a -> m b) -> m (Set b) setFor txIns f = Set.fromFoldable <$> for (fromFoldable txIns) f + +getSignersForCerts :: Transaction -> Set Ed25519KeyHash +getSignersForCerts = foldl worker Set.empty <<< view (_body <<< _certs) + where + worker :: Set Ed25519KeyHash -> Certificate -> Set Ed25519KeyHash + worker acc = + case _ of + StakeRegistration _ -> acc + StakeDeregistration cred -> addSigner $ unwrap cred + StakeDelegation cred _ -> addSigner $ unwrap cred + PoolRegistration poolParams -> Set.insert + (unwrap (unwrap poolParams).operator) + acc + PoolRetirement { poolKeyHash } -> Set.insert (unwrap poolKeyHash) acc + VoteDelegCert cred _ -> addSigner $ unwrap cred + StakeVoteDelegCert cred _ _ -> addSigner $ unwrap cred + StakeRegDelegCert cred _ _ -> addSigner $ unwrap cred + VoteRegDelegCert cred _ _ -> addSigner $ unwrap cred + StakeVoteRegDelegCert cred _ _ _ -> addSigner $ unwrap cred + AuthCommitteeHotCert { coldCred } -> addSigner coldCred + ResignCommitteeColdCert cred _ -> addSigner cred + RegDrepCert cred _ _ -> addSigner cred + UnregDrepCert cred _ -> addSigner cred + UpdateDrepCert cred _ -> addSigner cred + where + addSigner :: Credential -> Set Ed25519KeyHash + addSigner = maybe acc (flip Set.insert acc) <<< Credential.asPubKeyHash + +getSignersForWithdrawals :: Transaction -> Set Ed25519KeyHash +getSignersForWithdrawals = + foldl worker Set.empty <<< Map.keys <<< view (_body <<< _withdrawals) + where + worker :: Set Ed25519KeyHash -> RewardAddress -> Set Ed25519KeyHash + worker acc = + maybe acc (flip Set.insert acc) <<< Credential.asPubKeyHash <<< unwrap + <<< _.stakeCredential + +getSignersForVotingProcedures :: Transaction -> Set Ed25519KeyHash +getSignersForVotingProcedures = + foldl worker Set.empty <<< Map.keys <<< unwrap + <<< view (_body <<< _votingProcedures) + where + worker :: Set Ed25519KeyHash -> Voter -> Set Ed25519KeyHash + worker acc = + case _ of + Cc cred -> addSigner cred + Drep cred -> addSigner cred + Spo poolKeyHash -> Set.insert poolKeyHash acc + where + addSigner :: Credential -> Set Ed25519KeyHash + addSigner = maybe acc (flip Set.insert acc) <<< Credential.asPubKeyHash diff --git a/src/Internal/Testnet/Types.purs b/src/Internal/Testnet/Types.purs index 556027b52..ea735e05c 100644 --- a/src/Internal/Testnet/Types.purs +++ b/src/Internal/Testnet/Types.purs @@ -1,6 +1,6 @@ module Ctl.Internal.Testnet.Types ( CardanoTestnetStartupParams - , Era(Byron, Shelley, Allegra, Mary, Alonzo, Babbage) + , Era(Byron, Shelley, Allegra, Mary, Alonzo, Babbage, Conway) , LoggingFormat(LogAsJson, LogAsText) , TestnetPaths , Event(Ready872, Finished, Failed, StartupFailed) @@ -55,6 +55,7 @@ data Era | Mary | Alonzo | Babbage + | Conway data StartupFailure = SpawnFailed @@ -84,6 +85,7 @@ instance Show Era where Mary -> "mary-era" Alonzo -> "alonzo-era" Babbage -> "babbage-era" + Conway -> "conway-era" data LoggingFormat = LogAsJson | LogAsText diff --git a/src/Internal/Wallet.purs b/src/Internal/Wallet.purs index 0a424ffca..51e161918 100644 --- a/src/Internal/Wallet.purs +++ b/src/Internal/Wallet.purs @@ -10,8 +10,8 @@ module Ctl.Internal.Wallet import Prelude +import Cardano.Wallet.Cip30 (Api) import Cardano.Wallet.Cip30 (enable) as Cip30 -import Cardano.Wallet.Cip95 (Api) import Cardano.Wallet.Cip95 (enable) as Cip95 import Cardano.Wallet.Key ( KeyWallet @@ -30,7 +30,6 @@ import Effect.Aff (Aff, delay) import Effect.Aff.Class (class MonadAff, liftAff) import Effect.Class (liftEffect) import Effect.Console as Console -import Unsafe.Coerce (unsafeCoerce) foreign import isWalletAvailable :: String -> Effect Boolean @@ -80,7 +79,7 @@ mkWalletAff walletExtension = do enableWallet :: WalletExtension -> Aff Api enableWallet { name, exts: { cip95 } } | cip95 = Cip95.enable name - | otherwise = unsafeCoerce <$> Cip30.enable name mempty + | otherwise = Cip30.enable name mempty actionBasedOnWallet :: forall (m :: Type -> Type) (a :: Type) diff --git a/src/Internal/Wallet/Cip30.purs b/src/Internal/Wallet/Cip30.purs index 296723b25..57e1436ca 100644 --- a/src/Internal/Wallet/Cip30.purs +++ b/src/Internal/Wallet/Cip30.purs @@ -25,7 +25,6 @@ import Cardano.Types.Value as Value 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 , getRegisteredPubStakeKeys @@ -41,7 +40,6 @@ import Data.Variant (Variant, match) import Effect.Aff (Aff) import Effect.Class (liftEffect) import Effect.Exception (error, throw) -import Unsafe.Coerce (unsafeCoerce) type DataSignature = { key :: CborBytes @@ -93,27 +91,23 @@ type Cip30Wallet = , getUnregisteredPubStakeKeys :: Aff (Array PublicKey) } -mkCip30WalletAff - :: Cip95.Api - -- ^ A function to get wallet connection - -> Aff Cip30Wallet -mkCip30WalletAff conn95 = do - let connection = unsafeCoerce conn95 -- FIXME +mkCip30WalletAff :: Api -> Aff Cip30Wallet +mkCip30WalletAff conn = pure - { connection - , getNetworkId: Cip30.getNetworkId connection >>= handleApiError - , getUtxos: getUtxos connection - , getCollateral: getCollateral connection - , getBalance: getBalance connection - , getUsedAddresses: getUsedAddresses connection - , getUnusedAddresses: getUnusedAddresses connection - , getChangeAddress: getChangeAddress connection - , getRewardAddresses: getRewardAddresses connection - , signTx: signTx connection - , signData: signData connection - , getPubDrepKey: getPubDrepKey conn95 - , getRegisteredPubStakeKeys: getRegisteredPubStakeKeys conn95 - , getUnregisteredPubStakeKeys: getUnregisteredPubStakeKeys conn95 + { connection: conn + , getNetworkId: Cip30.getNetworkId conn >>= handleApiError + , getUtxos: getUtxos conn + , getCollateral: getCollateral conn + , getBalance: getBalance conn + , getUsedAddresses: getUsedAddresses conn + , getUnusedAddresses: getUnusedAddresses conn + , getChangeAddress: getChangeAddress conn + , getRewardAddresses: getRewardAddresses conn + , signTx: signTx conn + , signData: signData conn + , getPubDrepKey: getPubDrepKey conn + , getRegisteredPubStakeKeys: getRegisteredPubStakeKeys conn + , getUnregisteredPubStakeKeys: getUnregisteredPubStakeKeys conn } ------------------------------------------------------------------------------- @@ -244,22 +238,21 @@ getBalance conn = do liftM (error "CIP-30 getUsedAddresses returned non-address") <<< (hexToByteArray >=> fromBytes >>> map Value.fromCsl) -getCip30Collateral - :: Api -> Coin -> Aff (Maybe (Array String)) +getCip30Collateral :: Api -> Coin -> Aff (Maybe (Array String)) getCip30Collateral conn (Coin requiredValue) = do let requiredValueStr = byteArrayToHex $ toBytes $ unwrap requiredValue (Cip30.getCollateral conn requiredValueStr >>= handleApiError) `catchError` \err -> throwError $ error $ "Failed to call `getCollateral`: " <> show err -getPubDrepKey :: Cip95.Api -> Aff PublicKey +getPubDrepKey :: Api -> Aff PublicKey getPubDrepKey conn = do drepKeyHex <- handleApiError =<< Cip95.getPubDrepKey conn pubKeyFromHex drepKeyHex $ "CIP-95 getPubDRepKey returned invalid DRep key: " <> drepKeyHex -getRegisteredPubStakeKeys :: Cip95.Api -> Aff (Array PublicKey) +getRegisteredPubStakeKeys :: Api -> Aff (Array PublicKey) getRegisteredPubStakeKeys conn = do keys <- handleApiError =<< Cip95.getRegisteredPubStakeKeys conn for keys \pubStakeKeyHex -> @@ -267,7 +260,7 @@ getRegisteredPubStakeKeys conn = do "CIP-95 getRegisteredPubStakeKeys returned invalid key: " <> pubStakeKeyHex -getUnregisteredPubStakeKeys :: Cip95.Api -> Aff (Array PublicKey) +getUnregisteredPubStakeKeys :: Api -> Aff (Array PublicKey) getUnregisteredPubStakeKeys conn = do keys <- handleApiError =<< Cip95.getUnregisteredPubStakeKeys conn for keys \pubStakeKeyHex -> diff --git a/templates/ctl-scaffold/packages.dhall b/templates/ctl-scaffold/packages.dhall index 137f93203..946275ff1 100644 --- a/templates/ctl-scaffold/packages.dhall +++ b/templates/ctl-scaffold/packages.dhall @@ -142,13 +142,14 @@ let additions = { dependencies = [ "aff" , "aff-promise" + , "cip30" , "console" , "effect" , "newtype" , "prelude" ] , repo = "https://github.com/mlabs-haskell/purescript-cip95" - , version = "ddcabbcf96ec6e292ca821c86eada1f828da0daf" + , version = "3b2761237d54e85fc313f5a9439444ebf1b827a8" } , cip95-typesafe = { dependencies = diff --git a/templates/ctl-scaffold/spago-packages.nix b/templates/ctl-scaffold/spago-packages.nix index 82a95f22d..35264ce22 100644 --- a/templates/ctl-scaffold/spago-packages.nix +++ b/templates/ctl-scaffold/spago-packages.nix @@ -379,11 +379,11 @@ let "cip95" = pkgs.stdenv.mkDerivation { name = "cip95"; - version = "ddcabbcf96ec6e292ca821c86eada1f828da0daf"; + version = "3b2761237d54e85fc313f5a9439444ebf1b827a8"; src = pkgs.fetchgit { url = "https://github.com/mlabs-haskell/purescript-cip95"; - rev = "ddcabbcf96ec6e292ca821c86eada1f828da0daf"; - sha256 = "1vhan4fx4yq2h2p4ilvid97qnfs6wx5nj6rjyvyj30s1wi1cb1rz"; + rev = "3b2761237d54e85fc313f5a9439444ebf1b827a8"; + sha256 = "1javlxga7mv1z0inwpwlgh0w4b4dh0bikv5pypyvh1mk3s2605hp"; }; phases = "installPhase"; installPhase = "ln -s $src $out"; diff --git a/test/Testnet/Gov.purs b/test/Testnet/Gov.purs index 7f6afa3fa..1814b2f5f 100644 --- a/test/Testnet/Gov.purs +++ b/test/Testnet/Gov.purs @@ -8,20 +8,46 @@ import Cardano.Types.BigNum (fromInt) as BigNum import Contract.Test (ContractTest) import Contract.Test.Mote (TestPlanM) import Contract.Test.Testnet (InitialUTxOs, withKeyWallet, withWallets) -import Ctl.Examples.Gov.ManageDrep (contract) as Gov.RegisterDrep -import Mote (group, skip, test) +import Ctl.Examples.Gov.DelegateVoteAbstain (contract) as Gov.DelegateVoteAbstain +import Ctl.Examples.Gov.ManageDrep (contract) as Gov.ManageDrep +import Ctl.Examples.Gov.ManageDrepScript (contract) as Gov.ManageDrepScript +import Ctl.Examples.Gov.SubmitVote (contract) as Gov.SubmitVote +import Ctl.Examples.Gov.SubmitVoteScript (contract) as Gov.SubmitVoteScript +import Ctl.Internal.Test.UtxoDistribution (TestWalletSpec) +import Data.Maybe (Maybe(Just)) +import Data.Newtype (wrap) +import Mote (group, test) +import Test.Ctl.Testnet.Common (privateDrepKey, privateStakeKey) + +walletSpec :: TestWalletSpec +walletSpec = wrap + { utxos: + [ BigNum.fromInt 1_000_000_000 + , BigNum.fromInt 50_000_000 + ] + , stakeKey: Just privateStakeKey + , drepKey: Just privateDrepKey + } suite :: TestPlanM ContractTest Unit suite = do - -- FIXME: It's not yet possible to start cardano-testnet in Conway era - -- using its CLI - skip $ group "Governance" do - test "Registers as DRep (Gov.RegisterDrep example)" do - let - distribution :: InitialUTxOs - distribution = - [ BigNum.fromInt 5_000_000 - , BigNum.fromInt 50_000_000 - ] - withWallets distribution \alice -> - withKeyWallet alice Gov.RegisterDrep.contract + group "Governance" do + test "Gov.DelegateVoteAbstain" do + withWallets walletSpec \alice -> + withKeyWallet alice Gov.DelegateVoteAbstain.contract + + test "Gov.ManageDrep example" do + withWallets walletSpec \alice -> + withKeyWallet alice Gov.ManageDrep.contract + + test "Gov.ManageDrepScript example" do + withWallets walletSpec \alice -> + withKeyWallet alice Gov.ManageDrepScript.contract + + test "Gov.SubmitVote example" do + withWallets walletSpec \alice -> + withKeyWallet alice Gov.SubmitVote.contract + + test "Gov.SubmitVoteScript example" do + withWallets walletSpec \alice -> + withKeyWallet alice Gov.SubmitVoteScript.contract