explorer: Display upgradeable program details (#15348)

This commit is contained in:
Justin Starry 2021-02-16 18:30:02 +08:00 committed by GitHub
parent 53959b4bbc
commit aaa44355b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 171 additions and 2 deletions

View File

@ -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>
);
}

View File

@ -29,6 +29,7 @@ import { StakeHistoryCard } from "components/account/StakeHistoryCard";
import { BlockhashesCard } from "components/account/BlockhashesCard"; import { BlockhashesCard } from "components/account/BlockhashesCard";
import { ConfigAccountSection } from "components/account/ConfigAccountSection"; import { ConfigAccountSection } from "components/account/ConfigAccountSection";
import { useFlaggedAccounts } from "providers/accounts/flagged-accounts"; import { useFlaggedAccounts } from "providers/accounts/flagged-accounts";
import { UpgradeableProgramSection } from "components/account/UpgradeableProgramSection";
const TABS_LOOKUP: { [id: string]: Tab } = { const TABS_LOOKUP: { [id: string]: Tab } = {
"spl-token:mint": { "spl-token:mint": {
@ -174,7 +175,15 @@ function DetailsSections({ pubkey, tab }: { pubkey: PublicKey; tab?: string }) {
function InfoSection({ account }: { account: Account }) { function InfoSection({ account }: { account: Account }) {
const data = account?.details?.data; 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 ( return (
<StakeAccountSection <StakeAccountSection
account={account} account={account}
@ -290,7 +299,7 @@ function getTabs(data?: ProgramData): Tab[] {
]; ];
let programTypeKey = ""; let programTypeKey = "";
if (data && "type" in data.parsed) { if (data && "parsed" in data && "type" in data.parsed) {
programTypeKey = `${data.program}:${data.parsed.type}`; programTypeKey = `${data.program}:${data.parsed.type}`;
} }

View File

@ -19,6 +19,12 @@ import { NonceAccount } from "validators/accounts/nonce";
import { SysvarAccount } from "validators/accounts/sysvar"; import { SysvarAccount } from "validators/accounts/sysvar";
import { ConfigAccount } from "validators/accounts/config"; import { ConfigAccount } from "validators/accounts/config";
import { FlaggedAccountsProvider } from "./flagged-accounts"; import { FlaggedAccountsProvider } from "./flagged-accounts";
import {
ProgramAccount,
ProgramAccountInfo,
ProgramDataAccount,
ProgramDataAccountInfo,
} from "validators/accounts/upgradeable-program";
export { useAccountHistory } from "./history"; export { useAccountHistory } from "./history";
export type StakeProgramData = { export type StakeProgramData = {
@ -27,6 +33,12 @@ export type StakeProgramData = {
activation?: StakeActivationData; activation?: StakeActivationData;
}; };
export type UpgradeableProgramAccountData = {
program: "bpf-upgradeable-loader";
programData: ProgramDataAccountInfo;
programAccount: ProgramAccountInfo;
};
export type TokenProgramData = { export type TokenProgramData = {
program: "spl-token"; program: "spl-token";
parsed: TokenAccount; parsed: TokenAccount;
@ -53,6 +65,7 @@ export type ConfigProgramData = {
}; };
export type ProgramData = export type ProgramData =
| UpgradeableProgramAccountData
| StakeProgramData | StakeProgramData
| TokenProgramData | TokenProgramData
| VoteProgramData | VoteProgramData
@ -131,6 +144,8 @@ async function fetchAccountInfo(
let space; let space;
if (!("parsed" in result.data)) { if (!("parsed" in result.data)) {
space = result.data.length; space = result.data.length;
} else {
space = result.data.space;
} }
let data: ProgramData | undefined; let data: ProgramData | undefined;
@ -138,6 +153,40 @@ async function fetchAccountInfo(
try { try {
const info = coerce(result.data.parsed, ParsedInfo); const info = coerce(result.data.parsed, ParsedInfo);
switch (result.data.program) { 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": { case "stake": {
const parsed = coerce(info, StakeAccount); const parsed = coerce(info, StakeAccount);
const isDelegated = parsed.type === "delegated"; const isDelegated = parsed.type === "delegated";

View File

@ -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,
});