From 2e07b5557b90bb4fe041a50650d13a99a3b32e91 Mon Sep 17 00:00:00 2001 From: Anil Kumar <150643132+anil-rome@users.noreply.github.com> Date: Wed, 22 Jul 2026 01:38:55 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20verify=20--path=20solana-program=20?= =?UTF-8?q?=E2=80=94=20the=20cross-VM=20works-gate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An EVM-lane call drives a Solana program via CPI. Deploys a thin CPI wrapper (CpiMemoProbe — its constructor self-provisions the contract's external-auth PDA), then pings it to CPI the SPL Memo program through the 0xff..08 precompile. A successful EVM receipt is the proof; a failed CPI reverts the tx. EVM-lane only (ROME_EVM_KEY; no Solana key). --solana-rpc adds an opt-in deep check that confirms the memo in the Solana settlement logs (rome_solanaTxForEvmTx). Funded-verified on Hadrian: deploy + ping + memo confirmed in the program's logs. 83 unit tests + MCP smoke green. --- .gitignore | 1 + docs/ARCHITECTURE.md | 7 +-- docs/GUIDES.md | 20 +++++++- probe/CpiMemoProbe.sol | 41 +++++++++++++++ src/core/capabilities.ts | 10 ++-- src/core/probe.ts | 17 ++++++ src/core/verify.ts | 108 ++++++++++++++++++++++++++++++++++++--- test/verify.test.ts | 79 +++++++++++++++++++++++++++- 8 files changed, 268 insertions(+), 15 deletions(-) create mode 100644 probe/CpiMemoProbe.sol diff --git a/.gitignore b/.gitignore index b89653d..df90aed 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ dist/ .env .env.* .secrets/ +probe/out/ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 014a993..ad6da36 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -73,8 +73,9 @@ Chains resolve by id, name, or slug (`200010`, `hadrian`, `Rome Hadrian`) — by | `fund --from --amount ` | `ROME_EVM_KEY` | bridge USDC → Rome **gas** (CCTP); the "from home" on-ramp | | `bridge --from --amount [--intent gas\|wrapper]` | `ROME_EVM_KEY` | bridge USDC in as gas or wUSDC | | `verify [--path solidity]` | `ROME_EVM_KEY` + `ROME_SOLANA_KEY` | the **both-lane works-gate**: deploy a probe, drive it from the EVM lane *and* the Solana lane, assert parity | +| `verify --path solana-program` | `ROME_EVM_KEY` | the **cross-VM works-gate**: deploy a thin CPI wrapper; an EVM-lane call drives a Solana program (SPL Memo) via CPI. `--solana-rpc` adds the Solana-log deep check | -`fund`/`bridge` orchestrate `@rome-protocol/sdk/bridge` (quote → sign the source burn → settle → register → poll); `verify` orchestrates `submitRomeTx` (EVM lane) + `submitRomeTxSolanaLane` (Solana lane). Every action prints what it did; `fund`/`bridge` preview with `--dry-run`. +`fund`/`bridge` orchestrate `@rome-protocol/sdk/bridge` (quote → sign the source burn → settle → register → poll); `verify --path solidity` orchestrates `submitRomeTx` (EVM lane) + `submitRomeTxSolanaLane` (Solana lane); `verify --path solana-program` orchestrates `submitRomeTx` into a wrapper that CPIs a Solana program via the CPI precompile (`0xff…08`). Every action prints what it did; `fund`/`bridge` preview with `--dry-run`. ## Grounding — why the facts are trustworthy @@ -102,7 +103,7 @@ src/ bridge.ts fund · bridge — the inbound CCTP flow engine (orchestrates the SDK) doctor.ts preflight checklist tx.ts cross-VM diagnosis (rome_solanaTxForEvmTx; no debug_trace) - verify.ts the both-lane works-gate (+ probe.ts, the bundled Store probe) + verify.ts the path-aware works-gate (+ probe.ts: bundled Store + CPI-Memo probes) presets.ts foundry/hardhat config keys.ts requireEvmKey / requireSolanaKey (env-only) eip1193.ts Node EIP-1193 shim for submitRomeTx @@ -115,4 +116,4 @@ test/ per-module unit tests + alignment + behavioral CLI (+ mcp- ## Roadmap -Shipped: the full four-paths surface above (reads + actions), all funded-verified on a live Rome chain. Next: `verify --path solana-program` / `--path from-home` slices · bridge ETH (Wormhole) + outbound (`from-rome`) · `new` (wraps `create-rome-app`). Deploy/build stays orchestrated (Foundry/Hardhat/create-rome-app) — `rome` is the connective tissue + the Rome-unique gaps, not a re-implementation of the EVM toolchain. +Shipped: the full four-paths surface above (reads + actions), all funded-verified on a live Rome chain — including `verify --path solana-program` (an EVM-lane call driving a Solana program via CPI). Next: `verify --path from-home` · bridge ETH (Wormhole) + outbound (`from-rome`) · `new` (wraps `create-rome-app`). Deploy/build stays orchestrated (Foundry/Hardhat/create-rome-app) — `rome` is the connective tissue + the Rome-unique gaps, not a re-implementation of the EVM toolchain. diff --git a/docs/GUIDES.md b/docs/GUIDES.md index 1f5a193..d21522f 100644 --- a/docs/GUIDES.md +++ b/docs/GUIDES.md @@ -257,7 +257,25 @@ $ rome verify hadrian --path solidity } ``` -`ok: true` means one contract on Rome answered correctly whether an EVM wallet *or* a Solana wallet drove it — the dual-lane promise, verified on-chain. (`--path solana-program` and `--path from-home` follow.) +`ok: true` means one contract on Rome answered correctly whether an EVM wallet *or* a Solana wallet drove it — the dual-lane promise, verified on-chain. + +### `--path solana-program` — an EVM user drives your Solana program via CPI + +Brought a **Solana program**? The gate is different: prove an EVM-lane call reaches it through the CPI precompile. `verify --path solana-program` deploys a thin CPI wrapper (its constructor self-provisions the contract's external-auth PDA), then an EVM-lane `ping` drives the SPL Memo program via CPI. A successful EVM receipt is the proof — a failed CPI reverts the tx. This path needs **only `ROME_EVM_KEY`** (no Solana key — the EVM lane is the whole story). + +```console +$ rome verify hadrian --path solana-program +{ + "path": "solana-program", + "probe": "0x206d7101…", + "program": "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", + "evmTx": "0x82aae391…", + "cpiLanded": true, + "ok": true +} +``` + +Add `--solana-rpc ` for the deep check: `verify` resolves the EVM tx to its Solana settlement (`rome_solanaTxForEvmTx`) and confirms the memo landed in the program's logs (`"memoConfirmed": true`). (`--path from-home` follows.) --- diff --git a/probe/CpiMemoProbe.sol b/probe/CpiMemoProbe.sol new file mode 100644 index 0000000..a81caaa --- /dev/null +++ b/probe/CpiMemoProbe.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.36; + +// Minimal CPI wrapper for `rome verify --path solana-program`. Proves an EVM-lane +// call drives a Solana program (SPL Memo) through the CPI precompile (0xff..08), +// signing as this contract's own external-auth PDA — the "bring your Solana +// program" pattern, reduced to its smallest honest form. Compiled with solc 0.8.36. + +interface ICpi { + struct AccountMeta { + bytes32 pubkey; + bool is_signer; + bool is_writable; + } + function invoke(bytes32 program_id, AccountMeta[] memory accounts, bytes memory data) external; +} + +interface IHelper { + function pda(address user) external view returns (bytes32); + function create_pda(address user) external; +} + +contract CpiMemoProbe { + ICpi private constant CPI = ICpi(0xFF00000000000000000000000000000000000008); + IHelper private constant HELPER = IHelper(0xff00000000000000000000000000000000000009); + // SPL Memo v2 (MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr) as bytes32. + bytes32 private constant MEMO = 0x054a535a992921064d24e87160da387c7c35b5ddbc92bb81e41fa8404105448d; + + constructor() { + // Provision this contract's external-auth PDA so the CPI precompile can sign as it. + HELPER.create_pda(address(this)); + } + + // CPI into SPL Memo with this contract's PDA as the sole (required) signer account. + // A failed CPI reverts this call — that revert is the works-gate's negative signal. + function ping(string calldata memo) external { + ICpi.AccountMeta[] memory accts = new ICpi.AccountMeta[](1); + accts[0] = ICpi.AccountMeta({ pubkey: HELPER.pda(address(this)), is_signer: true, is_writable: false }); + CPI.invoke(MEMO, accts, bytes(memo)); + } +} diff --git a/src/core/capabilities.ts b/src/core/capabilities.ts index 6c7ce83..062fae5 100644 --- a/src/core/capabilities.ts +++ b/src/core/capabilities.ts @@ -210,12 +210,16 @@ export const CAPABILITIES: Capability[] = [ (a) => bridgeHandler(a), ), - // ── verify: the both-lane works-gate (CLI-only action; EVM key + Solana key) ── + // ── verify: the path-aware works-gate (CLI-only action; keys vary by path) ── verbAction( "verify", "verify", - "The both-lane works-gate: deploy a probe and prove the SAME contract answers on the EVM lane AND the Solana lane. Needs ROME_EVM_KEY + ROME_SOLANA_KEY. e.g. rome verify hadrian --path solidity", - [chainArg, { name: "path", required: false, description: "builder path (solidity; solana-program + from-home follow)" }], + "The path-aware works-gate. solidity: prove the SAME contract answers on the EVM lane AND the Solana lane (ROME_EVM_KEY + ROME_SOLANA_KEY). solana-program: an EVM-lane call drives a Solana program via CPI (ROME_EVM_KEY). e.g. rome verify hadrian --path solana-program", + [ + chainArg, + { name: "path", required: false, description: "builder path: solidity (default) | solana-program (from-home follows)" }, + { name: "solana-rpc", required: false, description: "Solana RPC for the opt-in deep check (confirm the CPI effect in Solana logs)" }, + ], (a) => verifyHandler(a), ), ]; diff --git a/src/core/probe.ts b/src/core/probe.ts index 1d58c85..7a5e8a6 100644 --- a/src/core/probe.ts +++ b/src/core/probe.ts @@ -9,3 +9,20 @@ export const STORE_PROBE = { bytecode: "0x6080604052348015600e575f5ffd5b5060a580601a5f395ff3fe6080604052348015600e575f5ffd5b50600436106030575f3560e01c806360fe47b11460345780636d4ce63c146045575b5f5ffd5b6043603f3660046059565b5f55565b005b5f5460405190815260200160405180910390f35b5f602082840312156068575f5ffd5b503591905056fea2646970667358221220960bbcb62c68f5e98b5bca2ceac80403075d01943318403b5e8bcf09cff64a0b64736f6c63430008240033", } as const; + +// A minimal CPI wrapper probe (solc 0.8.36, optimized). The constructor self-provisions +// this contract's external-auth PDA (`HelperProgram.create_pda`); `ping(string)` CPIs the +// SPL Memo program via the CPI precompile (0xff..08), signing as that PDA. `rome verify +// --path solana-program` deploys it and calls `ping` — a successful EVM tx proves an +// EVM-lane caller drove a Solana program via CPI (a failed CPI reverts the tx). +// Source: probe/CpiMemoProbe.sol. The driven program is SPL Memo v2. +export const CPI_MEMO_PROBE = { + abi: [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { inputs: [{ internalType: "string", name: "memo", type: "string" }], name: "ping", outputs: [], stateMutability: "nonpayable", type: "function" }, + ], + bytecode: + "0x6080604052348015600e575f5ffd5b5060405160016265549b60e11b0319815230600482015273ff000000000000000000000000000000000000099063ff3556ca906024015f604051808303815f87803b158015605a575f5ffd5b505af1158015606b573d5f5f3e3d5ffd5b5050505061031d8061007c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610029575f3560e01c80633adb191b1461002d575b5f5ffd5b61004061003b3660046101a4565b610042565b005b6040805160018082528183019092525f91816020015b604080516060810182525f80825260208083018290529282015282525f1990920191018161005857905050604080516060810191829052638854a29960e01b90915230606482015290915080600960ff60981b01638854a29960848301602060405180830381865afa1580156100d0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906100f49190610212565b8152600160208201525f6040909101819052825183919061011757610117610229565b6020908102919091010152604051633a4065c360e11b8152600860ff60981b0190637480cb8690610172907f054a535a992921064d24e87160da387c7c35b5ddbc92bb81e41fa8404105448d90859088908890600401610265565b5f604051808303815f87803b158015610189575f5ffd5b505af115801561019b573d5f5f3e3d5ffd5b50505050505050565b5f5f602083850312156101b5575f5ffd5b823567ffffffffffffffff8111156101cb575f5ffd5b8301601f810185136101db575f5ffd5b803567ffffffffffffffff8111156101f1575f5ffd5b856020828401011115610202575f5ffd5b6020919091019590945092505050565b5f60208284031215610222575f5ffd5b5051919050565b634e487b7160e01b5f52603260045260245ffd5b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b5f60608201868352606060208401528086518083526080850191506020880192505f5b818110156102c65783518051845260208101511515602085015260408101511515604085015250606083019250602084019350600181019050610288565b505083810360408501526102db81868861023d565b9897505050505050505056fea26469706673582212209834cec8d28a7f7526419d3efe61137125ae270d973218e633513339f7a8d35764736f6c63430008240033", + // The Solana program the probe drives via CPI (SPL Memo v2). + memoProgram: "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr", +} as const; diff --git a/src/core/verify.ts b/src/core/verify.ts index eb6a037..c3d1d5e 100644 --- a/src/core/verify.ts +++ b/src/core/verify.ts @@ -5,7 +5,7 @@ import { submitRomeTx, submitRomeTxSolanaLane } from "@rome-protocol/sdk"; import { getChainFacts } from "./facts.js"; import { requireEvmKey, requireSolanaKey } from "./keys.js"; import { eip1193FromAccount } from "./eip1193.js"; -import { STORE_PROBE } from "./probe.js"; +import { STORE_PROBE, CPI_MEMO_PROBE } from "./probe.js"; // `rome verify --path solidity` — the both-lane works-gate. Deploy a probe, then prove // the SAME contract answers when set from the EVM lane (submitRomeTx) AND the Solana lane @@ -106,12 +106,106 @@ export function defaultVerifyDeps(chain: string | number): VerifyDeps { }; } -/** `rome verify --path solidity` handler — validates the path, then runs the gate. */ -export async function verifyHandler(args: Record): Promise { - const path = (args.path ?? "solidity").toLowerCase(); - if (path !== "solidity") { - throw new Error(`Only --path solidity is supported in this slice (got "${path}"). solana-program + from-home follow.`); +// ───────────────────────────────────────────────────────────────────────────── +// `rome verify --path solana-program` — the cross-VM works-gate. Deploy a thin CPI +// wrapper (its constructor self-provisions the contract's external-auth PDA), then an +// EVM-lane `ping` drives the SPL Memo program via the CPI precompile (0xff..08). A +// successful EVM receipt IS the proof — a failed CPI reverts the tx. EVM-lane ONLY +// (ROME_EVM_KEY; no Solana key). The Solana-log confirmation is an opt-in deep check, +// gated on a Solana RPC (`--solana-rpc`), since the public CLI's default Solana RPC is +// substituted to public devnet and the core gate needs no RPC at all. + +export interface VerifySolanaProgramResult { + path: "solana-program"; + probe: `0x${string}`; + program: string; + memo: string; + evmTx: `0x${string}`; + cpiLanded: boolean; + solanaSettlement?: string; + memoConfirmed?: boolean; + ok: boolean; +} + +export interface VerifySolanaProgramDeps { + deployProbe(): Promise<`0x${string}`>; + ping(probe: `0x${string}`, memo: string): Promise<{ hash: `0x${string}`; success: boolean }>; + /** Opt-in deep check: confirm the memo landed in the Solana settlement logs. */ + confirmMemo?(evmTxHash: `0x${string}`, memo: string): Promise<{ settlement?: string; found: boolean }>; +} + +/** Deploy the CPI wrapper, drive it from the EVM lane, and (optionally) confirm the effect. */ +export async function runVerifySolanaProgram(deps: VerifySolanaProgramDeps): Promise { + const probe = await deps.deployProbe(); + const memo = `rome-verify:${probe}`; + const { hash, success } = await deps.ping(probe, memo); + + let solanaSettlement: string | undefined; + let memoConfirmed: boolean | undefined; + if (deps.confirmMemo) { + const c = await deps.confirmMemo(hash, memo); + solanaSettlement = c.settlement; + memoConfirmed = c.found; } + // Core gate = the EVM tx landed (a failed CPI reverts it). When the deep check runs, + // it must also find the memo we sent in the Solana settlement logs. + const ok = success && (memoConfirmed ?? true); + return { path: "solana-program", probe, program: CPI_MEMO_PROBE.memoProgram, memo, evmTx: hash, cpiLanded: success, solanaSettlement, memoConfirmed, ok }; +} + +/** Real deps: viem deploy of the CPI probe + `ping` via submitRomeTx. EVM key ONLY. */ +export function defaultVerifySolanaProgramDeps(chain: string | number, opts?: { solanaRpc?: string }): VerifySolanaProgramDeps { + const c = getChainFacts(chain); + const account = privateKeyToAccount(requireEvmKey()); // fail fast — no network, no Solana key required + const pub = createPublicClient({ transport: http(c.rpcUrl) }); + const provider = eip1193FromAccount(account, c.rpcUrl, c.chainId); + + return { + async deployProbe() { + const wallet = createWalletClient({ account, transport: http(c.rpcUrl) }); + const gp = await pub.getGasPrice(); + const hash = await wallet.deployContract({ + abi: CPI_MEMO_PROBE.abi as never, + bytecode: CPI_MEMO_PROBE.bytecode, + chain: null, + gas: 26_000_000n, + maxFeePerGas: (gp * 3n) / 2n, + maxPriorityFeePerGas: 0n, + }); + const rcpt = await pub.waitForTransactionReceipt({ hash }); + if (!rcpt.contractAddress) throw new Error("CPI probe deploy produced no contract address"); + return rcpt.contractAddress; + }, + async ping(probe, memo) { + const data = encodeFunctionData({ abi: CPI_MEMO_PROBE.abi, functionName: "ping", args: [memo] }); + const hash = await submitRomeTx(provider, { from: account.address, to: probe, data }); + const rcpt = await pub.waitForTransactionReceipt({ hash }); + return { hash, success: rcpt.status === "success" }; + }, + confirmMemo: opts?.solanaRpc + ? async (evmTxHash, memo) => { + const res = await fetch(c.rpcUrl, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "rome_solanaTxForEvmTx", params: [evmTxHash] }), + }); + const sigs: string[] = ((await res.json()) as { result?: string[] })?.result ?? []; + const connection = new Connection(opts.solanaRpc!, "confirmed"); + for (const sig of sigs) { + const stx = await connection.getTransaction(sig, { maxSupportedTransactionVersion: 0 }); + if ((stx?.meta?.logMessages ?? []).some((l) => l.includes(memo))) return { settlement: sig, found: true }; + } + return { settlement: sigs[0], found: false }; + } + : undefined, + }; +} + +/** `rome verify --path ` handler — validates the path, runs the gate. */ +export async function verifyHandler(args: Record): Promise { + const path = (args.path ?? "solidity").toLowerCase(); // async so a synchronous key/config throw surfaces as a rejected promise, not a throw. - return runVerifySolidity(defaultVerifyDeps(args.chain)); + if (path === "solidity") return runVerifySolidity(defaultVerifyDeps(args.chain)); + if (path === "solana-program") return runVerifySolanaProgram(defaultVerifySolanaProgramDeps(args.chain, { solanaRpc: args["solana-rpc"] })); + throw new Error(`--path ${path} is not supported yet (available: solidity, solana-program; from-home follows).`); } diff --git a/test/verify.test.ts b/test/verify.test.ts index c83e524..1cde311 100644 --- a/test/verify.test.ts +++ b/test/verify.test.ts @@ -1,5 +1,13 @@ import { describe, it, expect, vi } from "vitest"; -import { runVerifySolidity, verifyHandler, type VerifyDeps } from "../src/core/verify.js"; +import { + runVerifySolidity, + runVerifySolanaProgram, + verifyHandler, + defaultVerifyDeps, + defaultVerifySolanaProgramDeps, + type VerifyDeps, + type VerifySolanaProgramDeps, +} from "../src/core/verify.js"; import { CAPABILITIES } from "../src/core/capabilities.js"; import { buildMcpTools } from "../src/mcp.js"; @@ -50,6 +58,58 @@ describe("runVerifySolidity — both-lane works-gate", () => { }); }); +// `verify --path solana-program` — EVM-lane-drives-Solana-program-via-CPI gate. +// Deploy the CPI wrapper probe, `ping` it (EVM lane) → the probe CPIs SPL Memo. A +// successful EVM receipt IS the proof (a failed CPI reverts the tx). EVM key only. +// The Solana-log confirmation (`confirmMemo`) is an opt-in deep check (`--solana-rpc`). +function spDeps(over: Partial, order: string[] = []): VerifySolanaProgramDeps { + return { + deployProbe: vi.fn(async () => { + order.push("deploy"); + return "0xprobe00000000000000000000000000000000abcd" as `0x${string}`; + }), + ping: vi.fn(async () => { + order.push("ping"); + return { hash: "0xpingaaaa00000000000000000000000000000000000000000000000000000000" as `0x${string}`, success: true }; + }), + ...over, + }; +} + +describe("runVerifySolanaProgram — EVM drives a Solana program via CPI", () => { + it("GREEN when the ping tx lands (EVM receipt success); no Solana RPC required", async () => { + const order: string[] = []; + const r = await runVerifySolanaProgram(spDeps({}, order)); + expect(order).toEqual(["deploy", "ping"]); + expect(r.path).toBe("solana-program"); + expect(r.cpiLanded).toBe(true); + expect(r.memoConfirmed).toBeUndefined(); // deep check not run + expect(r.ok).toBe(true); + }); + + it("RED when the ping tx reverts (a failed CPI reverts the EVM tx)", async () => { + const r = await runVerifySolanaProgram( + spDeps({ ping: vi.fn(async () => ({ hash: "0xdead00000000000000000000000000000000000000000000000000000000beef" as `0x${string}`, success: false })) }), + ); + expect(r.cpiLanded).toBe(false); + expect(r.ok).toBe(false); + }); + + it("deep check GREEN: ok requires the memo in the Solana settlement logs when confirmMemo is present", async () => { + const r = await runVerifySolanaProgram(spDeps({ confirmMemo: vi.fn(async () => ({ settlement: "5Lwke7Wsig", found: true })) })); + expect(r.memoConfirmed).toBe(true); + expect(r.solanaSettlement).toBe("5Lwke7Wsig"); + expect(r.ok).toBe(true); + }); + + it("deep check RED: tx lands but the memo is NOT in the logs → not ok", async () => { + const r = await runVerifySolanaProgram(spDeps({ confirmMemo: vi.fn(async () => ({ found: false })) })); + expect(r.cpiLanded).toBe(true); + expect(r.memoConfirmed).toBe(false); + expect(r.ok).toBe(false); + }); +}); + describe("verify is a key-gated action, never on MCP", () => { it("registered as an action requiring a key, and absent from the MCP surface", () => { const cap = CAPABILITIES.find((c) => c.id === "verify.verify"); @@ -71,4 +131,21 @@ describe("verify is a key-gated action, never on MCP", () => { if (ps !== undefined) process.env.ROME_SOLANA_KEY = ps; } }); + + it("per-path key scoping: solidity needs the Solana key; solana-program needs only ROME_EVM_KEY", () => { + const pe = process.env.ROME_EVM_KEY; + const ps = process.env.ROME_SOLANA_KEY; + process.env.ROME_EVM_KEY = `0x${"11".repeat(32)}`; + delete process.env.ROME_SOLANA_KEY; + try { + // solidity drives BOTH lanes → demands the Solana key up front + expect(() => defaultVerifyDeps("hadrian")).toThrow(/ROME_SOLANA_KEY/); + // solana-program is EVM-lane only → builds with just the EVM key, no Solana key + expect(() => defaultVerifySolanaProgramDeps("hadrian")).not.toThrow(); + } finally { + if (pe !== undefined) process.env.ROME_EVM_KEY = pe; + else delete process.env.ROME_EVM_KEY; + if (ps !== undefined) process.env.ROME_SOLANA_KEY = ps; + } + }); });