Skip to content

Commit

Permalink
Merge branch 'develop' into dshuiski/fix-non-ada-change
Browse files Browse the repository at this point in the history
  • Loading branch information
errfrom committed Aug 8, 2022
2 parents 1df0b39 + be0c81b commit c7161c2
Show file tree
Hide file tree
Showing 14 changed files with 396 additions and 187 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -47,7 +47,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- `Contract.Transaction.getTxByHash` to retrieve contents of an on-chain transaction.
- `project.launchSearchablePursDocs` to create an `apps` output for serving Pursuit documentation locally ([#816](https://github.com/Plutonomicon/cardano-transaction-lib/issues/816))
- `KeyWallet.MintsAndSendsToken` example ([#802](https://github.com/Plutonomicon/cardano-transaction-lib/pull/802))
- `Contract.PlutusData.IsData` type class (`ToData` + `FromData`) ([#809](https://github.com/Plutonomicon/cardano-transaction-lib/pull/809))
- A check for port availability before Plutip runtime initialization attempt ([#837](https://github.com/Plutonomicon/cardano-transaction-lib/issues/837))
- `Contract.Address.addressToBech32` and `Contract.Address.addressWithNetworkTagToBech32` ([#846](https://github.com/Plutonomicon/cardano-transaction-lib/issues/846))
- `doc/e2e-testing.md` describes the process of E2E testing. ([#814](https://github.com/Plutonomicon/cardano-transaction-lib/pull/814))
- Added unzip to the `devShell`. New `purescriptProject.shell` flag `withChromium` also optionally adds Chromium to the `devShell` ([#799](https://github.com/Plutonomicon/cardano-transaction-lib/pull/799))

### Changed

Expand All @@ -61,6 +65,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- ServerConfig accepts a url `path` field ([#728](https://github.com/Plutonomicon/cardano-transaction-lib/issues/728)).
- Examples now wait for transactions to be confirmed and log success ([#739](https://github.com/Plutonomicon/cardano-transaction-lib/issues/739)).
- Updated CSL version to v11.0.0 ([#801](https://github.com/Plutonomicon/cardano-transaction-lib/issues/801))
- Better error message when attempting to initialize a wallet in NodeJS environment ([#778](https://github.com/Plutonomicon/cardano-transaction-lib/issues/778))
- The [`ctl-scaffold`](https://github.com/mlabs-haskell/ctl-scaffold) repository has been archived and deprecated and its contents moved to `templates.ctl-scaffold` in the CTL flake ([#760](https://github.com/Plutonomicon/cardano-transaction-lib/issues/760)).
- The CTL `overlay` output has been deprecated and replaced by `overlays.purescript` and `overlays.runtime` ([#796](https://github.com/Plutonomicon/cardano-transaction-lib/issues/796)).
- `buildCtlRuntime` and `launchCtlRuntime` now take an `extraServices` argument to add `docker-compose` services to the resulting Arion expression ([#769](https://github.com/Plutonomicon/cardano-transaction-lib/issues/769)).
Expand All @@ -85,6 +90,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

See https://github.com/cardano-foundation/CIPs/issues/303 for motivation
- `ogmios-datum-cache` now works on `x86_64-darwin`
- `TypedValidator` interface ([#808](https://github.com/Plutonomicon/cardano-transaction-lib/issues/808))

## [2.0.0-alpha] - 2022-07-05

Expand Down
133 changes: 133 additions & 0 deletions doc/e2e-testing.md
@@ -0,0 +1,133 @@
# E2E Testing in the Browser

CTL has basic machinery for E2E testing in the browser. This can be used to either run the included examples (in `examples`) or create a custom test suite for E2E testing.

- [E2E Testing in the Browser](#e2e-testing-in-the-browser)
- [Parts Involved](#parts-involved)
- [How to Run the Included Examples](#how-to-run-the-included-examples)
- [Accepted Command Line Options](#accepted-command-line-options)
- [How Wallets are Used](#how-wallets-are-used)
- [How to Use a Different Version of a Wallet](#how-to-use-a-different-version-of-a-wallet)
- [Where to Find the Installed Extensions](#where-to-find-the-installed-extensions)
- [Re-Package an Extension as a CRX File](#re-package-an-extension-as-a-crx-file)
- [Use a CRX File](#use-a-crx-file)
- [How to Use a Different User Wallet](#how-to-use-a-different-user-wallet)
- [How to Create Your Own Test Suite](#how-to-create-your-own-test-suite)
- [Using a reproducible `chromium` version](#using-a-reproducible-chromium-version)

## Parts Involved

[Puppeteer](https://github.com/puppeteer/puppeteer) (driven by [Toppokki](https://github.com/justinwoo/purescript-toppokki))
is used to drive the tests. Supported browsers are [Chromium](https://www.chromium.org/) and Google Chrome.
The browser can be run headless (default) or headful (useful during test development).

Any programs that should be tested must be deployed and running on some testserver (e.g. with `make run-dev` for the included examples).

An executable for concrete tests is also needed. For a working example see `test/E2E.purs`.

## How to Run the Included Examples

The process is as follows:

1. Set `ps-entrypoint` in Makefile to `Examples.ByURL`.
2. run `make run-dev`.
3. In another shell, run `make e2e-test`.
4. Examples will be run headless by default. In case of errors, the browser console will be printed to the console.

## Accepted Command Line Options

The provided test suite accepts some options. These can be passed via `make` after an additional double dash `--`, e.g. `make e2e-test -- --no-headless`. For usage examples, see the invocations in the `Makefile`, for a complete explanation, see `src/Contract/Test/E2E/Browser.purs`.

## How Wallets are Used

For purposes of testing, there are two parts to using a wallet: providing the right software version and importing a wallet with enough assets and a known password.

- The software just needs to be unpacked to some directory. This can either be the location where the browser unpacks it, or the result of unpacking a CRX file (see below).
- We provide the wallet data as tarballs which will be unpacked into the chrome profile before a test run.

### How to Use a Different Version of a Wallet

Chrome extensions are unpacked to some directory by the browser. From there, they can either be used directly by the tests (which gives no control over upgrades and instead uses always the current version), or they can be repackaged as CRX files. The default setup provides CRX versions which `make e2e-test` automatically unpacks on each test run.

The default test suite accepts the arguments `--nami-dir` and `--gero-dir` to point to the directories from which the extensions are loaded. (see the Makefile) In order to use the "live" version of an extension, just pass the arguments accordingly, e.g.:

```
@spago test --main Test.E2E -a "E2ETest --nami-dir ~/.config/google-chrome/Default/Extensions/lpfcbjknijpeeillifnkikgncikgfhdo/ --gero-dir ~/.config/google-chrome/Default/Extensions/iifeegfcfhlhhnilhfoeihllenamcfgc --chrome-exe google-chome
```

### Where to Find the Installed Extensions

1. Locate your browser profile directory. Commonly used locations include: `~/.config/{google-chrome,chromium}/Default` (where `Default` is the profile name), `~/snap/chromium/common/chromium/Default`.
2. Make sure that inside the profile, your desired extension is unpacked. Nami should be in `Extensions/lpfcbjknijpeeillifnkikgncikgfhdo`, Gero (testnet version) in `Extensions/iifeegfcfhlhhnilhfoeihllenamcfgc`.
3. Add the version as a subdirectory, too. The final path may look like `/home/user/.config/google-chrome/Default/Extensions/iifeegfcfhlhhnilhfoeihllenamcfgc/1.10.9_0`

### Re-Package an Extension as a CRX File

1. Make sure your browser is using the desired extension version.
2. Navigate to chrome://extensions/
3. Click the extension.
4. Switch on "Developer mode" (upper right corner).
5. Click "Pack extension".
6. Paste the extension's directory (see above) into "Extension root directory". You can leave "Private key file" empty.
7. Click "Pack extension".
8. The path of the CRX file is displayed in the browser.

(See [puppeteer-crx](https://www.npmjs.com/package/puppeteer-crx) for an effort to automate this process.)

### Use a CRX File

We use `unzip` to unpack it. However, `unzip` will issue a warning because of extra bytes at the beginning, and will exit with a non-zero code, so the exit code needs to be ignored. (we use `|| echo to achieve that`).

See the `Makefile` for an example:

```
e2e-test-nami := test-data/chrome-extensions/nami_3.2.5_1.crx
unzip ${e2e-test-nami} -d ${e2e-temp-dir}/nami > /dev/zero || echo "ignore warnings"
```

`${e2e-temp-dir}/nami` can then be passed to the test suite as nami directory.

### How to Use a Different User Wallet

In the test suite, the wallet settings are just unpacked using `tar xzf ${e2e-test-nami-settings}` (see `Makefile`).

A new settings tarball can be easily created, for example using the `Makefile`:

1. Adjust `${e2e-test-nami-settings}`, `${e2e-test-gero-settings}` and `${e2e-test-chrome-dir}` to point to where you want to store the settings and to chromes user-profile directory
2. Run `make e2e-run-browser-gero` or `make e2e-run-browser-nami` to fire up the test browser with one of the wallets loaded. Configure your wallet as usual.
3. Run `make nami-settings` or `make gero-settings` to store the settings to a tarball.

## How to Create Your Own Test Suite

If you are using CTL as a library, you can and should create your own test suite to test your own contracts.

1. Take `test/E2E.purs` as inspiration and create your own binary. You will find the necessary machinery in `Contract.Test.E2E`. Notable components:
- `withBrowser`: bracket to launch the browser with a specific extension, run something and clos the browser.
- `parseOptions`: Parses command line options, in case you want to use the same as our example suite.
- `publishTestFeedback`, `resetTestFeedback`, `retrieveTestFeedback`: Can be used to communicate success or failure from a contract to the tests.
- `geroConfirmAccess`, `geroSign`, `namiConfirmAccess`, `namiSign`: Confirm a transaction in the browser (i.e. enter the password, click "Sign")
- `withExample`: navigate to a URL, detect the wallet and get ready to run a contract.
2. Fire up your own contracts.
3. Take the `Makefile` as an inspiration, prepare your wallets and run the tests.

## Using a reproducible `chromium` version

Although most users will have some version of Chromium or Google Chrome installed system-wide, it can be a good idea to use the same version for all e2e testing. When creating your project's `devShell` using `purescriptProject`, you can set the `shell.withChromium` flag to `true` to include it in the shell's packages. This will be the version of `chromium` present in the `nixpkgs` you pass to create your project:

```nix
{
projectFor = system:
let
pkgs = nixpkgsFor system;
in
pkgs.purescriptProject {
inherit pkgs;
projectName = "my-project";
shell = {
withChromium = true;
# ...
};
# ...
};
}
```
14 changes: 8 additions & 6 deletions doc/plutus-comparison.md
Expand Up @@ -91,16 +91,18 @@ class ValidatorTypes (a :: Type) where
Purescript lacks most of Haskell's more advanced type-level faculties, including type/data families. Purescript does, however, support functional dependencies, allowing us to encode `ValidatorTypes` as follows:

```purescript
class ValidatorTypes :: Type -> Type -> Type -> Constraint
class
( DatumType a b
, RedeemerType a b
( DatumType validator datum
, RedeemerType validator redeemer
) <=
ValidatorTypes (a :: Type) (b :: Type)
| a -> b
ValidatorTypes validator datum redeemer
class DatumType (a :: Type) (b :: Type) | a -> b
class DatumType :: Type -> Type -> Constraint
class DatumType validator datum | validator -> datum
class RedeemerType (a :: Type) (b :: Type) | a -> b
class RedeemerType :: Type -> Type -> Constraint
class RedeemerType validator redeemer | validator -> redeemer
```

### Working with scripts
Expand Down
1 change: 0 additions & 1 deletion examples/KeyWallet/MintsAndSendsToken.js

This file was deleted.

9 changes: 2 additions & 7 deletions examples/KeyWallet/MintsAndSendsToken.purs
Expand Up @@ -2,7 +2,7 @@
-- | balance, and submit a smart-contract transaction. It creates a transaction
-- | that mints a token using the `AlwaysMints` policy and sends it along with
-- | the selected amount to the specified address.
module Examples.KeyWallet.MintsAndSendsToken where
module Examples.KeyWallet.MintsAndSendsToken (main) where

import Contract.Prelude

Expand All @@ -24,6 +24,7 @@ import Contract.TextEnvelope
import Contract.Transaction (balanceAndSignTx, submit)
import Contract.TxConstraints as Constraints
import Contract.Value as Value
import Examples.AlwaysMints (alwaysMintsPolicy)
import Examples.KeyWallet.Internal.Pkh2PkhContract (runKeyWalletContract_)

main :: Effect Unit
Expand Down Expand Up @@ -52,9 +53,3 @@ main = runKeyWalletContract_ \pkh lovelace unlock -> do
txId <- submit bsTx
logInfo' $ "Tx ID: " <> show txId
liftEffect unlock

foreign import alwaysMints :: String

alwaysMintsPolicy :: Contract () MintingPolicy
alwaysMintsPolicy = wrap <<< wrap <$> textEnvelopeBytes alwaysMints
PlutusScriptV1
7 changes: 6 additions & 1 deletion nix/default.nix
Expand Up @@ -82,6 +82,7 @@ let
# If `true`, `npm i` will only write to your `package-lock.json` instead
# of installing to a local `node_modules`
, packageLockOnly ? false
, withChromium ? false
}:
assert pkgs.lib.assertOneOf "formatter" formatter [ "purs-tidy" "purty" ];
pkgs.mkShell {
Expand All @@ -95,9 +96,13 @@ let
pkgs.easy-ps.psa
pkgs.easy-ps.spago2nix
pkgs.nodePackages.node2nix
pkgs.unzip
] ++ pkgs.lib.lists.optional
pursls
pkgs.easy-ps.purescript-language-server;
pkgs.easy-ps.purescript-language-server
++ pkgs.lib.lists.optional
withChromium
pkgs.chromium;
inherit packages inputsFrom;
shellHook = ''
export NODE_PATH="${nodeModules}/lib/node_modules"
Expand Down
27 changes: 25 additions & 2 deletions src/Contract/Address.purs
Expand Up @@ -4,6 +4,8 @@ module Contract.Address
, enterpriseAddressStakeValidatorHash
, enterpriseAddressValidatorHash
, getNetworkId
, addressWithNetworkTagToBech32
, addressToBech32
, getWalletAddress
, getWalletCollateral
, module ByteArray
Expand All @@ -12,6 +14,7 @@ module Contract.Address
, module ExportUnbalancedTransaction
, module Hash
, module SerializationAddress
, module TypeAliases
, ownPaymentPubKeyHash
, ownPubKeyHash
, ownStakePubKeyHash
Expand Down Expand Up @@ -44,7 +47,11 @@ import Plutus.Conversion
, toPlutusAddress
, toPlutusTxUnspentOutput
)
import Plutus.Types.Address (Address)
import Plutus.Conversion.Address (fromPlutusAddressWithNetworkTag)
import Plutus.Types.Address
( Address
, AddressWithNetworkTag(AddressWithNetworkTag)
)
import Plutus.Types.Address
( Address
, AddressWithNetworkTag(AddressWithNetworkTag)
Expand All @@ -68,7 +75,7 @@ import Scripts
, validatorHashBaseAddress
, validatorHashEnterpriseAddress
) as Scripts
import Serialization.Address (NetworkId(MainnetId))
import Serialization.Address (NetworkId(MainnetId), addressBech32)
import Serialization.Address
( Slot(Slot)
, BlockId(BlockId)
Expand All @@ -80,6 +87,8 @@ import Serialization.Address
) as SerializationAddress
import Serialization.Hash (Ed25519KeyHash) as Hash
import Serialization.Hash (ScriptHash)
import Types.Aliases (Bech32String)
import Types.Aliases (Bech32String) as TypeAliases
import Types.ByteArray (ByteArray) as ByteArray
import Types.PubKeyHash
( PaymentPubKeyHash(PaymentPubKeyHash)
Expand Down Expand Up @@ -154,6 +163,20 @@ getNetworkId = wrapContract Address.getNetworkId
-- `module Address`
--------------------------------------------------------------------------------

-- | Convert `Address` to `Bech32String`, using given `NetworkId` to determine
-- | Bech32 prefix.
addressWithNetworkTagToBech32 :: AddressWithNetworkTag -> Bech32String
addressWithNetworkTagToBech32 = fromPlutusAddressWithNetworkTag >>>
addressBech32

-- | Convert `Address` to `Bech32String`, using current `NetworkId` provided by
-- | `Contract` configuration to determine the network tag.
addressToBech32 :: forall (r :: Row Type). Address -> Contract r Bech32String
addressToBech32 address = do
networkId <- getNetworkId
pure $ addressWithNetworkTagToBech32
(AddressWithNetworkTag { address, networkId })

-- | Get the `ValidatorHash` with an Plutus `Address`
enterpriseAddressValidatorHash :: Address -> Maybe ValidatorHash
enterpriseAddressValidatorHash =
Expand Down
2 changes: 2 additions & 0 deletions src/Contract/PlutusData.purs
Expand Up @@ -9,6 +9,7 @@ module Contract.PlutusData
, module Datum
, module ExportQueryM
, module Hashing
, module IsData
, module PlutusData
, module Redeemer
, module FromData
Expand Down Expand Up @@ -95,6 +96,7 @@ import Types.Redeemer
, redeemerHash
, unitRedeemer
) as Redeemer
import IsData (class IsData) as IsData

-- | Get a `PlutusData` given a `DatumHash`.
getDatumByHash
Expand Down
36 changes: 16 additions & 20 deletions src/Contract/ScriptLookups.purs
Expand Up @@ -18,8 +18,7 @@ import Prelude
import Contract.Monad (Contract, wrapContract)
import Data.Either (Either, hush)
import Data.Maybe (Maybe)
import FromData (class FromData)
import ToData (class ToData)
import IsData (class IsData)
import Types.ScriptLookups
( MkUnbalancedTxError
( TypeCheckFailed
Expand Down Expand Up @@ -67,24 +66,21 @@ import Types.ScriptLookups
) as ScriptLookups
import Types.ScriptLookups (mkUnbalancedTx) as SL
import Types.TxConstraints (TxConstraints)
import Types.TypedValidator
( class DatumType
, class RedeemerType
)
import Types.TypedValidator (class ValidatorTypes)

-- | Create an `UnattachedUnbalancedTx` given `ScriptLookups` and
-- | `TxConstraints`. You will probably want to use this version as it returns
-- | datums and redeemers that require attaching (and maybe reindexing) in
-- | a separate call. In particular, this should be called in conjuction with
-- | `balanceAndSignTx`.
mkUnbalancedTx
:: forall (r :: Row Type) (a :: Type) (b :: Type)
. DatumType a b
=> RedeemerType a b
=> FromData b
=> ToData b
=> ScriptLookups.ScriptLookups a
-> TxConstraints b b
:: forall (r :: Row Type) (validator :: Type) (datum :: Type)
(redeemer :: Type)
. ValidatorTypes validator datum redeemer
=> IsData datum
=> IsData redeemer
=> ScriptLookups.ScriptLookups validator
-> TxConstraints redeemer datum
-> Contract r
( Either
ScriptLookups.MkUnbalancedTxError
Expand All @@ -94,12 +90,12 @@ mkUnbalancedTx lookups = wrapContract <<< SL.mkUnbalancedTx lookups

-- | Same as `mkUnbalancedTx` but hushes the error.
mkUnbalancedTxM
:: forall (r :: Row Type) (a :: Type) (b :: Type)
. DatumType a b
=> RedeemerType a b
=> FromData b
=> ToData b
=> ScriptLookups.ScriptLookups a
-> TxConstraints b b
:: forall (r :: Row Type) (validator :: Type) (datum :: Type)
(redeemer :: Type)
. ValidatorTypes validator datum redeemer
=> IsData datum
=> IsData redeemer
=> ScriptLookups.ScriptLookups validator
-> TxConstraints redeemer datum
-> Contract r (Maybe ScriptLookups.UnattachedUnbalancedTx)
mkUnbalancedTxM lookups = map hush <<< mkUnbalancedTx lookups
9 changes: 9 additions & 0 deletions src/IsData.purs
@@ -0,0 +1,9 @@
module IsData (class IsData) where

import FromData (class FromData)
import ToData (class ToData)

class IsData :: Type -> Constraint
class (FromData a, ToData a) <= IsData a

instance (FromData a, ToData a) => IsData a

0 comments on commit c7161c2

Please sign in to comment.