Skip to content

Commit 942f045

Browse files
feat(deploy): warn to rename contracts before redeploy
CDM smart-contract package names are claimed on-chain by the first account to deploy them (globally first-deploy-wins). A modded/cloned app ships names the current signer does not own, so redeploying its contracts fails late, after a funded, ownership-binding deploy (paritytech/playground-app#320, #341). When the user interactively selects "yes, I changed contracts", deploy now shows an acknowledgement explaining the ownership rule and pointing at the files to rename (cdm.json, the contract Cargo.toml, and frontend references). Original authors who own their names can continue; Enter proceeds, Esc exits the flow so the user can rename. The notice lives outside pickNextStage (like the README ack), reachable only via the interactive contracts Select, so the headless --contracts path never hits it and the state machine is unchanged.
1 parent b6d20a4 commit 942f045

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)