Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/native-contract-install.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"playground-cli": patch
---

Run `dot contract install` through dot's native TUI and the released CDM install backend instead of spawning the CDM CLI. `dot init` now installs `cargo-pvm-contract` directly instead of running the CDM CLI installer.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ The installer drops the binary into `~/.polkadot/bin/`, symlinks it at `~/.local
End-to-end first-run setup. Login and toolchain install run **concurrently**; account setup runs **once both have completed successfully**.

1. **Login via the Polkadot mobile app** — a QR code is printed to the terminal. Scan it with the app. If you already have a session persisted in `~/.polkadot-apps/`, this step is skipped.
2. **Toolchain install** — `rustup`, nightly, `rust-src`, `cdm`, IPFS, and `gh`. Existing installs are detected and skipped.
2. **Toolchain install** — `rustup`, nightly, `rust-src`, `cargo-pvm-contract`, IPFS, and `git`. Existing installs are detected and skipped.
3. **Account setup** (only if a session is available) — in order:
- **Fund** — if your balance on Paseo Asset Hub is below 1 PAS, Alice sends 10 PAS (testnet).
- **Map** — `Revive.map_account` is signed by you on the mobile app so an H160 is associated with your SS58 address.
Expand Down Expand Up @@ -83,7 +83,7 @@ CDM-backed workflows for contracts:
- `dot contract deploy` builds, deploys, and registers CDM contracts with dot's logged-in signer by default. Pass `--suri //Alice` for local/dev signing.
- `dot contract deploy --features <features>` forwards Cargo feature flags into CDM's build pipeline.
- `dot contract deploy --registry-address <address>` targets a specific CDM registry.
- `dot contract install [libraries...]` runs `cdm install [libraries...]`; CDM still owns dependency installation and post-install hooks.
- `dot contract install [libraries...]` uses the CDM install backend with dot's native TUI, then writes `cdm.json` and CDM post-install outputs.

### `dot mod`

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"test:e2e:nightly": "tools/e2e-local.sh nightly"
},
"dependencies": {
"@dotdm/contracts": "^3.0.0",
"@dotdm/cdm": "^0.6.13",
"@dotdm/contracts": "^3.1.0",
"@dotdm/env": "^2.0.0",
"@parity/dotns-cli": "0.6.1",
"@parity/product-sdk-address": "^0.1.1",
Expand Down
37 changes: 32 additions & 5 deletions pnpm-lock.yaml

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

179 changes: 158 additions & 21 deletions src/commands/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,39 +14,37 @@
// limitations under the License.

import { getRegistryAddress } from "@dotdm/env";
import { computeTargetHash, type CdmJson } from "@dotdm/contracts";
import { DEFAULT_MNEMONIC as BULLETIN_DEPLOY_DEFAULT_MNEMONIC } from "bulletin-deploy";
import { describe, expect, it } from "vitest";
import { getChainConfig } from "../config.js";
import {
cdmPassthroughArgs,
parseContractInstallLibraryArg,
resolveContractDeployTarget,
resolveContractInstallTarget,
resolveContractSignerOptions,
} from "./contract.js";

describe("cdmPassthroughArgs", () => {
it("returns arguments after the contract subcommand", () => {
expect(
cdmPassthroughArgs(
["node", "dot", "contract", "install", "@polkadot/reputation", "--name", "paseo"],
"install",
),
).toEqual(["@polkadot/reputation", "--name", "paseo"]);
describe("parseContractInstallLibraryArg", () => {
it("defaults to latest", () => {
expect(parseContractInstallLibraryArg("@polkadot/reputation")).toEqual({
library: "@polkadot/reputation",
requestedVersion: "latest",
});
});

it("handles the install alias", () => {
expect(
cdmPassthroughArgs(
["node", "dot", "contract", "i", "@polkadot/reputation:3"],
"install",
["i"],
),
).toEqual(["@polkadot/reputation:3"]);
it("parses explicit versions from the last colon", () => {
expect(parseContractInstallLibraryArg("@polkadot/reputation:3")).toEqual({
library: "@polkadot/reputation",
requestedVersion: 3,
});
});

it("falls back to the first matching subcommand without a contract parent", () => {
expect(cdmPassthroughArgs(["node", "dot", "deploy", "--features", "ci"], "deploy")).toEqual(
["--features", "ci"],
);
it("treats non-numeric suffixes as part of the package name", () => {
expect(parseContractInstallLibraryArg("@polkadot/reputation:beta")).toEqual({
library: "@polkadot/reputation:beta",
requestedVersion: "latest",
});
});
});

Expand Down Expand Up @@ -83,6 +81,145 @@ describe("resolveContractDeployTarget", () => {
});
});

describe("resolveContractInstallTarget", () => {
it("uses the active playground chain by default", () => {
const cfg = getChainConfig();
const ipfsGatewayUrl = cfg.bulletinGateway;
const registryAddress = getRegistryAddress(cfg.env);
expect(resolveContractInstallTarget({})).toEqual({
assethubUrl: cfg.assetHubRpc,
ipfsGatewayUrl,
registryAddress,
targetHash: computeTargetHash(cfg.assetHubRpc, ipfsGatewayUrl, registryAddress),
chainName: undefined,
});
});

it("prefers the first cdm.json target when no explicit target is supplied", () => {
const cdmJson: CdmJson = {
targets: {
abc123: {
"asset-hub": "wss://asset.example",
bulletin: "https://gateway.example/ipfs/",
registry: "0x1111111111111111111111111111111111111111",
},
},
dependencies: {},
contracts: {},
};

expect(resolveContractInstallTarget({}, cdmJson)).toEqual({
assethubUrl: "wss://asset.example",
ipfsGatewayUrl: "https://gateway.example/ipfs/",
registryAddress: "0x1111111111111111111111111111111111111111",
targetHash: "abc123",
chainName: undefined,
});
});

it("prefers a cdm.json target with dependencies when reinstalling", () => {
const cdmJson: CdmJson = {
targets: {
empty: {
"asset-hub": "wss://empty.example",
bulletin: "https://empty.example/ipfs",
registry: "0x1111111111111111111111111111111111111111",
},
withDeps: {
"asset-hub": "wss://deps.example",
bulletin: "https://deps.example/ipfs",
registry: "0x2222222222222222222222222222222222222222",
},
},
dependencies: {
withDeps: {
"@polkadot/contexts": "latest",
},
},
contracts: {},
};

expect(resolveContractInstallTarget({}, cdmJson)).toEqual({
assethubUrl: "wss://deps.example",
ipfsGatewayUrl: "https://deps.example/ipfs",
registryAddress: "0x2222222222222222222222222222222222222222",
targetHash: "withDeps",
chainName: undefined,
});
});

it("preserves legacy cdm.json target keys when resolving a saved target", () => {
const cdmJson: CdmJson = {
targets: {
legacyHash: {
"asset-hub": "wss://asset.example",
bulletin: "https://gateway.example/ipfs",
},
},
dependencies: {
legacyHash: {
"@polkadot/contexts": "latest",
},
},
contracts: {},
};

const target = resolveContractInstallTarget({}, cdmJson);
expect(target.targetHash).toBe("legacyHash");
expect(target.targetHash).not.toBe(
computeTargetHash(target.assethubUrl, target.ipfsGatewayUrl, target.registryAddress),
);
});

it("allows --name custom to reuse cdm.json target connection details", () => {
const cdmJson: CdmJson = {
targets: {
abc123: {
"asset-hub": "wss://asset.example",
bulletin: "https://gateway.example/ipfs/",
registry: "0x1111111111111111111111111111111111111111",
},
},
dependencies: {},
contracts: {},
};

expect(resolveContractInstallTarget({ name: "custom" }, cdmJson)).toEqual({
assethubUrl: "wss://asset.example",
ipfsGatewayUrl: "https://gateway.example/ipfs/",
registryAddress: "0x1111111111111111111111111111111111111111",
targetHash: "abc123",
chainName: undefined,
});
});

it("accepts explicit endpoint and registry overrides", () => {
expect(
resolveContractInstallTarget({
assethubUrl: "wss://asset.example",
ipfsGatewayUrl: "https://gateway.example/ipfs/",
registryAddress: "0x2222222222222222222222222222222222222222",
}),
).toEqual({
assethubUrl: "wss://asset.example",
ipfsGatewayUrl: "https://gateway.example/ipfs/",
registryAddress: "0x2222222222222222222222222222222222222222",
targetHash: computeTargetHash(
"wss://asset.example",
"https://gateway.example/ipfs/",
"0x2222222222222222222222222222222222222222",
),
chainName: undefined,
});
});

it("rejects non-H160 registry addresses", () => {
expect(() => resolveContractInstallTarget({ registryAddress: "0x1234" })).toThrow(
"Registry address must be a 20-byte hex address",
);
});
});

describe("resolveContractSignerOptions", () => {
it("preserves the default contract signer behavior", () => {
expect(resolveContractSignerOptions({})).toEqual({ suri: undefined });
Expand Down
Loading
Loading