diff --git a/ccip/devenv/README.md b/ccip/devenv/README.md index 92a25692f..e94f9821c 100644 --- a/ccip/devenv/README.md +++ b/ccip/devenv/README.md @@ -1,500 +1,56 @@ # devenv -The `devenv` package contains the needed implementations needed to properly spin up a CCIP -environment that includes Canton. +The `devenv` package provides helpers and bindings to spin up a CCIP environment that includes Canton. It is used by three independent test suites: **E2E**, **Integration**, and **Load**. ## Prerequisites -Make sure to checkout [chainlink-ccv](https://github.com/smartcontractkit/chainlink-ccv/) in a parallel directory, -i.e. so that chainlink-canton and chainlink-ccv have the same parent directory. This is needed so that we can build -needed docker images. +* Checkout the companion repository **[chainlink-ccv](https://github.com/smartcontractkit/chainlink-ccv/)** in a sibling directory so that `chainlink-canton` and `chainlink-ccv` share the same parent. This is required to build the Docker images used by the tests. -## Spin up an environment - -The quickest way to run the EVM/Canton environment is from the root of the repo: +## Spin up a local environment ```bash -# From chainlink-canton root +# From the repository root make start-devenv ``` -This will build all the necessary docker images from chainlink-ccv and chainlink-canton, and spin up an environment with -both EVM and Canton chains. - -## Run a test - -After the environment is spun up, you can run a test like: - -```bash -# From repo root -make run-e2e-tests -``` - -### E2E environment selection (`-ccip-env` / `CCIP_ENV`) - -Message e2e tests run against **local devenv** by default or **Canton TestNet + Sepolia** when prod-testnet is selected. Set the environment by name (not TOML path): - -| Value | Config file | Remote | -|---|---|---| -| `devenv` (default) | [`env-canton-evm-out.toml`](./env-canton-evm-out.toml) | no | -| `prod-testnet` | [`env-prod-testnet-out.toml`](./env-prod-testnet-out.toml) | yes | +This builds the Docker images from `chainlink-ccv` and `chainlink-canton` and starts a local EVM + Canton network. -Use the `-ccip-env` flag or `CCIP_ENV` env var (flag wins if both are set): +## Test suites -```bash -# Local devenv (default) -cd ccip/devenv/tests/e2e && go test -v -run 'TestCanton2EVM_Basic/EOA' -count=1 - -# Prod testnet -CCIP_ENV=prod-testnet \ - CANTON_GRPC_URL=... CANTON_PARTY_ID=... CANTON_AUTH_*=... \ - PRIVATE_KEY=... \ - go test -timeout 8m -v -count=1 -ccip-env=prod-testnet \ - -run 'TestEVM2Canton_Basic/message|TestCanton2EVM_Basic/EOA' -``` +The repository contains three test suites. Each suite has its own README with detailed usage instructions: -**Prod prerequisites** +* **[E2E tests](tests/e2e/README.md)** – end‑to‑end message‑flow tests. +* **[Integration tests](tests/integration/README.md)** – connectivity and smoke‑test checks. +* **[Load tests](tests/load/README.md)** – performance / stress tests using WASP. -- Canton party wallet funded with at least **50 Amulet units** (message fee) -- Canton auth env vars (`CANTON_GRPC_URL`, `CANTON_PARTY_ID`, `CANTON_AUTH_*`) — see [Prod testnet connection smoke test](#prod-testnet-connection-smoke-test) -- Sepolia gas via `PRIVATE_KEY` (EVM sender / receiver for prod runs) +All suites share a common environment selector (`-ccip-env` / `CCIP_ENV`). The supported environments are: -**Optional instance ID overrides** (defaults: `test-router`, `e2e-ccipsender`, `e2e-receiver`): +| Value | Config file | Remote | +|----------------------|---------------------------------|--------| +| `devenv` (default) | `env-canton-evm-out.toml` | no | +| `prod-testnet` | `env-prod-testnet-out.toml` | yes | +| `mainnet` (future) | – | – | -| Env var | Default | -|---|---| -| `CANTON_ROUTER_INSTANCE_ID` | `test-router` | -| `CANTON_SENDER_INSTANCE_ID` | `e2e-ccipsender` | -| `CANTON_RECEIVER_INSTANCE_ID` | `e2e-receiver` | +Use the flag `-ccip-env=` or the environment variable `CCIP_ENV` (the flag takes precedence) to choose the target. -Token e2e: **EVM→Canton token** and **Canton→EVM token** are supported on prod-testnet (see [EVM→Canton token e2e (prod-testnet)](#evm→canton-token-e2e-prod-testnet) and [Canton→EVM token e2e (prod-testnet)](#canton→evm-token-e2e-prod-testnet)). A second prod run reuses existing router/sender/receiver contracts on ledger when instance IDs match. +## Ledger bindings (`-tags=prodledger`) -### Ledger bindings (`-tags=prodledger`) +When targeting production contracts, build the tests with the tag `-tags=prodledger` so they use the older `bindings/generated/v1_0_0` layout. The default builds use `bindings/generated/latest`. -Devenv tests compile against `bindings/generated/latest` (current dev DAML module layout). Prod-testnet/mainnet contracts on ledger still use the older layout in `bindings/generated/v1_0_0`. - -Build prod-targeting tests with `-tags=prodledger` so devenv code resolves the correct template IDs (e.g. `CCIP.PerPartyRouter` vs `CCIP.RuntimeV1.PerPartyRouter`). Devenv runs omit the tag. - -| Target | Build tag | Bindings | -|---|---|---| -| devenv (default) | _(none)_ | `bindings/generated/latest` | -| prod-testnet / mainnet | `-tags=prodledger` | `bindings/generated/v1_0_0` | +| Target | Build tag | Bindings | +|----------------------|--------------------|------------------------| +| `devenv` (default) | _(none)_ | `bindings/generated/latest` | +| `prod-testnet` / `mainnet` | `-tags=prodledger` | `bindings/generated/v1_0_0` | Implementation lives in [`ccip/devenv/ledgertarget/`](./ledgertarget/). -## Load tests - -Load tests live in `ccip/devenv/tests/load`. They use [WASP](https://pkg.go.dev/github.com/smartcontractkit/chainlink-testing-framework/wasp) and run sequentially (RPS=1) because Canton holdings are single-flight. - -### Canton → EVM load - -Sequential Canton→EVM messages round-robined across every EVM destination in the env file. - -**Devenv** (requires `make start-devenv` so `ccip/devenv/env-canton-evm-out.toml` exists): pre-mints fee holdings and calls `SetupSend` once before WASP starts. Full send + EVM exec confirmation per message. - -Schedule is configured via env vars (defaults are `1/10s` for 90s): - -| Env var | Form | Default | Meaning | -|---|---|---|---| -| `CANTON_LOAD_MESSAGE_RATE` | `/` (e.g. `1/10s`, `1/20s`, `10/5m`) | `1/10s` | rate per rate-limit window | -| `CANTON_LOAD_DURATION` | Go duration (e.g. `90s`, `10m`, `1h`) | `90s` | total runtime | - -Example — 1 message every 20 seconds for 10 minutes: - -```bash -CANTON_LOAD_MESSAGE_RATE=1/20s CANTON_LOAD_DURATION=10m make run-canton2evm-load -``` - -```bash -# From repo root -make run-canton2evm-load -``` - -Equivalent: - -```bash -cd ccip/devenv/tests/load && go test -timeout 15m -v -count 1 -run '^TestCanton2EVM_Load$' -``` - -If the out file is missing the test skips with a hint. - -**Prod-testnet** (Canton TestNet + Sepolia): send-only message load — Canton send + confirm send, no `ConfirmExecOnDest` on EVM (executor not available on prod). Set `CANTON_LOAD_SKIP_EXEC_CONFIRM=true`. Verify delivery via indexer/CCIP ops; the test does not assert EVM execution. - -Prerequisites: `CANTON_GRPC_URL`, `CANTON_PARTY_ID`, `CANTON_AUTH_*`, `PRIVATE_KEY` (EVM message receiver wallet), and a pre-funded Canton party (~50 Amulet per message at `CantonToEVMFeeAmount`). - -```bash -CCIP_ENV=prod-testnet \ -CANTON_LOAD_SKIP_EXEC_CONFIRM=true \ -CANTON_GRPC_URL=... CANTON_PARTY_ID=... CANTON_AUTH_CLIENT_ID=... CANTON_AUTH_CLIENT_SECRET=... \ -PRIVATE_KEY=0x... \ -CANTON_LOAD_MESSAGE_RATE=1/10s \ -CANTON_LOAD_DURATION=5m \ -go test -timeout 30m -v -count=1 -ccip-env=prod-testnet \ - -run '^TestCanton2EVM_Load$' ./ccip/devenv/tests/load/ -``` - -Or from repo root: - -```bash -make run-canton2evm-load-prod -``` - -### Canton → EVM token load - -Separate test from message-only load: `TestCanton2EVM_TokenLoad`. Resolves the token lane declared in [`token_transfer_config.toml`](./tests/token_transfer_config.toml) (see [Token lane configuration](#token-lane-configuration)) against the source chain's `GetTokenTransferConfigs`, validating every destination has the lane. Runs WASP with full exec confirm; on devenv, asserts EVM receiver token balance delta. - -**Devenv** (requires running devenv + `env-canton-evm-out.toml`): pre-mints Canton fee + transfer holdings via `SetupCantonTokenSend`. - -```bash -make run-canton2evm-token-load -``` - -Equivalent: - -```bash -cd ccip/devenv/tests/load && go test -timeout 20m -v -count 1 -run '^TestCanton2EVM_TokenLoad$' -``` - -**Prod-testnet** (Canton TestNet + Sepolia): full per-message confirmation (send → receipt on EVM → `ConfirmExecOnDest`). Pre-fund the Canton party before the run: - -- **Amulet** for CCIP fees: `estimatedMessages × 130` (see `CantonToEVMTokenTransferFeeAmount`) -- **LINK** (`link-token`): `estimatedMessages × 100` fixed-point per send (from `[prod-testnet.canton_to_evm]` `transfer_amount`) - -Also set `PRIVATE_KEY` to a Sepolia wallet with ETH for execution gas on the EVM receiver. The test logs EVM receiver balance before/after but does **not** assert balance on prod — verify delivery via indexer/CCIP ops. - -```bash -CCIP_ENV=prod-testnet \ -CANTON_GRPC_URL=... CANTON_PARTY_ID=... CANTON_AUTH_CLIENT_ID=... CANTON_AUTH_CLIENT_SECRET=... \ -PRIVATE_KEY=0x... \ -CANTON_LOAD_MESSAGE_RATE=1/30s \ -CANTON_LOAD_DURATION=5m \ -CANTON_CONFIRM_EXEC_TIMEOUT=10m \ -go test -timeout 30m -v -count=1 -ccip-env=prod-testnet \ - -run '^TestCanton2EVM_TokenLoad$' ./ccip/devenv/tests/load/ -``` - -Or from repo root: - -```bash -make run-canton2evm-token-load-prod -``` - -### EVM → Canton load - -Sequential EVM→Canton messages against the Canton destination in the env file. Uses the same schedule env vars as Canton→EVM (`CANTON_LOAD_MESSAGE_RATE`, `CANTON_LOAD_DURATION`). - -**Devenv** (requires running devenv + `env-canton-evm-out.toml`): EVM accounts are pre-funded by devenv; no Canton pre-mint. - -Example — 1 message every 20 seconds for 10 minutes: - -```bash -CANTON_LOAD_MESSAGE_RATE=1/20s CANTON_LOAD_DURATION=10m make run-evm2canton-load -``` - -```bash -# From repo root -make run-evm2canton-load -``` - -Equivalent: - -```bash -cd ccip/devenv/tests/load && go test -timeout 15m -v -count 1 -run '^TestEVM2Canton_Load$' -``` - -**Prod-testnet** (Canton TestNet + Sepolia): message-only load with full per-message confirmation (send → receipt on EVM → `ConfirmExecOnDest` on Canton). Use a conservative rate — each iteration is synchronous (~30–60s end-to-end on prod), so WASP RPS=1 is effectively bounded by confirm latency. Budget for Sepolia gas per send plus Canton execution fees. Token load is also supported on prod (see [Canton → EVM token load](#canton--evm-token-load) and [EVM → Canton token load](#evm--canton-token-load)). - -Prerequisites: `CANTON_GRPC_URL`, `CANTON_PARTY_ID`, `CANTON_AUTH_*`, `PRIVATE_KEY` (Sepolia sender/receiver wallet), and a pre-funded Canton party. - -```bash -CCIP_ENV=prod-testnet \ -CANTON_GRPC_URL=... CANTON_PARTY_ID=... CANTON_AUTH_CLIENT_ID=... CANTON_AUTH_CLIENT_SECRET=... \ -PRIVATE_KEY=0x... \ -CANTON_LOAD_MESSAGE_RATE=1/30s \ -CANTON_LOAD_DURATION=5m \ -CANTON_CONFIRM_EXEC_TIMEOUT=10m \ -go test -timeout 45m -v -count=1 -ccip-env=prod-testnet \ - -run '^TestEVM2Canton_Load$' ./ccip/devenv/tests/load/ -``` - -Or from repo root: - -```bash -make run-evm2canton-load-prod -``` - -### EVM → Canton token load - -Separate test from message-only load: `TestEVM2Canton_TokenLoad`. Resolves the token lane declared in [`token_transfer_config.toml`](./tests/token_transfer_config.toml) (see [Token lane configuration](#token-lane-configuration)), logs EVM sender balance vs estimated transfer need, runs WASP, logs Canton holdings post-run. - -**Devenv** (requires running devenv + `env-canton-evm-out.toml`): EVM sender is pre-funded by devenv. - -```bash -make run-evm2canton-token-load -``` - -Equivalent: - -```bash -cd ccip/devenv/tests/load && go test -timeout 20m -v -count 1 -run '^TestEVM2Canton_TokenLoad$' -``` - -**Prod-testnet** (Canton TestNet + Sepolia): full per-message confirmation. Fund `PRIVATE_KEY` wallet with: - -- **TEST** ERC-20 tokens on Sepolia: `estimatedMessages × transfer_amount` from `[prod-testnet.evm_to_canton]` (default `1000000000000000000` wei per send) -- **Sepolia ETH** for send and possible router approve tx - -Also ensure the Canton party is funded for execution fees. - -```bash -CCIP_ENV=prod-testnet \ -CANTON_GRPC_URL=... CANTON_PARTY_ID=... CANTON_AUTH_CLIENT_ID=... CANTON_AUTH_CLIENT_SECRET=... \ -PRIVATE_KEY=0x... \ -CANTON_LOAD_MESSAGE_RATE=1/30s \ -CANTON_LOAD_DURATION=5m \ -CANTON_CONFIRM_EXEC_TIMEOUT=10m \ -go test -timeout 45m -v -count=1 -ccip-env=prod-testnet \ - -run '^TestEVM2Canton_TokenLoad$' ./ccip/devenv/tests/load/ -``` - -Or from repo root: - -```bash -make run-evm2canton-token-load-prod -``` - -### EVM→Canton token e2e (prod-testnet) - -Single EVM→Canton token transfer end-to-end on Canton TestNet + Sepolia via `TestEVM2Canton_Basic/token_transfer`. - -**Prerequisites** - -- `CCIP_ENV=prod-testnet` and `CCIP_CONFIG_FILE=env-prod-testnet.ci.toml` (or local `env-prod-testnet-out.toml`) -- Canton auth env vars (`CANTON_GRPC_URL`, `CANTON_PARTY_ID`, `CANTON_AUTH_*`) — see [Prod testnet connection smoke test](#prod-testnet-connection-smoke-test) -- `PRIVATE_KEY` wallet funded with: - - **TEST** ERC-20 tokens on Sepolia (≥ `transfer_amount` from `[prod-testnet.evm_to_canton]` in `token_transfer_config.toml`, default `1000000000000000000`) - - **Sepolia ETH** for send and possible router approve tx -- Canton party wallet funded for execution fees - -```bash -CCIP_ENV=prod-testnet \ -CCIP_CONFIG_FILE=env-prod-testnet.ci.toml \ -CANTON_GRPC_URL=... CANTON_PARTY_ID=... CANTON_AUTH_*=... \ -PRIVATE_KEY=0x... \ -CANTON_CONFIRM_EXEC_TIMEOUT=10m \ -make run-evm2canton-token-e2e-prod -``` - -Equivalent: - -```bash -cd ccip/devenv/tests/e2e && go test -timeout 15m -v -count=1 \ - -ccip-env=prod-testnet \ - -run '^TestEVM2Canton_Basic$/^token_transfer$' -``` - -### Canton→EVM token e2e (prod-testnet) - -Two sequential Canton→EVM LINK transfers end-to-end on Canton TestNet + Sepolia via `TestCanton2EVM_Basic/EOA receiver and default committee verifier token transfer`. - -**Prerequisites** - -- `CCIP_ENV=prod-testnet` and `CCIP_CONFIG_FILE=env-prod-testnet.ci.toml` (or local `env-prod-testnet-out.toml`) -- Canton auth env vars (`CANTON_GRPC_URL`, `CANTON_PARTY_ID`, `CANTON_AUTH_*`) — see [Prod testnet connection smoke test](#prod-testnet-connection-smoke-test) -- Canton party wallet funded with: - - **Amulet** for CCIP fees (≥ `260` for 2 sends at `CantonToEVMTokenTransferFeeAmount` = 130) - - **LINK** on Canton (`link-token`, ≥ `200` fixed-point for 2 sends at `transfer_amount` = `"100"` from `[prod-testnet.canton_to_evm]`) -- `PRIVATE_KEY` wallet on Sepolia with ETH for execution gas on the EVM receiver - -```bash -CCIP_ENV=prod-testnet \ -CCIP_CONFIG_FILE=env-prod-testnet.ci.toml \ -CANTON_GRPC_URL=... CANTON_PARTY_ID=... CANTON_AUTH_*=... \ -PRIVATE_KEY=0x... \ -CANTON_CONFIRM_EXEC_TIMEOUT=10m \ -make run-canton2evm-token-e2e-prod -``` - -Equivalent: - -```bash -cd ccip/devenv/tests/e2e && go test -timeout 15m -v -count=1 \ - -ccip-env=prod-testnet \ - -run '^TestCanton2EVM_Basic$/^EOA receiver and default committee verifier token transfer$' -``` - -Expected EVM receiver token delta: `2 × 100000000000` wei TEST (= `2 × 0.0000001` LINK at `transfer_amount` = `"100"` fixed-point). - -### Token lane configuration - -Token transfer tests (both e2e and load) declare the token lane to send in [`tests/token_transfer_config.toml`](./tests/token_transfer_config.toml). The test resolves the declared **token pool identity** against the source chain's `GetTokenTransferConfigs`, then validates that every destination chain has that lane configured before running. On prod-testnet, when combo discovery fails (simple `TEST`/`LINK` qualifiers), the resolver falls back to direct datastore lookups using `remote_pool_*` fields. - -Env selection follows `-ccip-env` / `CCIP_ENV` (same as the CCIP harness). Override the entire file path with `CANTON_TOKEN_TEST_CONFIG`: - -```bash -CANTON_TOKEN_TEST_CONFIG=/path/to/custom.toml make run-canton2evm-token-load -``` - -The file uses env-keyed sections (`[devenv.*]`, `[prod-testnet.*]`) with one block per direction (`evm_to_canton`, `canton_to_evm`): - -| Key | Required | Meaning | -|---|---|---| -| `pool_type` | yes | token pool contract type on the source chain (e.g. `BurnMintTokenPool`, `LockReleaseTokenPool`) | -| `pool_version` | yes | semantic version of the token pool (e.g. `2.0.0`) | -| `pool_qualifier` | yes | datastore qualifier identifying the exact pool | -| `transfer_amount` | no | per-message token amount; **integer wei** for `evm_to_canton`, **integer 10^10 fixed-point** for `canton_to_evm` (see below) | -| `execution_gas_limit` | no | per-message execution gas limit; falls back to a per-direction default | -| `finality_config` | no | per-message finality config; falls back to a per-direction default | -| `remote_pool_type` | prod fallback | remote pool type for prod datastore fallback | -| `remote_pool_version` | prod fallback | remote pool version for prod datastore fallback | -| `remote_pool_qualifier` | prod fallback | remote pool qualifier for prod datastore fallback | -| `transfer_instrument_id` | Canton→EVM | Canton transfer instrument when Canton is source (e.g. `LINK`; defaults to Amulet) | - -**`transfer_amount` format by direction** - -| Direction | Format | Example | Sent amount | -|---|---|---|---| -| `evm_to_canton` | Integer **wei** | `"100000000000"` | `100000000000` wei on Sepolia TEST | -| `canton_to_evm` | Integer **10^10 fixed-point** | `"1000"` | `0.0000001` LINK (`1000` fixed-point units) | -| `canton_to_evm` send boundary | same fixed-point in `TokenAmount.Amount` | `"1000"` | passed directly to `TokenAmount.Amount` | -| `canton_to_evm` EVM assert | `fixedPoint × 10^8` wei | `1000` → `100000000000` wei | matches reciprocal EVM→Canton | - -Fixed-point scale: `1000000000` = `0.1` LINK, `1000` = `0.0000001` LINK (same 10-decimal scale as Canton NUMERIC). - -Token **identity** (`pool_type` / `pool_version` / `pool_qualifier`) is always required and is never defaulted; only the numeric send params have code-level fallbacks. If the qualifier matches zero or multiple pools, or a destination lacks the lane, the test fails fast listing the available pool refs / selectors. - -```toml -[devenv.evm_to_canton] -pool_type = "BurnMintTokenPool" -pool_version = "2.0.0" -pool_qualifier = "TEST (BurnMintTokenPool 2.0.0 [default], LockReleaseTokenPool 2.0.0 [default])::BurnMintTokenPool 2.0.0 [default]" -transfer_amount = "100000000000" -execution_gas_limit = 200000 -finality_config = 1 - -[prod-testnet.evm_to_canton] -pool_type = "BurnMintTokenPool" -pool_version = "2.0.0" -pool_qualifier = "TEST" -transfer_amount = "1000000000000000000" -execution_gas_limit = 200000 -finality_config = 1 -remote_pool_type = "BurnMintTokenPool" -remote_pool_version = "2.0.0" -remote_pool_qualifier = "LINK" - -[prod-testnet.canton_to_evm] -pool_type = "BurnMintTokenPool" -pool_version = "2.0.0" -pool_qualifier = "LINK" -transfer_amount = "100" -execution_gas_limit = 500000 -finality_config = 1 -remote_pool_type = "BurnMintTokenPool" -remote_pool_version = "2.0.0" -remote_pool_qualifier = "TEST" -transfer_instrument_id = "link-token" -``` - -### CI (on demand) - -Load tests use the composite action (`.github/actions/ccip-load-test`) from **CCIP Canton Load Tests** (`ccip-load-tests.yml`) via manual `workflow_dispatch`. - -#### Manual workflow (`workflow_dispatch`) - -| Input | Default (devenv) | Default (prod-testnet) | Maps to | -|---|---|---|---| -| `ccip_env` | `devenv` | select `prod-testnet` | `-ccip-env` | -| `direction` | `canton2evm` | same | test `-run` regex | -| `message_rate` | `1/10s` | `1/10s` | `CANTON_LOAD_MESSAGE_RATE` | -| `load_duration` | `90s` | `2m` (when left at devenv default) | `CANTON_LOAD_DURATION` | -| `test_timeout` | `40m` | `30m` / `45m` for evm2canton or evm2canton-token | `go test -timeout` | -| `config_file` | — | `env-prod-testnet.ci.toml` | `CCIP_CONFIG_FILE` | -| `skip_exec_confirm` | `false` | `true` for canton2evm only; `false` for token + evm2canton | `CANTON_LOAD_SKIP_EXEC_CONFIRM` | -| `confirm_exec_timeout` | — | `10m` | `CANTON_CONFIRM_EXEC_TIMEOUT` | - -**Devenv** spins up Docker via `setup-ccip-devenv` (same as CCIP E2E). **Prod-testnet** hits live Canton TestNet + Sepolia with no local devenv. - -#### Prod-testnet config file - -`*-out.toml` files are gitignored (local devenv output). CI uses the committed snapshot [`env-prod-testnet.ci.toml`](./env-prod-testnet.ci.toml) instead. Override with `config_file=` or `CCIP_CONFIG_FILE` when running locally: - -```bash -CCIP_CONFIG_FILE=env-prod-testnet.ci.toml CCIP_ENV=prod-testnet go test ... -``` - -#### GitHub secrets (prod-testnet CI) - -Workflows pass these secrets to `.github/actions/ccip-load-test` using the same names (1:1); the composite action maps them to test env vars: - -| Secret (input name) | Env var | -|---|---| -| `CANTON_OKTA_AUTHORIZER_TESTNET` | `CANTON_AUTH_URL` | -| `CANTON_OKTA_CLIENT_ID_TESTNET` | `CANTON_CLIENT_ID` | -| `CANTON_OKTA_CLIENT_SECRET_TESTNET` | `CANTON_CLIENT_SECRET` | -| `CCIP_PROD_TESTNET_PRIVATE_KEY` | `PRIVATE_KEY` | - -Devenv secrets unchanged: `CCV_IAM_ROLE`, `JD_REGISTRY`, `JD_IMAGE`. - -chainlink-ccv is pinned in `.github/actions/setup-ccip-devenv` (devenv only). - ## Shortcut -If you want to build docker images, spin up a new env, and run the test in a single command, the following is useful: +To build the Docker images, start the environment, and run the E2E suite in one step: ```bash -# From repo root +# From the repository root make build-run-e2e-tests ``` -## Prod testnet connection smoke test - -Minimal Canton-only connectivity check against real testnet infrastructure. Uses [`env-prod-testnet-out.toml`](./env-prod-testnet-out.toml) (Canton TestNet ↔ Sepolia message lane: contract refs, indexer URLs, EDS URL, verifier issuers); auth secrets and party identity come from environment variables. - -The test is opt-in: it skips unless `CANTON_GRPC_URL` is set (even though the TOML file includes default URLs). This keeps CI green while allowing manual or workflow-triggered runs. - -### Environment variables - -| Variable | Required | Notes | -|---|---|---| -| `CANTON_PARTY_ID` | yes | Ledger party to query; skips `GetUser` when set | -| `CANTON_AUTH_URL` | yes | OIDC issuer | -| `CANTON_CLIENT_ID` | yes | OAuth2 client ID | -| `CANTON_AUTH_TYPE` | no | `authorizationCode` (local), `clientCredentials` (CI), `static`, `insecureStatic` | -| `CANTON_USER_ID` | no | Required for `clientCredentials`; optional for `authorizationCode` (extracted from token `sub` after login) | -| `CANTON_CLIENT_SECRET` | CI only | Required when `CANTON_AUTH_TYPE=clientCredentials` | -| `CANTON_JWT` | static only | For `static` / `insecureStatic` auth | -| `CANTON_GRPC_URL` | opt-in signal | Must be set to run the test; overrides TOML gRPC URL | -| `CANTON_VALIDATOR_API_URL` | no | Overrides TOML validator API URL | - -**Local (browser Okta login):** - -```bash -export CANTON_GRPC_URL='testnet.cv1.bcy-v.metalhosts.com:443' -export CANTON_PARTY_ID='u_0e0328cbbcb7::1220c250c23c55120f7c758bccc5cbc739629015ab921594e1c29656981f985bffa7' -export CANTON_AUTH_URL='https://smartcontract.okta.com/oauth2/austsuml9q2WhPBMM5d7' -export CANTON_CLIENT_ID='0oau1l22b1Jv3dcih5d7' -export CANTON_AUTH_TYPE='authorizationCode' -``` - -**CI (`clientCredentials`, no browser):** - -```bash -export CANTON_GRPC_URL='testnet.cv1.bcy-v.metalhosts.com:443' -export CANTON_PARTY_ID='...' -export CANTON_AUTH_URL='https://smartcontract.okta.com/oauth2/austsuml9q2WhPBMM5d7' -export CANTON_CLIENT_ID='0oau1l22b1Jv3dcih5d7' -export CANTON_AUTH_TYPE='clientCredentials' -export CANTON_CLIENT_SECRET='...' -export CANTON_USER_ID='...' -``` - -### Run command - -```bash -cd ccip/devenv/tests/integration && go test -v -run TestIntegration_CantonProdTestnet_Connection -count=1 -``` - -Use `-ccip-env=prod-testnet` (or `CCIP_ENV=prod-testnet`) so the test loads `env-prod-testnet-out.toml` locally, or `CCIP_CONFIG_FILE=env-prod-testnet.ci.toml` for the committed CI snapshot; default devenv config is `env-canton-evm-out.toml` via `-ccip-env=devenv`. - -The test connects via `NewCLDF`, asserts `PartyID` is set, and lists holdings for the party (empty balance is OK). +For detailed usage of each test suite, follow the links above. diff --git a/ccip/devenv/env-prod-testnet.ci.toml b/ccip/devenv/env-prod-testnet.ci.toml index 46d47c6f5..63996b455 100644 --- a/ccip/devenv/env-prod-testnet.ci.toml +++ b/ccip/devenv/env-prod-testnet.ci.toml @@ -7,9 +7,9 @@ indexer_endpoints = [ [cldf] addresses = [ '[{"address":"0x181Ac7dC295f1C8C87342d07CFaBA90bC477DB5d","chainSelector":16015286601757825753,"labels":[],"qualifier":"","type":"OnRamp","version":"2.0.0"},{"address":"0xc6A246A9AcdAaE651708706494720F79C3E5d0A1","chainSelector":16015286601757825753,"labels":[],"qualifier":"","type":"OffRamp","version":"2.0.0"},{"address":"0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59","chainSelector":16015286601757825753,"labels":[],"qualifier":"0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59-Router","type":"Router","version":"1.2.0"},{"address":"0x8632C3025FAFdD85A299211FD5838b5fBE2df816","chainSelector":16015286601757825753,"labels":[],"qualifier":"","type":"FeeQuoter","version":"2.0.0"},{"address":"0x8f3ee3c77D2B27c32306a89D367654F959Db223D","chainSelector":16015286601757825753,"labels":[],"qualifier":"default","type":"CommitteeVerifierResolver","version":"2.0.0"},{"address":"0x66f9E0738a4a6fe54aE62DEd00Ca1F72bDecc092","chainSelector":16015286601757825753,"labels":[],"qualifier":"default","type":"ExecutorProxy","version":"2.0.0"},{"address":"0x78874Df86F55b207d5584093C17B59220E8c6B15","chainSelector":16015286601757825753,"labels":[],"qualifier":"default","type":"Executor","version":"2.0.0"},{"address":"0x097D90c9d3E0B50Ca60e1ae45F6A81010f9FB534","chainSelector":16015286601757825753,"labels":[],"qualifier":"","type":"WETH9","version":"1.0.0"},{"address":"0x779877A7B0D9E8603169DdbD7836e478b4624789","chainSelector":16015286601757825753,"labels":[],"qualifier":"","type":"LinkToken","version":"1.0.0"},{"address":"0x5185b41F1588FC8C541360709C992794925D484C","chainSelector":16015286601757825753,"labels":[],"qualifier":"TEST","type":"BurnMintTokenPool","version":"2.0.0"},{"address":"0xeEe6675b20fE5950eb51361b93021D076289F612","chainSelector":16015286601757825753,"labels":[],"qualifier":"TEST","type":"BurnMintERC20WithDrip","version":"1.5.0"},{"address":"0xA0a507CE0709D3D40F71166c730a860aa29f3491","chainSelector":16015286601757825753,"labels":[],"qualifier":"TEST","type":"AdvancedPoolHooks","version":"2.0.0"}]', - '[{"address":"0x03519eac48d545c4d0ecdc3e3022e443d9e878867827eecb37d5e5a60ae0c989","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"OnRamp","version":"2.0.0"},{"address":"0xd03375cb15a7179bfbfa7cfb843250a6b26e4ddc7a4c30e7bc1777cf3afbf580","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"OffRamp","version":"2.0.0"},{"address":"0x18a00775f6781bfe3032d1b9ceeee225416fb36143218969818df860cb1e29c6","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"FeeQuoter","version":"2.0.0"},{"address":"0x45f673fb23aa33fdaa07e7ae7ea0d37218bcff8e63575a5582a2356cbfaa8883","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"CantonGlobalConfig","version":"2.0.0"},{"address":"0x665cfb57b33b2b74383e99af880ff7d967ad85ef5966ac912f9a3cd4faaee5f9","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"CantonPerPartyRouterFactory","version":"2.0.0"},{"address":"0xe8df5a00cc82df74bae0ca2a032e4d5a7fb5424c1194c4036caabe5fd9f40f81","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"TokenAdminRegistry","version":"2.0.0"},{"address":"0xd4678dbaa95efbf0631162b4fbbae08bf42bbde19038692fec94b6f6b5528fec","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"RMNRemote","version":"2.0.0"},{"address":"0xec1e288bcf8bbf034ac2d31b67f9b15a3f1f828d086c5b9d8fc2866129cd02fe","chainSelector":9268731218649498074,"labels":["committeeverifier-tqkny@ccvOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"default","type":"CommitteeVerifier","version":"2.0.0"},{"address":"0x4b19a247edd769630a136840ef703bd55ca3eda382317ce4384f2aadd47ddbaa","chainSelector":9268731218649498074,"labels":[],"qualifier":"default","type":"Executor","version":"2.0.0"},{"address":"0x6ac67a53d53ac425440550d27afeb1da16f6d41c224dcd1ed8e9ab1ae20f7ace","chainSelector":9268731218649498074,"labels":["burnminttokenpool-LINK@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"LINK","type":"BurnMintTokenPool","version":"2.0.0"},{"address":"0x6ac67a53d53ac425440550d27afeb1da16f6d41c224dcd1ed8e9ab1ae20f7ace","chainSelector":9268731218649498074,"labels":["burnminttokenpool-LINK@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"LINK","type":"CantonBurnMintTokenPool","version":"2.0.0"},{"address":"39c6212f2291365bed155b2533abbd7ddf340498ccbbb358cee07b83106986ad","chainSelector":9268731218649498074,"labels":["instrument-admin:ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551","instrument-id:link-token","ccip-owner:ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"LINK","type":"Token","version":"2.0.0"},{"address":"0x576182aab988a0804a1aa13081902c076ed6108c1162a04b3e971e871a608527","chainSelector":9268731218649498074,"labels":["linkregistry@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"","type":"LinkRegistry","version":"2.0.0"}]', + '[{"address":"0xfc8e207fed38a5350b77320b38c4cb068fec9968b4b5bf9ad24cb25ad23a4c3e","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"OnRamp","version":"2.0.0"},{"address":"0xae98b0b310e418c3e8290f7ead586220379c957b7f046eda61990b610c0a70f3","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"OffRamp","version":"2.0.0"},{"address":"0x90541f4566ffd75ee426c816bba807c2fc551b80d96acc40872a360e8772330b","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"FeeQuoter","version":"2.0.0"},{"address":"0x1dbb8b5bc06b6f19a8d0bd87442c2482e298cc5d4fe97cafe7c06e237d6356b8","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"CantonGlobalConfig","version":"2.0.0"},{"address":"0xc95b6f77f2a651157f23ad9a86e36a19bb2ea37cfde335d7708c67922b3c33f4","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"CantonPerPartyRouterFactory","version":"2.0.0"},{"address":"0x445db2d90c1c7f882edc5012df2a29c520e1fbe6af2dfe2f576e48b8ae495875","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"TokenAdminRegistry","version":"2.0.0"},{"address":"0x8a0578120edf16ad8d64c1e192a5dbd987dc95181152cf2b944cddbe10d10131","chainSelector":9268731218649498074,"labels":[],"qualifier":"","type":"RMNRemote","version":"2.0.0"},{"address":"0x5b92820da106d238d043eedd1556487af175ec1eacd2bfa10c2962f257958dbc","chainSelector":9268731218649498074,"labels":["committeeverifier-pttst@ccvOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"default","type":"CommitteeVerifier","version":"2.0.0"},{"address":"0x3c692ca3b5b5dbea45e4c303c9247dc79b0046ac088cfe81675c0f24f960a35d","chainSelector":9268731218649498074,"labels":[],"qualifier":"default","type":"Executor","version":"2.0.0"},{"address":"0x5af237c261d7ecdcb2dfa802eb79743664f393f35fcd8e240e55e66436facf04","chainSelector":9268731218649498074,"labels":["burnminttokenpool-LINKv2@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"LINKv2","type":"BurnMintTokenPool","version":"2.0.0"},{"address":"0x5af237c261d7ecdcb2dfa802eb79743664f393f35fcd8e240e55e66436facf04","chainSelector":9268731218649498074,"labels":["burnminttokenpool-LINKv2@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"LINKv2","type":"CantonBurnMintTokenPool","version":"2.0.0"},{"address":"39c6212f2291365bed155b2533abbd7ddf340498ccbbb358cee07b83106986ad","chainSelector":9268731218649498074,"labels":["instrument-admin:ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551","instrument-id:link-token","ccip-owner:ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"LINKv2","type":"Token","version":"2.0.0"},{"address":"0x576182aab988a0804a1aa13081902c076ed6108c1162a04b3e971e871a608527","chainSelector":9268731218649498074,"labels":["linkregistry@ccipOwner::1220e382f4e57b0815e6be737006e381e6b7de448e06bd033ece6df498017879f551"],"qualifier":"","type":"LinkRegistry","version":"2.0.0"}]', ] -env_metadata = "{\"metadata\":{\"cantonConfigs\":{\"eds_url\":\"https://eds.testnet.ccip.chain.link\"},\"offchainConfigs\":{\"indexers\":{\"indexer-1\":{\"verifiers\":[{\"issuerAddresses\":[\"0x8f3ee3c77D2B27c32306a89D367654F959Db223D\",\"0xec1e288bcf8bbf034ac2d31b67f9b15a3f1f828d086c5b9d8fc2866129cd02fe\"],\"name\":\"default-verifier\"}]}}}}}" +env_metadata = "{\"metadata\":{\"cantonConfigs\":{\"eds_url\":\"https://eds.testnet.ccip.chain.link\"},\"offchainConfigs\":{\"indexers\":{\"indexer-1\":{\"verifiers\":[{\"issuerAddresses\":[\"0x8f3ee3c77D2B27c32306a89D367654F959Db223D\",\"0x5b92820da106d238d043eedd1556487af175ec1eacd2bfa10c2962f257958dbc\"],\"name\":\"default-verifier\"}]}}}}}" [[blockchains]] container_name = "ethereum-testnet-sepolia" diff --git a/ccip/devenv/tests/e2e/README.md b/ccip/devenv/tests/e2e/README.md new file mode 100644 index 000000000..95a2f089e --- /dev/null +++ b/ccip/devenv/tests/e2e/README.md @@ -0,0 +1,53 @@ +# E2E test suite + +The **E2E** suite validates end‑to‑end message‑flow behaviour of the CCIP system (Canton ↔ EVM). It exercises the full stack, including contract deployment, message sending, and execution confirmation. + +## Running the suite + +All E2E tests share the common environment selector (`-ccip-env` / `CCIP_ENV`). The supported environments are the same as described in the top‑level README: + +| Value | Config file | Remote | +|----------------------|---------------------------------|--------| +| `devenv` (default) | `env-canton-evm-out.toml` | no | +| `prod-testnet` | `env-prod-testnet-out.toml` | yes | + +### Local `devenv` + +```bash +cd ccip/devenv/tests/e2e +go test -v -count=1 -run 'TestCanton2EVM_Basic/EOA' +``` + +### Remote `prod-testnet` + +```bash +CCIP_ENV=prod-testnet \ + CANTON_GRPC_URL=... CANTON_PARTY_ID=... CANTON_AUTH_CLIENT_ID=... CANTON_AUTH_CLIENT_SECRET=... \ + PRIVATE_KEY=... \ + go test -timeout 8m -v -count=1 -ccip-env=prod-testnet \ + -run 'TestEVM2Canton_Basic/message|TestCanton2EVM_Basic/EOA' +``` + +**Prod‑testnet prerequisites** + +* Canton party wallet funded with at least **50 Amulet** units (message fee). +* Auth environment variables (`CANTON_GRPC_URL`, `CANTON_PARTY_ID`, `CANTON_AUTH_*`). See the [Prod testnet connection smoke test](../README.md#prod-testnet-connection-smoke-test) for details. +* Sepolia gas via `PRIVATE_KEY` (EVM sender/receiver). + +### Optional instance ID overrides + +The following variables can be set to reuse existing router/sender/receiver contracts on the ledger: + +| Variable | Default | +|------------------------------|---------| +| `CANTON_ROUTER_INSTANCE_ID` | `test-router` | +| `CANTON_SENDER_INSTANCE_ID` | `e2e-ccipsender` | +| `CANTON_RECEIVER_INSTANCE_ID` | `e2e-receiver` | + +### Token‑E2E tests (prod‑testnet only) + +Both **EVM → Canton** and **Canton → EVM** token transfers are supported on `prod-testnet`. They use the same command line as above; the test runner will automatically exercise the token lanes defined in `tests/token_transfer_config.toml`. + +--- + +For a one‑step build‑run‑E2E shortcut, see the top‑level README. \ No newline at end of file diff --git a/ccip/devenv/tests/integration/README.md b/ccip/devenv/tests/integration/README.md new file mode 100644 index 000000000..104810e9a --- /dev/null +++ b/ccip/devenv/tests/integration/README.md @@ -0,0 +1,30 @@ +# Integration test suite + +The **Integration** suite provides lightweight checks that verify basic connectivity and configuration of the CCIP stack. It is primarily used as a smoke‑test before running the more exhaustive E2E or Load suites. + +## Running the suite + +All integration tests respect the common environment selector (`-ccip-env` / `CCIP_ENV`). The same environment table from the top‑level README applies. + +### Local `devenv` (default) + +The only integration test currently lives in `curse_detection_test.go` and is named `TestIntegration_SourceReader_CurseDetection`. It runs **only against the local `devenv` environment** because it relies on the in‑process CLDF configuration generated by `make start-devenv`. + +```bash +cd ccip/devenv/tests/integration +go test -v -run TestIntegration_SourceReader_CurseDetection -count=1 +``` + +When executed against the default `devenv` environment the test verifies that the local Canton node is reachable, that the configured party ID is valid, and that the source‑reader curse‑detection logic works as expected. + +### Remote `prod-testnet` + +At the moment there is **no remote integration test** – the curse‑detection test depends on the local CLDF output (`env-canton-evm-out.toml`). If you need to perform a connectivity check against a remote Canton TestNet, use the **Prod testnet connection smoke test** located in the top‑level README. + +--- + +For a quick one‑step verification that also runs the integration test, you can use the shortcut from the top‑level README: + +```bash +make build-run-e2e-tests # runs the full E2E suite, which includes the integration checks +``` \ No newline at end of file diff --git a/ccip/devenv/tests/load/README.md b/ccip/devenv/tests/load/README.md new file mode 100644 index 000000000..7b177fee8 --- /dev/null +++ b/ccip/devenv/tests/load/README.md @@ -0,0 +1,88 @@ +# Load test suite + +The **Load** suite exercises the CCIP system under sustained traffic using the [WASP](https://pkg.go.dev/github.com/smartcontractkit/chainlink-testing-framework/wasp) framework. Tests are run sequentially (RPS = 1) because Canton holdings are single‑flight. + +## Common configuration + +All load tests share two environment variables that control the schedule. They can be overridden on the command line, via `make` variables, or by exporting them in a `.env` file that `make` will automatically source. + +| Variable | Form | Default | +|------------------------------|--------------------------|---------| +| `CANTON_LOAD_MESSAGE_RATE` | `/` (e.g. `1/10s`) | `1/10s` | +| `CANTON_LOAD_DURATION` | Go duration (e.g. `90s`) | `90s` | + +### Using a `.env` file + +Create a file named `.env` in the repository root (or any path you pass to `make ENV_FILE=…`). Example: + +```text +# .env +CCIP_ENV=prod-testnet +CANTON_GRPC_URL=https://... +CANTON_PARTY_ID=… +CANTON_AUTH_CLIENT_ID=… +CANTON_AUTH_CLIENT_SECRET=… +PRIVATE_KEY=0x… +``` + +`make` will automatically load this file before invoking the test target, so you do not need to repeat the variables on the command line. + +## Running the suite + +Each direction has its own make target (defined in the repository root `Makefile`). The commands below assume you are in the repository root. + +### Canton → EVM + +```bash +# Local devenv (default) +make run-canton2evm-load + +# Remote prod‑testnet (send‑only, no EVM exec confirmation) +make run-canton2evm-load-prod +``` + +**Remote `prod-testnet` details** + +* **Address source** – The test reads contract addresses from the output TOML generated by the environment (`env‑prod‑testnet‑out.toml`). This file is produced by `make start-devenv` when the `prod-testnet` configuration is selected, or you can point the test at a custom file via the `CCIP_CONFIG_FILE` environment variable. +* **Authentication** – Set `CANTON_GRPC_URL`, `CANTON_PARTY_ID`, and the `CANTON_AUTH_*` variables (client ID/secret or static JWT). See the [Prod testnet connection smoke test](../README.md#prod-testnet-connection-smoke-test) for a full list. +* **Sepolia wallet** – `PRIVATE_KEY` must hold enough ETH to pay gas for the EVM receiver side. +* **Canton funding** – The Canton party must be funded with at least **50 Amulet** per message (the fee amount). Funding can be done manually via the Canton CLI or by using a helper script if provided. +* **Skipping execution confirmation** – The `‑prod` target automatically sets `CANTON_LOAD_SKIP_EXEC_CONFIRM=true` because the remote testnet does not expose an EVM executor. + +### Canton → EVM token load + +```bash +make run-canton2evm-token-load # local devenv +make run-canton2evm-token-load-prod # prod‑testnet +``` + +In addition to the generic schedule variables, the test requires the token‑lane configuration defined in `tests/token_transfer_config.toml`. The same file is used by both local and remote runs; you can override it with `CANTON_TOKEN_TEST_CONFIG=/path/to/custom.toml`. + +### EVM → Canton + +```bash +make run-evm2canton-load # local devenv +make run-evm2canton-load-prod # prod‑testnet +``` + +### EVM → Canton token load + +```bash +make run-evm2canton-token-load # local devenv +make run-evm2canton-token-load-prod # prod‑testnet +``` + +## Token‑E2E tests (prod‑testnet only) + +The token‑specific end‑to‑end tests are part of the **E2E** suite, but the same token lane configuration is used by the load tests above. + +--- + +For a quick one‑step build‑run of the load suite, you can combine the build and start steps manually: + +```bash +make start-devenv # spin up local environment (or `make start-devenv CCIP_ENV=prod-testnet` for remote) +make run-canton2evm-load # or any other load target +``` + +Refer to the top‑level README for the full list of environments and the shortcut command. \ No newline at end of file