feat: introduce upgradeable bpf loader instruction cards (#15563)

* feat: introduce upgradeable bpf loader instruction cards

* feat: text-muted datatype labels consistently
This commit is contained in:
Josh 2021-02-26 15:50:37 -08:00 committed by GitHub
parent b934e1af17
commit e7bb1b7cd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 195 additions and 5 deletions

View File

@ -24,7 +24,9 @@ export function RawDetails({ ix }: { ix: TransactionInstruction }) {
))}
<tr>
<td>Instruction Data (Hex)</td>
<td>
Instruction Data <span className="text-muted">(Hex)</span>
</td>
<td className="text-lg-right">
<pre className="d-inline-block text-left mb-0 data-wrap">{data}</pre>
</td>

View File

@ -13,9 +13,11 @@ export function RawParsedDetails({
{children}
<tr>
<td>Instruction Data (JSON)</td>
<td>
Instruction Data <span className="text-muted">(JSON)</span>
</td>
<td className="text-lg-right">
<pre className="d-inline-block text-left">
<pre className="d-inline-block text-left json-wrap">
{JSON.stringify(ix.parsed, null, 2)}
</pre>
</td>

View File

@ -84,7 +84,7 @@ export function BpfLoaderWriteDetailsCard(props: Props<WriteInfo>) {
<tr>
<td>
Bytes <span className="text-muted">(base 64)</span>
Bytes <span className="text-muted">(Base 64)</span>
</td>
<td className="text-lg-right">
<pre className="d-inline-block text-left mb-0">{bytes}</pre>

View File

@ -0,0 +1,112 @@
import React from "react";
import {
ParsedTransaction,
ParsedInstruction,
SignatureResult,
PublicKey,
} from "@solana/web3.js";
import { Address } from "components/common/Address";
import { coerce, Struct } from "superstruct";
import { camelToTitleCase } from "utils";
import { reportError } from "utils/sentry";
import { ParsedInfo } from "validators";
import { InstructionCard } from "../InstructionCard";
import { UnknownDetailsCard } from "../UnknownDetailsCard";
import {
DeployWithMaxDataLenInfo,
InitializeBufferInfo,
SetAuthorityInfo,
UpgradeInfo,
WriteInfo,
} from "./types";
type DetailsProps = {
tx: ParsedTransaction;
ix: ParsedInstruction;
index: number;
result: SignatureResult;
innerCards?: JSX.Element[];
childIndex?: number;
};
export function UpgradeableBpfLoaderDetailsCard(props: DetailsProps) {
try {
const parsed = coerce(props.ix.parsed, ParsedInfo);
switch (parsed.type) {
case "write": {
return renderDetails<WriteInfo>(props, parsed, WriteInfo);
}
case "upgrade": {
return renderDetails<UpgradeInfo>(props, parsed, UpgradeInfo);
}
case "setAuthority": {
return renderDetails<SetAuthorityInfo>(props, parsed, SetAuthorityInfo);
}
case "deployWithMaxDataLen": {
return renderDetails<DeployWithMaxDataLenInfo>(
props,
parsed,
DeployWithMaxDataLenInfo
);
}
case "initializeBuffer": {
return renderDetails<InitializeBufferInfo>(
props,
parsed,
InitializeBufferInfo
);
}
default:
return <UnknownDetailsCard {...props} />;
}
} catch (error) {
reportError(error, {
signature: props.tx.signatures[0],
});
return <UnknownDetailsCard {...props} />;
}
}
function renderDetails<T>(
props: DetailsProps,
parsed: ParsedInfo,
struct: Struct<T>
) {
const info = coerce(parsed.info, struct);
const attributes: JSX.Element[] = [];
for (let [key, value] of Object.entries(info)) {
if (value instanceof PublicKey) {
value = <Address pubkey={value} alignRight link />;
} else if (key === "bytes") {
value = (
<pre className="d-inline-block text-left mb-0 data-wrap">{value}</pre>
);
}
attributes.push(
<tr key={key}>
<td>
{camelToTitleCase(key)}{" "}
{key === "bytes" && <span className="text-muted">(Base 64)</span>}
</td>
<td className="text-lg-right">{value}</td>
</tr>
);
}
return (
<InstructionCard
{...props}
title={`Upgradeable BPF Loader: ${camelToTitleCase(parsed.type)}`}
>
<tr>
<td>Program</td>
<td className="text-lg-right">
<Address pubkey={props.ix.programId} alignRight link />
</td>
</tr>
{attributes}
</InstructionCard>
);
}

View File

@ -0,0 +1,62 @@
/* eslint-disable @typescript-eslint/no-redeclare */
import { enums, nullable, number, pick, string, StructType } from "superstruct";
import { Pubkey } from "validators/pubkey";
export type WriteInfo = StructType<typeof WriteInfo>;
export const WriteInfo = pick({
account: Pubkey,
authority: Pubkey,
bytes: string(),
offset: number(),
});
export type InitializeBufferInfo = StructType<typeof InitializeBufferInfo>;
export const InitializeBufferInfo = pick({
account: Pubkey,
authority: Pubkey,
});
export type UpgradeInfo = StructType<typeof UpgradeInfo>;
export const UpgradeInfo = pick({
programDataAccount: Pubkey,
programAccount: Pubkey,
bufferAccount: Pubkey,
spillAccount: Pubkey,
authority: Pubkey,
rentSysvar: Pubkey,
clockSysvar: Pubkey,
});
export type SetAuthorityInfo = StructType<typeof SetAuthorityInfo>;
export const SetAuthorityInfo = pick({
account: Pubkey,
authority: Pubkey,
newAuthority: nullable(Pubkey),
});
export type DeployWithMaxDataLenInfo = StructType<
typeof DeployWithMaxDataLenInfo
>;
export const DeployWithMaxDataLenInfo = pick({
programDataAccount: Pubkey,
programAccount: Pubkey,
payerAccount: Pubkey,
bufferAccount: Pubkey,
authority: Pubkey,
rentSysvar: Pubkey,
clockSysvar: Pubkey,
systemProgram: Pubkey,
maxDataLen: number(),
});
export type UpgradeableBpfLoaderInstructionType = StructType<
typeof UpgradeableBpfLoaderInstructionType
>;
export const UpgradeableBpfLoaderInstructionType = enums([
"initializeBuffer",
"deployWithMaxDataLen",
"setAuthority",
"write",
"finalize",
]);

View File

@ -35,6 +35,7 @@ import {
} from "providers/transactions";
import { Cluster, useCluster } from "providers/cluster";
import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard";
import { UpgradeableBpfLoaderDetailsCard } from "components/instruction/upgradeable-bpf-loader/UpgradeableBpfLoaderDetailsCard";
export function InstructionsSection({ signature }: SignatureProps) {
const status = useTransactionStatus(signature);
@ -162,6 +163,8 @@ function renderInstructionCard({
return <TokenDetailsCard {...props} />;
case "bpf-loader":
return <BpfLoaderDetailsCard {...props} />;
case "bpf-upgradeable-loader":
return <UpgradeableBpfLoaderDetailsCard {...props} />;
case "system":
return <SystemDetailsCard {...props} />;
case "stake":

View File

@ -341,7 +341,7 @@ div.inner-cards {
border: 1px solid red;
}
pre.data-wrap {
pre.data-wrap, pre.json-wrap {
max-width: 23rem;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
@ -349,3 +349,7 @@ pre.data-wrap {
white-space: -o-pre-wrap;
word-wrap: break-word;
}
pre.json-wrap {
max-width: 36rem;
}

View File

@ -110,3 +110,8 @@ export function localStorageIsAvailable() {
return false;
}
}
export function camelToTitleCase(str: string): string {
const result = str.replace(/([A-Z])/g, " $1");
return result.charAt(0).toUpperCase() + result.slice(1);
}