Skip to content

Commit 2e8b649

Browse files
Merge pull request #380 from paritytech/deploy-contract-rename-warning
feat(deploy): warn to rename contracts before redeploy
2 parents 35e1498 + 942f045 commit 2e8b649

4 files changed

Lines changed: 106 additions & 2 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"playground-cli": patch
3+
---
4+
5+
deploy: warn before redeploying contracts that CDM package names are owned by their first deployer. When you choose "yes, I changed contracts", `playground deploy` now shows an acknowledgement explaining that if the app is a mod (or you edited contracts someone else authored), the names in `cdm.json` belong to the original author and the deploy will fail unless you rename them first. Press Enter to continue or Esc to exit and rename.

src/commands/deploy/DeployScreen.tsx

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ import {
7979
DOMAIN_HELP,
8080
BUILD_DIR_HINT,
8181
DOMAIN_HINT,
82+
CONTRACTS_RENAME_NOTICE_TITLE,
83+
CONTRACTS_RENAME_NOTICE_BODY,
84+
CONTRACTS_RENAME_NOTICE_HINT,
8285
} from "./promptHelp.js";
8386
import { ContractPipelineStatusAdapter } from "../contractPipelineStatus.js";
8487
import { ContractDeployStatusView, precomputeContractDeployDisplay } from "../contractDeployUi.js";
@@ -117,6 +120,7 @@ export type Stage =
117120
| { kind: "prompt-build" }
118121
| { kind: "prompt-signer" }
119122
| { kind: "prompt-contracts" }
123+
| { kind: "prompt-contracts-notice" }
120124
| { kind: "prompt-buildDir" }
121125
| { kind: "prompt-domain" }
122126
| { kind: "validate-domain"; domain: string }
@@ -367,14 +371,27 @@ export function DeployScreen({
367371
initialIndex={0}
368372
onSelect={(yes) => {
369373
setDeployContracts(yes);
370-
const nextSkipBuild = yes ? false : skipBuild;
371374
if (yes) setSkipBuild(false);
372-
advance(nextSkipBuild, mode, yes);
375+
// Redeploying contracts is when the CDM-name ownership
376+
// footgun bites (a mod ships names the signer doesn't
377+
// own). Gate "yes" on an explicit rename ack; "no"
378+
// skips straight ahead. The notice lives outside
379+
// pickNextStage, so the headless --contracts path
380+
// never hits it.
381+
if (yes) setStage({ kind: "prompt-contracts-notice" });
382+
else advance(skipBuild, mode, false);
373383
}}
374384
/>
375385
</Box>
376386
)}
377387

388+
{stage.kind === "prompt-contracts-notice" && (
389+
<ContractsRenameNotice
390+
onContinue={() => advance(false, mode, true)}
391+
onExit={() => onDone(null, { graceful: true })}
392+
/>
393+
)}
394+
378395
{stage.kind === "prompt-buildDir" && (
379396
<Box flexDirection="column">
380397
<PromptHint text={BUILD_DIR_HINT} />
@@ -670,6 +687,35 @@ function AckStage({ onContinue, onExit }: { onContinue: () => void; onExit: () =
670687
);
671688
}
672689

690+
/**
691+
* Interstitial shown when the user opts to redeploy contracts. Warns that CDM
692+
* package names are owned by their first deployer, so a mod (or any project that
693+
* edited someone else's contracts) must rename before publishing or the deploy
694+
* fails late. Enter continues; Esc exits the flow gracefully so they can rename.
695+
*/
696+
function ContractsRenameNotice({
697+
onContinue,
698+
onExit,
699+
}: {
700+
onContinue: () => void;
701+
onExit: () => void;
702+
}) {
703+
useInput((_input, key) => {
704+
if (key.return) onContinue();
705+
else if (key.escape) onExit();
706+
});
707+
return (
708+
<Box flexDirection="column">
709+
<Callout tone="warning" title={CONTRACTS_RENAME_NOTICE_TITLE}>
710+
<Text>{CONTRACTS_RENAME_NOTICE_BODY}</Text>
711+
</Callout>
712+
<Box marginTop={1}>
713+
<Hint>{CONTRACTS_RENAME_NOTICE_HINT}</Hint>
714+
</Box>
715+
</Box>
716+
);
717+
}
718+
673719
// ── Domain validation ────────────────────────────────────────────────────────
674720

675721
function ValidateDomainStage({

src/commands/deploy/promptHelp.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import {
2424
DOMAIN_HELP,
2525
BUILD_DIR_HINT,
2626
DOMAIN_HINT,
27+
CONTRACTS_RENAME_NOTICE_TITLE,
28+
CONTRACTS_RENAME_NOTICE_BODY,
29+
CONTRACTS_RENAME_NOTICE_HINT,
2730
type PromptBox,
2831
} from "./promptHelp.js";
2932

@@ -83,6 +86,32 @@ describe("plain-language anchors (the prompts the feedback called out)", () => {
8386
});
8487
});
8588

89+
describe("contract-redeploy rename notice", () => {
90+
it("has a non-empty title, body, and hint", () => {
91+
expect(CONTRACTS_RENAME_NOTICE_TITLE.trim()).not.toBe("");
92+
expect(CONTRACTS_RENAME_NOTICE_BODY.trim()).not.toBe("");
93+
expect(CONTRACTS_RENAME_NOTICE_HINT.trim()).not.toBe("");
94+
});
95+
96+
it("scopes the warning to mods / others' contracts and points at the files to rename", () => {
97+
const body = CONTRACTS_RENAME_NOTICE_BODY.toLowerCase();
98+
// The qualifier the user asked for: it only matters for a mod or when
99+
// you edited contracts someone else authored.
100+
expect(body).toContain("mod");
101+
// Concrete remediation: rename, and the files that hold the name.
102+
expect(body).toContain("rename");
103+
expect(body).toContain("cdm.json");
104+
expect(body).toContain("cargo.toml");
105+
});
106+
107+
it("offers enter-to-continue and esc-to-exit on one line", () => {
108+
expect(CONTRACTS_RENAME_NOTICE_HINT).not.toContain("\n");
109+
const hint = CONTRACTS_RENAME_NOTICE_HINT.toLowerCase();
110+
expect(hint).toContain("enter");
111+
expect(hint).toContain("esc");
112+
});
113+
});
114+
86115
describe("trivial-input hints", () => {
87116
it("are one-line, non-empty strings", () => {
88117
for (const [name, hint] of Object.entries({ BUILD_DIR_HINT, DOMAIN_HINT })) {

src/commands/deploy/promptHelp.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,30 @@ export const DOMAIN_HELP: PromptBox = {
9292
"names of 5 or fewer are reserved.",
9393
};
9494

95+
/**
96+
* Warning shown after the user confirms they changed contracts (and we're about
97+
* to redeploy them). CDM package names are globally first-deploy-wins: a modded
98+
* fork (or any project that edited contracts someone else authored) ships
99+
* names the current signer does not own, so the publish fails late, after a
100+
* funded, ownership-binding deploy (paritytech/playground-app#320,
101+
* paritytech/playground-cli#341). We surface it up front and gate on an explicit
102+
* ack so the user can rename first. Kept as plain strings (not a `PromptBox`) so
103+
* it renders as a `warning` Callout and isn't bound by the info-box length cap.
104+
*/
105+
export const CONTRACTS_RENAME_NOTICE_TITLE = "Check your contract names before redeploying";
106+
107+
export const CONTRACTS_RENAME_NOTICE_BODY =
108+
"Each smart-contract package name is claimed on-chain by the first account to " +
109+
"deploy it, and only that account can redeploy under it. If you wrote these " +
110+
"contracts yourself, you already own the names, so just continue. But if this " +
111+
"app is a mod, or you edited contracts that someone else originally wrote, the " +
112+
"names in cdm.json still belong to them and your deploy will fail. To publish " +
113+
"under your own account, first rename each contract to a name you own: update " +
114+
"its package name in cdm.json, in the contract's Cargo.toml, and anywhere your " +
115+
"frontend code refers to it.";
116+
117+
export const CONTRACTS_RENAME_NOTICE_HINT = "enter to continue · esc to exit and rename";
118+
95119
/** One-line hints for the trivial text inputs (no bordered box). */
96120
export const BUILD_DIR_HINT =
97121
"The folder holding your built site (the files we upload). The default fits most projects.";

0 commit comments

Comments
 (0)