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>
|
<tr>
|
||||||
<td>Instruction Data (Hex)</td>
|
<td>
|
||||||
|
Instruction Data <span className="text-muted">(Hex)</span>
|
||||||
|
</td>
|
||||||
<td className="text-lg-right">
|
<td className="text-lg-right">
|
||||||
<pre className="d-inline-block text-left mb-0 data-wrap">{data}</pre>
|
<pre className="d-inline-block text-left mb-0 data-wrap">{data}</pre>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -13,9 +13,11 @@ export function RawParsedDetails({
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Instruction Data (JSON)</td>
|
<td>
|
||||||
|
Instruction Data <span className="text-muted">(JSON)</span>
|
||||||
|
</td>
|
||||||
<td className="text-lg-right">
|
<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)}
|
{JSON.stringify(ix.parsed, null, 2)}
|
||||||
</pre>
|
</pre>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -84,7 +84,7 @@ export function BpfLoaderWriteDetailsCard(props: Props<WriteInfo>) {
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
Bytes <span className="text-muted">(base 64)</span>
|
Bytes <span className="text-muted">(Base 64)</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="text-lg-right">
|
<td className="text-lg-right">
|
||||||
<pre className="d-inline-block text-left mb-0">{bytes}</pre>
|
<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";
|
} from "providers/transactions";
|
||||||
import { Cluster, useCluster } from "providers/cluster";
|
import { Cluster, useCluster } from "providers/cluster";
|
||||||
import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard";
|
import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard";
|
||||||
|
import { UpgradeableBpfLoaderDetailsCard } from "components/instruction/upgradeable-bpf-loader/UpgradeableBpfLoaderDetailsCard";
|
||||||
|
|
||||||
export function InstructionsSection({ signature }: SignatureProps) {
|
export function InstructionsSection({ signature }: SignatureProps) {
|
||||||
const status = useTransactionStatus(signature);
|
const status = useTransactionStatus(signature);
|
||||||
|
@ -162,6 +163,8 @@ function renderInstructionCard({
|
||||||
return <TokenDetailsCard {...props} />;
|
return <TokenDetailsCard {...props} />;
|
||||||
case "bpf-loader":
|
case "bpf-loader":
|
||||||
return <BpfLoaderDetailsCard {...props} />;
|
return <BpfLoaderDetailsCard {...props} />;
|
||||||
|
case "bpf-upgradeable-loader":
|
||||||
|
return <UpgradeableBpfLoaderDetailsCard {...props} />;
|
||||||
case "system":
|
case "system":
|
||||||
return <SystemDetailsCard {...props} />;
|
return <SystemDetailsCard {...props} />;
|
||||||
case "stake":
|
case "stake":
|
||||||
|
|
|
@ -341,7 +341,7 @@ div.inner-cards {
|
||||||
border: 1px solid red;
|
border: 1px solid red;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre.data-wrap {
|
pre.data-wrap, pre.json-wrap {
|
||||||
max-width: 23rem;
|
max-width: 23rem;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
white-space: -moz-pre-wrap;
|
white-space: -moz-pre-wrap;
|
||||||
|
@ -349,3 +349,7 @@ pre.data-wrap {
|
||||||
white-space: -o-pre-wrap;
|
white-space: -o-pre-wrap;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre.json-wrap {
|
||||||
|
max-width: 36rem;
|
||||||
|
}
|
||||||
|
|
|
@ -110,3 +110,8 @@ export function localStorageIsAvailable() {
|
||||||
return false;
|
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