fix: expand vote details card to cover all instructions (#15602)

This commit is contained in:
Josh 2021-03-01 13:45:19 -08:00 committed by GitHub
parent d679eff3fa
commit 8c73187b1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 177 additions and 76 deletions

View File

@ -1,91 +1,127 @@
import React from "react";
import { ParsedInstruction, SignatureResult } from "@solana/web3.js";
import { coerce } from "superstruct";
import { PublicKey } from "@solana/web3.js";
import { coerce, Struct } from "superstruct";
import { ParsedInfo } from "validators";
import { VoteInfo } from "./types";
import {
UpdateCommissionInfo,
UpdateValidatorInfo,
VoteInfo,
VoteSwitchInfo,
WithdrawInfo,
AuthorizeInfo,
} from "./types";
import { InstructionCard } from "../InstructionCard";
import { Address } from "components/common/Address";
import { displayTimestamp } from "utils/date";
import { UnknownDetailsCard } from "../UnknownDetailsCard";
import { InstructionDetailsProps } from "components/transaction/InstructionsSection";
import { camelToTitleCase } from "utils";
import { useCluster } from "providers/cluster";
import { reportError } from "utils/sentry";
export function VoteDetailsCard(props: {
ix: ParsedInstruction;
index: number;
result: SignatureResult;
innerCards?: JSX.Element[];
childIndex?: number;
}) {
const { ix, index, result, innerCards, childIndex } = props;
const parsed = coerce(props.ix.parsed, ParsedInfo);
const info = coerce(parsed.info, VoteInfo);
export function VoteDetailsCard(props: InstructionDetailsProps) {
const { url } = useCluster();
try {
const parsed = coerce(props.ix.parsed, ParsedInfo);
switch (parsed.type) {
case "vote":
return renderDetails<VoteInfo>(props, parsed, VoteInfo);
case "authorize":
return renderDetails<AuthorizeInfo>(props, parsed, AuthorizeInfo);
case "withdraw":
return renderDetails<WithdrawInfo>(props, parsed, WithdrawInfo);
case "updateValidator":
return renderDetails<UpdateValidatorInfo>(
props,
parsed,
UpdateValidatorInfo
);
case "updateCommission":
return renderDetails<UpdateCommissionInfo>(
props,
parsed,
UpdateCommissionInfo
);
case "voteSwitch":
return renderDetails<VoteSwitchInfo>(props, parsed, VoteSwitchInfo);
}
} catch (error) {
reportError(error, {
url,
});
}
return <UnknownDetailsCard {...props} />;
}
function renderDetails<T>(
props: InstructionDetailsProps,
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 />;
}
if (key === "vote") {
attributes.push(
<tr key="vote-hash">
<td>Vote Hash</td>
<td className="text-lg-right">
<pre className="d-inline-block text-left mb-0">{value.hash}</pre>
</td>
</tr>
);
if (value.timestamp) {
attributes.push(
<tr>
<td>Timestamp</td>
<td className="text-lg-right text-monospace">
{displayTimestamp(value.timestamp * 1000)}
</td>
</tr>
);
}
attributes.push(
<tr key="vote-slots">
<td>Slots</td>
<td className="text-lg-right text-monospace">
<pre className="d-inline-block text-left mb-0">
{value.slots.join("\n")}
</pre>
</td>
</tr>
);
} else {
attributes.push(
<tr key={key}>
<td>{camelToTitleCase(key)} </td>
<td className="text-lg-right">{value}</td>
</tr>
);
}
}
return (
<InstructionCard
ix={ix}
index={index}
result={result}
title="Vote"
innerCards={innerCards}
childIndex={childIndex}
{...props}
title={`Vote: ${camelToTitleCase(parsed.type)}`}
>
<tr>
<td>Program</td>
<td className="text-lg-right">
<Address pubkey={ix.programId} alignRight link />
</td>
</tr>
<tr>
<td>Vote Account</td>
<td className="text-lg-right">
<Address pubkey={info.voteAccount} alignRight link />
</td>
</tr>
<tr>
<td>Vote Authority</td>
<td className="text-lg-right">
<Address pubkey={info.voteAuthority} alignRight link />
</td>
</tr>
<tr>
<td>Clock Sysvar</td>
<td className="text-lg-right">
<Address pubkey={info.clockSysvar} alignRight link />
</td>
</tr>
<tr>
<td>Slot Hashes Sysvar</td>
<td className="text-lg-right">
<Address pubkey={info.slotHashesSysvar} alignRight link />
</td>
</tr>
<tr>
<td>Vote Hash</td>
<td className="text-lg-right">
<pre className="d-inline-block text-left mb-0">{info.vote.hash}</pre>
</td>
</tr>
{info.vote.timestamp && (
<tr>
<td>Timestamp</td>
<td className="text-lg-right text-monospace">
{displayTimestamp(info.vote.timestamp * 1000)}
</td>
</tr>
)}
<tr>
<td>Slots</td>
<td className="text-lg-right text-monospace">
<pre className="d-inline-block text-left mb-0">
{info.vote.slots.join("\n")}
</pre>
<Address pubkey={props.ix.programId} alignRight link />
</td>
</tr>
{attributes}
</InstructionCard>
);
}

View File

@ -3,6 +3,26 @@
import { array, number, optional, pick, string, StructType } from "superstruct";
import { Pubkey } from "validators/pubkey";
export type InitializeInfo = StructType<typeof InitializeInfo>;
export const InitializeInfo = pick({
voteAccount: Pubkey,
rentSysvar: Pubkey,
clockSysvar: Pubkey,
node: Pubkey,
authorizedVoter: Pubkey,
authorizedWithdrawer: Pubkey,
commission: number(),
});
export type AuthorizeInfo = StructType<typeof AuthorizeInfo>;
export const AuthorizeInfo = pick({
voteAccount: Pubkey,
clockSysvar: Pubkey,
authority: Pubkey,
newAuthority: Pubkey,
authorityType: number(),
});
export type VoteInfo = StructType<typeof VoteInfo>;
export const VoteInfo = pick({
clockSysvar: Pubkey,
@ -15,3 +35,39 @@ export const VoteInfo = pick({
timestamp: optional(number()),
}),
});
export type WithdrawInfo = StructType<typeof WithdrawInfo>;
export const WithdrawInfo = pick({
voteAccount: Pubkey,
destination: Pubkey,
withdrawAuthority: Pubkey,
lamports: number(),
});
export type UpdateValidatorInfo = StructType<typeof UpdateValidatorInfo>;
export const UpdateValidatorInfo = pick({
voteAccount: Pubkey,
newValidatorIdentity: Pubkey,
withdrawAuthority: Pubkey,
});
export type UpdateCommissionInfo = StructType<typeof UpdateCommissionInfo>;
export const UpdateCommissionInfo = pick({
voteAccount: Pubkey,
withdrawAuthority: Pubkey,
commission: number(),
});
export type VoteSwitchInfo = StructType<typeof VoteSwitchInfo>;
export const VoteSwitchInfo = pick({
voteAccount: Pubkey,
slotHashesSysvar: Pubkey,
clockSysvar: Pubkey,
voteAuthority: Pubkey,
vote: pick({
hash: string(),
slots: array(number()),
timestamp: number(),
}),
hash: string(),
});

View File

@ -34,8 +34,17 @@ import {
useTransactionStatus,
} 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";
import { VoteDetailsCard } from "components/instruction/vote/VoteDetailsCard";
export type InstructionDetailsProps = {
tx: ParsedTransaction;
ix: ParsedInstruction;
index: number;
result: SignatureResult;
innerCards?: JSX.Element[];
childIndex?: number;
};
export function InstructionsSection({ signature }: SignatureProps) {
const status = useTransactionStatus(signature);
@ -171,8 +180,8 @@ function renderInstructionCard({
return <StakeDetailsCard {...props} />;
case "spl-memo":
return <MemoDetailsCard {...props} />;
/*case "vote":
return <VoteDetailsCard {...props} />;*/
case "vote":
return <VoteDetailsCard {...props} />;
default:
return <UnknownDetailsCard {...props} />;
}