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:
parent
b934e1af17
commit
e7bb1b7cd5
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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",
|
||||
]);
|
|
@ -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":
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue