Skip to content

Commit

Permalink
Run cardano-testnet in Conway era, other changes below
Browse files Browse the repository at this point in the history
- Reuse Api type from purescript-cip30

- Add cardano-testnet test suite for governance examples

- Consider signers for tx certificates, withdrawals, and voting
  procedures when calculating min fee
  • Loading branch information
errfrom committed Aug 9, 2024
1 parent 0fdc13c commit c86bdce
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 64 deletions.
8 changes: 4 additions & 4 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion packages.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
6 changes: 3 additions & 3 deletions spago-packages.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/Contract/Test/Testnet.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -52,7 +52,7 @@ defaultTestnetConfig =
, hooks: emptyHooks
, clusterConfig:
{ testnetMagic: 2
, era: Babbage
, era: Conway
, slotLength: Seconds 0.1
, epochSize: Nothing
}
Expand Down
100 changes: 96 additions & 4 deletions src/Internal/Contract/MinFee.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
4 changes: 3 additions & 1 deletion src/Internal/Testnet/Types.purs
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -55,6 +55,7 @@ data Era
| Mary
| Alonzo
| Babbage
| Conway

data StartupFailure
= SpawnFailed
Expand Down Expand Up @@ -84,6 +85,7 @@ instance Show Era where
Mary -> "mary-era"
Alonzo -> "alonzo-era"
Babbage -> "babbage-era"
Conway -> "conway-era"

data LoggingFormat = LogAsJson | LogAsText

Expand Down
5 changes: 2 additions & 3 deletions src/Internal/Wallet.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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)
Expand Down
47 changes: 20 additions & 27 deletions src/Internal/Wallet/Cip30.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}

-------------------------------------------------------------------------------
Expand Down Expand Up @@ -244,30 +238,29 @@ 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 ->
pubKeyFromHex pubStakeKeyHex $
"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 ->
Expand Down
3 changes: 2 additions & 1 deletion templates/ctl-scaffold/packages.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down
6 changes: 3 additions & 3 deletions templates/ctl-scaffold/spago-packages.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit c86bdce

Please sign in to comment.