Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for addresses with staking keys and wallet lookups. #136

Merged
merged 40 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e560415
WIP: Add stake keys to BpiWallet
MitchyCola Sep 6, 2022
05d1613
WIP: refactor
MitchyCola Sep 6, 2022
e36cbe1
Translate BpiWallet to Address.
zmrocze Sep 12, 2022
1ac8336
Refactor key paths.
zmrocze Sep 13, 2022
4866a84
Set pcOwnStakePubKeyHash.
zmrocze Sep 15, 2022
dcbfdfa
[WIP] Init with stake keys.
zmrocze Sep 17, 2022
a96df29
Pass WalletInfo's to contracts instead of PubKeyHash's.
zmrocze Sep 20, 2022
a343b49
Update tests with onEnterpriseAddresses.
zmrocze Sep 20, 2022
a63d2bb
Generate stake keys based on wallet inits.
zmrocze Sep 20, 2022
415f740
Rename & Extend walletError type.
zmrocze Sep 22, 2022
39b9a80
Add usingLookups.
zmrocze Sep 22, 2022
8c56b01
Tagged lookups [WIP].
zmrocze Sep 23, 2022
055c737
Tagged lookups.
zmrocze Sep 24, 2022
be4f410
Fix executables.
zmrocze Sep 24, 2022
4563454
Fix tests [WIP].
zmrocze Sep 25, 2022
c90824f
Fix GADT introduced type errors. [WIP]
zmrocze Sep 25, 2022
bad8b84
Change lookups result type to Contract.
zmrocze Sep 25, 2022
5c56ead
Format.
zmrocze Sep 25, 2022
342efdd
Fix MonadFail error.
zmrocze Sep 26, 2022
c759555
Fix test.
zmrocze Sep 26, 2022
1edb746
Shorter names.
zmrocze Sep 26, 2022
81b5b73
Format.
zmrocze Sep 26, 2022
22a33bb
More short names.
zmrocze Sep 26, 2022
ca13d1c
Update comments.
zmrocze Sep 26, 2022
43785cd
Merge branch 'master' into staking-keys-using-lookups
zmrocze Sep 26, 2022
874340c
Update docs.
zmrocze Sep 26, 2022
1fa9fd0
Add wallet lookups test.
zmrocze Sep 26, 2022
8f9273f
Mention lookupAddress in docs.
zmrocze Sep 26, 2022
53aded9
changing index type to `Text` only
mikekeke Sep 28, 2022
ef56712
Remove now unneeded substituteTags.
zmrocze Sep 28, 2022
7ee8562
refactoring
mikekeke Oct 13, 2022
457005a
wip: refactoring
mikekeke Oct 13, 2022
a74e214
bump BPI version
mikekeke Oct 14, 2022
0060a00
removing types that looks unnecessary
mikekeke Oct 14, 2022
55979f9
mormatting, updating docs
mikekeke Oct 14, 2022
6d06eab
a bit more docs fixes
mikekeke Oct 14, 2022
3d68bc0
more docs adjustments
mikekeke Oct 14, 2022
2c2526b
plutip-server update
mikekeke Oct 17, 2022
a865a4c
BPI dep update
mikekeke Oct 17, 2022
abb3437
Merge branch 'master' into karol/staking-keys-using-lookups
mikekeke Oct 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ This format is based on [Keep A Changelog](https://keepachangelog.com/en/1.0.0).
- `initLovelaceAssertValue`
- `initLovelaceAssertValueWith`
- `withCollateral`
- Initialising wallets with staking keys.
- Access to initialised wallets via `WalletLookups`

## 0.1 -- 2022-02-14

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ NOTE: This branch launches local network in `Vasil`. It was tested with node `1.

## Tutorials

* NEW: [Address types](docs/wallet-tags-and-addresses.md)
* [Running disposable local network and building own runners](./local-cluster/README.md)
* [Tasty integration](./docs/tasty-integration.md)
* [Running Contracts is REPL](./docs/interactive-plutip.md)
Expand Down
9 changes: 6 additions & 3 deletions docs/interactive-plutip.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ setup = do
pure (env, ownWallet)

addWalletWithAdas :: [Ada] -> ReaderT ClusterEnv IO BpiWallet
addWalletWithAdas = addSomeWallet . map (fromInteger . Ada.toLovelace)
addWalletWithAdas funds =
addSomeWallet
(EntTag "w1")
(map (fromInteger . Ada.toLovelace) funds)
```

> Aside: Feel free to choose the amount of ada you want to fund your wallet with. Just remember: `addSomeWallet` takes a list of _lovelace_ amounts. Here, I've actually made my custom `Ada` type as well some helper utilities (not the same as `Plutus.V1.Ledger.Ada` as that is removed in newer `plutus-ledger-api` versions).
Expand Down Expand Up @@ -179,7 +182,7 @@ import Test.Plutip.Contract.Types (TestContractConstraints)
newtype ContractRunner = ContrRunner
{ runContr ::
forall w e a.
TestContractConstraints w e a =>
TestContractConstraints w e Int a =>
Contract w EmptySchema e a ->
IO (Either (FailureReason e) a)
}
Expand All @@ -192,7 +195,7 @@ begin = do
setup = do
env <- ask
-- Gotta have all those utxos for the collaterals.
ownWallet <- addWalletWithAdas $ 300 : replicate 50 10
ownWallet <- addWalletWithAdas (300 : replicate 50 10)
-- Wait for faucet funds to be added.
waitSeconds 2
pure (env, ownWallet)
Expand Down
97 changes: 63 additions & 34 deletions docs/tasty-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ test =
withConfiguredCluster def -- 1
"Basic integration: launch, add wallet, tx from wallet to wallet" -- 2
$ [ assertExecution "Contract 1" -- 3
(initAda [100,200] <> initLovelace 10_000_000) -- 3.1
(withContract $ \[wallet2pkh] -> someContract) -- 3.2
(initAda (EntTag "w0") [100,200] <> initLovelace (BaseTag "w1") [10_000_000]) -- 3.1
(withContract $ \ws -> someContract) -- 3.2
[ shouldSucceed -- 3.3
]
]
Expand All @@ -20,8 +20,8 @@ test =
1. Will start the local network with default config (more on configuring below)
2. Description of test group that will be run on current instance of the network
3. Test scenario that will be performed on the local network with it's description. Scenario includes:
1. (3.1.) Initialization of `wallets`. In this case two addresses will be funded: first will have 2 UTxOs with 100 and 200 Ada, second - single UTxO with 10 Ada.
2. (3.2) Execution of "`someContract :: Contract w s e a`". `PaymentPubKeyHash` of *first wallet* will be accessible in `someContract` as "own PaymentPubKeyHash". e.g with `ownFirstPaymentPubKeyHash`. `PaymentPubKeyHash` of *second* initiated wallet is brought into scope by `wallet2pkh` during pattern match on list (more on that below).
1. (3.1.) Initialization of `wallets`. In this case two addresses will be funded: first - enterprise address - will have 2 UTxOs with 100 and 200 Ada, second - base address - single UTxO with 10 Ada.
2. (3.2) Execution of "`someContract :: Contract w s e a`". `PaymentPubKeyHash` of *first wallet* will be accessible in `someContract` as "own PaymentPubKeyHash". e.g with `ownFirstPaymentPubKeyHash`. `PaymentPubKeyHash` of *second* initiated wallet is accessible through `ws :: WalletLookups` (more on that below).
3. (3.3) List of checks or `predicates` which will be performed for the result of `someContract` execution.

It is possible to run several scenarios on single network instance - note that `withConfiguredCluster` accepts list of `assertExecution`'s.
Expand All @@ -33,39 +33,68 @@ It is possible to initialize arbitrary number of `wallets` in second argument of
E.g. if `wallets` initialized like

```haskell
(initAda [100] <> initAda [200] <> initAda [300])
(initAda (EntTag "w0") [100] <> initAda (EntTag "w1") [200] <> initAda (BaseTag "w2") [300])
```

we will get 3 funded addresses represented by 3 corresponding `wallets`:
we will get 3 funded addresses represented by 3 corresponding `wallets`:

* `PaymentPubKeyHash` of wallet `initAda [100]` will be "own" `PaymentPubKeyHash` for contract executed it test case.
* `PaymentPubKeyHash` of `wallets` `initAda [200]` and `initAda [300]` will be available via lambda argument. I.e.:
* `PaymentPubKeyHash` of wallet `w0` will be "own" `PaymentPubKeyHash` for contract executed it test case.
* `PaymentPubKeyHash` of `wallets` `w1` and `w2` will be available via lambda wallet lookups argument. I.e.:

```haskell
withContract $ \ws -> do
EntWallet pkh1 <- lookupWallet ws (EntTag "w1")
BaseWallet pkh2 spkh2 <- lookupWallet ws (BaseTag "w2")
someContract
```

Note that the lookup return type depends on a query tag.

* `pkh1` is `PaymentPubKeyHash` of `wallet` `initAda (EntTag "w1") [200]`
* `pkh2` is `PaymentPubKeyHash` of `wallet` `initAda (BaseTag "w2") [300]` and `spkh2` is its `StakePubKeyHash`

`PaymentPubKeyHash` of `wallet` `initAda (EntTag "w0") [100]` is meant to be `pkh0` and not presented in the lookups.

You can execute a contract with base address as contracts address:

```haskell
(initAda (BaseTag "w0") [100])
```

and witness in contract

```haskell
withContract $ \[pkh1, pkh2] -> someContract
withContract $ \_ -> do
ourAddr :| _ <- Contract.ownAddresses
case ourAddr of
Address (PubKeyCredential ourPkh) (Just (StakingHash (PubKeyCredential ourSpkh))) -> logInfo "This is the address we will get."
_ -> error "Nothing else matters"
```

where
Use `mustPayToPubKeyAddress` instead of `mustPayToPubKey` when your address has staking keys.

* `pkh1` is `PaymentPubKeyHash` of `wallet` `initAda [200]`
* `pkh2` is `PaymentPubKeyHash` of `wallet` `initAda [300]`
You can also query for wallet address right away:

`PaymentPubKeyHash` of `wallet` `initAda [100]` is meant to be `pkh0` and not presented in the list.
```haskell
withContract $ \ws -> do
addr1 <- lookupAddress ws "w1"
addr2 <- lookupAddress ws "w2"
someContract
```

## Executing contracts

It is possible to run arbitrary number of contracts in 3d argument of `assertExecution` using its monadic nature. E.g.:

```haskell
assertExecution "Some description"
( initAda [100])
( initAda (EntTag "w1") [100])
( do
void $
withContract $
\pkhs -> contract1
withContractAs 1 $
\pkhs -> contract2
\ws -> contract1
withContractAs "w1" $
\ws -> contract2
)
[shouldSucceed]
```
Expand All @@ -85,33 +114,33 @@ For example, consider the following scenario:

```haskell
assertExecution "Some description"
( initAda [100] -- walletA
<> initAda [200] -- walletB
<> initAda [300] -- walletC
( initAda (EntTag "a") [100] -- walletA
<> initAda (EntTag "b") [200] -- walletB
<> initAda (EntTag "c") [300] -- walletC
)
( do
void $
withContractAs 1 $ -- running contract with walletB
\[walletA_PKH, walletC_PKH] -> setupContract1
withContractAs "b" $ -- running contract with walletB
\ws -> do
wallA <- lookupWallet ws (EntTag "a")
setupContract1
void $
withContractAs 2 $ -- running contract with walletC
\[walletA_PKH, walletB_PKH] -> setupContract2
withContract $
\pkhs -> theContract
withContractAs "c" $ -- running contract with walletC
\ws -> do
wallB <- lookupWallet ws (EntTag "b")
setupContract2
withContract $ -- uses first wallet, walletA
\ws -> theContract
)
[shouldSucceed]
```

Under the hood, test runner builds list of wallets like this `[walletA, walletB, walletC]` and by calling `withContractAs` we can refer to an index (0 based) of specific wallet in this list. In that case, `PaymentPubKeyHash` of referenced `wallet` becomes "own" `PaymentPubKeyHash` of the contract, and `PaymentPubKeyHash`'es in argument of lambda will be rearranged. E.g. in case of `withContractAs 1`:

* `PaymentPubKeyHash` of `walletB` will become own `PaymentPubKeyHash`
* argument of lambada will contain `PaymentPubKeyHash`'es of `walletA` and `walletC`.

Actually, `withContract` is just shortcut for `withContractAs 0`.
`withContractAs` asks explicitly for the name of a wallet to be used as contract's.
Instead `withContract` uses the first wallet, first in the order of how the initializations are written.

## Assertions

To assert the result of contract execution user specifies list of checks or `predicates` as 4th argument of `assertExecution`. There are several `predicates` provided by the library that could be found in `Test.Plutip.Contract` module. Existing `predicates` allows to make assertions on Contracts state (`w`), error (`e`) and result (`a`) (consider type `Contract w s e a`).
To assert the result of contract execution user specifies list of checks or `predicates` as 4th argument of `assertExecution`. There are several `predicates` provided by the library that could be found in `Test.Plutip.Contract` module. Existing `predicates` allows to make assertions on Contracts state (`w`), error (`e`) and result (`a`) (consider type `Contract w s e a`).

There are also predicates for making assertions on scripts execution budgets (e.g. `budgetsFitUnder` or `assertOverallBudget`). But be aware, that budget of script submitted to private network can differ from testnet or mainnet, at least because different amount of input UTxOs could be added during balancing, so this assertions are mostly useful for rough estimation and regression testing.

Expand Down Expand Up @@ -148,7 +177,7 @@ E.g. scenario like this
assertExecutionWith
[ShowBudgets]
"Lock then spend contract"
(initAda (replicate 3 300))
(initAda (EntTag "w1") (replicate 3 300))
(withContract $ const lockThenSpend)
[ shouldSucceed
]
Expand Down
71 changes: 71 additions & 0 deletions docs/wallet-tags-and-addresses.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Address types and `WalletTag`

There are [several types of addresses](https://docs.cardano.org/learn/cardano-addresses) on Cardano network. Currently Plutip supports two types of addresses in both [interactive](./interactive-plutip.md) and [tasty](tasty-integration.md) modes:

* ***Enterprise Address*** - carry no stake rights, backed only by payment keys
* ***Base Address*** - directly specifies the staking key, backed by both payment and staking keys

To pick which type of address to create for wallet, Plutip provides `WalletTag`. `WalletTag` has two constructors:

* `BaseTag Text` - will create wallet with `Base Address`
* `EntTag Text` - will create wallet with `Enterprise Address`

Wallet tag also gives wallet it's name (the `Text` argument of constructor) and can be used to lookup desired wallet by name. There is special functionality for `WalletTag` available tasty framework, but lets see simple example with local cluster eDSL first.

## `WalletTag` in interactive mode and local cluster

Whenever you use eDSL function like `addSomeWallet` ([example 1](interactive-plutip.md), [example 2](../local-cluster/README.md)), from now on you can specify `WalletTag` as first argument to pick what type of address wallet will have. The `Text` argument of constructor will be added as textual tag to created wallet. E.g.:

```haskell
main :: IO ()
main = do
(st, _) <- startCluster def $ do
wallet1 <- addSomeWallet (BaseTag "wallet1") [100_000_000]
waitSeconds 2
stopCluster st
```

`wallet1` has type `BpiWallet`, and textual tag can be obtained with

```haskell
wTag :: Text
wTag = bwTag wallet1
```

It could useful if you want to print some info about wallet and distinguish output by tag, or when you initialize several wallets with something like `mapM` - can use tags to enable some lookups:

```haskell
let mkTag idx = EntTag $ T.pack $ "wallet" <> show idx
wallets <-
for [0..42] $ \idx ->
addSomeWalletDir (mkTag idx) [100_000_000]
...
let maybeMyWallet = find ((== "wallet13") . bwTag) wallets
```

## `WalletTag` and tasty Plutip

In tasty integration it is also possible to pick address type now with functions like `initAda`. And on top of that new lookup system was added, that lets you find initialized wallets easier (no more pattern matching on lists and figuring out correct indexes):

```haskell
assertExecution
"Some test"
( initAndAda (EntTag "w0") [100]
<> initAnd (EntTag "w1") [100]
)
( do
void $ withContract $ \ws -> do
-- lookup wallet by it's tag
EntWallet pkh0 <- lookupWallet ws (EntTag "w0")
payToPubKey pkh0 10_000_000
-- select wallet with tag "w1" to be "own" wallet
withContractAs "w1" $ \ws -> do
-- lookup address by wallet tag
addr1 <- lookupAddress ws "w1"
payToPubKeyAddress addr1 10_000_000
)
[shouldSucceed]

```

For more examples see [tasty integration tutorial](./tasty-integration.md).
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 @@ -11,7 +11,7 @@
flake = false;
};
bot-plutus-interface.url =
"github:mlabs-haskell/bot-plutus-interface?ref=d6cf1e3686bc31bb2571c6feefbe28e3a2c8bb06";
"github:mlabs-haskell/bot-plutus-interface/74e9d951a96eada55efa3ed0178f3f2a4cffd492";
};

outputs =
Expand Down
2 changes: 2 additions & 0 deletions hie.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ cradle:
component: "test-suite:plutip-tests"
- path: "./local-cluster/"
component: "exe:local-cluster"
- path: "./plutip-server/"
component: "exe:plutip-server"
24 changes: 15 additions & 9 deletions local-cluster/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,30 @@
module Main (main) where

import Control.Applicative (optional, (<**>))
import Control.Monad (forM_, replicateM, void)
import Control.Monad (forM_, void)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Reader (ReaderT (ReaderT))
import Data.Default (def)
import Data.Traversable (for)
import Numeric.Positive (Positive)
import Options.Applicative (Parser, helper, info)
import Options.Applicative qualified as Options
import Test.Plutip.Config
( PlutipConfig (clusterWorkingDir),
WorkingDirectory (Fixed, Temporary),
)
import Test.Plutip.Internal.BotPlutusInterface.Wallet (addSomeWalletDir, walletPkh)
import Test.Plutip.Internal.BotPlutusInterface.Wallet (addSomeWalletDir)
import Test.Plutip.Internal.Types (nodeSocket)
import Test.Plutip.LocalCluster
( mkMainnetAddress,
startCluster,
stopCluster,
waitSeconds,
walletPaymentPkh
)
import GHC.Natural (Natural)
import Test.Plutip.Internal.BotPlutusInterface.Types (WalletTag(EntTag), BpiWallet (bwTag))
import Data.Text qualified as T

main :: IO ()
main = do
Expand All @@ -43,7 +47,7 @@ main = do
waitSeconds 2 -- let wallet Tx finish, it can take more time with bigger slot length

separate
liftIO $ forM_ (zip ws [(1 :: Int) ..]) printWallet
liftIO $ forM_ ws printWallet
printNodeRelatedInfo
separate

Expand All @@ -65,12 +69,14 @@ main = do
amt -> Right $ fromInteger . toInteger $ amt

initWallets numWallets numUtxos amt dirWallets = do
replicateM (max 0 numWallets) $
addSomeWalletDir (replicate numUtxos amt) dirWallets

printWallet (w, n) = do
putStrLn $ "Wallet " ++ show n ++ " PKH: " ++ show (walletPkh w)
putStrLn $ "Wallet " ++ show n ++ " mainnet address: " ++ show (mkMainnetAddress w)
for [0..(max 0 numWallets - 1)] $ \idx ->
addSomeWalletDir (EntTag $ T.pack $ show idx)
(replicate numUtxos amt) dirWallets

printWallet w = do
putStrLn $ "Wallet " ++ show (bwTag w) ++ " PKH: " ++ show (walletPaymentPkh w)
putStrLn $ "Wallet " ++ show (bwTag w) ++ " mainnet address: "
++ show (mkMainnetAddress w)

toAda = (* 1_000_000)

Expand Down
2 changes: 1 addition & 1 deletion local-cluster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ main = do
ask >>= \cEnv -> runContract cEnv wallet contract

(st, _) <- startCluster def $ do
w <- addSomeWallet [100_000_000]
w <- addSomeWallet (BaseTag "wallet1") [100_000_000]
waitSeconds 2
result <- executeContract w someContract
doSomething result
Expand Down
2 changes: 1 addition & 1 deletion plutip-server/Api.hs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ server serverOptions =
:<|> stopClusterHandler

appServer :: Env -> Server Api
appServer env@(Env {options}) =
appServer env@Env {options} =
hoistServer api appHandler (server options)
where
appHandler :: forall (a :: Type). AppM a -> Handler a
Expand Down
Loading