explorer: Display upgradeable program details (#15348)
This commit is contained in:
parent
53959b4bbc
commit
aaa44355b1
|
@ -0,0 +1,83 @@
|
|||
import React from "react";
|
||||
import { TableCardBody } from "components/common/TableCardBody";
|
||||
import { lamportsToSolString } from "utils";
|
||||
import { Account, useFetchAccountInfo } from "providers/accounts";
|
||||
import { Address } from "components/common/Address";
|
||||
import {
|
||||
ProgramAccountInfo,
|
||||
ProgramDataAccountInfo,
|
||||
} from "validators/accounts/upgradeable-program";
|
||||
import { Slot } from "components/common/Slot";
|
||||
|
||||
export function UpgradeableProgramSection({
|
||||
account,
|
||||
programAccount,
|
||||
programData,
|
||||
}: {
|
||||
account: Account;
|
||||
programAccount: ProgramAccountInfo;
|
||||
programData: ProgramDataAccountInfo;
|
||||
}) {
|
||||
const refresh = useFetchAccountInfo();
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<h3 className="card-header-title mb-0 d-flex align-items-center">
|
||||
Program Account
|
||||
</h3>
|
||||
<button
|
||||
className="btn btn-white btn-sm"
|
||||
onClick={() => refresh(account.pubkey)}
|
||||
>
|
||||
<span className="fe fe-refresh-cw mr-2"></span>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={account.pubkey} alignRight raw />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Balance (SOL)</td>
|
||||
<td className="text-lg-right text-uppercase">
|
||||
{lamportsToSolString(account.lamports || 0)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Executable</td>
|
||||
<td className="text-lg-right">Yes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Executable Data</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={programAccount.programData} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upgradeable</td>
|
||||
<td className="text-lg-right">
|
||||
{programData.authority !== null ? "Yes" : "No"}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Deployed Slot</td>
|
||||
<td className="text-lg-right">
|
||||
<Slot slot={programData.slot} link />
|
||||
</td>
|
||||
</tr>
|
||||
{programData.authority !== null && (
|
||||
<tr>
|
||||
<td>Upgrade Authority</td>
|
||||
<td className="text-lg-right">
|
||||
<Address pubkey={programData.authority} alignRight link />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</TableCardBody>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -29,6 +29,7 @@ import { StakeHistoryCard } from "components/account/StakeHistoryCard";
|
|||
import { BlockhashesCard } from "components/account/BlockhashesCard";
|
||||
import { ConfigAccountSection } from "components/account/ConfigAccountSection";
|
||||
import { useFlaggedAccounts } from "providers/accounts/flagged-accounts";
|
||||
import { UpgradeableProgramSection } from "components/account/UpgradeableProgramSection";
|
||||
|
||||
const TABS_LOOKUP: { [id: string]: Tab } = {
|
||||
"spl-token:mint": {
|
||||
|
@ -174,7 +175,15 @@ function DetailsSections({ pubkey, tab }: { pubkey: PublicKey; tab?: string }) {
|
|||
function InfoSection({ account }: { account: Account }) {
|
||||
const data = account?.details?.data;
|
||||
|
||||
if (data && data.program === "stake") {
|
||||
if (data && data.program === "bpf-upgradeable-loader") {
|
||||
return (
|
||||
<UpgradeableProgramSection
|
||||
account={account}
|
||||
programAccount={data.programAccount}
|
||||
programData={data.programData}
|
||||
/>
|
||||
);
|
||||
} else if (data && data.program === "stake") {
|
||||
return (
|
||||
<StakeAccountSection
|
||||
account={account}
|
||||
|
@ -290,7 +299,7 @@ function getTabs(data?: ProgramData): Tab[] {
|
|||
];
|
||||
|
||||
let programTypeKey = "";
|
||||
if (data && "type" in data.parsed) {
|
||||
if (data && "parsed" in data && "type" in data.parsed) {
|
||||
programTypeKey = `${data.program}:${data.parsed.type}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,12 @@ import { NonceAccount } from "validators/accounts/nonce";
|
|||
import { SysvarAccount } from "validators/accounts/sysvar";
|
||||
import { ConfigAccount } from "validators/accounts/config";
|
||||
import { FlaggedAccountsProvider } from "./flagged-accounts";
|
||||
import {
|
||||
ProgramAccount,
|
||||
ProgramAccountInfo,
|
||||
ProgramDataAccount,
|
||||
ProgramDataAccountInfo,
|
||||
} from "validators/accounts/upgradeable-program";
|
||||
export { useAccountHistory } from "./history";
|
||||
|
||||
export type StakeProgramData = {
|
||||
|
@ -27,6 +33,12 @@ export type StakeProgramData = {
|
|||
activation?: StakeActivationData;
|
||||
};
|
||||
|
||||
export type UpgradeableProgramAccountData = {
|
||||
program: "bpf-upgradeable-loader";
|
||||
programData: ProgramDataAccountInfo;
|
||||
programAccount: ProgramAccountInfo;
|
||||
};
|
||||
|
||||
export type TokenProgramData = {
|
||||
program: "spl-token";
|
||||
parsed: TokenAccount;
|
||||
|
@ -53,6 +65,7 @@ export type ConfigProgramData = {
|
|||
};
|
||||
|
||||
export type ProgramData =
|
||||
| UpgradeableProgramAccountData
|
||||
| StakeProgramData
|
||||
| TokenProgramData
|
||||
| VoteProgramData
|
||||
|
@ -131,6 +144,8 @@ async function fetchAccountInfo(
|
|||
let space;
|
||||
if (!("parsed" in result.data)) {
|
||||
space = result.data.length;
|
||||
} else {
|
||||
space = result.data.space;
|
||||
}
|
||||
|
||||
let data: ProgramData | undefined;
|
||||
|
@ -138,6 +153,40 @@ async function fetchAccountInfo(
|
|||
try {
|
||||
const info = coerce(result.data.parsed, ParsedInfo);
|
||||
switch (result.data.program) {
|
||||
case "bpf-upgradeable-loader": {
|
||||
let programAccount: ProgramAccountInfo;
|
||||
let programData: ProgramDataAccountInfo;
|
||||
|
||||
if (info.type === "programData") {
|
||||
break;
|
||||
}
|
||||
|
||||
const parsed = coerce(info, ProgramAccount);
|
||||
programAccount = parsed.info;
|
||||
const result = (
|
||||
await connection.getParsedAccountInfo(parsed.info.programData)
|
||||
).value;
|
||||
if (
|
||||
result &&
|
||||
"parsed" in result.data &&
|
||||
result.data.program === "bpf-upgradeable-loader"
|
||||
) {
|
||||
const info = coerce(result.data.parsed, ParsedInfo);
|
||||
programData = coerce(info, ProgramDataAccount).info;
|
||||
} else {
|
||||
throw new Error(
|
||||
`invalid program data account for program: ${pubkey.toBase58()}`
|
||||
);
|
||||
}
|
||||
|
||||
data = {
|
||||
program: result.data.program,
|
||||
programData,
|
||||
programAccount,
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
case "stake": {
|
||||
const parsed = coerce(info, StakeAccount);
|
||||
const isDelegated = parsed.type === "delegated";
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/* eslint-disable @typescript-eslint/no-redeclare */
|
||||
|
||||
import { StructType, pick, number, nullable, literal } from "superstruct";
|
||||
import { Pubkey } from "validators/pubkey";
|
||||
|
||||
export type ProgramAccountInfo = StructType<typeof ProgramAccountInfo>;
|
||||
export const ProgramAccountInfo = pick({
|
||||
programData: Pubkey,
|
||||
});
|
||||
|
||||
export type ProgramAccount = StructType<typeof ProgramDataAccount>;
|
||||
export const ProgramAccount = pick({
|
||||
type: literal("program"),
|
||||
info: ProgramAccountInfo,
|
||||
});
|
||||
|
||||
export type ProgramDataAccountInfo = StructType<typeof ProgramDataAccountInfo>;
|
||||
export const ProgramDataAccountInfo = pick({
|
||||
authority: nullable(Pubkey),
|
||||
// don't care about data yet
|
||||
slot: number(),
|
||||
});
|
||||
|
||||
export type ProgramDataAccount = StructType<typeof ProgramDataAccount>;
|
||||
export const ProgramDataAccount = pick({
|
||||
type: literal("programData"),
|
||||
info: ProgramDataAccountInfo,
|
||||
});
|
Loading…
Reference in New Issue