mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
## Contracts upgrade command with simple version tracking This PR aims to take the most minimal changes from #438 to make the upgrade command available. So it adds the `bun cli contracts upgrade` command for deploying a new `DataHavenServiceManager` implementation and upgrading the proxy, and includes a simple version tracking via a `contracts/VERSION` file. ### Contracts **`DataHavenServiceManager.sol`** - Added `_version` storage variable - Added `DATAHAVEN_VERSION()` view function, - Added `updateVersion(string)` function gated by `onlyProxyAdmin` - Added `VersionUpdated` event - The version is set at initialization and updated atomically with proxy upgrades via `upgradeAndCall`. ### CLI **`bun cli contracts upgrade`** works in two modes: _dry-run_ or _execute_. **Dry-run (default)** Deploys the new implementation on-chain (signed by the deployer key), then prints a ready-to-submit JSON payload for the multisig to execute the proxy upgrade. No AVS owner key required. ```bash # Uses version from contracts/VERSION (standard workflow) bun cli contracts upgrade --chain hoodi # Override version for this upgrade only (warns if it differs from contracts/VERSION) bun cli contracts upgrade --chain hoodi --target x.y.z ``` Example output: ```json { "to": "0xProxyAdmin...", "value": "0", "data": "0x...", "description": "Upgrade ServiceManager proxy to 0xNewImpl... and set version to 1.1.0" } ``` **Execute mode (`--execute`)** Deploys the implementation and broadcasts the proxy upgrade + version update in a single atomic `upgradeAndCall` transaction. Requires `AVS_OWNER_PRIVATE_KEY`. Used mostly for testing. ```bash bun cli contracts upgrade --chain anvil --execute ``` --- ### Expected flow - Bump mannually contracts/VERSION (e.g., 1.1.0) - Run bun cli contracts upgrade --chain anvil|hoodi|mainnet
102 lines
3 KiB
TypeScript
102 lines
3 KiB
TypeScript
import { parseArgs } from "node:util";
|
|
import { datahaven } from "@polkadot-api/descriptors";
|
|
import { createClient } from "polkadot-api";
|
|
import { withPolkadotSdkCompat } from "polkadot-api/polkadot-sdk-compat";
|
|
import { getWsProvider } from "polkadot-api/ws-provider/node";
|
|
import { getEvmEcdsaSigner, logger, SUBSTRATE_FUNDED_ACCOUNTS } from "utils";
|
|
import { parseJsonToParameters } from "utils/types";
|
|
|
|
/**
|
|
* Sets DataHaven runtime parameters on the specified RPC URL from a JSON file.
|
|
*/
|
|
export const setDataHavenParameters = async (
|
|
rpcUrl: string,
|
|
parametersFilePath: string
|
|
): Promise<boolean> => {
|
|
const parametersJson = await Bun.file(parametersFilePath).json();
|
|
const parameters = parseJsonToParameters(parametersJson).filter((p) => p.value !== undefined);
|
|
|
|
if (parameters.length === 0) {
|
|
logger.warn("⚠️ No parameters to set.");
|
|
return false;
|
|
}
|
|
|
|
const client = createClient(withPolkadotSdkCompat(getWsProvider(rpcUrl)));
|
|
|
|
try {
|
|
const dhApi = client.getTypedApi(datahaven);
|
|
const signer = getEvmEcdsaSigner(SUBSTRATE_FUNDED_ACCOUNTS.ALITH.privateKey);
|
|
|
|
// Log parameters being set
|
|
for (const p of parameters) {
|
|
logger.debug(`🔧 Setting ${p.name} = ${p.value!.asHex()}`);
|
|
}
|
|
|
|
// Build parameter calls
|
|
const calls = parameters.map(
|
|
(p) =>
|
|
dhApi.tx.Parameters.set_parameter({
|
|
key_value: {
|
|
type: "RuntimeConfig",
|
|
value: { type: p.name as any, value: [p.value] }
|
|
}
|
|
}).decodedCall
|
|
);
|
|
|
|
// Batch all calls and wrap in sudo
|
|
const tx = dhApi.tx.Sudo.sudo({
|
|
call: dhApi.tx.Utility.batch_all({ calls }).decodedCall
|
|
});
|
|
|
|
const result = await tx.signAndSubmit(signer);
|
|
|
|
// sudo always returns Ok at the extrinsic level — check the Sudid event
|
|
// for the inner call result
|
|
const sudidEvent = result.events.find(
|
|
(e: any) => e.type === "Sudo" && e.value?.type === "Sudid"
|
|
);
|
|
|
|
if (!sudidEvent) {
|
|
logger.error("❌ Sudo.Sudid event not found in transaction events");
|
|
return false;
|
|
}
|
|
|
|
const sudoResult = (sudidEvent.value as any).value.sudo_result;
|
|
if (sudoResult.type === "Err") {
|
|
logger.error(`❌ Sudo inner call failed: ${JSON.stringify(sudoResult)}`);
|
|
return false;
|
|
}
|
|
|
|
logger.success("Runtime parameters set successfully");
|
|
return true;
|
|
} catch (error) {
|
|
logger.error(`❌ ${error instanceof Error ? error.message : error}`);
|
|
return false;
|
|
} finally {
|
|
client.destroy();
|
|
}
|
|
};
|
|
|
|
// CLI entry point
|
|
if (import.meta.main) {
|
|
const { values } = parseArgs({
|
|
args: process.argv,
|
|
options: {
|
|
rpcUrl: { type: "string", short: "r" },
|
|
parametersFile: { type: "string", short: "f" }
|
|
},
|
|
strict: true
|
|
});
|
|
|
|
if (!values.rpcUrl || !values.parametersFile) {
|
|
console.error("Usage: --rpc-url <url> --parameters-file <path>");
|
|
process.exit(1);
|
|
}
|
|
|
|
setDataHavenParameters(values.rpcUrl, values.parametersFile)
|
|
.then((ok) => process.exit(ok ? 0 : 1))
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
});
|
|
}
|